| 1 | import assert from "node:assert/strict" |
| 2 | import * as Schema from "../../../src/Schema.ts" |
| 3 | import * as SchemaTransformation from "../../../src/SchemaTransformation.ts" |
| 4 | |
| 5 | export const caseNames = [ |
| 6 | "literal", |
| 7 | "struct-2", |
| 8 | "struct-32", |
| 9 | "struct-256", |
| 10 | "nested", |
| 11 | "composed", |
| 12 | "encoded", |
| 13 | "union-8", |
| 14 | "struct-ready-32", |
| 15 | "struct-ready-256" |
| 16 | ] |
| 17 | |
| 18 | export function fixture(name: string) { |
| 19 | if (name === "literal") { |
| 20 | return { |
| 21 | build: (index: number) => Schema.Literal(index), |
| 22 | input: (index: number) => index, |
| 23 | output: (index: number) => index, |
| 24 | invalid: null |
| 25 | } |
| 26 | } |
| 27 | if (name.startsWith("struct-")) { |
| 28 | const ready = name.startsWith("struct-ready-") |
| 29 | const size = Number(name.slice(ready ? "struct-ready-".length : "struct-".length)) |
| 30 | assert.ok([2, 32, 256].includes(size)) |
| 31 | const keys = Array.from({ length: size }, (_, index) => `field${index}`) |
| 32 | const value = Object.fromEntries(keys.map((key) => [key, "value"])) |
| 33 | if (ready) { |
| 34 | const fields = Object.fromEntries(keys.map((key) => [key, Schema.String])) |
| 35 | return { |
| 36 | build: () => Schema.Struct(fields), |
| 37 | input: () => value, |
| 38 | output: () => value, |
| 39 | invalid: {} |
| 40 | } |
| 41 | } |
| 42 | return { |
| 43 | build: () => Schema.Struct(Object.fromEntries(keys.map((key) => [key, Schema.String]))), |
| 44 | input: () => value, |
| 45 | output: () => value, |
| 46 | invalid: {} |
| 47 | } |
| 48 | } |
| 49 | if (name === "nested") { |
| 50 | const value = (index: number) => ({ |
| 51 | endpoint: index, |
| 52 | payload: { name: "Ada", count: 1, location: { country: "IT", city: "Rome" } } |
| 53 | }) |
| 54 | return { |
| 55 | build: (index: number) => |
| 56 | Schema.Struct({ |
| 57 | endpoint: Schema.Literal(index), |
| 58 | payload: Schema.Struct({ |
| 59 | name: Schema.String, |
| 60 | count: Schema.Number, |
| 61 | location: Schema.Struct({ country: Schema.Literal("IT"), city: Schema.String }) |
| 62 | }) |
| 63 | }), |
| 64 | input: value, |
| 65 | output: value, |
| 66 | invalid: {} |
| 67 | } |
| 68 | } |
| 69 | if (name === "composed") { |
| 70 | const value = (index: number) => ({ |
| 71 | endpoint: index, |
| 72 | name: "Ada", |
| 73 | items: [{ sku: "A", quantity: 1 }], |
| 74 | status: "open" |
| 75 | }) |
| 76 | return { |
| 77 | build: (index: number) => |
| 78 | Schema.Struct({ |
| 79 | endpoint: Schema.Literal(index), |
| 80 | name: Schema.String.check(Schema.isMinLength(1)), |
| 81 | items: Schema.Array(Schema.Struct({ |
| 82 | sku: Schema.String.check(Schema.isMinLength(1)), |
| 83 | quantity: Schema.Number.check(Schema.isGreaterThanOrEqualTo(1)) |
| 84 | })), |
| 85 | note: Schema.optionalKey(Schema.String), |
| 86 | status: Schema.Literals(["open", "closed"]) |
| 87 | }), |
| 88 | input: value, |
| 89 | output: value, |
| 90 | invalid: { endpoint: 0, name: "", items: [{ sku: "", quantity: 0 }], status: "unknown" } |
| 91 | } |
| 92 | } |
| 93 | if (name === "encoded") { |
| 94 | return { |
| 95 | build: (index: number) => { |
| 96 | const amount = Schema.String.pipe(Schema.decodeTo( |
| 97 | Schema.Number.check(Schema.isGreaterThanOrEqualTo(0)), |
| 98 | SchemaTransformation.numberFromString |
| 99 | )) |
| 100 | return Schema.Struct({ |
| 101 | endpoint: Schema.Literal(index), |
| 102 | amount, |
| 103 | values: Schema.Array(amount), |
| 104 | note: Schema.optionalKey(Schema.String) |
| 105 | }) |
| 106 | }, |
| 107 | input: (index: number) => ({ endpoint: index, amount: "1", values: ["2"] }), |
| 108 | output: (index: number) => ({ endpoint: index, amount: 1, values: [2] }), |
| 109 | invalid: { endpoint: 0, amount: "-1", values: ["-2"] } |
| 110 | } |
| 111 | } |
| 112 | if (name === "union-8") { |
| 113 | const value = (index: number) => ({ kind: index * 8 + 7, value: "Ada" }) |
| 114 | return { |
| 115 | build: (index: number) => |
| 116 | Schema.Union(Array.from({ length: 8 }, (_, member) => |
| 117 | Schema.Struct({ |
| 118 | kind: Schema.Literal(index * 8 + member), |
| 119 | value: Schema.String |
| 120 | }))), |
| 121 | input: value, |
| 122 | output: value, |
| 123 | invalid: { kind: -1, value: "Ada" } |
| 124 | } |
| 125 | } |
| 126 | throw new Error(`Unknown fixture: ${name}`) |
| 127 | } |
| 128 | |
| 129 | export function validate(name: string) { |
| 130 | const { build, input, output, invalid } = fixture(name) |
| 131 | for (const index of [0, 1, 999]) { |
| 132 | const schema = build(index) |
| 133 | assert.deepEqual(Schema.decodeUnknownSync(schema)(input(index)), output(index)) |
| 134 | assert.deepEqual(schema.make(output(index) as never), output(index)) |
| 135 | assert.throws(() => Schema.decodeUnknownSync(schema)(invalid)) |
| 136 | } |
| 137 | } |