| 1 | import * as Str from "effect/String" |
| 2 | import assert from "node:assert/strict" |
| 3 | import * as z from "zod/v4" |
| 4 | |
| 5 | const person = () => z.object({ name: z.string(), age: z.number(), active: z.boolean() }) |
| 6 | const input = { name: "Ada", age: 37, active: true } |
| 7 | const small = person() |
| 8 | const nestedSchema = z.object({ user: person(), tags: z.array(z.string()) }) |
| 9 | const arraySchema = z.array(person()) |
| 10 | const arrayInput = Array.from({ length: 32 }, () => ({ ...input })) |
| 11 | const recordSchema = z.record(z.string(), person()) |
| 12 | const recordInput = Object.fromEntries(arrayInput.map((value, i) => [String(i), value])) |
| 13 | const transformedKeyRecordSchema = z.record( |
| 14 | z.string().overwrite(Str.snakeToCamel), |
| 15 | z.string() |
| 16 | ) |
| 17 | const transformedKeyRecordInput = Object.fromEntries( |
| 18 | Array.from({ length: 32 }, (_, i) => [`field_${i}_value`, String(i)]) |
| 19 | ) |
| 20 | const transformedKeyRecordOutput = Object.fromEntries( |
| 21 | Array.from({ length: 32 }, (_, i) => [`field${i}Value`, String(i)]) |
| 22 | ) |
| 23 | const unionSchema = z.union( |
| 24 | Array.from({ length: 8 }, (_, i) => z.object({ tag: z.literal(i), value: z.number() })) as [ |
| 25 | z.ZodObject, |
| 26 | z.ZodObject, |
| 27 | ...Array<z.ZodObject> |
| 28 | ] |
| 29 | ) |
| 30 | const fields = Object.fromEntries( |
| 31 | Array.from({ length: 32 }, (_, i) => [`v${i}`, z.string().transform(Number)]) |
| 32 | ) |
| 33 | const transformedSchema = z.object(fields) |
| 34 | const transformedInput = Object.fromEntries(Array.from({ length: 32 }, (_, i) => [`v${i}`, String(i)])) |
| 35 | const transformedOutput = Object.fromEntries(Array.from({ length: 32 }, (_, i) => [`v${i}`, i])) |
| 36 | const checkedTransform = z.string().transform(Number).pipe(z.number().positive()) |
| 37 | const declarationSchema = z.set(person()) |
| 38 | const declarationInput = new Set(arrayInput) |
| 39 | const defaultsSchema = z.object( |
| 40 | Object.fromEntries(Array.from({ length: 32 }, (_, i) => [`v${i}`, z.number().default(i)])) |
| 41 | ) |
| 42 | |
| 43 | type Case = { |
| 44 | readonly schema: z.ZodType |
| 45 | readonly input: unknown |
| 46 | readonly expected: unknown |
| 47 | readonly operation?: "is" | "make" | "encode" |
| 48 | readonly invalid?: boolean |
| 49 | } |
| 50 | |
| 51 | const cases: Record<string, Case> = { |
| 52 | parseValid: { schema: small, input, expected: input }, |
| 53 | parseExtra: { schema: small, input: { ...input, extra: 1 }, expected: input }, |
| 54 | parseInvalid: { schema: small, input: { ...input, age: "bad" }, expected: true, invalid: true }, |
| 55 | isValid: { schema: small, input, expected: true, operation: "is" }, |
| 56 | isInvalid: { schema: small, input: { ...input, age: "bad" }, expected: false, operation: "is" }, |
| 57 | encode: { schema: small, input, expected: input, operation: "encode" }, |
| 58 | strict: { |
| 59 | schema: z.strictObject({ name: z.string(), age: z.number(), active: z.boolean() }), |
| 60 | input, |
| 61 | expected: input |
| 62 | }, |
| 63 | nested: { |
| 64 | schema: nestedSchema, |
| 65 | input: { user: input, tags: ["a", "b"] }, |
| 66 | expected: { user: input, tags: ["a", "b"] } |
| 67 | }, |
| 68 | array: { schema: arraySchema, input: arrayInput, expected: arrayInput }, |
| 69 | arrayInvalid: { |
| 70 | schema: arraySchema, |
| 71 | input: [...arrayInput, { ...input, age: "bad" }], |
| 72 | expected: true, |
| 73 | invalid: true |
| 74 | }, |
| 75 | record: { schema: recordSchema, input: recordInput, expected: recordInput }, |
| 76 | recordTransformedKeys: { |
| 77 | schema: transformedKeyRecordSchema, |
| 78 | input: transformedKeyRecordInput, |
| 79 | expected: transformedKeyRecordOutput |
| 80 | }, |
| 81 | union: { schema: unionSchema, input: { tag: 7, value: 1 }, expected: { tag: 7, value: 1 } }, |
| 82 | transform: { schema: transformedSchema, input: transformedInput, expected: transformedOutput }, |
| 83 | transformInvalid: { schema: checkedTransform, input: "-1", expected: true, invalid: true }, |
| 84 | declaration: { schema: declarationSchema, input: declarationInput, expected: declarationInput }, |
| 85 | makeStruct: { schema: defaultsSchema, input: {}, expected: transformedOutput, operation: "make" }, |
| 86 | makeArray: { schema: arraySchema, input: arrayInput, expected: arrayInput, operation: "make" }, |
| 87 | makeUnion: { |
| 88 | schema: unionSchema, |
| 89 | input: { tag: 7, value: 1 }, |
| 90 | expected: { tag: 7, value: 1 }, |
| 91 | operation: "make" |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | const makeFixture = ( |
| 96 | { input, expected, invalid }: Case, |
| 97 | parse: (input: unknown) => unknown |
| 98 | ) => { |
| 99 | return { |
| 100 | run: invalid ? |
| 101 | () => { |
| 102 | try { |
| 103 | parse(input) |
| 104 | return false |
| 105 | } catch { |
| 106 | return true |
| 107 | } |
| 108 | } : |
| 109 | () => parse(input), |
| 110 | validate: (result: unknown) => assert.deepEqual(result, expected) |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | export const compiledFixture = (name: string) => { |
| 115 | const value = cases[name] |
| 116 | const schema = z.compile(value.schema, { strict: true }) |
| 117 | const parse = value.operation === "is" ? |
| 118 | (input: unknown) => z.validate(schema, input) |
| 119 | : value.operation === "encode" ? |
| 120 | (input: unknown) => z.encode(schema, input) |
| 121 | : (input: unknown) => schema.parse(input) |
| 122 | return makeFixture(value, parse) |
| 123 | } |
| 124 | |
| 125 | export const jitlessFixture = (name: string) => { |
| 126 | const value = cases[name] |
| 127 | const parse = value.operation === "is" ? |
| 128 | (input: unknown) => z.validate(value.schema, input, { jitless: true }) |
| 129 | : value.operation === "encode" ? |
| 130 | (input: unknown) => z.encode(value.schema, input, { jitless: true }) |
| 131 | : (input: unknown) => value.schema.parse(input, { jitless: true }) |
| 132 | return makeFixture(value, parse) |
| 133 | } |