From bedba24ca1369d1c693aaf1b5c25755e05f1cb42 Mon Sep 17 00:00:00 2001 From: Giulio Canti Date: Sun, 20 Aug 2023 14:54:22 +0200 Subject: [PATCH] simplify compose signature (#395) --- .changeset/modern-tips-smell.md | 2 +- README.md | 22 +------------------ dtslint/Schema.ts | 19 ++++++---------- src/Parser.ts | 23 ++++++++----------- src/Schema.ts | 39 ++++++--------------------------- test/ParseJson.ts | 2 +- test/compose.ts | 8 +++---- 7 files changed, 30 insertions(+), 85 deletions(-) diff --git a/.changeset/modern-tips-smell.md b/.changeset/modern-tips-smell.md index 80ba78e5b..8b50def6d 100644 --- a/.changeset/modern-tips-smell.md +++ b/.changeset/modern-tips-smell.md @@ -2,4 +2,4 @@ "@effect/schema": patch --- -compose: allow forcing decoding / encoding (through additional options argument) +compose: allow forcing decoding / encoding diff --git a/README.md b/README.md index 434d9eb41..3789e364f 100644 --- a/README.md +++ b/README.md @@ -1135,24 +1135,6 @@ In this example, we have two schemas, `schema1` and `schema2`. The first schema, Now, by using the `compose` combinator, we can create a new schema, `composedSchema`, that combines the functionality of both `schema1` and `schema2`. This allows us to parse a string and directly obtain an array of numbers as a result. -If you need to compose two schemas, `Schema` and `Schema`, where `C` is a subtype of `B` then you have to add an extra option, `force: "decoding"`: - -```ts -import * as S from "@effect/schema/Schema"; - -// $ExpectType Schema -const schema1 = S.ParseJson; - -// $ExpectType Schema<{ readonly a: number; }, { readonly a: number; }> -const schema2 = S.struct({ a: S.number }); - -// $ExpectType Schema -const composedSchema = S.compose(schema1, schema2, { - // { readonly a: number; } is a subtype of unknown, you need to add an extra option - force: "decoding" -}); -``` - ## InstanceOf In the following section, we demonstrate how to use the `instanceOf` combinator to create a `Schema` for a class instance. @@ -1383,9 +1365,7 @@ You can also compose the `ParseJson` schema with other schemas to refine the par import * as S from "@effect/schema/Schema"; // $ExpectType Schema -const schema = S.ParseJson.pipe( - S.compose(S.struct({ a: S.number }), { force: "decoding" }) -); +const schema = S.ParseJson.pipe(S.compose(S.struct({ a: S.number }))); ``` In this example, we've composed the `ParseJson` schema with a struct schema to ensure that the result will have a specific shape, including an object with a numeric property "a". diff --git a/dtslint/Schema.ts b/dtslint/Schema.ts index 759eb20fd..c7d414b91 100644 --- a/dtslint/Schema.ts +++ b/dtslint/Schema.ts @@ -533,28 +533,23 @@ S.compose(S.split(S.string, ","), S.array(S.NumberFromString)) // $ExpectType Schema S.split(S.string, ",").pipe(S.compose(S.array(S.NumberFromString))) -// $ExpectType Schema -S.compose(S.array(S.NumberFromString))(S.split(S.string, ",")) - // decoding -// @ts-expect-error -S.compose(S.union(S.null, S.string), S.NumberFromString) - // $ExpectType Schema -S.compose(S.union(S.null, S.string), S.NumberFromString, { force: 'decoding' }) +S.compose(S.union(S.null, S.string), S.NumberFromString) // $ExpectType Schema -S.union(S.null, S.string).pipe(S.compose(S.NumberFromString, { force: 'decoding' })) +S.union(S.null, S.string).pipe(S.compose(S.NumberFromString)) // encoding -// @ts-expect-error +// $ExpectType Schema S.compose(S.NumberFromString, S.union(S.null, S.number)) // $ExpectType Schema -S.compose(S.NumberFromString, S.union(S.null, S.number), { force: 'encoding' }) +S.NumberFromString.pipe(S.compose(S.union(S.null, S.number))) -// $ExpectType Schema -S.NumberFromString.pipe(S.compose(S.union(S.null, S.number), { force: 'encoding' })) +// mismatch +// @ts-expect-error +S.compose(S.string, S.number) diff --git a/src/Parser.ts b/src/Parser.ts index e3ac450d1..e5e559dfe 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -298,21 +298,16 @@ const go = (ast: AST.AST, isBoundary = true): Parser => { } } case "Transform": { + const from = go(ast.from, isBoundary) const to = go(ast.to, false) - if (isBoundary) { - const from = go(ast.from) - return (i1, options) => - handleForbidden( - PR.flatMap( - from(i1, options), - (a) => PR.flatMap(ast.decode(a, options), (i2) => to(i2, options)) - ), - options - ) - } else { - return (a, options) => - handleForbidden(PR.flatMap(ast.decode(a, options), (i2) => to(i2, options)), options) - } + return (i1, options) => + handleForbidden( + PR.flatMap( + from(i1, options), + (a) => PR.flatMap(ast.decode(a, options), (i2) => to(i2, options)) + ), + options + ) } case "Declaration": { const decode = ast.decode(...ast.typeParameters) diff --git a/src/Schema.ts b/src/Schema.ts index 3ef69131e..29b8b33ec 100644 --- a/src/Schema.ts +++ b/src/Schema.ts @@ -936,39 +936,14 @@ export const extend: { * @since 1.0.0 */ export const compose: { - (bc: Schema): (ab: Schema) => Schema - ( - cd: Schema, - options: { force: "decoding" } - ): (ab: Schema) => Schema - ( - cd: Schema, - options: { force: "encoding" } - ): (ab: Schema) => Schema - (ab: Schema, bc: Schema): Schema - ( - ab: Schema, - cd: Schema, - options: { force: "decoding" } - ): Schema - ( - ab: Schema, - cd: Schema, - options: { force: "encoding" } - ): Schema + (bc: Schema): (ab: Schema) => Schema + (bc: Schema): (ab: Schema) => Schema + (ab: Schema, cd: Schema): Schema + (ab: Schema, cd: Schema): Schema } = dual( - (args) => isSchema(args[1]), - (ab: any, cd: any, options?: { force: "decoding" | "encoding" }) => { - if (options) { - if (options.force === "decoding") { - return transformResult(ab, cd, P.validateResult(from(cd)), PR.success) - } else { - return transformResult(ab, cd, PR.success, P.validateResult(to(ab))) - } - } else { - return transform(ab, cd, identity, identity) - } - } + 2, + (ab: Schema, cd: Schema): Schema => + transform(ab, cd, identity, identity) ) /** diff --git a/test/ParseJson.ts b/test/ParseJson.ts index fa55a4778..765622678 100644 --- a/test/ParseJson.ts +++ b/test/ParseJson.ts @@ -41,7 +41,7 @@ describe.concurrent("ParseJson", () => { }) it("compose", async () => { - const schema = S.ParseJson.pipe(S.compose(S.struct({ a: S.number }), { force: "decoding" })) + const schema = S.ParseJson.pipe(S.compose(S.struct({ a: S.number }))) await Util.expectParseSuccess(schema, `{"a":1}`, { a: 1 }) await Util.expectParseFailure(schema, `{"a"}`, `Unexpected token } in JSON at position 4`) await Util.expectParseFailure(schema, `{"a":"b"}`, `/a Expected number, actual "b"`) diff --git a/test/compose.ts b/test/compose.ts index 51debc1ad..e90ad5218 100644 --- a/test/compose.ts +++ b/test/compose.ts @@ -10,12 +10,12 @@ describe.concurrent("compose", async () => { }) it("force decoding: (A U B) compose (B -> C)", async () => { - const schema1 = S.compose(S.union(S.null, S.string), S.NumberFromString, { force: "decoding" }) + const schema1 = S.compose(S.union(S.null, S.string), S.NumberFromString) await Util.expectParseSuccess(schema1, "1", 1) await Util.expectParseFailure(schema1, "a", `Expected string -> number, actual "a"`) await Util.expectParseFailure(schema1, null, "Expected string, actual null") const schema2 = S.union(S.null, S.string).pipe( - S.compose(S.NumberFromString, { force: "decoding" }) + S.compose(S.NumberFromString) ) await Util.expectParseSuccess(schema2, "1", 1) await Util.expectParseFailure(schema2, "a", `Expected string -> number, actual "a"`) @@ -23,11 +23,11 @@ describe.concurrent("compose", async () => { }) it("force encoding: (A -> B) compose (C U B)", async () => { - const schema1 = S.compose(S.NumberFromString, S.union(S.null, S.number), { force: "encoding" }) + const schema1 = S.compose(S.NumberFromString, S.union(S.null, S.number)) await Util.expectEncodeSuccess(schema1, 1, "1") await Util.expectEncodeFailure(schema1, null, "Expected number, actual null") const schema2 = S.NumberFromString.pipe( - S.compose(S.union(S.null, S.number), { force: "encoding" }) + S.compose(S.union(S.null, S.number)) ) await Util.expectEncodeSuccess(schema2, 1, "1") await Util.expectEncodeFailure(schema2, null, "Expected number, actual null")