From ad7e1de948745c0751bfdac96671028ff4b7a727 Mon Sep 17 00:00:00 2001 From: Giulio Canti Date: Tue, 24 Sep 2024 16:02:40 +0200 Subject: [PATCH] add more description annotations (#3673) --- .changeset/pink-files-dance.md | 1 + .changeset/poor-pots-relate.md | 5 + .../platform-node/test/fixtures/openapi.json | 9 +- packages/platform/src/OpenApiJsonSchema.ts | 53 +++++---- .../platform/test/OpenApiJsonSchema.test.ts | 55 +++------ packages/schema/src/JSONSchema.ts | 58 ++++++---- packages/schema/src/Schema.ts | 104 ++++++++++-------- packages/schema/test/JSONSchema.test.ts | 23 ++-- .../test/Schema/Duration/Duration.test.ts | 8 +- .../test/Schema/Map/MapFromRecord.test.ts | 10 +- .../ReadonlyMap/ReadonlyMapFromRecord.test.ts | 10 +- .../test/Schema/Uint8Array/Uint8Array.test.ts | 2 +- 12 files changed, 190 insertions(+), 148 deletions(-) create mode 100644 .changeset/poor-pots-relate.md diff --git a/.changeset/pink-files-dance.md b/.changeset/pink-files-dance.md index b30b0783a3..7c1c8653e5 100644 --- a/.changeset/pink-files-dance.md +++ b/.changeset/pink-files-dance.md @@ -1,4 +1,5 @@ --- +"@effect/platform": patch "@effect/schema": patch --- diff --git a/.changeset/poor-pots-relate.md b/.changeset/poor-pots-relate.md new file mode 100644 index 0000000000..bd3f2e1c0a --- /dev/null +++ b/.changeset/poor-pots-relate.md @@ -0,0 +1,5 @@ +--- +"@effect/schema": patch +--- + +Add more description annotations. diff --git a/packages/platform-node/test/fixtures/openapi.json b/packages/platform-node/test/fixtures/openapi.json index cdb4d09724..3b71befafe 100644 --- a/packages/platform-node/test/fixtures/openapi.json +++ b/packages/platform-node/test/fixtures/openapi.json @@ -15,7 +15,6 @@ "in": "path", "schema": { "description": "a string that will be parsed into a number", - "title": "string", "type": "string" }, "required": true @@ -283,7 +282,6 @@ "in": "path", "schema": { "description": "a string that will be parsed into a number", - "title": "string", "type": "string" }, "required": true @@ -310,7 +308,7 @@ "type": "string" }, "createdAt": { - "description": "a DateTime.Utc instance", + "description": "a string that will be parsed into a DateTime.Utc", "type": "string" } }, @@ -427,7 +425,7 @@ "type": "string" }, "createdAt": { - "description": "a DateTime.Utc instance", + "description": "a string that will be parsed into a DateTime.Utc", "type": "string" } }, @@ -559,7 +557,6 @@ "in": "header", "schema": { "description": "a string that will be parsed into a number", - "title": "string", "type": "string" }, "required": false @@ -588,7 +585,7 @@ "type": "string" }, "createdAt": { - "description": "a DateTime.Utc instance", + "description": "a string that will be parsed into a DateTime.Utc", "type": "string" } }, diff --git a/packages/platform/src/OpenApiJsonSchema.ts b/packages/platform/src/OpenApiJsonSchema.ts index f0e0b1331d..aa376b821f 100644 --- a/packages/platform/src/OpenApiJsonSchema.ts +++ b/packages/platform/src/OpenApiJsonSchema.ts @@ -256,6 +256,33 @@ const getJsonSchemaAnnotations = (annotated: AST.Annotated): Annotations => default: AST.getDefaultAnnotation(annotated) }) +const removeDefaultJsonSchemaAnnotations = ( + jsonSchemaAnnotations: Annotations, + ast: AST.AST +): Annotations => { + if (jsonSchemaAnnotations["title"] === ast.annotations[AST.TitleAnnotationId]) { + delete jsonSchemaAnnotations["title"] + } + if (jsonSchemaAnnotations["description"] === ast.annotations[AST.DescriptionAnnotationId]) { + delete jsonSchemaAnnotations["description"] + } + return jsonSchemaAnnotations +} + +const getASTJsonSchemaAnnotations = (ast: AST.AST): Annotations => { + const jsonSchemaAnnotations = getJsonSchemaAnnotations(ast) + switch (ast._tag) { + case "StringKeyword": + return removeDefaultJsonSchemaAnnotations(jsonSchemaAnnotations, AST.stringKeyword) + case "NumberKeyword": + return removeDefaultJsonSchemaAnnotations(jsonSchemaAnnotations, AST.numberKeyword) + case "BooleanKeyword": + return removeDefaultJsonSchemaAnnotations(jsonSchemaAnnotations, AST.booleanKeyword) + default: + return jsonSchemaAnnotations + } +} + const pruneUndefinedKeyword = (ps: AST.PropertySignature): AST.AST | undefined => { const type = ps.type if (AST.isUnion(type) && Option.isNone(AST.getJSONSchemaAnnotation(type))) { @@ -390,24 +417,12 @@ const go = ( ...constAnyObject, ...getJsonSchemaAnnotations(ast) } - case "StringKeyword": { - return ast === AST.stringKeyword ? { type: "string" } : { - type: "string", - ...getJsonSchemaAnnotations(ast) - } - } - case "NumberKeyword": { - return ast === AST.numberKeyword ? { type: "number" } : { - type: "number", - ...getJsonSchemaAnnotations(ast) - } - } - case "BooleanKeyword": { - return ast === AST.booleanKeyword ? { type: "boolean" } : { - type: "boolean", - ...getJsonSchemaAnnotations(ast) - } - } + case "StringKeyword": + return { type: "string", ...getASTJsonSchemaAnnotations(ast) } + case "NumberKeyword": + return { type: "number", ...getASTJsonSchemaAnnotations(ast) } + case "BooleanKeyword": + return { type: "boolean", ...getASTJsonSchemaAnnotations(ast) } case "BigIntKeyword": throw new Error(getJSONSchemaMissingAnnotationErrorMessage(path, ast)) case "SymbolKeyword": @@ -611,7 +626,7 @@ const go = ( } } return { - ...getJsonSchemaAnnotations(ast.to), + ...getASTJsonSchemaAnnotations(ast.to), ...go(ast.from, $defs, true, path), ...getJsonSchemaAnnotations(ast) } diff --git a/packages/platform/test/OpenApiJsonSchema.test.ts b/packages/platform/test/OpenApiJsonSchema.test.ts index 8384e72e4b..e40bac81f5 100644 --- a/packages/platform/test/OpenApiJsonSchema.test.ts +++ b/packages/platform/test/OpenApiJsonSchema.test.ts @@ -207,9 +207,7 @@ schema (Declaration): DateFromSelf` type: "string" }) expectJSONSchema(Schema.String.annotations({}), { - type: "string", - description: "a string", - title: "string" + type: "string" }) }) @@ -218,9 +216,7 @@ schema (Declaration): DateFromSelf` type: "number" }, false) expectJSONSchema(Schema.Number.annotations({}), { - type: "number", - description: "a number", - title: "number" + type: "number" }, false) }) @@ -229,9 +225,7 @@ schema (Declaration): DateFromSelf` type: "boolean" }) expectJSONSchema(Schema.Boolean.annotations({}), { - type: "boolean", - description: "a boolean", - title: "boolean" + type: "boolean" }) }) @@ -514,7 +508,6 @@ schema (Declaration): DateFromSelf` "items": [ { "type": "string", - "title": "string", "description": "e" }, { @@ -1376,8 +1369,6 @@ schema (Suspend): ` it("examples support", () => { expectJSONSchema(Schema.String.annotations({ examples: ["a", "b"] }), { "type": "string", - "title": "string", - "description": "a string", "examples": ["a", "b"] }) }) @@ -1385,8 +1376,6 @@ schema (Suspend): ` it("default support", () => { expectJSONSchema(Schema.String.annotations({ default: "" }), { "type": "string", - "title": "string", - "description": "a string", "default": "" }) }) @@ -1503,9 +1492,7 @@ schema (Suspend): ` "$ref": "#/$defs/Name", "$defs": { "Name": { - "type": "string", - "description": "a string", - "title": "string" + "type": "string" } } }) @@ -1866,9 +1853,8 @@ schema (Suspend): ` ], "properties": { "a": { - "type": "string", description: "a string that will be parsed into a number", - title: "string" + "type": "string" } }, "additionalProperties": false @@ -2066,9 +2052,8 @@ schema (Suspend): ` a: { contentMediaType: "application/json", contentSchema: { - type: "string", description: "a string that will be parsed into a number", - title: "string" + type: "string" }, type: "string" } @@ -2110,15 +2095,14 @@ schema (Suspend): ` value: Schema.NumberFromString }), { - description: "ReadonlyMap", - type: "object", - required: [], - properties: {}, + "description": "a record that will be parsed into a ReadonlyMap", + "type": "object", + "required": [], + "properties": {}, "patternProperties": { "": { - description: "a string that will be parsed into a number", - title: "string", - type: "string" + "description": "a string that will be parsed into a number", + "type": "string" } }, "propertyNames": { @@ -2137,15 +2121,14 @@ schema (Suspend): ` value: Schema.NumberFromString }), { - type: "object", - description: "Map", - required: [], - properties: {}, + "type": "object", + "description": "a record that will be parsed into a Map", + "required": [], + "properties": {}, "patternProperties": { "": { - description: "a string that will be parsed into a number", - title: "string", - type: "string" + "description": "a string that will be parsed into a number", + "type": "string" } }, "propertyNames": { @@ -2173,7 +2156,6 @@ schema (Suspend): ` Schema.DateFromString, { "type": "string", - "title": "string", "description": "a string that will be parsed into a Date" } ) @@ -2184,7 +2166,6 @@ schema (Suspend): ` Schema.Date, { "type": "string", - "title": "string", "description": "a string that will be parsed into a Date" } ) diff --git a/packages/schema/src/JSONSchema.ts b/packages/schema/src/JSONSchema.ts index e7e0de9330..4cfa214f09 100644 --- a/packages/schema/src/JSONSchema.ts +++ b/packages/schema/src/JSONSchema.ts @@ -261,6 +261,33 @@ const getJsonSchemaAnnotations = (annotated: AST.Annotated): JsonSchemaAnnotatio default: AST.getDefaultAnnotation(annotated) }) +const removeDefaultJsonSchemaAnnotations = ( + jsonSchemaAnnotations: JsonSchemaAnnotations, + ast: AST.AST +): JsonSchemaAnnotations => { + if (jsonSchemaAnnotations["title"] === ast.annotations[AST.TitleAnnotationId]) { + delete jsonSchemaAnnotations["title"] + } + if (jsonSchemaAnnotations["description"] === ast.annotations[AST.DescriptionAnnotationId]) { + delete jsonSchemaAnnotations["description"] + } + return jsonSchemaAnnotations +} + +const getASTJsonSchemaAnnotations = (ast: AST.AST): JsonSchemaAnnotations => { + const jsonSchemaAnnotations = getJsonSchemaAnnotations(ast) + switch (ast._tag) { + case "StringKeyword": + return removeDefaultJsonSchemaAnnotations(jsonSchemaAnnotations, AST.stringKeyword) + case "NumberKeyword": + return removeDefaultJsonSchemaAnnotations(jsonSchemaAnnotations, AST.numberKeyword) + case "BooleanKeyword": + return removeDefaultJsonSchemaAnnotations(jsonSchemaAnnotations, AST.booleanKeyword) + default: + return jsonSchemaAnnotations + } +} + const pruneUndefinedKeyword = (ps: AST.PropertySignature): AST.AST | undefined => { const type = ps.type if (AST.isUnion(type) && Option.isNone(AST.getJSONSchemaAnnotation(type))) { @@ -306,20 +333,6 @@ const isOverrideAnnotation = (jsonSchema: JsonSchema7): boolean => { ("enum" in jsonSchema) || ("$ref" in jsonSchema) } -const removeDefaultJsonSchemaAnnotations = (out: JsonSchema7, def: AST.AST, ast: AST.AST): JsonSchema7 => { - if (def === ast) { - return out - } - const jsonSchemaAnnotations = getJsonSchemaAnnotations(ast) - if (jsonSchemaAnnotations["title"] === def.annotations[AST.TitleAnnotationId]) { - delete jsonSchemaAnnotations["title"] - } - if (jsonSchemaAnnotations["description"] === def.annotations[AST.DescriptionAnnotationId]) { - delete jsonSchemaAnnotations["description"] - } - return merge(out, jsonSchemaAnnotations) -} - const go = ( ast: AST.AST, $defs: Record, @@ -333,9 +346,16 @@ const go = ( const t = getRefinementInnerTransformation(ast) if (t === undefined) { try { - return merge(merge(go(ast.from, $defs, true, path), getJsonSchemaAnnotations(ast)), handler) + return { + ...go(ast.from, $defs, true, path), + ...getJsonSchemaAnnotations(ast), + ...handler + } } catch (e) { - return merge(getJsonSchemaAnnotations(ast), handler) + return { + ...getJsonSchemaAnnotations(ast), + ...handler + } } } else if (!isOverrideAnnotation(handler)) { return go(t, $defs, true, path) @@ -386,11 +406,11 @@ const go = ( case "ObjectKeyword": return merge(objectJsonSchema, getJsonSchemaAnnotations(ast)) case "StringKeyword": - return removeDefaultJsonSchemaAnnotations({ type: "string" }, AST.stringKeyword, ast) + return { type: "string", ...getASTJsonSchemaAnnotations(ast) } case "NumberKeyword": - return removeDefaultJsonSchemaAnnotations({ type: "number" }, AST.numberKeyword, ast) + return { type: "number", ...getASTJsonSchemaAnnotations(ast) } case "BooleanKeyword": - return removeDefaultJsonSchemaAnnotations({ type: "boolean" }, AST.booleanKeyword, ast) + return { type: "boolean", ...getASTJsonSchemaAnnotations(ast) } case "BigIntKeyword": throw new Error(errors_.getJSONSchemaMissingAnnotationErrorMessage(path, ast)) case "SymbolKeyword": diff --git a/packages/schema/src/Schema.ts b/packages/schema/src/Schema.ts index b52b3aca26..926a21f26f 100644 --- a/packages/schema/src/Schema.ts +++ b/packages/schema/src/Schema.ts @@ -4362,7 +4362,7 @@ export const nonEmptyString = ( * @since 0.67.0 */ export class Lowercase extends transform( - String$, + String$.annotations({ description: "a string that will be converted to lowercase" }), Lowercased, { strict: true, decode: (s) => s.toLowerCase(), encode: identity } ).annotations({ identifier: "Lowercase" }) {} @@ -4374,7 +4374,7 @@ export class Lowercase extends transform( * @since 0.67.0 */ export class Uppercase extends transform( - String$, + String$.annotations({ description: "a string that will be converted to uppercase" }), Uppercased, { strict: true, decode: (s) => s.toUpperCase(), encode: identity } ).annotations({ identifier: "Uppercase" }) {} @@ -4386,7 +4386,7 @@ export class Uppercase extends transform( * @since 0.68.18 */ export class Capitalize extends transform( - String$, + String$.annotations({ description: "a string that will be converted to a capitalized format" }), Capitalized, { strict: true, decode: (s) => string_.capitalize(s), encode: identity } ).annotations({ identifier: "Capitalize" }) {} @@ -4398,7 +4398,7 @@ export class Capitalize extends transform( * @since 0.68.18 */ export class Uncapitalize extends transform( - String$, + String$.annotations({ description: "a string that will be converted to an uncapitalized format" }), Uncapitalized, { strict: true, decode: (s) => string_.uncapitalize(s), encode: identity } ).annotations({ identifier: "Uncapitalize" }) {} @@ -4436,7 +4436,7 @@ export class NonEmptyTrimmedString extends Trimmed.pipe( * @since 0.67.0 */ export class Trim extends transform( - String$, + String$.annotations({ description: "a string that will be trimmed" }), Trimmed, { strict: true, decode: (s) => s.trim(), encode: identity } ).annotations({ identifier: "Trim" }) {} @@ -4449,7 +4449,7 @@ export class Trim extends transform( */ export const split = (separator: string): transform> => transform( - String$, + String$.annotations({ description: "a string that will be split" }), Array$(String$), { strict: true, decode: string_.split(separator), encode: array_.join(separator) } ) @@ -5001,11 +5001,15 @@ export class JsonNumber extends Number$.pipe( * @category boolean transformations * @since 0.67.0 */ -export class Not extends transform(Boolean$, Boolean$, { strict: true, decode: boolean_.not, encode: boolean_.not }) {} +export class Not extends transform(Boolean$.annotations({ description: "a boolean that will be negated" }), Boolean$, { + strict: true, + decode: boolean_.not, + encode: boolean_.not +}) {} /** @ignore */ class Symbol$ extends transform( - String$, + String$.annotations({ description: "a string that will be converted to a symbol" }), SymbolFromSelf, { strict: false, decode: (s) => Symbol.for(s), encode: (sym) => sym.description } ).annotations({ identifier: "symbol" }) {} @@ -5217,7 +5221,7 @@ export const clampBigInt = /** @ignore */ class BigInt$ extends transformOrFail( - String$, + String$.annotations({ description: "a string that will be parsed into a bigint" }), BigIntFromSelf, { strict: true, @@ -5311,7 +5315,7 @@ export const NonNegativeBigInt: filter> = BigInt$.pipe( * @since 0.67.0 */ export class BigIntFromNumber extends transformOrFail( - Number$, + Number$.annotations({ description: "a number that will be parsed into a bigint" }), BigIntFromSelf, { strict: true, @@ -5446,7 +5450,7 @@ export class DurationFromSelf extends declare( * @since 0.67.0 */ export class DurationFromNanos extends transformOrFail( - BigIntFromSelf, + BigIntFromSelf.annotations({ description: "a bigint that will be parsed into a Duration" }), DurationFromSelf, { strict: true, @@ -5467,7 +5471,7 @@ export class DurationFromNanos extends transformOrFail( * @since 0.67.0 */ export class DurationFromMillis extends transform( - Number$, + Number$.annotations({ description: "a number that will be parsed into a Duration" }), DurationFromSelf, { strict: true, decode: (ms) => duration_.millis(ms), encode: (n) => duration_.toMillis(n) } ).annotations({ identifier: "DurationFromMillis" }) {} @@ -5494,7 +5498,7 @@ const hrTime: Schema = Tuple( * @since 0.67.0 */ export class Duration extends transform( - hrTime, + hrTime.annotations({ description: "a tuple of seconds and nanos that will be parsed into a Duration" }), DurationFromSelf, { strict: true, @@ -5658,7 +5662,7 @@ const Uint8Array$: Schema> = transform( title: "8-bit unsigned integer", description: "a 8-bit unsigned integer" }) - )).annotations({ description: "an array of 8-bit unsigned integers" }), + )).annotations({ description: "an array of 8-bit unsigned integers that will be parsed into a Uint8Array" }), Uint8ArrayFromSelf, { strict: true, decode: (numbers) => Uint8Array.from(numbers), encode: (uint8Array) => Array.from(uint8Array) } ).annotations({ identifier: "Uint8Array" }) @@ -5679,7 +5683,7 @@ const makeUint8ArrayTransformation = ( encode: (u: Uint8Array) => string ) => transformOrFail( - String$, + String$.annotations({ description: "a string that will be parsed into a Uint8Array" }), Uint8ArrayFromSelf, { strict: true, @@ -5728,13 +5732,15 @@ export const Uint8ArrayFromHex: Schema = makeUint8ArrayTrans Encoding.encodeHex ) -const makeStringTransformation = ( +const makeEncodingTransformation = ( id: string, decode: (s: string) => either_.Either, encode: (u: string) => string ) => transformOrFail( - String$, + String$.annotations({ + description: `A string that is interpreted as being ${id}-encoded and will be decoded into a UTF-8 string` + }), String$, { strict: true, @@ -5745,7 +5751,7 @@ const makeStringTransformation = ( ), encode: (u) => ParseResult.succeed(encode(u)) } - ).annotations({ identifier: id }) + ).annotations({ identifier: `StringFrom${id}` }) /** * Decodes a base64 (RFC4648) encoded string into a UTF-8 string. @@ -5753,8 +5759,8 @@ const makeStringTransformation = ( * @category string transformations * @since 0.67.0 */ -export const StringFromBase64: Schema = makeStringTransformation( - "StringFromBase64", +export const StringFromBase64: Schema = makeEncodingTransformation( + "Base64", Encoding.decodeBase64String, Encoding.encodeBase64 ) @@ -5765,8 +5771,8 @@ export const StringFromBase64: Schema = makeStringTransformation( * @category string transformations * @since 0.67.0 */ -export const StringFromBase64Url: Schema = makeStringTransformation( - "StringFromBase64Url", +export const StringFromBase64Url: Schema = makeEncodingTransformation( + "Base64Url", Encoding.decodeBase64UrlString, Encoding.encodeBase64Url ) @@ -5777,8 +5783,8 @@ export const StringFromBase64Url: Schema = makeStringTransformation( * @category string transformations * @since 0.67.0 */ -export const StringFromHex: Schema = makeStringTransformation( - "StringFromHex", +export const StringFromHex: Schema = makeEncodingTransformation( + "Hex", Encoding.decodeHexString, Encoding.encodeHex ) @@ -6161,7 +6167,7 @@ export { * @since 0.67.0 */ export class DateFromNumber extends transform( - Number$, + Number$.annotations({ description: "a number that will be parsed into a Date" }), DateFromSelf, { strict: true, decode: (n) => new Date(n), encode: (d) => d.getTime() } ).annotations({ identifier: "DateFromNumber" }) {} @@ -6196,7 +6202,7 @@ const decodeDateTime = (input: A, _: ParseOpt * @since 0.70.0 */ export class DateTimeUtcFromNumber extends transformOrFail( - Number$, + Number$.annotations({ description: "a number that will be parsed into a DateTime.Utc" }), DateTimeUtcFromSelf, { strict: true, @@ -6212,7 +6218,7 @@ export class DateTimeUtcFromNumber extends transformOrFail( * @since 0.70.0 */ export class DateTimeUtc extends transformOrFail( - String$, + String$.annotations({ description: "a string that will be parsed into a DateTime.Utc" }), DateTimeUtcFromSelf, { strict: true, @@ -6247,7 +6253,7 @@ export class TimeZoneOffsetFromSelf extends declare( * @since 0.70.0 */ export class TimeZoneOffset extends transform( - Number$, + Number$.annotations({ description: "a number that will be parsed into a TimeZone.Offset" }), TimeZoneOffsetFromSelf, { strict: true, decode: dateTime.zoneMakeOffset, encode: (tz) => tz.offset } ).annotations({ identifier: "TimeZoneOffset" }) {} @@ -6278,7 +6284,7 @@ export class TimeZoneNamedFromSelf extends declare( * @since 0.70.0 */ export class TimeZoneNamed extends transformOrFail( - String$, + String$.annotations({ description: "a string that will be parsed into a TimeZone.Named" }), TimeZoneNamedFromSelf, { strict: true, @@ -6312,7 +6318,7 @@ export const TimeZoneFromSelf: TimeZoneFromSelf = Union(TimeZoneOffsetFromSelf, * @since 0.70.0 */ export class TimeZone extends transformOrFail( - String$, + String$.annotations({ description: "a string that will be parsed into a TimeZone" }), TimeZoneFromSelf, { strict: true, @@ -6356,7 +6362,7 @@ export class DateTimeZonedFromSelf extends declare( * @since 0.70.0 */ export class DateTimeZoned extends transformOrFail( - String$, + String$.annotations({ description: "a string that will be parsed into a DateTime.Zoned" }), DateTimeZonedFromSelf, { strict: true, @@ -6999,11 +7005,17 @@ export const ReadonlyMapFromRecord = ({ key, value }: { key: Schema value: Schema }): Schema, { readonly [x: string]: VI }, KR | VR> => - transform(Record({ key: encodedBoundSchema(key), value }), ReadonlyMapFromSelf({ key, value: typeSchema(value) }), { - strict: true, - decode: (record) => new Map(Object.entries(record)), - encode: record_.fromEntries - }) + transform( + Record({ key: encodedBoundSchema(key), value }).annotations({ + description: "a record that will be parsed into a ReadonlyMap" + }), + ReadonlyMapFromSelf({ key, value: typeSchema(value) }), + { + strict: true, + decode: (record) => new Map(Object.entries(record)), + encode: record_.fromEntries + } + ) /** * @category Map transformations @@ -7013,11 +7025,17 @@ export const MapFromRecord = ({ key, value }: { key: Schema value: Schema }): Schema, { readonly [x: string]: VI }, KR | VR> => - transform(Record({ key: encodedBoundSchema(key), value }), MapFromSelf({ key, value: typeSchema(value) }), { - strict: true, - decode: (record) => new Map(Object.entries(record)), - encode: record_.fromEntries - }) + transform( + Record({ key: encodedBoundSchema(key), value }).annotations({ + description: "a record that will be parsed into a Map" + }), + MapFromSelf({ key, value: typeSchema(value) }), + { + strict: true, + decode: (record) => new Map(Object.entries(record)), + encode: record_.fromEntries + } + ) const setArbitrary = (item: LazyArbitrary, ctx: GenerationContext): LazyArbitrary> => (fc) => { const items = fc.array(item(fc)) @@ -7178,7 +7196,7 @@ export class BigDecimalFromSelf extends declare( * @since 0.67.0 */ export class BigDecimal extends transformOrFail( - String$, + String$.annotations({ description: "a string that will be parsed into a BigDecimal" }), BigDecimalFromSelf, { strict: true, @@ -7199,7 +7217,7 @@ export class BigDecimal extends transformOrFail( * @since 0.67.0 */ export class BigDecimalFromNumber extends transformOrFail( - Number$, + Number$.annotations({ description: "a number that will be parsed into a BigDecimal" }), BigDecimalFromSelf, { strict: true, diff --git a/packages/schema/test/JSONSchema.test.ts b/packages/schema/test/JSONSchema.test.ts index cc93bda672..bb92bdecbe 100644 --- a/packages/schema/test/JSONSchema.test.ts +++ b/packages/schema/test/JSONSchema.test.ts @@ -1930,17 +1930,20 @@ schema (Suspend): ` it("refinement of a transformation without an override annotation", () => { expectJSONSchema(Schema.Trim.pipe(Schema.nonEmptyString()), { "$schema": "http://json-schema.org/draft-07/schema#", - "type": "string" + "type": "string", + "description": "a string that will be trimmed" }, false) expectJSONSchema(Schema.Trim.pipe(Schema.nonEmptyString({ jsonSchema: { title: "Description" } })), { "$schema": "http://json-schema.org/draft-07/schema#", - "type": "string" + "type": "string", + "description": "a string that will be trimmed" }, false) expectJSONSchema( Schema.Trim.pipe(Schema.nonEmptyString()).annotations({ jsonSchema: { title: "Description" } }), { "$schema": "http://json-schema.org/draft-07/schema#", - "type": "string" + "type": "string", + "description": "a string that will be trimmed" }, false ) @@ -2205,9 +2208,10 @@ schema (Suspend): ` }), { "$schema": "http://json-schema.org/draft-07/schema#", - type: "object", - required: [], - properties: {}, + "type": "object", + "description": "a record that will be parsed into a ReadonlyMap", + "required": [], + "properties": {}, "patternProperties": { "": { "type": "string", @@ -2231,9 +2235,10 @@ schema (Suspend): ` }), { "$schema": "http://json-schema.org/draft-07/schema#", - type: "object", - required: [], - properties: {}, + "type": "object", + "description": "a record that will be parsed into a Map", + "required": [], + "properties": {}, "patternProperties": { "": { "type": "string", diff --git a/packages/schema/test/Schema/Duration/Duration.test.ts b/packages/schema/test/Schema/Duration/Duration.test.ts index 759e901a2b..f6d7d6087d 100644 --- a/packages/schema/test/Schema/Duration/Duration.test.ts +++ b/packages/schema/test/Schema/Duration/Duration.test.ts @@ -17,7 +17,7 @@ describe("Duration", () => { [-500, 0], `Duration └─ Encoded side transformation failure - └─ readonly [seconds, nanos] + └─ a tuple of seconds and nanos that will be parsed into a Duration └─ [0] └─ seconds └─ From side refinement failure @@ -30,7 +30,7 @@ describe("Duration", () => { [0, -123], `Duration └─ Encoded side transformation failure - └─ readonly [seconds, nanos] + └─ a tuple of seconds and nanos that will be parsed into a Duration └─ [1] └─ nanos └─ From side refinement failure @@ -43,14 +43,14 @@ describe("Duration", () => { 123, `Duration └─ Encoded side transformation failure - └─ Expected readonly [seconds, nanos], actual 123` + └─ Expected a tuple of seconds and nanos that will be parsed into a Duration, actual 123` ) await Util.expectDecodeUnknownFailure( schema, 123n, `Duration └─ Encoded side transformation failure - └─ Expected readonly [seconds, nanos], actual 123n` + └─ Expected a tuple of seconds and nanos that will be parsed into a Duration, actual 123n` ) }) diff --git a/packages/schema/test/Schema/Map/MapFromRecord.test.ts b/packages/schema/test/Schema/Map/MapFromRecord.test.ts index b89f126882..03d275951e 100644 --- a/packages/schema/test/Schema/Map/MapFromRecord.test.ts +++ b/packages/schema/test/Schema/Map/MapFromRecord.test.ts @@ -15,14 +15,14 @@ describe("MapFromRecord", () => { await Util.expectDecodeUnknownFailure( schema, null, - `({ readonly [x: string]: NumberFromString } <-> Map) + `(a record that will be parsed into a Map <-> Map) └─ Encoded side transformation failure - └─ Expected { readonly [x: string]: NumberFromString }, actual null` + └─ Expected a record that will be parsed into a Map, actual null` ) await Util.expectDecodeUnknownFailure( schema, { a: "1" }, - `({ readonly [x: string]: NumberFromString } <-> Map) + `(a record that will be parsed into a Map <-> Map) └─ Type side transformation failure └─ Map └─ ReadonlyArray @@ -36,9 +36,9 @@ describe("MapFromRecord", () => { await Util.expectDecodeUnknownFailure( schema, { 1: "a" }, - `({ readonly [x: string]: NumberFromString } <-> Map) + `(a record that will be parsed into a Map <-> Map) └─ Encoded side transformation failure - └─ { readonly [x: string]: NumberFromString } + └─ a record that will be parsed into a Map └─ ["1"] └─ NumberFromString └─ Transformation process failure diff --git a/packages/schema/test/Schema/ReadonlyMap/ReadonlyMapFromRecord.test.ts b/packages/schema/test/Schema/ReadonlyMap/ReadonlyMapFromRecord.test.ts index ed7ffadc9a..74d5d0b00e 100644 --- a/packages/schema/test/Schema/ReadonlyMap/ReadonlyMapFromRecord.test.ts +++ b/packages/schema/test/Schema/ReadonlyMap/ReadonlyMapFromRecord.test.ts @@ -15,14 +15,14 @@ describe("ReadonlyMapFromRecord", () => { await Util.expectDecodeUnknownFailure( schema, null, - `({ readonly [x: string]: NumberFromString } <-> ReadonlyMap) + `(a record that will be parsed into a ReadonlyMap <-> ReadonlyMap) └─ Encoded side transformation failure - └─ Expected { readonly [x: string]: NumberFromString }, actual null` + └─ Expected a record that will be parsed into a ReadonlyMap, actual null` ) await Util.expectDecodeUnknownFailure( schema, { a: "1" }, - `({ readonly [x: string]: NumberFromString } <-> ReadonlyMap) + `(a record that will be parsed into a ReadonlyMap <-> ReadonlyMap) └─ Type side transformation failure └─ ReadonlyMap └─ ReadonlyArray @@ -36,9 +36,9 @@ describe("ReadonlyMapFromRecord", () => { await Util.expectDecodeUnknownFailure( schema, { 1: "a" }, - `({ readonly [x: string]: NumberFromString } <-> ReadonlyMap) + `(a record that will be parsed into a ReadonlyMap <-> ReadonlyMap) └─ Encoded side transformation failure - └─ { readonly [x: string]: NumberFromString } + └─ a record that will be parsed into a ReadonlyMap └─ ["1"] └─ NumberFromString └─ Transformation process failure diff --git a/packages/schema/test/Schema/Uint8Array/Uint8Array.test.ts b/packages/schema/test/Schema/Uint8Array/Uint8Array.test.ts index 5d8276afd3..c4130d8537 100644 --- a/packages/schema/test/Schema/Uint8Array/Uint8Array.test.ts +++ b/packages/schema/test/Schema/Uint8Array/Uint8Array.test.ts @@ -20,7 +20,7 @@ describe("Uint8Array > Uint8Array", () => { [12354], `Uint8Array └─ Encoded side transformation failure - └─ an array of 8-bit unsigned integers + └─ an array of 8-bit unsigned integers that will be parsed into a Uint8Array └─ [0] └─ 8-bit unsigned integer └─ Predicate refinement failure