diff --git a/.changeset/hip-cups-sleep.md b/.changeset/hip-cups-sleep.md new file mode 100644 index 000000000..0b762a330 --- /dev/null +++ b/.changeset/hip-cups-sleep.md @@ -0,0 +1,5 @@ +--- +"@effect/schema": minor +--- + +update to latest @effect/io diff --git a/.changeset/tame-candles-sing.md b/.changeset/tame-candles-sing.md new file mode 100644 index 000000000..37bafb65f --- /dev/null +++ b/.changeset/tame-candles-sing.md @@ -0,0 +1,5 @@ +--- +"@effect/schema": minor +--- + +refactor optional APIs (Default values and Optional fields as `Option`s) diff --git a/README.md b/README.md index 83597b5a7..38ca35e41 100644 --- a/README.md +++ b/README.md @@ -848,7 +848,7 @@ Optional fields can be configured to accept a default value, making the field op ```ts // $ExpectType Schema<{ readonly a?: number; }, { readonly a: number; }> -const schema = S.struct({ a. S.optional.withDefault(S.number, () => 0) }); +const schema = S.struct({ a. S.optional(S.number).withDefault(() => 0) }); const parse = S.parse(schema) @@ -869,7 +869,7 @@ Optional fields can be configured to transform a value of type `A` into `Option< import * as O from "@effect/data/Option" // $ExpectType Schema<{ readonly a?: number; }, { readonly a: Option; }> -const schema = S.struct({ a. S.optional.toOption(S.number) }); +const schema = S.struct({ a. S.optional(S.number).toOption() }); const parse = S.parse(schema) diff --git a/docs/modules/AST.ts.md b/docs/modules/AST.ts.md index 02690009e..058913784 100644 --- a/docs/modules/AST.ts.md +++ b/docs/modules/AST.ts.md @@ -82,6 +82,7 @@ Added in v1.0.0 - [model](#model) - [AST (type alias)](#ast-type-alias) - [Annotated (interface)](#annotated-interface) + - [Annotations (interface)](#annotations-interface) - [AnyKeyword (interface)](#anykeyword-interface) - [BigIntKeyword (interface)](#bigintkeyword-interface) - [BooleanKeyword (interface)](#booleankeyword-interface) @@ -864,12 +865,22 @@ Added in v1.0.0 ```ts export interface Annotated { - readonly annotations: Record + readonly annotations: Annotations } ``` Added in v1.0.0 +## Annotations (interface) + +**Signature** + +```ts +export interface Annotations extends Record {} +``` + +Added in v1.0.0 + ## AnyKeyword (interface) **Signature** diff --git a/docs/modules/Schema.ts.md b/docs/modules/Schema.ts.md index e61de00f1..e22b9d481 100644 --- a/docs/modules/Schema.ts.md +++ b/docs/modules/Schema.ts.md @@ -85,6 +85,7 @@ Added in v1.0.0 - [json](#json) - [literal](#literal) - [make](#make) + - [propertySignature](#propertysignature) - [readonlyMapFromSelf](#readonlymapfromself) - [readonlySetFromSelf](#readonlysetfromself) - [templateLiteral](#templateliteral) @@ -199,13 +200,12 @@ Added in v1.0.0 - [UUIDTypeId](#uuidtypeid) - [ValidDateTypeId](#validdatetypeid) - [utils](#utils) + - [FromOptionalKeys (type alias)](#fromoptionalkeys-type-alias) - [Join (type alias)](#join-type-alias) - - [OptionalKeys (type alias)](#optionalkeys-type-alias) - [PropertySignature (interface)](#propertysignature-interface) - - [PropertySignatureId](#propertysignatureid) - - [PropertySignatureId (type alias)](#propertysignatureid-type-alias) - [Spread (type alias)](#spread-type-alias) - [ToAsserts](#toasserts) + - [ToOptionalKeys (type alias)](#tooptionalkeys-type-alias) - [from](#from) - [getPropertySignatures](#getpropertysignatures) - [optional](#optional) @@ -675,7 +675,7 @@ export declare const declare: ( decode: ( ...typeParameters: ReadonlyArray> ) => (input: unknown, options?: ParseOptions | undefined) => ParseResult, - annotations?: Record | undefined + annotations?: AST.Annotations | undefined ) => Schema ``` @@ -778,10 +778,7 @@ Added in v1.0.0 **Signature** ```ts -export declare const lazy: ( - f: () => Schema, - annotations?: Record | undefined -) => Schema +export declare const lazy: (f: () => Schema, annotations?: AST.Annotations | undefined) => Schema ``` Added in v1.0.0 @@ -950,20 +947,20 @@ export declare const struct: < string | number | symbol, | Schema | Schema - | PropertySignature - | PropertySignature + | PropertySignature + | PropertySignature > >( fields: Fields ) => Schema< Spread< - { readonly [K in Exclude>]: From } & { - readonly [K in OptionalKeys]?: From | undefined + { readonly [K in Exclude>]: From } & { + readonly [K in FromOptionalKeys]?: From | undefined } >, Spread< - { readonly [K in Exclude>]: To } & { - readonly [K in OptionalKeys]?: To | undefined + { readonly [K in Exclude>]: To } & { + readonly [K in ToOptionalKeys]?: To | undefined } > > @@ -1115,6 +1112,19 @@ export declare const make: (ast: AST.AST) => Schema Added in v1.0.0 +## propertySignature + +**Signature** + +```ts +export declare const propertySignature: ( + schema: Schema, + annotations?: AST.Annotations | undefined +) => PropertySignature +``` + +Added in v1.0.0 + ## readonlyMapFromSelf **Signature** @@ -1157,7 +1167,7 @@ Added in v1.0.0 ```ts export declare const uniqueSymbol: ( symbol: S, - annotations?: Record | undefined + annotations?: AST.Annotations | undefined ) => Schema ``` @@ -2303,30 +2313,30 @@ Added in v1.0.0 # utils -## Join (type alias) +## FromOptionalKeys (type alias) **Signature** ```ts -export type Join = T extends [infer Head, ...infer Tail] - ? `${Head & (string | number | bigint | boolean | null | undefined)}${Tail extends [] ? '' : Join}` - : never +export type FromOptionalKeys = { + [K in keyof Fields]: Fields[K] extends + | PropertySignature + | PropertySignature + ? K + : never +}[keyof Fields] ``` Added in v1.0.0 -## OptionalKeys (type alias) +## Join (type alias) **Signature** ```ts -export type OptionalKeys = { - [K in keyof Fields]: Fields[K] extends - | PropertySignature - | PropertySignature - ? K - : never -}[keyof Fields] +export type Join = T extends [infer Head, ...infer Tail] + ? `${Head & (string | number | bigint | boolean | null | undefined)}${Tail extends [] ? '' : Join}` + : never ``` Added in v1.0.0 @@ -2336,42 +2346,19 @@ Added in v1.0.0 **Signature** ```ts -export interface PropertySignature { +export interface PropertySignature { readonly From: (_: From) => From + readonly FromIsOptional: FromIsOptional readonly To: (_: To) => To readonly ToIsOptional: ToIsOptional - readonly _id: PropertySignatureId - readonly options?: - | { readonly to: 'Option' } - | { - readonly to: 'default' - readonly value: LazyArg - } + readonly optional: () => PropertySignature + readonly withDefault: (value: () => To) => PropertySignature + readonly toOption: () => PropertySignature, false> } ``` Added in v1.0.0 -## PropertySignatureId - -**Signature** - -```ts -export declare const PropertySignatureId: typeof PropertySignatureId -``` - -Added in v1.0.0 - -## PropertySignatureId (type alias) - -**Signature** - -```ts -export type PropertySignatureId = typeof PropertySignatureId -``` - -Added in v1.0.0 - ## Spread (type alias) **Signature** @@ -2396,6 +2383,22 @@ export declare const ToAsserts: P.ToAsserts Added in v1.0.0 +## ToOptionalKeys (type alias) + +**Signature** + +```ts +export type ToOptionalKeys = { + [K in keyof Fields]: Fields[K] extends + | PropertySignature + | PropertySignature + ? K + : never +}[keyof Fields] +``` + +Added in v1.0.0 + ## from **Signature** @@ -2445,14 +2448,10 @@ Added in v1.0.0 **Signature** ```ts -export declare const optional: { - (schema: Schema): PropertySignature - toOption: (schema: Schema) => PropertySignature, false> - withDefault: { - (value: LazyArg): (schema: Schema) => PropertySignature - (schema: Schema, value: LazyArg): PropertySignature - } -} +export declare const optional: ( + schema: Schema, + annotations?: AST.Annotations | undefined +) => PropertySignature ``` Added in v1.0.0 diff --git a/dtslint/ts4.9/Schema.ts b/dtslint/ts4.9/Schema.ts index dff6828cc..b3c39c5ab 100644 --- a/dtslint/ts4.9/Schema.ts +++ b/dtslint/ts4.9/Schema.ts @@ -255,32 +255,32 @@ S.struct({ a: pipe(S.string, S.optional) }) S.struct({ a: S.optional(S.never) }) // --------------------------------------------- -// optional.withDefault +// optional().withDefault() // --------------------------------------------- // $ExpectType Schema<{ readonly a: string; readonly b: number; readonly c?: boolean; }, { readonly a: string; readonly b: number; readonly c: boolean; }> -S.struct({ a: S.string, b: S.number, c: S.optional.withDefault(S.boolean, () => false) }); +S.struct({ a: S.string, b: S.number, c: S.optional(S.boolean).withDefault(() => false) }); // $ExpectType Schema<{ readonly a: string; readonly b: number; readonly c?: string; }, { readonly a: string; readonly b: number; readonly c: number; }> -S.struct({ a: S.string, b: S.number, c: S.optional.withDefault(NumberFromString, () => 0) }); +S.struct({ a: S.string, b: S.number, c: S.optional(NumberFromString).withDefault(() => 0) }); // piping // $ExpectType Schema<{ readonly a?: string; }, { readonly a: string; }> -S.struct({ a: pipe(S.string, S.optional.withDefault(() => '')) }) +S.struct({ a: pipe(S.string, S.optional).withDefault(() => '') }) // --------------------------------------------- -// optional.toOption +// optional().toOption() // --------------------------------------------- // $ExpectType Schema<{ readonly a: string; readonly b: number; readonly c?: boolean; }, { readonly a: string; readonly b: number; readonly c: Option; }> -S.struct({ a: S.string, b: S.number, c: S.optional.toOption(S.boolean) }); +S.struct({ a: S.string, b: S.number, c: S.optional(S.boolean).toOption() }); // $ExpectType Schema<{ readonly a: string; readonly b: number; readonly c?: string; }, { readonly a: string; readonly b: number; readonly c: Option; }> -S.struct({ a: S.string, b: S.number, c: S.optional.toOption(NumberFromString) }); +S.struct({ a: S.string, b: S.number, c: S.optional(NumberFromString).toOption() }); // piping // $ExpectType Schema<{ readonly a?: string; }, { readonly a: Option; }> -S.struct({ a: pipe(S.string, S.optional.toOption) }) +S.struct({ a: pipe(S.string, S.optional).toOption() }) // --------------------------------------------- // pick diff --git a/package.json b/package.json index fcd4f1065..dc7efa050 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ }, "dependencies": { "@effect/data": "^0.12.2", - "@effect/io": "^0.22.2", + "@effect/io": "^0.24.2", "fast-check": "^3.8.0" }, "pnpm": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9941630ef..8c62bc470 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,7 +14,7 @@ specifiers: '@effect-ts/build-utils': ^0.40.7 '@effect-ts/core': ^0.60.5 '@effect/data': ^0.12.2 - '@effect/io': ^0.22.2 + '@effect/io': ^0.24.2 '@effect/language-service': ^0.0.19 '@repo-tooling/eslint-plugin-dprint': ^0.0.4 '@types/benchmark': ^2.1.2 @@ -57,7 +57,7 @@ specifiers: dependencies: '@effect/data': 0.12.2 - '@effect/io': 0.22.2 + '@effect/io': 0.24.2 fast-check: 3.8.0 devDependencies: @@ -631,8 +631,8 @@ packages: resolution: {integrity: sha512-bmWRTcsSRG1FlBswT+D8SxW6r3c4B3pKJrjYfitCD03tXHnup5gLHaWqbFgIjiyYITS+UsJRRjVrNxvyGG4ZNQ==} dev: false - /@effect/io/0.22.2: - resolution: {integrity: sha512-6Teo25WVHXiIQoLLVp0BVyjTsQ/Avtr3GtTHPi0R2rgOHrNUzOf9feV7+EHzIjL7I5Xfv7P7kx8MrAc5Gv5QDg==} + /@effect/io/0.24.2: + resolution: {integrity: sha512-T0itAO6H6rVZAmCF4b3uLa1aSOBRnM7W+3HrN21U+M8ZC9tBRtxJkg85SYkDClw265bBZTxuZQSy2O0CXydx2w==} dependencies: '@effect/data': 0.12.2 dev: false diff --git a/src/AST.ts b/src/AST.ts index e0c01e3b2..10141cc78 100644 --- a/src/AST.ts +++ b/src/AST.ts @@ -161,12 +161,18 @@ export const DocumentationAnnotationId = "@effect/schema/DocumentationAnnotation // models // --------------------------------------------- +/** + * @category model + * @since 1.0.0 + */ +export interface Annotations extends Record {} + /** * @category model * @since 1.0.0 */ export interface Annotated { - readonly annotations: Record + readonly annotations: Annotations } /** diff --git a/src/Schema.ts b/src/Schema.ts index 12d100dc5..e98219e44 100644 --- a/src/Schema.ts +++ b/src/Schema.ts @@ -449,63 +449,92 @@ export type Spread = { /** * @since 1.0.0 */ -export const PropertySignatureId = Symbol.for( - "@effect/schema/Schema/PropertySignatureId" -) +export interface PropertySignature { + readonly From: (_: From) => From + readonly FromIsOptional: FromIsOptional + readonly To: (_: To) => To + readonly ToIsOptional: ToIsOptional + readonly optional: () => PropertySignature + readonly withDefault: (value: () => To) => PropertySignature + readonly toOption: () => PropertySignature, false> +} + +class PropertySignatureImpl + implements PropertySignature +{ + readonly From!: (_: From) => From + readonly FromIsOptional!: FromIsOptional + readonly To!: (_: To) => To + readonly ToIsOptional!: ToIsOptional + + constructor( + readonly _from: AST.AST, + readonly _annotations?: AST.Annotated["annotations"], + readonly _optional?: + | { readonly to: "optional" } + | { readonly to: "Option" } + | { + readonly to: "default" + readonly value: LazyArg + } + ) {} + + optional(): PropertySignature { + if (this._optional) { + throw new Error(`duplicate optional configuration`) + } + return new PropertySignatureImpl(this._from, this._annotations, { to: "optional" }) + } + + withDefault(value: () => To): PropertySignature { + if (this._optional && this._optional.to !== "optional") { + throw new Error(`duplicate optional configuration`) + } + return new PropertySignatureImpl(this._from, this._annotations, { to: "default", value }) + } + + toOption(): PropertySignature, false> { + if (this._optional && this._optional.to !== "optional") { + throw new Error(`duplicate optional configuration`) + } + return new PropertySignatureImpl(this._from, this._annotations, { to: "Option" }) + } +} /** * @since 1.0.0 + * @category constructors */ -export type PropertySignatureId = typeof PropertySignatureId +export const propertySignature = ( + schema: Schema, + annotations?: AST.Annotated["annotations"] +): PropertySignature => new PropertySignatureImpl(schema.ast, annotations) /** * @since 1.0.0 */ -export interface PropertySignature { - readonly From: (_: From) => From - readonly To: (_: To) => To - readonly ToIsOptional: ToIsOptional - readonly _id: PropertySignatureId - readonly options?: { readonly to: "Option" } | { - readonly to: "default" - readonly value: LazyArg - } -} - -const isPropertySignature = ( - schema: object -): schema is PropertySignature => "_id" in schema && schema["_id"] === PropertySignatureId - -const _optional = ( +export const optional = ( schema: Schema, - options?: { readonly to: "Option" } | { readonly to: "default"; readonly value: LazyArg } -): PropertySignature => { - const out: any = make(schema.ast) - out["_id"] = PropertySignatureId - out["options"] = options - return out -} + annotations?: AST.Annotated["annotations"] +): PropertySignature => propertySignature(schema, annotations).optional() /** * @since 1.0.0 */ -export const optional: { - (schema: Schema): PropertySignature - toOption: (schema: Schema) => PropertySignature, false> - withDefault: { - (value: LazyArg): (schema: Schema) => PropertySignature - (schema: Schema, value: LazyArg): PropertySignature - } -} = ((schema: Schema) => _optional(schema)) as any -optional.toOption = (schema) => _optional(schema, { to: "Option" }) as any -optional.withDefault = dual(2, (schema, value) => _optional(schema, { to: "default", value })) +export type ToOptionalKeys = { + [K in keyof Fields]: Fields[K] extends + | PropertySignature + | PropertySignature ? K + : never +}[keyof Fields] /** * @since 1.0.0 */ -export type OptionalKeys = { +export type FromOptionalKeys = { [K in keyof Fields]: Fields[K] extends - PropertySignature | PropertySignature ? K + | PropertySignature + | PropertySignature ? K : never }[keyof Fields] @@ -518,79 +547,115 @@ export const struct = < PropertyKey, | Schema | Schema - | PropertySignature - | PropertySignature + | PropertySignature + | PropertySignature > >( fields: Fields ): Schema< Spread< - & { readonly [K in Exclude>]: From } - & { readonly [K in OptionalKeys]?: From } + & { readonly [K in Exclude>]: From } + & { readonly [K in FromOptionalKeys]?: From } >, Spread< - & { readonly [K in Exclude>]: To } - & { readonly [K in OptionalKeys]?: To } + & { readonly [K in Exclude>]: To } + & { readonly [K in ToOptionalKeys]?: To } > > => { const ownKeys = I.ownKeys(fields) + const propertySignatures: Array = [] const fromPropertySignatures: Array = [] const toPropertySignatures: Array = [] const propertySignatureTransformations: Array = [] for (let i = 0; i < ownKeys.length; i++) { const key = ownKeys[i] - const schema: Schema = fields[key] as any - if (isPropertySignature(schema)) { - fromPropertySignatures.push(AST.createPropertySignature(key, schema.ast, true, true)) - const options = schema.options - if (options) { - switch (options.to) { - case "default": + const field: Schema | PropertySignatureImpl = + fields[key] as any + if (field instanceof PropertySignatureImpl) { + const optional = field._optional + if (optional) { + switch (optional.to) { + case "optional": { + propertySignatures.push( + AST.createPropertySignature(key, field._from, true, true, field._annotations) + ) + fromPropertySignatures.push(AST.createPropertySignature(key, field._from, true, true)) + toPropertySignatures.push( + AST.createPropertySignature( + key, + AST.getTo(field._from), + true, + true, + field._annotations + ) + ) + break + } + case "default": { + fromPropertySignatures.push(AST.createPropertySignature(key, field._from, true, true)) + toPropertySignatures.push( + AST.createPropertySignature( + key, + AST.getTo(field._from), + false, + true, + field._annotations + ) + ) propertySignatureTransformations.push(AST.createPropertySignatureTransformation( key, key, - O.orElse(() => O.some(options.value())), + O.orElse(() => O.some(optional.value())), identity )) + break + } + case "Option": { + fromPropertySignatures.push(AST.createPropertySignature(key, field._from, true, true)) toPropertySignatures.push( - AST.createPropertySignature(key, AST.getTo(schema.ast), false, true) + AST.createPropertySignature( + key, + optionFromSelf(make(AST.getTo(field._from))).ast, + false, + true, + field._annotations + ) ) - break - case "Option": propertySignatureTransformations.push(AST.createPropertySignatureTransformation( key, key, O.some, O.flatten )) - toPropertySignatures.push( - AST.createPropertySignature(key, optionFromSelf(to(schema)).ast, false, true) - ) break + } } } else { + propertySignatures.push( + AST.createPropertySignature(key, field._from, false, true, field._annotations) + ) + fromPropertySignatures.push(AST.createPropertySignature(key, field._from, false, true)) toPropertySignatures.push( - AST.createPropertySignature(key, AST.getTo(schema.ast), true, true) + AST.createPropertySignature(key, AST.getTo(field._from), false, true, field._annotations) ) } } else { - fromPropertySignatures.push(AST.createPropertySignature(key, schema.ast, false, true)) - toPropertySignatures.push( - AST.createPropertySignature(key, AST.getTo(schema.ast), false, true) - ) + propertySignatures.push(AST.createPropertySignature(key, field.ast, false, true)) + fromPropertySignatures.push(AST.createPropertySignature(key, field.ast, false, true)) + toPropertySignatures.push(AST.createPropertySignature(key, AST.getTo(field.ast), false, true)) } } - const from = AST.createTypeLiteral(fromPropertySignatures, []) if (propertySignatureTransformations.length > 0) { return make( AST.createTransformByPropertySignatureTransformations( - from, + AST.createTypeLiteral(fromPropertySignatures, []), AST.createTypeLiteral(toPropertySignatures, []), propertySignatureTransformations ) ) + } else { + return make(AST.createTypeLiteral(propertySignatures, [])) } - return make(from) } /** diff --git a/test/Schema.ts b/test/Schema.ts index 803cd176c..4d5fbf894 100644 --- a/test/Schema.ts +++ b/test/Schema.ts @@ -45,7 +45,6 @@ describe.concurrent("Schema", () => { expect(S.IncludesTypeId).exist expect(S.UUIDTypeId).exist - expect(S.PropertySignatureId).exist expect(S.nullable).exist expect(S.parseResult).exist diff --git a/test/extend.ts b/test/extend.ts index 78de0307c..973be29cc 100644 --- a/test/extend.ts +++ b/test/extend.ts @@ -5,7 +5,7 @@ import * as Util from "@effect/schema/test/util" describe.concurrent("extend", () => { it(`struct with defaults extend struct`, async () => { const schema = pipe( - S.struct({ a: S.optional.withDefault(S.string, () => ""), b: S.string }), + S.struct({ a: S.optional(S.string).withDefault(() => ""), b: S.string }), S.extend(S.struct({ c: S.number })) ) await Util.expectParseSuccess(schema, { b: "b", c: 1 }, { a: "", b: "b", c: 1 }) @@ -15,7 +15,7 @@ describe.concurrent("extend", () => { const schema = pipe( S.struct({ a: S.number }), S.extend( - S.struct({ b: S.string, c: S.optional.withDefault(S.string, () => "") }) + S.struct({ b: S.string, c: S.optional(S.string).withDefault(() => "") }) ) ) await Util.expectParseSuccess(schema, { a: 1, b: "b" }, { a: 1, b: "b", c: "" }) @@ -23,9 +23,9 @@ describe.concurrent("extend", () => { it(`struct with defaults extend struct with defaults `, async () => { const schema = pipe( - S.struct({ a: S.optional.withDefault(S.string, () => ""), b: S.string }), + S.struct({ a: S.optional(S.string).withDefault(() => ""), b: S.string }), S.extend( - S.struct({ c: S.optional.withDefault(S.number, () => 0), d: S.boolean }) + S.struct({ c: S.optional(S.number).withDefault(() => 0), d: S.boolean }) ) ) await Util.expectParseSuccess(schema, { b: "b", d: true }, { a: "", b: "b", c: 0, d: true }) @@ -35,22 +35,22 @@ describe.concurrent("extend", () => { const schema = pipe( S.union( S.struct({ - a: S.optional.withDefault(S.string, () => "a"), + a: S.optional(S.string).withDefault(() => "a"), b: S.string }), S.struct({ - c: S.optional.withDefault(S.string, () => "c"), + c: S.optional(S.string).withDefault(() => "c"), d: S.string }) ), S.extend( S.union( S.struct({ - e: S.optional.withDefault(S.string, () => "e"), + e: S.optional(S.string).withDefault(() => "e"), f: S.string }), S.struct({ - g: S.optional.withDefault(S.string, () => "g"), + g: S.optional(S.string).withDefault(() => "g"), h: S.string }) ) diff --git a/test/optional.ts b/test/optional.ts index f575f9feb..f7cc020af 100644 --- a/test/optional.ts +++ b/test/optional.ts @@ -5,7 +5,7 @@ import * as Util from "@effect/schema/test/util" describe.concurrent("optional", () => { it("default", async () => { const schema = S.struct({ - a: S.optional.withDefault(S.NumberFromString, () => 0) + a: S.optional(S.NumberFromString).withDefault(() => 0) }) await Util.expectParseSuccess(schema, {}, { a: 0 }) await Util.expectParseSuccess(schema, { a: "1" }, { a: 1 }) @@ -16,7 +16,7 @@ describe.concurrent("optional", () => { }) it("Option", async () => { - const schema = S.struct({ a: S.optional.toOption(S.NumberFromString) }) + const schema = S.struct({ a: S.optional(S.NumberFromString).toOption() }) await Util.expectParseSuccess(schema, {}, { a: O.none() }) await Util.expectParseSuccess(schema, { a: "1" }, { a: O.some(1) }) await Util.expectParseFailure(schema, { a: "a" }, `/a Expected string -> number, actual "a"`) @@ -35,8 +35,8 @@ describe.concurrent("optional", () => { const schema = S.struct({ a: S.boolean, b: S.optional(S.NumberFromString), - c: S.optional.withDefault(S.Trim, () => "-"), - d: S.optional.toOption(S.Date) + c: S.optional(S.Trim).withDefault(() => "-"), + d: S.optional(S.Date).toOption() }) await Util.expectParseSuccess(schema, { a: true }, { a: true, c: "-", d: O.none() }) await Util.expectParseSuccess(schema, { a: true, b: "1" }, { diff --git a/test/propertySignature.ts b/test/propertySignature.ts new file mode 100644 index 000000000..eb5659e8a --- /dev/null +++ b/test/propertySignature.ts @@ -0,0 +1,65 @@ +import * as Option from "@effect/data/Option" +import * as S from "@effect/schema/Schema" + +describe.concurrent("propertySignature", () => { + it("should throw on duplicate optional() calls", () => { + expect(() => S.propertySignature(S.string).optional().optional()).toThrowError( + new Error("duplicate optional configuration") + ) + }) + + it("should throw on duplicate withDefault() calls", () => { + expect(() => S.propertySignature(S.string).withDefault(() => "").withDefault(() => "-")) + .toThrowError( + new Error("duplicate optional configuration") + ) + }) + + it("should throw on duplicate toOption() calls", () => { + expect(() => S.propertySignature(S.string).toOption().toOption()).toThrowError( + new Error("duplicate optional configuration") + ) + }) + + it("should throw on duplicate withDefault() / toOption() calls", () => { + expect(() => S.propertySignature(S.string).withDefault(() => "").toOption()).toThrowError( + new Error("duplicate optional configuration") + ) + expect(() => S.propertySignature(S.string).toOption().withDefault(() => Option.none())) + .toThrowError( + new Error("duplicate optional configuration") + ) + }) + + it("should add annotations to propertySignature", () => { + const schema = S.struct({ + a: S.propertySignature(S.string, { a: "a" }) + }) + const ast: any = schema.ast + expect(ast.propertySignatures[0].annotations).toEqual({ a: "a" }) + }) + + it("should add annotations to propertySignature().optional()", () => { + const schema = S.struct({ + a: S.propertySignature(S.string, { a: "a" }).optional() + }) + const ast: any = schema.ast + expect(ast.propertySignatures[0].annotations).toEqual({ a: "a" }) + }) + + it("should add annotations to propertySignature().withDefault()", () => { + const schema = S.struct({ + a: S.propertySignature(S.string, { a: "a" }).withDefault(() => "") + }) + const ast: any = schema.ast + expect(ast.to.propertySignatures[0].annotations).toEqual({ a: "a" }) + }) + + it("should add annotations to propertySignature().toOption()", () => { + const schema = S.struct({ + a: S.propertySignature(S.string, { a: "a" }).toOption() + }) + const ast: any = schema.ast + expect(ast.to.propertySignatures[0].annotations).toEqual({ a: "a" }) + }) +})