import { Cause, DateTime, Duration, Effect, HashMap, Option, Redacted, Result, Schema, SchemaGetter, SchemaIssue, SchemaParser, SchemaTransformation } from "effect" import { TestSchema } from "effect/testing" import { describe, it } from "vitest" import { assertTrue, deepStrictEqual, strictEqual, throws } from "../utils/assert.ts" const isDeno = "Deno" in globalThis const FiniteFromDate = Schema.Date.pipe(Schema.decodeTo( Schema.Number, SchemaTransformation.transform({ decode: (date) => date.getTime(), encode: (n) => new Date(n) }) )) describe("Serializers", () => { describe("toCodecJson", () => { it("exposes the source schema", () => { const schema = Schema.FiniteFromString const serializer = Schema.toCodecJson(schema) strictEqual(serializer.schema, schema) }) it("should reorder the types in the Union based on the encoded side", async () => { const schema = Schema.Union([ Schema.String, Schema.String.pipe(Schema.encodeTo(Schema.BigInt, { decode: SchemaGetter.transform((n: bigint) => String(n) + "a"), encode: SchemaGetter.transform(() => 0n) })) ]) const serializer = Schema.toCodecJson(schema) const asserts = new TestSchema.Asserts(Schema.toCodecJson(serializer)) const decoding = asserts.decoding() await decoding.succeed("1", "1a") }) describe("schemas without encoding", () => { describe("Unsupported schemas", () => { it("Struct with Symbol property name", () => { const a = Symbol.for("a") const schema = Schema.Struct({ [a]: Schema.String }) throws( () => Schema.toCodecJson(schema), "Objects property names must be strings" ) }) }) describe("Declaration", () => { it("instanceOf without annotations", async () => { const schema = Schema.instanceOf(URL) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(new URL("https://effect.website"), null) const decoding = asserts.decoding() await decoding.fail("https://effect.website/", `Expected null, got "https://effect.website/"`) }) describe("instanceOf with annotation", () => { it("arg: message: string", async () => { class MyError extends Error { constructor(message?: string) { super(message) this.name = "MyError" Object.setPrototypeOf(this, MyError.prototype) } } const schema = Schema.instanceOf( MyError, { title: "MyError", toCodec: () => Schema.link()( Schema.String, SchemaTransformation.transform({ decode: (message) => new MyError(message), encode: (e) => e.message }) ) } ) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(new MyError("a"), "a") const decoding = asserts.decoding() await decoding.succeed("a", new MyError("a")) }) it("arg: struct", async () => { class MyError extends Error { static Props = Schema.Struct({ message: Schema.String, cause: Schema.String }) constructor(props: typeof MyError.Props["Type"]) { super(props.message, { cause: props.cause }) this.name = "MyError" Object.setPrototypeOf(this, MyError.prototype) } static schema = Schema.instanceOf( MyError, { title: "MyError", toCodec: () => Schema.link()( MyError.Props, SchemaTransformation.transform({ decode: (props) => new MyError(props), encode: (e) => ({ message: e.message, cause: typeof e.cause === "string" ? e.cause : String(e.cause) }) }) ) } ) } const schema = MyError.schema const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(new MyError({ message: "a", cause: "b" }), { message: "a", cause: "b" }) const decoding = asserts.decoding() await decoding.succeed({ message: "a", cause: "b" }, new MyError({ message: "a", cause: "b" })) }) }) }) describe("should return the same reference if nothing changed", () => { it("Struct", async () => { const schema = Schema.Struct({ a: Schema.String, b: Schema.Boolean }) const serializer = Schema.toCodecJson(schema) strictEqual(serializer.ast, schema.ast) }) it("Record", async () => { const schema = Schema.Record(Schema.String, Schema.Boolean) const serializer = Schema.toCodecJson(schema) strictEqual(serializer.ast, schema.ast) }) it("Tuple", async () => { const schema = Schema.Tuple([Schema.String, Schema.Boolean]) const serializer = Schema.toCodecJson(schema) strictEqual(serializer.ast, schema.ast) }) it("Array", async () => { const schema = Schema.Array(Schema.String) const serializer = Schema.toCodecJson(schema) strictEqual(serializer.ast, schema.ast) }) it("Union", async () => { const schema = Schema.Union([Schema.String, Schema.Boolean]) const serializer = Schema.toCodecJson(schema) strictEqual(serializer.ast, schema.ast) }) }) it("should apply the construction process to the provided link in the serializer annotation", async () => { const schema = Schema.Struct({ a: Schema.Date.annotate({ toCodec: () => Schema.link()( Schema.Date, SchemaTransformation.passthrough() ) }), b: Schema.Number }) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed({ a: new Date("2021-01-01"), b: 1 }, { a: "2021-01-01T00:00:00.000Z", b: 1 }) }) it("Never", async () => { const schema = Schema.Never const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.fail({}, "Expected never, got {}") }) it("Any", async () => { const schema = Schema.Any const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(() => {}) const decoding = asserts.decoding() await decoding.succeed(() => {}) }) it("Unknown", async () => { const schema = Schema.Unknown const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed("a") await encoding.succeed(1) await encoding.succeed(true) await encoding.succeed(null) await encoding.succeed({ a: "a", b: 1, c: true }) await encoding.succeed(["a", 1, true]) await encoding.fail({ a: 1n }, `Expected JSON value, got {"a":1n}`) const decoding = asserts.decoding() await decoding.succeed("a") await decoding.succeed(1) await decoding.succeed(true) await decoding.succeed(null) await decoding.succeed({ a: "a", b: 1, c: true }) await decoding.succeed(["a", 1, true]) await decoding.fail({ a: 1n }, `Expected JSON value, got {"a":1n}`) }) it("ObjectKeyword", async () => { const schema = Schema.ObjectKeyword const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed({ a: "a", b: 1, c: true }) await encoding.succeed(["a", 1, true]) await encoding.fail("a", `Expected object | array | function, got "a"`) await encoding.fail({ a: 1n }, `Expected JSON value, got {"a":1n}`) const decoding = asserts.decoding() await decoding.succeed({ a: "a", b: 1, c: true }) await decoding.succeed(["a", 1, true]) await decoding.fail("a", `Expected object | array | function, got "a"`) await decoding.fail({ a: 1n }, `Expected JSON value, got {"a":1n}`) }) it("Undefined", async () => { const schema = Schema.Undefined const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(undefined, null) }) it("Void", async () => { const schema = Schema.Void const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(undefined, null) }) it("Null", async () => { const schema = Schema.Null const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(null) }) it("String", async () => { const schema = Schema.String const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed("a") const decoding = asserts.decoding() await decoding.succeed("a", "a") }) describe("Number", () => { it("Number", async () => { const schema = Schema.Number const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(1) await encoding.succeed(-1) await encoding.succeed(1.2) await encoding.succeed(Infinity, "Infinity") await encoding.succeed(-Infinity, "-Infinity") await encoding.succeed(NaN, "NaN") const decoding = asserts.decoding() await decoding.succeed(1) await decoding.succeed(-1) await decoding.succeed(1.2) await decoding.succeed("Infinity", Infinity) await decoding.succeed("-Infinity", -Infinity) await decoding.succeed("NaN", NaN) await decoding.succeed(Infinity) await decoding.succeed(-Infinity) await decoding.succeed(NaN) await decoding.fail(null, `Expected number | "Infinity" | "-Infinity" | "NaN", got null`) await decoding.fail("a", `Expected "Infinity" | "-Infinity" | "NaN", got "a"`) }) describe("checks", () => { it("Finite", async () => { const schema = Schema.Finite const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(1) await encoding.succeed(-1) await encoding.succeed(1.2) await encoding.fail(Infinity, "Expected a finite number, got Infinity") await encoding.fail(-Infinity, "Expected a finite number, got -Infinity") await encoding.fail(NaN, "Expected a finite number, got NaN") const decoding = asserts.decoding() await decoding.succeed(1) await decoding.succeed(-1) await decoding.succeed(1.2) await decoding.fail("Infinity", `Expected number, got "Infinity"`) await decoding.fail("-Infinity", `Expected number, got "-Infinity"`) await decoding.fail("NaN", `Expected number, got "NaN"`) await decoding.fail(Infinity, `Expected a finite number, got Infinity`) await decoding.fail(-Infinity, `Expected a finite number, got -Infinity`) await decoding.fail(NaN, `Expected a finite number, got NaN`) await decoding.fail(null, `Expected number, got null`) await decoding.fail("a", `Expected number, got "a"`) }) it("Int", async () => { const schema = Schema.Int const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(1) await encoding.succeed(-1) await encoding.fail(1.2, `Expected an integer, got 1.2`) await encoding.fail(Infinity, "Expected an integer, got Infinity") await encoding.fail(-Infinity, "Expected an integer, got -Infinity") await encoding.fail(NaN, "Expected an integer, got NaN") const decoding = asserts.decoding() await decoding.succeed(1) await decoding.succeed(-1) await decoding.fail(1.2, `Expected an integer, got 1.2`) await decoding.fail("Infinity", `Expected number, got "Infinity"`) await decoding.fail("-Infinity", `Expected number, got "-Infinity"`) await decoding.fail("NaN", `Expected number, got "NaN"`) await decoding.fail(Infinity, `Expected an integer, got Infinity`) await decoding.fail(-Infinity, `Expected an integer, got -Infinity`) await decoding.fail(NaN, `Expected an integer, got NaN`) await decoding.fail(null, `Expected number, got null`) await decoding.fail("a", `Expected number, got "a"`) }) it("isGreaterThanOrEqualTo", async () => { const schema = Schema.Number.check(Schema.isGreaterThanOrEqualTo(1)) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(1) await encoding.fail(-1, `Expected a value greater than or equal to 1, got -1`) await encoding.succeed(1.2) await encoding.succeed(Infinity, "Infinity") await encoding.fail(-Infinity, "Expected a value greater than or equal to 1, got -Infinity") await encoding.fail(NaN, "Expected a value greater than or equal to 1, got NaN") const decoding = asserts.decoding() await decoding.succeed(1) await encoding.fail(-1, `Expected a value greater than or equal to 1, got -1`) await decoding.succeed(1.2) await decoding.succeed("Infinity", Infinity) await decoding.fail("-Infinity", `Expected a value greater than or equal to 1, got -Infinity`) await decoding.fail("NaN", `Expected a value greater than or equal to 1, got NaN`) await decoding.succeed(Infinity) await decoding.fail(-Infinity, `Expected a value greater than or equal to 1, got -Infinity`) await decoding.fail(NaN, `Expected a value greater than or equal to 1, got NaN`) await decoding.fail(null, `Expected number | "Infinity" | "-Infinity" | "NaN", got null`) await decoding.fail("a", `Expected "Infinity" | "-Infinity" | "NaN", got "a"`) }) }) }) it("Boolean", async () => { const schema = Schema.Boolean const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(true) const decoding = asserts.decoding() await decoding.succeed(true, true) }) it("Symbol", async () => { const schema = Schema.Symbol const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(Symbol.for("a"), "Symbol(a)") await encoding.fail( Symbol("a"), "cannot serialize to string, Symbol is not registered" ) await encoding.fail( Symbol(), "cannot serialize to string, Symbol is not registered" ) const decoding = asserts.decoding() await decoding.succeed("Symbol(a)", Symbol.for("a")) }) it("UniqueSymbol", async () => { const schema = Schema.UniqueSymbol(Symbol.for("a")) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(Symbol.for("a"), "Symbol(a)") const decoding = asserts.decoding() await decoding.succeed("Symbol(a)", Symbol.for("a")) }) it("BigInt", async () => { const schema = Schema.BigInt const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(1n, "1") const decoding = asserts.decoding() await decoding.succeed("1", 1n) }) it("PropertyKey", async () => { const schema = Schema.PropertyKey const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed("a") await encoding.succeed(1) await encoding.succeed(Symbol.for("a"), "Symbol(a)") const decoding = asserts.decoding() await decoding.succeed("a") await decoding.succeed(1) await decoding.succeed("Symbol(a)", Symbol.for("a")) }) describe("Literal", () => { it("string", async () => { const schema = Schema.Literal("a") const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed("a") const decoding = asserts.decoding() await decoding.succeed("a") }) it("number", async () => { const schema = Schema.Literal(1) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(1) const decoding = asserts.decoding() await decoding.succeed(1) }) it("boolean", async () => { const schema = Schema.Literal(true) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(true) const decoding = asserts.decoding() await decoding.succeed(true) }) it("bigint", async () => { const schema = Schema.Literal(1n) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(1n, "1") const decoding = asserts.decoding() await decoding.succeed("1", 1n) }) }) it("Literals", async () => { const schema = Schema.Literals(["a", 1, 2n, true]) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const decoding = asserts.decoding() await decoding.fail( "-", `Expected "a" | 1 | "2" | true, got "-"` ) }) describe("TemplateLiteral", () => { it("1n + string", async () => { const schema = Schema.TemplateLiteral([1n, Schema.String]) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed("1a") const decoding = asserts.decoding() await decoding.succeed("1a") }) it(`"a" + bigint`, async () => { const schema = Schema.TemplateLiteral(["a", Schema.BigInt]) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed("a1") const decoding = asserts.decoding() await decoding.succeed("a1") }) }) it("Enum", async () => { enum Fruits { Apple, Banana } const schema = Schema.Enum(Fruits) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(Fruits.Apple, 0) await encoding.succeed(Fruits.Banana, 1) const decoding = asserts.decoding() await decoding.succeed(0, Fruits.Apple) await decoding.succeed(1, Fruits.Banana) }) describe("Struct", () => { it("Date", async () => { const schema = Schema.Struct({ a: Schema.Date }) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( { a: new Date("2021-01-01") }, { a: "2021-01-01T00:00:00.000Z" } ) }) it("UndefinedOr(Date)", async () => { const schema = Schema.Struct({ a: Schema.UndefinedOr(Schema.Date) }) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed({ a: new Date("2021-01-01") }, { a: "2021-01-01T00:00:00.000Z" }) await encoding.succeed({ a: undefined }, { a: null }) const decoding = asserts.decoding() await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, { a: new Date("2021-01-01") }) await decoding.succeed({ a: null }, { a: undefined }) }) it("NullOr(Date)", async () => { const schema = Schema.Struct({ a: Schema.NullOr(Schema.Date) }) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed({ a: new Date("2021-01-01") }, { a: "2021-01-01T00:00:00.000Z" }) await encoding.succeed({ a: null }, { a: null }) const decoding = asserts.decoding() await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, { a: new Date("2021-01-01") }) await decoding.succeed({ a: null }, { a: null }) }) it("optionalKey(Date)", async () => { const schema = Schema.Struct({ a: Schema.optionalKey(Schema.Date) }) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed({ a: new Date("2021-01-01") }, { a: "2021-01-01T00:00:00.000Z" }) await encoding.succeed({}, {}) const decoding = asserts.decoding() await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, { a: new Date("2021-01-01") }) await decoding.succeed({}, {}) }) it("optional(Date)", async () => { const schema = Schema.Struct({ a: Schema.optional(Schema.Date) }) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed({ a: new Date("2021-01-01") }, { a: "2021-01-01T00:00:00.000Z" }) await encoding.succeed({}, {}) await encoding.succeed({ a: undefined }, { a: null }) const decoding = asserts.decoding() await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, { a: new Date("2021-01-01") }) await decoding.succeed({}, {}) await decoding.succeed({ a: null }, { a: undefined }) }) }) it("Record(Symbol, Date)", async () => { const schema = Schema.Record(Schema.Symbol, Schema.Date) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( { [Symbol.for("a")]: new Date("2021-01-01"), [Symbol.for("b")]: new Date("2021-01-01") }, { "Symbol(a)": "2021-01-01T00:00:00.000Z", "Symbol(b)": "2021-01-01T00:00:00.000Z" } ) const decoding = asserts.decoding() await decoding.succeed( { "Symbol(a)": "2021-01-01T00:00:00.000Z", "Symbol(b)": "2021-01-01T00:00:00.000Z" }, { [Symbol.for("a")]: new Date("2021-01-01"), [Symbol.for("b")]: new Date("2021-01-01") } ) }) describe("Tuple", () => { it("Date", async () => { const schema = Schema.Tuple([Schema.Date]) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( [new Date("2021-01-01")], ["2021-01-01T00:00:00.000Z"] ) const decoding = asserts.decoding() await decoding.succeed( ["2021-01-01T00:00:00.000Z"], [new Date("2021-01-01")] ) }) it("UndefinedOr(Date)", async () => { const schema = Schema.Tuple([Schema.UndefinedOr(Schema.Date)]) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( [new Date("2021-01-01")], ["2021-01-01T00:00:00.000Z"] ) await encoding.succeed([undefined], [null]) const decoding = asserts.decoding() await decoding.succeed(["2021-01-01T00:00:00.000Z"], [ new Date("2021-01-01") ]) await decoding.succeed([null], [undefined]) }) it("NullOr(Date)", async () => { const schema = Schema.Tuple([Schema.NullOr(Schema.Date)]) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed([new Date("2021-01-01")], [ "2021-01-01T00:00:00.000Z" ]) await encoding.succeed([null], [null]) const decoding = asserts.decoding() await decoding.succeed(["2021-01-01T00:00:00.000Z"], [ new Date("2021-01-01") ]) await decoding.succeed([null], [null]) }) it("optionalKey(Date)", async () => { const schema = Schema.Tuple([Schema.optionalKey(Schema.Date)]) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed([new Date("2021-01-01")], [ "2021-01-01T00:00:00.000Z" ]) await encoding.succeed([], []) const decoding = asserts.decoding() await decoding.succeed(["2021-01-01T00:00:00.000Z"], [ new Date("2021-01-01") ]) await decoding.succeed([], []) }) it("optional(Date)", async () => { const schema = Schema.Tuple([Schema.optional(Schema.Date)]) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed([new Date("2021-01-01")], [ "2021-01-01T00:00:00.000Z" ]) await encoding.succeed([], []) await encoding.succeed([undefined], [null]) const decoding = asserts.decoding() await decoding.succeed(["2021-01-01T00:00:00.000Z"], [ new Date("2021-01-01") ]) await decoding.succeed([], []) await decoding.succeed([null], [undefined]) }) }) it("Array(Date)", async () => { const schema = Schema.Array(Schema.Date) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( [new Date("2021-01-01"), new Date("2021-01-02")], ["2021-01-01T00:00:00.000Z", "2021-01-02T00:00:00.000Z"] ) }) describe("Union", () => { it("NullOr(String)", async () => { const schema = Schema.NullOr(Schema.String) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed("a") await encoding.succeed(null) const decoding = asserts.decoding() await decoding.succeed(null) await decoding.succeed("a") }) it("NullOr(Number)", async () => { const schema = Schema.NullOr(Schema.Number) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(1) await encoding.succeed(null) const decoding = asserts.decoding() await decoding.succeed(null) await decoding.succeed(1) }) it("Array(NullOr(Number))", async () => { const schema = Schema.Array(Schema.NullOr(Schema.Number)) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed([1, null]) const decoding = asserts.decoding() await decoding.succeed([1, null]) }) it("Union(Schema.Date, Finite)", async () => { const schema = Schema.Union([Schema.Date, Schema.Finite]) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( new Date("2021-01-01"), "2021-01-01T00:00:00.000Z" ) await encoding.succeed(0) }) }) it("Suspend", async () => { interface Category { readonly a: number readonly categories: ReadonlyArray } const schema = Schema.Struct({ a: Schema.Finite.check(Schema.isGreaterThan(0)), categories: Schema.Array(Schema.suspend((): Schema.Codec => schema)) }) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed({ a: 1, categories: [] }, { a: 1, categories: [] }) await encoding.succeed({ a: 1, categories: [{ a: 2, categories: [] }] }, { a: 1, categories: [ { a: 2, categories: [] } ] }) const decoding = asserts.decoding() await decoding.succeed({ a: 1, categories: [] }, { a: 1, categories: [] }) await decoding.succeed({ a: 1, categories: [ { a: 2, categories: [] } ] }, { a: 1, categories: [{ a: 2, categories: [] }] }) }) it("Class", async () => { class A extends Schema.Class("A")(Schema.Struct({ a: Schema.Finite })) {} const asserts = new TestSchema.Asserts(Schema.toCodecJson(Schema.toType(A))) const encoding = asserts.encoding() await encoding.succeed(new A({ a: 0 }), { a: 0 }) const decoding = asserts.decoding() await decoding.succeed({ a: 0 }, new A({ a: 0 })) }) it("ErrorClass", async () => { class E extends Schema.ErrorClass("E")({ a: Schema.Finite }) {} const asserts = new TestSchema.Asserts(Schema.toCodecJson(Schema.toType(E))) const encoding = asserts.encoding() await encoding.succeed(new E({ a: 0 }), { a: 0 }) const decoding = asserts.decoding() await decoding.succeed({ a: 0 }, new E({ a: 0 })) }) it("Date", async () => { const schema = Schema.Date const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( new Date("2021-01-01"), "2021-01-01T00:00:00.000Z" ) }) it("Error", async () => { const schema = Schema.Error() const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( new Error("a"), { name: "Error", message: "a" } ) await encoding.succeed( new Error("a", { cause: new Error("b") }), { name: "Error", message: "a", cause: { name: "Error", message: "b" } } ) await encoding.succeed( new Error("a", { cause: "b" }), { name: "Error", message: "a", cause: "b" } ) const selfCause = new Error("a") selfCause.stack = "stack" selfCause.cause = selfCause await encoding.succeed( selfCause, { name: "Error", message: "a", cause: "[Circular]" } ) const cyclicCause: Record = {} cyclicCause.self = cyclicCause await encoding.succeed( new Error("a", { cause: cyclicCause }), { name: "Error", message: "a", cause: {} } ) const decoding = asserts.decoding() // Error: message only await decoding.succeed( { message: "a" }, new Error("a") ) // Error: message and name await decoding.succeed( { name: "b", message: "a" }, (() => { const err = new Error("a") err.name = "b" return err })() ) // Error: message, name, and stack await decoding.succeed( { name: "b", message: "a", stack: "c" }, (() => { const err = new Error("a") err.name = "b" err.stack = "c" return err })() ) // Error: message and cause await decoding.succeed( { message: "a", cause: { message: "b" } }, new Error("a", { cause: new Error("b") }) ) // Error: explicit null cause await decoding.succeed( { message: "a", cause: null }, new Error("a", { cause: null }) ) }) it("Error with stack", async () => { const schema = Schema.Error({ includeStack: true }) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const error = new Error("a") error.stack = "stack" const customError = new Error("b") customError.name = "CustomError" customError.stack = "custom stack" const encoding = asserts.encoding() await encoding.succeed(error, { name: "Error", message: "a", stack: "stack" }) await encoding.succeed(customError, { name: "CustomError", message: "b", stack: "custom stack" }) const decoding = asserts.decoding() await decoding.succeed( { message: "a", stack: "stack" }, error ) await decoding.succeed( { name: "CustomError", message: "b", stack: "custom stack" }, customError ) }) it("Error with excluded cause", async () => { const schema = Schema.Error({ excludeCause: true }) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( new Error("a", { cause: new Error("b") }), { name: "Error", message: "a" } ) }) it("URL", async () => { const schema = Schema.URL const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( new URL("https://example.com"), "https://example.com/" ) const decoding = asserts.decoding() await decoding.succeed( "https://example.com", new URL("https://example.com") ) await decoding.succeed( "https://example.com/", new URL("https://example.com") ) await decoding.fail( "not a url", `Invalid URL string: not a url` ) }) it("URLSearchParams", async () => { const schema = Schema.URLSearchParams const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( new URLSearchParams("a=1&b=two"), "a=1&b=two" ) const decoding = asserts.decoding() await decoding.succeed( "a=1&b=two", new URLSearchParams("a=1&b=two") ) }) it.skipIf(isDeno)("File", async () => { const schema = Schema.File const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( new File(["hi"], "note.txt", { type: "text/plain", lastModified: 123 }), { data: "aGk=", type: "text/plain", name: "note.txt", lastModified: 123 } ) const decoding = asserts.decoding() await decoding.succeed( { data: "aGk=", type: "text/plain", name: "note.txt", lastModified: 123 }, new File(["hi"], "note.txt", { type: "text/plain", lastModified: 123 }) ) }) it.skipIf(isDeno)("FormData", async () => { const schema = Schema.FormData const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() const formData = new FormData() formData.append("a", "1") formData.append("b", new File(["hi"], "note.txt", { type: "text/plain", lastModified: 123 })) await encoding.succeed( formData, [ ["a", { _tag: "String", value: "1" }], ["b", { _tag: "File", value: { data: "aGk=", type: "text/plain", name: "note.txt", lastModified: 123 } }] ] ) const decoding = asserts.decoding() const expected = new FormData() expected.append("a", "1") expected.append("b", new File(["hi"], "note.txt", { type: "text/plain", lastModified: 123 })) await decoding.succeed( [ ["a", { _tag: "String", value: "1" }], ["b", { _tag: "File", value: { data: "aGk=", type: "text/plain", name: "note.txt", lastModified: 123 } }] ], expected ) }) it("RegExp", async () => { const schema = Schema.RegExp const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(new RegExp("a"), { source: "a", flags: "" }) await encoding.succeed(new RegExp("a", "i"), { source: "a", flags: "i" }) const decoding = asserts.decoding() await decoding.succeed({ source: "a", flags: "" }, new RegExp("a")) await decoding.succeed({ source: "a", flags: "i" }, new RegExp("a", "i")) await decoding.fail( { source: "(", flags: "" }, `SyntaxError: Invalid regular expression: /(/: Unterminated group` ) await decoding.fail( { source: "a", flags: "x" }, `SyntaxError: Invalid flags supplied to RegExp constructor 'x'` ) }) it("Uint8Array", async () => { const schema = Schema.Uint8Array const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(new Uint8Array([1, 2, 3]), "AQID") const decoding = asserts.decoding() await decoding.succeed("AQID", new Uint8Array([1, 2, 3])) await decoding.fail( "not a base64 string", "Length must be a multiple of 4, but is 19" ) }) it("Duration", async () => { const schema = Schema.Duration const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(Duration.infinity, { _tag: "Infinity" }) await encoding.succeed(Duration.negativeInfinity, { _tag: "NegativeInfinity" }) await encoding.succeed(Duration.nanos(1000n), { _tag: "Nanos", value: "1000" }) await encoding.succeed(Duration.nanos(-1000n), { _tag: "Nanos", value: "-1000" }) await encoding.succeed(Duration.millis(1), { _tag: "Millis", value: 1 }) await encoding.succeed(Duration.millis(-1), { _tag: "Millis", value: -1 }) await encoding.succeed(Duration.zero, { _tag: "Millis", value: 0 }) const decoding = asserts.decoding() await decoding.succeed({ _tag: "Infinity" }, Duration.infinity) await decoding.succeed({ _tag: "NegativeInfinity" }, Duration.negativeInfinity) await decoding.succeed({ _tag: "Millis", value: 1 }, Duration.millis(1)) await decoding.succeed({ _tag: "Millis", value: -1 }, Duration.millis(-1)) await decoding.succeed({ _tag: "Nanos", value: "1000" }, Duration.nanos(1000n)) await decoding.succeed({ _tag: "Nanos", value: "-1000" }, Duration.nanos(-1000n)) }) it("DateTimeUtc", async () => { const schema = Schema.DateTimeUtc const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"), "2021-01-01T00:00:00.000Z" ) const decoding = asserts.decoding() await decoding.succeed( "2021-01-01T00:00:00.000Z", DateTime.makeUnsafe("2021-01-01T00:00:00.000Z") ) }) it("Option(Date)", async () => { const schema = Schema.Option(Schema.Date) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(Option.some(new Date("2021-01-01")), { _tag: "Some", value: "2021-01-01T00:00:00.000Z" }) await encoding.succeed(Option.none(), { _tag: "None" }) }) describe("Redacted", () => { it("Redacted(Option(String))", async () => { const schema = Schema.Redacted(Schema.Option(Schema.String)) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( Redacted.make(Option.none()), { _tag: "None" } ) await encoding.succeed( Redacted.make(Option.some("a")), { _tag: "Some", value: "a" } ) const decoding = asserts.decoding() await decoding.succeed( { _tag: "None" }, Redacted.make(Option.none()) ) await decoding.succeed( { _tag: "Some", value: "a" }, Redacted.make(Option.some("a")) ) }) it("Redacted(Option(String)) disallowJsonEncode", async () => { const schema = Schema.Redacted(Schema.Option(Schema.String), { disallowJsonEncode: true }) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.fail( Redacted.make(Option.none()), `Cannot serialize Redacted` ) await encoding.fail( Redacted.make(Option.some("a")), `Cannot serialize Redacted` ) const decoding = asserts.decoding() await decoding.succeed( { _tag: "None" }, Redacted.make(Option.none()) ) await decoding.succeed( { _tag: "Some", value: "a" }, Redacted.make(Option.some("a")) ) }) it("encoding a Redacted with a label", async () => { const schema = Schema.Redacted(Schema.String, { label: "password" }) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.fail( Redacted.make("a", { label: "API key" }), `Expected "password", got "API key" at ["label"]` ) }) it("encoding a Redacted with a different label", async () => { const schema = Schema.Redacted(Schema.String) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( Redacted.make("a", { label: "API key" }), "a" ) }) }) it("ReadonlySet", async () => { const schema = Schema.ReadonlySet(Schema.Option(Schema.Date)) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(new Set([Option.some(new Date("2021-01-01"))]), [{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }]) const decoding = asserts.decoding() await decoding.succeed( [{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }], new Set([Option.some(new Date("2021-01-01"))]) ) }) it("ReadonlyMap", async () => { const schema = Schema.ReadonlyMap(Schema.Option(Schema.Date), Schema.Finite) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( new Map([[Option.some(new Date("2021-01-01")), 0]]), [[ { _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, 0 ]] ) const decoding = asserts.decoding() await decoding.succeed( [[{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, 0]], new Map([[Option.some(new Date("2021-01-01")), 0]]) ) }) it("HashMap", async () => { const schema = Schema.HashMap(Schema.Option(Schema.Date), Schema.Finite) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( HashMap.make([Option.some(new Date("2021-01-01")), 0]), [[ { _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, 0 ]] ) const decoding = asserts.decoding() await decoding.succeed( [[{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, 0]], HashMap.make([Option.some(new Date("2021-01-01")), 0]) ) }) }) describe("schemas with encoding", () => { it("FiniteFromDate", async () => { const schema = FiniteFromDate const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(0, "1970-01-01T00:00:00.000Z") }) it("Struct", async () => { const schema = Schema.Struct({ a: FiniteFromDate, b: FiniteFromDate }) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( { a: 0, b: 0 }, { a: "1970-01-01T00:00:00.000Z", b: "1970-01-01T00:00:00.000Z" } ) }) it("Tuple(Schema.Date, Schema.Date)", async () => { const schema = Schema.Tuple([FiniteFromDate, FiniteFromDate]) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( [0, 0], ["1970-01-01T00:00:00.000Z", "1970-01-01T00:00:00.000Z"] ) }) it("Class", async () => { class A extends Schema.Class("A")(Schema.Struct({ a: FiniteFromDate })) {} const asserts = new TestSchema.Asserts(Schema.toCodecJson(A)) const encoding = asserts.encoding() await encoding.succeed(new A({ a: 0 }), { a: "1970-01-01T00:00:00.000Z" }) const decoding = asserts.decoding() await decoding.succeed({ a: "1970-01-01T00:00:00.000Z" }, new A({ a: 0 })) }) it("Error", async () => { class E extends Schema.ErrorClass("E")({ a: FiniteFromDate }) {} const asserts = new TestSchema.Asserts(Schema.toCodecJson(E)) const encoding = asserts.encoding() await encoding.succeed(new E({ a: 0 }), { a: "1970-01-01T00:00:00.000Z" }) const decoding = asserts.decoding() await decoding.succeed({ a: "1970-01-01T00:00:00.000Z" }, new E({ a: 0 })) }) it("Enum", async () => { enum Fruits { Apple, Banana = "banana" } const schema = Schema.Enum(Fruits) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(Fruits.Apple, 0) await encoding.succeed(Fruits.Banana, "banana") const decoding = asserts.decoding() await decoding.succeed(0, Fruits.Apple) }) it("Option(Option(FiniteFromDate))", async () => { const schema = Schema.Option(Schema.Option(FiniteFromDate)) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(Option.some(Option.some(0)), { _tag: "Some", value: { _tag: "Some", value: "1970-01-01T00:00:00.000Z" } }) }) it("ReadonlyMap(Option(Symbol), Date)", async () => { const schema = Schema.ReadonlyMap(Schema.Option(Schema.Symbol), Schema.Date) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( new Map([[Option.some(Symbol.for("a")), new Date("2021-01-01")]]), [[ { _tag: "Some", value: "Symbol(a)" }, "2021-01-01T00:00:00.000Z" ]] ) const decoding = asserts.decoding() await decoding.succeed( [[{ _tag: "Some", value: "Symbol(a)" }, "2021-01-01T00:00:00.000Z"]], new Map([[Option.some(Symbol.for("a")), new Date("2021-01-01")]]) ) }) it("Defect", async () => { const schema = Schema.Defect() const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(new Error("a"), { name: "Error", message: "a" }) await encoding.succeed( new Error("a", { cause: new Error("b") }), { name: "Error", message: "a", cause: { name: "Error", message: "b" } } ) await encoding.succeed( new Error("a", { cause: "b" }), { name: "Error", message: "a", cause: "b" } ) await encoding.succeed( new Cause.NoSuchElementError(), { name: "NoSuchElementError", message: "" } ) const cyclicDefect: Record = {} cyclicDefect.self = cyclicDefect await encoding.succeed(cyclicDefect, {}) await encoding.succeed("a") await encoding.succeed({ a: 1 }) const decoding = asserts.decoding() await decoding.succeed( { message: "a", cause: { message: "b" } }, new Error("a", { cause: new Error("b") }) ) await decoding.succeed( { message: "a", cause: null }, new Error("a", { cause: null }) ) }) it("Defect with excluded cause", async () => { const schema = Schema.Defect({ excludeCause: true }) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( new Error("a", { cause: new Error("b") }), { name: "Error", message: "a" } ) }) it("Cause(Option(Finite), Option(String))", async () => { const schema = Schema.Cause(Schema.Option(Schema.Finite), Schema.Option(Schema.String)) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(Cause.fail(Option.some(1)), [{ _tag: "Fail", error: { _tag: "Some", value: 1 } }]) await encoding.succeed(Cause.die(Option.some("a")), [{ _tag: "Die", defect: { _tag: "Some", value: "a" } }]) await encoding.succeed(Cause.interrupt(1), [{ _tag: "Interrupt", fiberId: 1 }]) await encoding.succeed(Cause.interrupt(), [{ _tag: "Interrupt", fiberId: null }]) }) it("DateTimeUtcFromValidDate", async () => { const schema = Schema.DateTimeUtcFromDate const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed( DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"), "2021-01-01T00:00:00.000Z" ) const decoding = asserts.decoding() await decoding.succeed( "2021-01-01T00:00:00.000Z", DateTime.makeUnsafe("2021-01-01T00:00:00.000Z") ) }) }) it("StandardSchemaV1FailureResult", async () => { const b = Symbol.for("b") const schema = Schema.Struct({ a: Schema.NonEmptyString, [b]: Schema.Finite, c: Schema.Tuple([Schema.String]) }) const r = SchemaParser.decodeUnknownExit(schema)({ a: "", c: [] }, { errors: "all" }) assertTrue(r._tag === "Failure") assertTrue(r.cause.reasons.length === 1) const failure = r.cause.reasons[0] assertTrue(failure._tag === "Fail") const failureResult = SchemaIssue.makeFormatterStandardSchemaV1({ leafHook: SchemaIssue.defaultLeafHook })(failure.error) const asserts = new TestSchema.Asserts(Schema.toCodecJson(Schema.StandardSchemaV1FailureResult)) const encoding = asserts.encoding() await encoding.succeed(failureResult, { issues: [ { path: ["a"], message: `Expected a value with a length of at least 1, got ""` }, { path: ["c", 0], message: "Missing key" }, { path: ["Symbol(b)"], message: "Missing key" } ] }) const decoding = asserts.decoding() await decoding.succeed({ issues: [ { path: ["a"], message: `Expected a value with a length of at least 1, got ""` }, { path: ["c", 0], message: "Missing key" }, { path: ["Symbol(b)"], message: "Missing key" } ] }, failureResult) }) }) describe("toCodecStringTree", () => { it("exposes the source schema", () => { const schema = Schema.FiniteFromString const serializer = Schema.toCodecStringTree(schema) strictEqual(serializer.schema, schema) }) it("should reorder the types in the Union based on the encoded side", async () => { const schema = Schema.Union([ Schema.String, Schema.String.pipe(Schema.encodeTo(Schema.BigInt, { decode: SchemaGetter.transform((n: bigint) => String(n) + "a"), encode: SchemaGetter.transform(() => 0n) })) ]) const serializer = Schema.toCodecStringTree(schema) const asserts = new TestSchema.Asserts(Schema.toCodecJson(serializer)) const decoding = asserts.decoding() await decoding.succeed("1", "1a") }) describe("should return the same reference if nothing changed", () => { it("String", async () => { const schema = Schema.String const serializer = Schema.toCodecStringTree(schema) strictEqual(serializer.ast, schema.ast) }) it("Struct({ a: String })", async () => { const schema = Schema.Struct({ a: Schema.String }) const serializer = Schema.toCodecStringTree(schema) strictEqual(serializer.ast, schema.ast) }) }) describe("should memoize the result", () => { it("Struct", async () => { const schema = Schema.Struct({ a: Schema.Finite }) const serializer = Schema.toCodecStringTree(schema) strictEqual(serializer.ast, Schema.toCodecStringTree(serializer).ast) }) it("Array", async () => { const schema = Schema.Array(Schema.Finite) const serializer = Schema.toCodecStringTree(schema) strictEqual(serializer.ast, Schema.toCodecStringTree(serializer).ast) }) }) describe("schemas without encoding", () => { describe("Unsupported schemas", () => { it("Struct with Symbol property name", () => { const a = Symbol.for("a") const schema = Schema.Struct({ [a]: Schema.String }) throws( () => Schema.toCodecStringTree(schema), "Objects property names must be strings" ) }) }) it("Declaration", async () => { const schema = Schema.instanceOf(URL) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(new URL("https://effect.website"), undefined) const decoding = asserts.decoding() await decoding.fail("https://effect.website/", `Expected undefined, got "https://effect.website/"`) }) it("Unknown", async () => { const schema = Schema.Unknown const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed("a") await encoding.fail(1, `Expected StringTree, got 1`) await encoding.succeed({ a: "a" }) await encoding.succeed(["a"]) await encoding.fail({ a: 1 }, `Expected StringTree, got {"a":1}`) const decoding = asserts.decoding() await decoding.succeed("a") await decoding.fail(1, `Expected StringTree, got 1`) await decoding.succeed({ a: "a" }) await decoding.succeed(["a"]) await decoding.fail({ a: 1 }, `Expected StringTree, got {"a":1}`) }) it("ObjectKeyword", async () => { const schema = Schema.ObjectKeyword const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.fail("a", `Expected object | array | function, got "a"`) await encoding.fail(1, `Expected object | array | function, got 1`) await encoding.succeed({ a: "a" }) await encoding.succeed(["a"]) await encoding.fail({ a: 1 }, `Expected StringTree, got {"a":1}`) const decoding = asserts.decoding() await decoding.fail("a", `Expected object | array | function, got "a"`) await decoding.fail(1, `Expected StringTree, got 1`) await decoding.succeed({ a: "a" }) await decoding.succeed(["a"]) await decoding.fail({ a: 1 }, `Expected StringTree, got {"a":1}`) }) it("Never", async () => { const schema = Schema.Never const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.fail({}, "Expected never, got {}") }) it("Any should be an escape hatch", async () => { const schema = Schema.Any const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(() => {}) const decoding = asserts.decoding() await decoding.succeed(() => {}) }) it("Undefined", async () => { const schema = Schema.Undefined const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(undefined) const decoding = asserts.decoding() await decoding.succeed(undefined) }) it("Void", async () => { const schema = Schema.Void const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(undefined) const decoding = asserts.decoding() await decoding.succeed(undefined) }) it("Null", async () => { const schema = Schema.Null const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(null, "null") const decoding = asserts.decoding() await decoding.succeed("null", null) }) it("String", async () => { const schema = Schema.String const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed("a") const decoding = asserts.decoding() await decoding.succeed("a") }) describe("Number", () => { it("Number", async () => { const schema = Schema.Number const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(1, "1") await encoding.succeed(-1, "-1") await encoding.succeed(1.2, "1.2") await encoding.succeed(Infinity, "Infinity") await encoding.succeed(-Infinity, "-Infinity") await encoding.succeed(NaN, "NaN") const decoding = asserts.decoding() await decoding.succeed("1", 1) await decoding.succeed("-1", -1) await decoding.succeed("1.2", 1.2) await decoding.succeed("Infinity", Infinity) await decoding.succeed("-Infinity", -Infinity) await decoding.succeed("NaN", NaN) await decoding.fail(Infinity, `Expected string | "Infinity" | "-Infinity" | "NaN", got Infinity`) await decoding.fail(-Infinity, `Expected string | "Infinity" | "-Infinity" | "NaN", got -Infinity`) await decoding.fail(NaN, `Expected string | "Infinity" | "-Infinity" | "NaN", got NaN`) await decoding.fail(null, `Expected string | "Infinity" | "-Infinity" | "NaN", got null`) await decoding.fail( "a", `Expected a string representing a finite number, got "a" Expected "Infinity" | "-Infinity" | "NaN", got "a"` ) }) describe("checks", () => { it("Finite", async () => { const schema = Schema.Finite const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(1, "1") await encoding.succeed(-1, "-1") await encoding.succeed(1.2, "1.2") await encoding.fail(Infinity, "Expected a finite number, got Infinity") await encoding.fail(-Infinity, "Expected a finite number, got -Infinity") await encoding.fail(NaN, "Expected a finite number, got NaN") const decoding = asserts.decoding() await decoding.succeed("1", 1) await decoding.succeed("-1", -1) await decoding.succeed("1.2", 1.2) await decoding.fail("Infinity", `Expected a string representing a finite number, got "Infinity"`) await decoding.fail("-Infinity", `Expected a string representing a finite number, got "-Infinity"`) await decoding.fail("NaN", `Expected a string representing a finite number, got "NaN"`) await decoding.fail(Infinity, `Expected string, got Infinity`) await decoding.fail(-Infinity, `Expected string, got -Infinity`) await decoding.fail(NaN, `Expected string, got NaN`) await decoding.fail(null, `Expected string, got null`) await decoding.fail("a", `Expected a string representing a finite number, got "a"`) }) it("Int", async () => { const schema = Schema.Int const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(1, "1") await encoding.succeed(-1, "-1") await encoding.fail(1.2, `Expected an integer, got 1.2`) await encoding.fail(Infinity, `Expected an integer, got Infinity`) await encoding.fail(-Infinity, `Expected an integer, got -Infinity`) await encoding.fail(NaN, `Expected an integer, got NaN`) const decoding = asserts.decoding() await decoding.succeed("1", 1) await decoding.succeed("-1", -1) await decoding.fail("1.2", `Expected an integer, got 1.2`) await decoding.fail("Infinity", `Expected a string representing a finite number, got "Infinity"`) await decoding.fail("-Infinity", `Expected a string representing a finite number, got "-Infinity"`) await decoding.fail("NaN", `Expected a string representing a finite number, got "NaN"`) await decoding.fail(Infinity, `Expected string, got Infinity`) await decoding.fail(-Infinity, `Expected string, got -Infinity`) await decoding.fail(NaN, `Expected string, got NaN`) await decoding.fail(null, `Expected string, got null`) await decoding.fail("a", `Expected a string representing a finite number, got "a"`) }) it("isGreaterThanOrEqualTo", async () => { const schema = Schema.Number.check(Schema.isGreaterThanOrEqualTo(1)) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(1, "1") await encoding.fail(-1, `Expected a value greater than or equal to 1, got -1`) await encoding.succeed(1.2, "1.2") await encoding.succeed(Infinity, "Infinity") await encoding.fail(-Infinity, "Expected a value greater than or equal to 1, got -Infinity") await encoding.fail(NaN, "Expected a value greater than or equal to 1, got NaN") const decoding = asserts.decoding() await decoding.succeed("1", 1) await decoding.fail("-1", `Expected a value greater than or equal to 1, got -1`) await decoding.succeed("1.2", 1.2) await decoding.succeed("Infinity", Infinity) await decoding.fail("-Infinity", `Expected a value greater than or equal to 1, got -Infinity`) await decoding.fail("NaN", `Expected a value greater than or equal to 1, got NaN`) await decoding.fail(Infinity, `Expected string | "Infinity" | "-Infinity" | "NaN", got Infinity`) await decoding.fail(-Infinity, `Expected string | "Infinity" | "-Infinity" | "NaN", got -Infinity`) await decoding.fail(NaN, `Expected string | "Infinity" | "-Infinity" | "NaN", got NaN`) await decoding.fail(null, `Expected string | "Infinity" | "-Infinity" | "NaN", got null`) await decoding.fail( "a", `Expected a string representing a finite number, got "a" Expected "Infinity" | "-Infinity" | "NaN", got "a"` ) }) }) }) it("Boolean", async () => { const schema = Schema.Boolean const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(true, "true") await encoding.succeed(false, "false") const decoding = asserts.decoding() await decoding.succeed("true", true) await decoding.succeed("false", false) }) it("Symbol", async () => { const schema = Schema.Symbol const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(Symbol.for("a"), "Symbol(a)") await encoding.fail( Symbol("a"), "cannot serialize to string, Symbol is not registered" ) await encoding.fail( Symbol(), "cannot serialize to string, Symbol is not registered" ) const decoding = asserts.decoding() await decoding.succeed("Symbol(a)", Symbol.for("a")) await decoding.fail("a", `Expected a string representing a symbol, got "a"`) }) it("UniqueSymbol", async () => { const schema = Schema.UniqueSymbol(Symbol.for("a")) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(Symbol.for("a"), "Symbol(a)") const decoding = asserts.decoding() await decoding.succeed("Symbol(a)", Symbol.for("a")) await decoding.fail("a", `Expected a string representing a symbol, got "a"`) }) it("BigInt", async () => { const schema = Schema.BigInt const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(1n, "1") const decoding = asserts.decoding() await decoding.succeed("1", 1n) await decoding.fail("a", `Expected a string representing a bigint, got "a"`) }) it("PropertyKey", async () => { const schema = Schema.PropertyKey const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed("a", "a") await encoding.succeed(1, "1") await encoding.succeed(Symbol.for("a"), "Symbol(a)") const decoding = asserts.decoding() await decoding.succeed("a", "a") await decoding.succeed("1", 1) await decoding.succeed("Symbol(a)", Symbol.for("a")) }) describe("Literal", () => { it("string", async () => { const schema = Schema.Literal("a") const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed("a", "a") const decoding = asserts.decoding() await decoding.succeed("a", "a") }) it("number", async () => { const schema = Schema.Literal(1) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(1, "1") const decoding = asserts.decoding() await decoding.succeed("1", 1) }) it("boolean", async () => { const schema = Schema.Literal(true) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(true, "true") const decoding = asserts.decoding() await decoding.succeed("true", true) }) it("bigint", async () => { const schema = Schema.Literal(1n) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(1n, "1") const decoding = asserts.decoding() await decoding.succeed("1", 1n) }) }) it("Literals", async () => { const schema = Schema.Literals(["a", 1, 2n, true]) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const decoding = asserts.decoding() await decoding.fail( "-", `Expected "a" | "1" | "2" | "true", got "-"` ) }) describe("TemplateLiteral", () => { it("1n + string", async () => { const schema = Schema.TemplateLiteral([1n, Schema.String]) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed("1a") const decoding = asserts.decoding() await decoding.succeed("1a") }) it(`"a" + bigint`, async () => { const schema = Schema.TemplateLiteral(["a", Schema.BigInt]) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed("a1") const decoding = asserts.decoding() await decoding.succeed("a1") }) }) it("Enum", async () => { enum Fruits { Apple, Banana } const schema = Schema.Enum(Fruits) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(Fruits.Apple, "0") await encoding.succeed(Fruits.Banana, "1") const decoding = asserts.decoding() await decoding.succeed("0", Fruits.Apple) await decoding.succeed("1", Fruits.Banana) }) describe("Struct", () => { it("Date", async () => { const schema = Schema.Struct({ a: Schema.Date }) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed({ a: new Date("2021-01-01") }, { a: "2021-01-01T00:00:00.000Z" }) const decoding = asserts.decoding() await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, { a: new Date("2021-01-01") }) }) it("UndefinedOr(Date)", async () => { const schema = Schema.Struct({ a: Schema.UndefinedOr(Schema.Date) }) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed({ a: new Date("2021-01-01") }, { a: "2021-01-01T00:00:00.000Z" }) await encoding.succeed({ a: undefined }, { a: undefined }) const decoding = asserts.decoding() await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, { a: new Date("2021-01-01") }) await decoding.succeed({ a: undefined }, { a: undefined }) }) it("NullOr(Date)", async () => { const schema = Schema.Struct({ a: Schema.NullOr(Schema.Date) }) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed({ a: new Date("2021-01-01") }, { a: "2021-01-01T00:00:00.000Z" }) await encoding.succeed({ a: null }, { a: "null" }) const decoding = asserts.decoding() await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, { a: new Date("2021-01-01") }) await decoding.succeed({ a: "null" }, { a: null }) }) it("optionalKey(Date)", async () => { const schema = Schema.Struct({ a: Schema.optionalKey(Schema.Date) }) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed({ a: new Date("2021-01-01") }, { a: "2021-01-01T00:00:00.000Z" }) await encoding.succeed({}, {}) const decoding = asserts.decoding() await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, { a: new Date("2021-01-01") }) await decoding.succeed({}, {}) }) it("optional(Date)", async () => { const schema = Schema.Struct({ a: Schema.optional(Schema.Date) }) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed({ a: new Date("2021-01-01") }, { a: "2021-01-01T00:00:00.000Z" }) await encoding.succeed({}, {}) await encoding.succeed({ a: undefined }, { a: undefined }) const decoding = asserts.decoding() await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, { a: new Date("2021-01-01") }) await decoding.succeed({}, {}) await decoding.succeed({ a: undefined }, { a: undefined }) }) }) it("Record(Symbol, Date)", async () => { const schema = Schema.Record(Schema.Symbol, Schema.Date) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed( { [Symbol.for("a")]: new Date("2021-01-01"), [Symbol.for("b")]: new Date("2021-01-01") }, { "Symbol(a)": "2021-01-01T00:00:00.000Z", "Symbol(b)": "2021-01-01T00:00:00.000Z" } ) const decoding = asserts.decoding() await decoding.succeed( { "Symbol(a)": "2021-01-01T00:00:00.000Z", "Symbol(b)": "2021-01-01T00:00:00.000Z" }, { [Symbol.for("a")]: new Date("2021-01-01"), [Symbol.for("b")]: new Date("2021-01-01") } ) }) describe("Tuple", () => { it("Date", async () => { const schema = Schema.Tuple([Schema.Date]) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed( [new Date("2021-01-01")], ["2021-01-01T00:00:00.000Z"] ) const decoding = asserts.decoding() await decoding.succeed( ["2021-01-01T00:00:00.000Z"], [new Date("2021-01-01")] ) }) it("UndefinedOr(Date)", async () => { const schema = Schema.Tuple([Schema.UndefinedOr(Schema.Date)]) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed( [new Date("2021-01-01")], ["2021-01-01T00:00:00.000Z"] ) await encoding.succeed([undefined]) const decoding = asserts.decoding() await decoding.succeed(["2021-01-01T00:00:00.000Z"], [ new Date("2021-01-01") ]) await decoding.succeed([undefined]) }) it("NullOr(Date)", async () => { const schema = Schema.Tuple([Schema.NullOr(Schema.Date)]) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed([new Date("2021-01-01")], [ "2021-01-01T00:00:00.000Z" ]) await encoding.succeed([null], ["null"]) const decoding = asserts.decoding() await decoding.succeed(["2021-01-01T00:00:00.000Z"], [ new Date("2021-01-01") ]) await decoding.succeed(["null"], [null]) }) it("optionalKey(Date)", async () => { const schema = Schema.Tuple([Schema.optionalKey(Schema.Date)]) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed([new Date("2021-01-01")], [ "2021-01-01T00:00:00.000Z" ]) await encoding.succeed([], []) const decoding = asserts.decoding() await decoding.succeed(["2021-01-01T00:00:00.000Z"], [ new Date("2021-01-01") ]) await decoding.succeed([], []) }) it("optional(Date)", async () => { const schema = Schema.Tuple([Schema.optional(Schema.Date)]) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed([new Date("2021-01-01")], [ "2021-01-01T00:00:00.000Z" ]) await encoding.succeed([], []) await encoding.succeed([undefined]) const decoding = asserts.decoding() await decoding.succeed(["2021-01-01T00:00:00.000Z"], [ new Date("2021-01-01") ]) await decoding.succeed([], []) await decoding.succeed([undefined]) }) }) it("Array(Date)", async () => { const schema = Schema.Array(Schema.Date) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed( [new Date("2021-01-01"), new Date("2021-01-02")], ["2021-01-01T00:00:00.000Z", "2021-01-02T00:00:00.000Z"] ) }) it("Array(Finite) preserves the top-level AST", async () => { const serializer = Schema.toCodecStringTree(Schema.Array(Schema.Finite)) strictEqual(serializer.ast._tag, "Arrays") const asserts = new TestSchema.Asserts(serializer) const encoding = asserts.encoding() await encoding.succeed([1, 2], ["1", "2"]) const decoding = asserts.decoding() await decoding.fail("1,2", `Expected array, got "1,2"`) await decoding.succeed(["1", "2"], [1, 2]) }) describe("Union", () => { it("NullOr(Date)", async () => { const schema = Schema.NullOr(Schema.String) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed("a", "a") await encoding.succeed(null, "null") const decoding = asserts.decoding() await decoding.succeed("null", null) await decoding.succeed("a", "a") }) it("NullOr(Number)", async () => { const schema = Schema.NullOr(Schema.Number) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(1, "1") await encoding.succeed(null, "null") const decoding = asserts.decoding() await decoding.succeed("null", null) await decoding.succeed("1", 1) }) it("Array(NullOr(Number))", async () => { const schema = Schema.Array(Schema.NullOr(Schema.Number)) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed([1, null], ["1", "null"]) const decoding = asserts.decoding() await decoding.succeed(["1", "null"], [1, null]) }) it("Union(Date, Finite)", async () => { const schema = Schema.Union([Schema.Date, Schema.Finite]) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed( new Date("2021-01-01"), "2021-01-01T00:00:00.000Z" ) await encoding.succeed(0, "0") }) }) it("Suspend", async () => { interface Category { readonly a: number readonly categories: ReadonlyArray } const schema = Schema.Struct({ a: Schema.Finite.check(Schema.isGreaterThan(0)), categories: Schema.Array(Schema.suspend((): Schema.Codec => schema)) }) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed({ a: 1, categories: [] }, { a: "1", categories: [] }) await encoding.succeed({ a: 1, categories: [{ a: 2, categories: [] }] }, { a: "1", categories: [ { a: "2", categories: [] } ] }) const decoding = asserts.decoding() await decoding.succeed({ a: "1", categories: [] }, { a: 1, categories: [] }) await decoding.succeed({ a: "1", categories: [ { a: "2", categories: [] } ] }, { a: 1, categories: [{ a: 2, categories: [] }] }) }) it("Class", async () => { class A extends Schema.Class("A")(Schema.Struct({ a: Schema.Finite })) {} const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(Schema.toType(A))) const encoding = asserts.encoding() await encoding.succeed(new A({ a: 0 }), { a: "0" }) const decoding = asserts.decoding() await decoding.succeed({ a: "0" }, new A({ a: 0 })) }) it("ErrorClass", async () => { class E extends Schema.ErrorClass("E")({ a: Schema.Finite }) {} const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(Schema.toType(E))) const encoding = asserts.encoding() await encoding.succeed(new E({ a: 0 }), { a: "0" }) const decoding = asserts.decoding() await decoding.succeed({ a: "0" }, new E({ a: 0 })) }) it("Date", async () => { const schema = Schema.Date const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed( new Date("2021-01-01"), "2021-01-01T00:00:00.000Z" ) }) it("Error", async () => { const schema = Schema.Error() const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed( new Error("a"), { name: "Error", message: "a" } ) const decoding = asserts.decoding() // Error: message only await decoding.succeed( { message: "a" }, new Error("a") ) // Error: message and name await decoding.succeed( { name: "b", message: "a" }, (() => { const err = new Error("a") err.name = "b" return err })() ) // Error: message, name, and stack await decoding.succeed( { name: "b", message: "a", stack: "c" }, (() => { const err = new Error("a") err.name = "b" err.stack = "c" return err })() ) }) it("URL", async () => { const schema = Schema.URL const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed( new URL("https://example.com"), "https://example.com/" ) const decoding = asserts.decoding() await decoding.succeed( "https://example.com", new URL("https://example.com") ) await decoding.succeed( "https://example.com/", new URL("https://example.com") ) await decoding.fail( "not a url", `Invalid URL string: not a url` ) }) it("RegExp", async () => { const schema = Schema.RegExp const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(new RegExp("a"), { source: "a", flags: "" }) await encoding.succeed(new RegExp("a", "i"), { source: "a", flags: "i" }) const decoding = asserts.decoding() await decoding.succeed({ source: "a", flags: "" }, new RegExp("a")) await decoding.succeed({ source: "a", flags: "i" }, new RegExp("a", "i")) await decoding.fail( { source: "a", flags: "x" }, `SyntaxError: Invalid flags supplied to RegExp constructor 'x'` ) }) it("Option(Date)", async () => { const schema = Schema.Option(Schema.Date) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed(Option.some(new Date("2021-01-01")), { _tag: "Some", value: "2021-01-01T00:00:00.000Z" }) await encoding.succeed(Option.none(), { _tag: "None" }) }) it("Redacted(Option(String))", async () => { const schema = Schema.Redacted(Schema.Option(Schema.String)) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed( Redacted.make(Option.none()), { _tag: "None" } ) await encoding.succeed( Redacted.make(Option.some("a")), { _tag: "Some", value: "a" } ) const decoding = asserts.decoding() await decoding.succeed( { _tag: "None" }, Redacted.make(Option.none()) ) await decoding.succeed( { _tag: "Some", value: "a" }, Redacted.make(Option.some("a")) ) }) it("Redacted(Option(String)) disallowJsonEncode", async () => { const schema = Schema.Redacted(Schema.Option(Schema.String), { disallowJsonEncode: true }) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.fail( Redacted.make(Option.none()), "Cannot serialize Redacted" ) await encoding.fail( Redacted.make(Option.some("a")), "Cannot serialize Redacted" ) const decoding = asserts.decoding() await decoding.succeed( { _tag: "None" }, Redacted.make(Option.none()) ) await decoding.succeed( { _tag: "Some", value: "a" }, Redacted.make(Option.some("a")) ) }) it("ReadonlySet", async () => { const schema = Schema.ReadonlySet(Schema.Option(Schema.Date)) const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema)) const encoding = asserts.encoding() await encoding.succeed(new Set([Option.some(new Date("2021-01-01"))]), [{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }]) const decoding = asserts.decoding() await decoding.succeed( [{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }], new Set([Option.some(new Date("2021-01-01"))]) ) }) it("ReadonlyMap", async () => { const schema = Schema.ReadonlyMap(Schema.Option(Schema.Date), Schema.Finite) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed( new Map([[Option.some(new Date("2021-01-01")), 0]]), [[ { _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, "0" ]] ) const decoding = asserts.decoding() await decoding.succeed( [[{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, "0"]], new Map([[Option.some(new Date("2021-01-01")), 0]]) ) }) it("HashMap", async () => { const schema = Schema.HashMap(Schema.Option(Schema.Date), Schema.Finite) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema)) const encoding = asserts.encoding() await encoding.succeed( HashMap.make([Option.some(new Date("2021-01-01")), 0]), [[ { _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, "0" ]] ) const decoding = asserts.decoding() await decoding.succeed( [[{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, "0"]], HashMap.make([Option.some(new Date("2021-01-01")), 0]) ) }) }) it("should handle optional keys", async () => { const schema = Schema.Struct({ a: Schema.optionalKey(Schema.NonEmptyArray(Schema.String)) }) const serializer = Schema.toCodecStringTree(schema) const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(serializer)) const decoding = asserts.decoding() await decoding.succeed({}) await decoding.succeed({ a: ["a"] }) await decoding.fail({ a: "a" }, `Expected array, got "a"\n at ["a"]`) }) }) describe("toCodecArrayFromSingle", () => { it("accepts string and array inputs for a top-level array", async () => { const serializer = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Array(Schema.Finite))) strictEqual(serializer.ast._tag, "Arrays") const asserts = new TestSchema.Asserts(serializer) const encoding = asserts.encoding() await encoding.succeed([1, 2], ["1", "2"]) await encoding.fail(1 as any, "Expected array, got 1") const decoding = asserts.decoding() await decoding.succeed("1", [1]) await decoding.fail("1,2", `Expected a string representing a finite number, got "1,2"\n at [0]`) await decoding.succeed(["1", "2"], [1, 2]) }) it("accepts string and array inputs for required array fields", async () => { const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Struct({ a: Schema.Array(Schema.Finite) }))) const asserts = new TestSchema.Asserts(schema) const decoding = asserts.decoding() await decoding.succeed({ a: "1" }, { a: [1] }) await decoding.succeed({ a: ["1", "2"] }, { a: [1, 2] }) }) it("accepts string and array inputs for nested optional array fields", async () => { const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Struct({ a: Schema.optionalKey(Schema.NonEmptyArray(Schema.String)) }))) const asserts = new TestSchema.Asserts(schema) const decoding = asserts.decoding() await decoding.succeed({}) await decoding.succeed({ a: ["a"] }) await decoding.succeed({ a: "a" }, { a: ["a"] }) }) it("accepts string and array inputs for tuples with optional elements", async () => { const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Tuple([ Schema.optionalKey(Schema.String) ]))) const asserts = new TestSchema.Asserts(schema) const decoding = asserts.decoding() await decoding.succeed([]) await decoding.succeed("a", ["a"]) await decoding.succeed(["a"]) }) it("applies recursively to nested arrays", async () => { const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Array(Schema.Array(Schema.Finite)))) const asserts = new TestSchema.Asserts(schema) const decoding = asserts.decoding() await decoding.succeed("1", [[1]]) await decoding.succeed(["1", "2"], [[1], [2]]) await decoding.succeed([["1", "2"]], [[1, 2]]) }) it("is idempotent", () => { const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Array(Schema.Finite))) strictEqual(schema.ast, Schema.toCodecArrayFromSingle(schema).ast) }) }) describe("toEncoderXml", () => { async function assertXml(schema: Schema.Codec, value: T, expected: string) { const serializer = Schema.toEncoderXml(Schema.toCodecStringTree(schema)) strictEqual(await Effect.runPromise(serializer(value)), expected) } async function assertXmlFailure(schema: Schema.Codec, value: T, message: string) { const serializer = Schema.toEncoderXml(Schema.toCodecStringTree(schema)) const r = await serializer(value).pipe( Effect.mapError((err) => err.issue.toString()), Effect.result, Effect.runPromise ) deepStrictEqual(r, Result.fail(message)) } describe("Schemas without annotations", () => { it("Declaration", async () => { await assertXml(Schema.instanceOf(URL), new URL("https://effect.website"), "") }) it("Unknown", async () => { await assertXml(Schema.Unknown, "value", "") }) it("ObjectKeyword", async () => { await assertXml(Schema.ObjectKeyword, { a: "value" }, "") }) }) it("should use the identifier as the root name", async () => { await assertXml(Schema.String.annotate({ identifier: "a" }), "value", "value") await assertXml(Schema.String.annotate({ identifier: "a b" }), "value", `value`) await assertXml(Schema.String.annotate({ identifier: "a", title: "b" }), "value", "value") }) it("should use the title as the root name", async () => { await assertXml(Schema.String.annotate({ title: "a" }), "value", "value") await assertXml(Schema.String.annotate({ title: "a b" }), "value", `value`) }) it("should escape the text", async () => { await assertXml(Schema.String, "value&", `value&`) await assertXml(Schema.String, "", `<value/>`) }) it("should escape the attributes", async () => { await assertXml(Schema.String.annotate({ title: "value&" }), "", ``) await assertXml( Schema.String.annotate({ title: "" }), "", `<__value__ data-name="<value/>">` ) }) it("Never", async () => { await assertXmlFailure(Schema.Never, "test", `Expected never, got "test"`) }) it("Any", async () => { await assertXml(Schema.Any, "test", "test") await assertXml(Schema.Any, 42, "42") await assertXml( Schema.Any, { a: 1 }, ` 1 ` ) }) it("Void", async () => { await assertXml(Schema.Void, undefined, "") }) it("Undefined", async () => { await assertXml(Schema.Undefined, undefined, "") }) it("Null", async () => { await assertXml(Schema.Null, null, "null") }) it("Number", async () => { await assertXml(Schema.Number, 1, "1") await assertXml(Schema.Number, 0, "0") await assertXml(Schema.Number, -1.5, "-1.5") await assertXml(Schema.Number, Infinity, "Infinity") await assertXml(Schema.Number, -Infinity, "-Infinity") await assertXml(Schema.Number, NaN, "NaN") }) it("Boolean", async () => { await assertXml(Schema.Boolean, true, "true") await assertXml(Schema.Boolean, false, "false") }) it("BigInt", async () => { await assertXml(Schema.BigInt, BigInt(42), "42") await assertXml(Schema.BigInt, BigInt(0), "0") await assertXml(Schema.BigInt, BigInt(-123), "-123") }) it("Symbol", async () => { const sym = Symbol.for("test") await assertXml(Schema.Symbol, sym, "Symbol(test)") }) it("TemplateLiteral", async () => { const schema = Schema.TemplateLiteral([Schema.Literal("Hello "), Schema.String, Schema.Literal("!")]) await assertXml(schema, "Hello World!", "Hello World!") }) it("Struct", async () => { await assertXml( Schema.Struct({ a: Schema.Number, "a b": Schema.Number }), { a: 1, "a b": 2 }, ` 1 2 ` ) }) it("Array", async () => { await assertXml(Schema.Array(Schema.Number), [], "") await assertXml( Schema.Array(Schema.Number), [1, 2, 3], ` 1 2 3 ` ) }) it("Array with custom item name", async () => { const serializer = Schema.toEncoderXml(Schema.toCodecStringTree(Schema.Array(Schema.Number)), { arrayItemName: "number" }) strictEqual( await Effect.runPromise(serializer([1, 2, 3])), ` 1 2 3 ` ) }) it("Union", async () => { await assertXml(Schema.Union([Schema.String, Schema.Number]), "test", "test") await assertXml(Schema.Union([Schema.String, Schema.Number]), 42, "42") }) it("Tuple", async () => { await assertXml(Schema.Tuple([]), [], "") await assertXml( Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]), ["a", 1, true], ` a 1 true ` ) }) it("Record", async () => { await assertXml(Schema.Record(Schema.String, Schema.Number), {}, "") await assertXml( Schema.Record(Schema.String, Schema.Number), { a: 1, b: 2 }, ` 1 2 ` ) }) it("NullOr", async () => { await assertXml(Schema.NullOr(Schema.String), "test", "test") await assertXml(Schema.NullOr(Schema.String), null, "null") }) it("TaggedUnion", async () => { const schema = Schema.TaggedUnion({ A: { value: Schema.String }, B: { value: Schema.Number } }) await assertXml( schema, { _tag: "A", value: "test" }, ` <_tag>A test ` ) await assertXml( schema, { _tag: "B", value: 42 }, ` <_tag>B 42 ` ) }) it("TaggedStruct", async () => { const schema = Schema.TaggedStruct("User", { name: Schema.String, age: Schema.Number }) await assertXml( schema, { _tag: "User", name: "John", age: 30 }, ` <_tag>User 30 John ` ) }) it("Enum", async () => { const schema = Schema.Enum({ A: "a", B: "b" }) await assertXml(schema, "a", "a") await assertXml(schema, "b", "b") }) it("Literals", async () => { const schema = Schema.Literals(["a", 1, true, 1n]) await assertXml(schema, "a", "a") await assertXml(schema, 1, "1") await assertXml(schema, true, "true") await assertXml(schema, 1n, "1") }) it("Nested Structures", async () => { const schema = Schema.Struct({ user: Schema.Struct({ name: Schema.String, age: Schema.Number }), tags: Schema.Array(Schema.String) }) await assertXml( schema, { user: { name: "John", age: 30 }, tags: ["admin", "user"] }, ` admin user 30 John ` ) }) it("Special Characters in Text", async () => { await assertXml(Schema.String, "&<>\"'", `&<>"'`) await assertXml( Schema.String, "line1\nline2", `line1 line2` ) await assertXml(Schema.String, "tab\there", "tab here") }) it("Special Characters in Attributes", async () => { await assertXml( Schema.String.annotate({ title: "test&value" }), "content", `content` ) await assertXml( Schema.String.annotate({ title: "test" }), "content", `content` ) await assertXml( Schema.String.annotate({ title: "test\"value" }), "content", `content` ) }) it("XML Reserved Names", async () => { await assertXml( Schema.String.annotate({ title: "xml" }), "content", `<_xml data-name="xml">content` ) await assertXml( Schema.String.annotate({ title: "XML" }), "content", `<_XML data-name="XML">content` ) await assertXml( Schema.String.annotate({ title: "xmlns" }), "content", `<_xmlns data-name="xmlns">content` ) }) it("Invalid XML Tag Names", async () => { await assertXml( Schema.String.annotate({ title: "123invalid" }), "content", `<_123invalid data-name="123invalid">content` ) await assertXml( Schema.String.annotate({ title: "invalid name" }), "content", `content` ) }) it("Empty String", async () => { await assertXml(Schema.String, "", "") }) it("Whitespace Only String", async () => { await assertXml(Schema.String, " ", " ") await assertXml(Schema.String, "\n\t", "\n\t") }) it("Unicode Characters", async () => { await assertXml(Schema.String, "Hello 世界", "Hello 世界") await assertXml(Schema.String, "🚀🌟✨", "🚀🌟✨") await assertXml(Schema.String, "αβγδε", "αβγδε") }) it("XML Encoder Options - rootName", async () => { const serializer = Schema.toEncoderXml(Schema.toCodecStringTree(Schema.String), { rootName: "custom" }) strictEqual(await Effect.runPromise(serializer("test")), "test") }) it("XML Encoder Options - pretty: false", async () => { const serializer = Schema.toEncoderXml( Schema.toCodecStringTree(Schema.Struct({ a: Schema.Number, b: Schema.String })), { pretty: false } ) strictEqual(await Effect.runPromise(serializer({ a: 1, b: "test" })), "1test") }) it("XML Encoder Options - custom indent", async () => { const serializer = Schema.toEncoderXml( Schema.toCodecStringTree(Schema.Struct({ a: Schema.Number })), { indent: " " } ) strictEqual( await Effect.runPromise(serializer({ a: 1 })), ` 1 ` ) }) it("XML Encoder Options - sortKeys: false", async () => { const serializer = Schema.toEncoderXml( Schema.toCodecStringTree(Schema.Struct({ z: Schema.Number, a: Schema.Number, m: Schema.Number })), { sortKeys: false } ) strictEqual( await Effect.runPromise(serializer({ z: 3, a: 1, m: 2 })), ` 3 1 2 ` ) }) it("Circular Reference Detection", async () => { const obj: any = { name: "test" } obj.self = obj const serializer = Schema.toEncoderXml(Schema.toCodecStringTree(Schema.Any)) try { await Effect.runPromise(serializer(obj)) throw new Error("Expected error") } catch (error: any) { strictEqual(error.message, "Cycle detected while serializing to XML.") } }) it("Nested Arrays", async () => { const schema = Schema.Array(Schema.Array(Schema.Number)) await assertXml( schema, [[1, 2], [3, 4]], ` 1 2 3 4 ` ) }) it("Record with Number Keys", async () => { const schema = Schema.Record(Schema.Number, Schema.String) await assertXml( schema, { 1: "one", 2: "two" }, ` <_1 data-name="1">one <_2 data-name="2">two ` ) }) it("Record with Symbol Keys", async () => { const sym1 = Symbol.for("key1") const sym2 = Symbol.for("key2") const schema = Schema.Record(Schema.Symbol, Schema.String) await assertXml( schema, { [sym1]: "value1", [sym2]: "value2" }, ` value1 value2 ` ) }) it("Tuple with Rest", async () => { const schema = Schema.TupleWithRest(Schema.Tuple([Schema.String, Schema.Number]), [Schema.Boolean]) await assertXml( schema, ["test", 42, true, false], ` test 42 true false ` ) }) it("Suspend (Recursive Types)", async () => { interface Tree { readonly value: number readonly children: ReadonlyArray } const Tree: Schema.Codec = Schema.Struct({ value: Schema.Number, children: Schema.Array(Schema.suspend(() => Tree)) }) const tree: Tree = { value: 1, children: [ { value: 2, children: [] }, { value: 3, children: [] } ] } await assertXml( Tree, tree, ` 2 3 1 ` ) }) }) })