diff --git a/.changeset/plenty-sheep-clean.md b/.changeset/plenty-sheep-clean.md new file mode 100644 index 000000000..3949bc770 --- /dev/null +++ b/.changeset/plenty-sheep-clean.md @@ -0,0 +1,5 @@ +--- +"@effect/schema": patch +--- + +fix encode discriminated union with transformation diff --git a/src/Parser.ts b/src/Parser.ts index 2bdee8b42..c7df60202 100644 --- a/src/Parser.ts +++ b/src/Parser.ts @@ -808,7 +808,7 @@ const go = (ast: AST.AST, isDecoding: boolean): Parser<any, any> => { } } case "Union": { - const searchTree = _getSearchTree(ast.types) + const searchTree = _getSearchTree(ast.types, isDecoding) const ownKeys = Internal.ownKeys(searchTree.keys) const len = ownKeys.length const map = new Map<any, Parser<any, any>>() @@ -942,16 +942,17 @@ const fromRefinement = /** @internal */ export const _getLiterals = ( - ast: AST.AST + ast: AST.AST, + isDecoding: boolean ): ReadonlyArray<[PropertyKey, AST.Literal]> => { switch (ast._tag) { case "Declaration": - return _getLiterals(ast.type) + return _getLiterals(ast.type, isDecoding) case "TypeLiteral": { const out: Array<[PropertyKey, AST.Literal]> = [] for (let i = 0; i < ast.propertySignatures.length; i++) { const propertySignature = ast.propertySignatures[i] - const type = AST.from(propertySignature.type) + const type = isDecoding ? AST.from(propertySignature.type) : AST.to(propertySignature.type) if (AST.isLiteral(type) && !propertySignature.isOptional) { out.push([propertySignature.name, type]) } @@ -959,8 +960,9 @@ export const _getLiterals = ( return out } case "Refinement": + return _getLiterals(ast.from, isDecoding) case "Transform": - return _getLiterals(ast.from) + return _getLiterals(isDecoding ? ast.from : ast.to, isDecoding) } return [] } @@ -980,7 +982,8 @@ export const _getLiterals = ( * @internal */ export const _getSearchTree = ( - members: ReadonlyArray<AST.AST> + members: ReadonlyArray<AST.AST>, + isDecoding: boolean ): { keys: { readonly [key: PropertyKey]: { @@ -999,7 +1002,7 @@ export const _getSearchTree = ( const otherwise: Array<AST.AST> = [] for (let i = 0; i < members.length; i++) { const member = members[i] - const tags = _getLiterals(member) + const tags = _getLiterals(member, isDecoding) if (tags.length > 0) { for (let j = 0; j < tags.length; j++) { const [key, literal] = tags[j] diff --git a/test/Parser.test.ts b/test/Parser.test.ts index 790775f05..0e63a368d 100644 --- a/test/Parser.test.ts +++ b/test/Parser.test.ts @@ -130,6 +130,34 @@ describe("Parser", () => { it("encodeEither", () => { const schema = S.NumberFromString expect(P.encodeEither(schema)(1)).toEqual(E.right("1")) + expect( + P.encodeEither( + S.union( + S.transform( + S.struct({ _tag: S.literal("a") }), + S.struct({ _tag: S.literal("b") }), + () => ({ _tag: "b" as const }), + () => ({ _tag: "a" as const }) + ), + S.struct({ _tag: S.literal("c") }) + ) + )({ _tag: "b" }) + ).toEqual(E.right({ _tag: "a" })) + expect( + P.encodeEither( + S.union( + S.struct({ + _tag: S.transform( + S.literal("a"), + S.literal("b"), + () => "b" as const, + () => "a" as const + ) + }), + S.struct({ _tag: S.literal("c") }) + ) + )({ _tag: "b" }) + ).toEqual(E.right({ _tag: "a" })) }) it("encodePromise", async () => { @@ -138,16 +166,17 @@ describe("Parser", () => { }) it("_getLiterals", () => { - expect(P._getLiterals(S.string.ast)).toEqual([]) + expect(P._getLiterals(S.string.ast, true)).toEqual([]) // TypeLiteral - expect(P._getLiterals(S.struct({ _tag: S.literal("a") }).ast)) + expect(P._getLiterals(S.struct({ _tag: S.literal("a") }).ast, true)) .toEqual([["_tag", AST.createLiteral("a")]]) // Refinement expect( P._getLiterals( S.struct({ _tag: S.literal("a") }).pipe( S.filter(() => true) - ).ast + ).ast, + true ) ).toEqual([["_tag", AST.createLiteral("a")]]) // declare @@ -157,25 +186,49 @@ describe("Parser", () => { [], S.struct({ _tag: S.literal("a") }), () => P.parse(S.struct({ _tag: S.literal("a") })) - ).ast + ).ast, + true ) ).toEqual([["_tag", AST.createLiteral("a")]]) // Transform expect( P._getLiterals( - S.struct({ radius: S.number }).pipe(S.attachPropertySignature("kind", "circle")).ast + S.struct({ radius: S.number }).pipe(S.attachPropertySignature("kind", "circle")).ast, + true ) ).toEqual([]) + // Transform encode + expect( + P._getLiterals( + S.struct({ radius: S.number }).pipe(S.attachPropertySignature("kind", "circle")).ast, + false + ) + ).toEqual([["kind", AST.createLiteral("circle")]]) + // property Transform encode + expect( + P._getLiterals( + S.struct({ + _tag: S.transform( + S.literal("a"), + S.literal("b"), + () => "b" as const, + () => "a" as const + ) + }) + .ast, + false + ) + ).toEqual([["_tag", AST.createLiteral("b")]]) }) it("_getSearchTree", () => { - expect(P._getSearchTree([S.string.ast, S.number.ast])).toEqual({ + expect(P._getSearchTree([S.string.ast, S.number.ast], true)).toEqual({ keys: {}, otherwise: [S.string.ast, S.number.ast] }) - expect(P._getSearchTree([S.struct({ _tag: S.literal("a") }).ast, S.number.ast])).toEqual( + expect(P._getSearchTree([S.struct({ _tag: S.literal("a") }).ast, S.number.ast], true)).toEqual( { keys: { _tag: { @@ -193,7 +246,7 @@ describe("Parser", () => { P._getSearchTree([ S.struct({ _tag: S.literal("a") }).ast, S.struct({ _tag: S.literal("b") }).ast - ]) + ], true) ).toEqual({ keys: { _tag: { @@ -211,7 +264,7 @@ describe("Parser", () => { P._getSearchTree([ S.struct({ a: S.literal("A"), c: S.string }).ast, S.struct({ b: S.literal("B"), d: S.number }).ast - ]) + ], true) ).toEqual({ keys: { a: { @@ -236,7 +289,7 @@ describe("Parser", () => { S.struct({ category: S.literal("catA"), tag: S.literal("a") }).ast, S.struct({ category: S.literal("catA"), tag: S.literal("b") }).ast, S.struct({ category: S.literal("catA"), tag: S.literal("c") }).ast - ]) + ], true) ).toEqual({ keys: { category: { @@ -271,7 +324,7 @@ describe("Parser", () => { ) const types = (schema.ast as AST.Union).types expect( - P._getSearchTree(types) + P._getSearchTree(types, true) ).toEqual({ keys: { type: {