| 1 | import { Effect, Result, type SchemaAST, SchemaParser } from "effect" |
| 2 | import type * as CompilerRegistryModule from "effect/internal/schema/compilerRegistry" |
| 3 | import assert from "node:assert/strict" |
| 4 | import { join } from "node:path" |
| 5 | import { pathToFileURL } from "node:url" |
| 6 | import { asyncFixture, events, lazy, proof, roots, schemas, suspendEvaluations, synchronous } from "./aot.ts" |
| 7 | import { |
| 8 | Constructed, |
| 9 | constructionCases, |
| 10 | constructionEvents, |
| 11 | constructionOptions, |
| 12 | constructionSchemas |
| 13 | } from "./construction.ts" |
| 14 | |
| 15 | const CompilerRegistry: typeof CompilerRegistryModule = await import( |
| 16 | new URL("../../../src/internal/schema/compilerRegistry.ts", import.meta.url).href |
| 17 | ) |
| 18 | |
| 19 | const options: ReadonlyArray<SchemaAST.ParseOptions | undefined> = [ |
| 20 | undefined, |
| 21 | { errors: "all", reportInput: true }, |
| 22 | { onExcessProperty: "error" }, |
| 23 | { disableChecks: true } |
| 24 | ] |
| 25 | |
| 26 | const snapshot = () => |
| 27 | Object.fromEntries( |
| 28 | Object.entries(synchronous).map(([name, { inputs, schema }]) => { |
| 29 | const is = SchemaParser.is(schema) |
| 30 | const results = options.map((option) => { |
| 31 | const decode = SchemaParser.decodeUnknownResult(schema, option) |
| 32 | return inputs.map((input) => { |
| 33 | events.length = 0 |
| 34 | const result = decode(input) |
| 35 | const calls = [...events] |
| 36 | return { |
| 37 | // Template diagnostics construct internal ASTs with fresh functions. |
| 38 | result: name === "templateLiteral" || name === "templateLiteralParser" |
| 39 | ? Result.mapError(result, (issue) => JSON.stringify(issue)) |
| 40 | : result, |
| 41 | calls, |
| 42 | is: is(input) |
| 43 | } |
| 44 | }) |
| 45 | }) |
| 46 | return [name, results] |
| 47 | }) |
| 48 | ) |
| 49 | |
| 50 | const snapshotAsync = async () => { |
| 51 | const results = [] |
| 52 | const decode = SchemaParser.decodeUnknownEffect(asyncFixture.schema) |
| 53 | for (const input of asyncFixture.inputs) { |
| 54 | events.length = 0 |
| 55 | const result = await Effect.runPromise(Effect.result(decode(input))) |
| 56 | results.push({ result, calls: [...events] }) |
| 57 | } |
| 58 | return results |
| 59 | } |
| 60 | |
| 61 | assert.throws(() => new Function("return true"), EvalError) |
| 62 | const interpreted = snapshot() |
| 63 | const interpretedAsync = await snapshotAsync() |
| 64 | const snapshotConstruction = async () => { |
| 65 | const out = [] |
| 66 | for (const fixture of Object.values(constructionCases)) { |
| 67 | const make = SchemaParser.makeEffect(fixture.schema) |
| 68 | for (const parseOptions of constructionOptions) { |
| 69 | for (const input of fixture.inputs) { |
| 70 | constructionEvents.length = 0 |
| 71 | const result = await Effect.runPromise(Effect.result(make(input as never, { parseOptions }))) |
| 72 | out.push({ result, events: [...constructionEvents] }) |
| 73 | } |
| 74 | } |
| 75 | } |
| 76 | return out |
| 77 | } |
| 78 | const interpretedConstruction = await snapshotConstruction() |
| 79 | assert.equal(suspendEvaluations, 0) |
| 80 | |
| 81 | const before = CompilerRegistry.resolve(schemas.struct.ast) |
| 82 | const empty = await import(pathToFileURL(join(process.argv[2], "empty.mjs")).href) |
| 83 | assert.equal(empty.install([]), undefined) |
| 84 | assert.equal(CompilerRegistry.resolve(schemas.struct.ast), before) |
| 85 | |
| 86 | if (process.argv[3] === "multiple") { |
| 87 | const generated = await import(pathToFileURL(join(process.argv[2], "all.mjs")).href) |
| 88 | assert.equal(CompilerRegistry.resolve(schemas.struct.ast), before) |
| 89 | assert.equal(generated.install(roots), undefined) |
| 90 | } else { |
| 91 | for (const [name, schema] of Object.entries(schemas)) { |
| 92 | const before = name === "struct" ? CompilerRegistry.resolve(schema.ast) : undefined |
| 93 | const generated = await import(pathToFileURL(join(process.argv[2], `${name}.mjs`)).href) |
| 94 | if (before !== undefined) assert.equal(CompilerRegistry.resolve(schema.ast), before) |
| 95 | assert.equal(generated.install([schema.ast]), undefined) |
| 96 | } |
| 97 | } |
| 98 | assert.equal(suspendEvaluations, 0) |
| 99 | |
| 100 | for ( |
| 101 | const name of [ |
| 102 | "struct", |
| 103 | "array", |
| 104 | "tuple", |
| 105 | "tagged", |
| 106 | "sentinel", |
| 107 | "sentinelLookup", |
| 108 | "record", |
| 109 | "transformed", |
| 110 | "transformedStruct", |
| 111 | "pureTransformedStruct", |
| 112 | "checkedTransformedStruct", |
| 113 | "encodingCheckedTransformedStruct", |
| 114 | "asynchronous", |
| 115 | "middleware" |
| 116 | ] |
| 117 | ) { |
| 118 | assert.equal(typeof CompilerRegistry.resolve(schemas[name].ast).compiled, "function", name) |
| 119 | } |
| 120 | |
| 121 | assert.deepEqual(snapshot(), interpreted) |
| 122 | assert.deepEqual(await snapshotAsync(), interpretedAsync) |
| 123 | for (const [name, schema] of Object.entries(constructionSchemas)) { |
| 124 | if (name === "construct-declaration") continue |
| 125 | assert.equal(typeof CompilerRegistry.resolve(schema.ast).compiled, "function", name) |
| 126 | } |
| 127 | assert.deepEqual(await snapshotConstruction(), interpretedConstruction) |
| 128 | const instance = Constructed.make({}) |
| 129 | constructionEvents.length = 0 |
| 130 | assert.equal(Constructed.make(instance), instance) |
| 131 | assert.equal(await Effect.runPromise(Constructed.makeEffect(instance)), instance) |
| 132 | assert.deepEqual(constructionEvents, []) |
| 133 | |
| 134 | const transform = SchemaParser.decodeUnknownResult(schemas.transformed) |
| 135 | events.length = 0 |
| 136 | assert.deepEqual(transform("2"), Result.succeed(2)) |
| 137 | assert.deepEqual(events, ["transform"]) |
| 138 | events.length = 0 |
| 139 | assert.ok(Result.isFailure(transform("-1"))) |
| 140 | assert.deepEqual(events, ["transform"]) |
| 141 | |
| 142 | events.length = 0 |
| 143 | assert.deepEqual(SchemaParser.decodeUnknownResult(schemas.middleware)("-1"), Result.succeed(1)) |
| 144 | assert.deepEqual(events, ["transform", "middleware", "recover"]) |
| 145 | |
| 146 | assert.deepEqual(SchemaParser.decodeUnknownSync(proof)({ value: "a", extra: true }), { value: "a" }) |
| 147 | assert.deepEqual(SchemaParser.make(proof)({ value: "constructed" }), { value: "constructed" }) |
| 148 | assert.ok(Result.isFailure(SchemaParser.decodeUnknownResult(proof)({ value: 1 }))) |
| 149 | assert.equal(SchemaParser.is(proof)({ value: "a" }), true) |
| 150 | assert.equal(SchemaParser.is(proof)({ value: 1 }), false) |
| 151 | assert.deepEqual(SchemaParser.decodeUnknownSync(schemas.proofArray)([{ value: "a", extra: true }]), [{ value: "a" }]) |
| 152 | assert.ok(Result.isFailure(SchemaParser.decodeUnknownResult(schemas.proofArray)([{ value: 1 }]))) |
| 153 | let invalidReads = 0 |
| 154 | assert.ok(Result.isFailure( |
| 155 | SchemaParser.decodeUnknownResult(schemas.proofArray)([{ |
| 156 | get value() { |
| 157 | invalidReads++ |
| 158 | return 1 |
| 159 | } |
| 160 | }]) |
| 161 | )) |
| 162 | assert.equal(invalidReads, 2) |
| 163 | |
| 164 | assert.deepEqual(SchemaParser.decodeUnknownSync(lazy)({ value: "lazy" }), { value: "lazy" }) |
| 165 | assert.equal(suspendEvaluations, 1) |
| 166 | process.stdout.write("AOT integration passed\n") |