| 1 | import { assert, describe, it } from "@effect/vitest" |
| 2 | import { Schema, SchemaRepresentation } from "effect" |
| 3 | |
| 4 | function decode(schema: Schema.Top, input: unknown): unknown { |
| 5 | return Schema.decodeUnknownSync(schema as Schema.Codec<unknown>)(input) |
| 6 | } |
| 7 | |
| 8 | describe("SchemaRepresentation.fromRepresentations", () => { |
| 9 | it("preserves root order", () => { |
| 10 | const schemas = SchemaRepresentation.fromRepresentations({ |
| 11 | representations: [ |
| 12 | { _tag: "String", checks: [] }, |
| 13 | { _tag: "Boolean", checks: [] }, |
| 14 | { _tag: "Number", checks: [] } |
| 15 | ], |
| 16 | references: {} |
| 17 | }, { revivers: [] }) |
| 18 | |
| 19 | assert.strictEqual(decode(schemas[0], "value"), "value") |
| 20 | assert.strictEqual(decode(schemas[1], true), true) |
| 21 | assert.strictEqual(decode(schemas[2], 1), 1) |
| 22 | }) |
| 23 | |
| 24 | it("does not revive unreachable references", () => { |
| 25 | const schemas = SchemaRepresentation.fromRepresentations({ |
| 26 | representations: [{ _tag: "String", checks: [] }], |
| 27 | references: { |
| 28 | Unused: { |
| 29 | _tag: "Declaration", |
| 30 | typeParameters: [], |
| 31 | representation: { id: "missing", payload: null }, |
| 32 | checks: [] |
| 33 | } |
| 34 | } |
| 35 | }, { revivers: [] }) |
| 36 | |
| 37 | assert.strictEqual(decode(schemas[0], "value"), "value") |
| 38 | }) |
| 39 | |
| 40 | it("normalizes aliases while preserving the outer reference", () => { |
| 41 | const schemas = SchemaRepresentation.fromRepresentations({ |
| 42 | representations: [{ _tag: "Reference", $ref: "Alias" }], |
| 43 | references: { |
| 44 | Alias: { _tag: "Reference", $ref: "Value" }, |
| 45 | Value: { _tag: "String", checks: [] } |
| 46 | } |
| 47 | }, { revivers: [] }) |
| 48 | |
| 49 | assert.strictEqual(schemas[0].ast._tag, "String") |
| 50 | assert.strictEqual(decode(schemas[0], "value"), "value") |
| 51 | assert.deepStrictEqual(SchemaRepresentation.toRepresentation(schemas[0].ast).representation, { |
| 52 | _tag: "Reference", |
| 53 | $ref: "Alias" |
| 54 | }) |
| 55 | }) |
| 56 | |
| 57 | it("shares a resolved reference between roots", () => { |
| 58 | const schemas = SchemaRepresentation.fromRepresentations({ |
| 59 | representations: [ |
| 60 | { _tag: "Reference", $ref: "Shared" }, |
| 61 | { _tag: "Reference", $ref: "Shared" } |
| 62 | ], |
| 63 | references: { |
| 64 | Shared: { _tag: "Number", checks: [] } |
| 65 | } |
| 66 | }, { revivers: [] }) |
| 67 | |
| 68 | assert.strictEqual(schemas[0], schemas[1]) |
| 69 | assert.strictEqual(schemas[0].ast._tag, "Number") |
| 70 | }) |
| 71 | |
| 72 | it("resolves a reachable __proto__ reference", () => { |
| 73 | const references: Record<string, SchemaRepresentation.Representation> = {} |
| 74 | Object.defineProperty(references, "__proto__", { |
| 75 | value: { _tag: "String", checks: [] }, |
| 76 | enumerable: true |
| 77 | }) |
| 78 | const schemas = SchemaRepresentation.fromRepresentations({ |
| 79 | representations: [{ _tag: "Reference", $ref: "__proto__" }], |
| 80 | references |
| 81 | }, { revivers: [] }) |
| 82 | |
| 83 | assert.strictEqual(decode(schemas[0], "value"), "value") |
| 84 | }) |
| 85 | |
| 86 | it("revives recursive definitions", () => { |
| 87 | const schemas = SchemaRepresentation.fromRepresentations({ |
| 88 | representations: [{ _tag: "Reference", $ref: "Recursive" }], |
| 89 | references: { |
| 90 | Recursive: { |
| 91 | _tag: "Objects", |
| 92 | checks: [], |
| 93 | propertySignatures: [ |
| 94 | { |
| 95 | name: "value", |
| 96 | type: { _tag: "Number", checks: [] }, |
| 97 | isOptional: false, |
| 98 | isMutable: false |
| 99 | }, |
| 100 | { |
| 101 | name: "next", |
| 102 | type: { _tag: "Reference", $ref: "Recursive" }, |
| 103 | isOptional: true, |
| 104 | isMutable: false |
| 105 | } |
| 106 | ], |
| 107 | indexSignatures: [] |
| 108 | } |
| 109 | } |
| 110 | }, { revivers: [] }) |
| 111 | |
| 112 | assert.strictEqual(schemas[0].ast._tag, "Objects") |
| 113 | if (schemas[0].ast._tag === "Objects") { |
| 114 | assert.strictEqual(schemas[0].ast.propertySignatures[1].type._tag, "Suspend") |
| 115 | } |
| 116 | assert.deepStrictEqual( |
| 117 | decode(schemas[0], { |
| 118 | value: 1, |
| 119 | next: { value: 2 } |
| 120 | }), |
| 121 | { |
| 122 | value: 1, |
| 123 | next: { value: 2 } |
| 124 | } |
| 125 | ) |
| 126 | }) |
| 127 | |
| 128 | it("revives mutually recursive definitions with concrete roots", () => { |
| 129 | const schemas = SchemaRepresentation.fromRepresentations({ |
| 130 | representations: [ |
| 131 | { _tag: "Reference", $ref: "A" }, |
| 132 | { _tag: "Reference", $ref: "B" } |
| 133 | ], |
| 134 | references: { |
| 135 | A: { |
| 136 | _tag: "Objects", |
| 137 | checks: [], |
| 138 | propertySignatures: [{ |
| 139 | name: "b", |
| 140 | type: { _tag: "Reference", $ref: "B" }, |
| 141 | isOptional: true, |
| 142 | isMutable: false |
| 143 | }], |
| 144 | indexSignatures: [] |
| 145 | }, |
| 146 | B: { |
| 147 | _tag: "Objects", |
| 148 | checks: [], |
| 149 | propertySignatures: [{ |
| 150 | name: "a", |
| 151 | type: { _tag: "Reference", $ref: "A" }, |
| 152 | isOptional: true, |
| 153 | isMutable: false |
| 154 | }], |
| 155 | indexSignatures: [] |
| 156 | } |
| 157 | } |
| 158 | }, { revivers: [] }) |
| 159 | |
| 160 | assert.strictEqual(schemas[0].ast._tag, "Objects") |
| 161 | assert.strictEqual(schemas[1].ast._tag, "Objects") |
| 162 | if (schemas[0].ast._tag === "Objects" && schemas[1].ast._tag === "Objects") { |
| 163 | const b = schemas[0].ast.propertySignatures[0].type |
| 164 | const a = schemas[1].ast.propertySignatures[0].type |
| 165 | assert.isTrue(b._tag === "Suspend" || a._tag === "Suspend") |
| 166 | } |
| 167 | assert.deepStrictEqual(decode(schemas[0], { b: { a: {} } }), { b: { a: {} } }) |
| 168 | assert.deepStrictEqual(decode(schemas[1], { a: { b: {} } }), { a: { b: {} } }) |
| 169 | }) |
| 170 | }) |