import { JsonSchema, Schema, SchemaRepresentation } from "effect" import { describe, it } from "vitest" import { deepStrictEqual, strictEqual } from "../../utils/assert.ts" type Category = { readonly name: string readonly children: ReadonlyArray } const OuterCategory = Schema.Struct({ name: Schema.String, children: Schema.Array(Schema.suspend((): Schema.Codec => OuterCategory)) }).annotate({ identifier: "Category" }) const InnerCategory = Schema.Struct({ name: Schema.String, children: Schema.Array( Schema.suspend((): Schema.Codec => InnerCategory.annotate({ identifier: "Category" })) ) }) describe("toCodeDocument", () => { type Expected = { readonly codes: SchemaRepresentation.Code | ReadonlyArray readonly references?: { readonly nonRecursives?: ReadonlyArray<{ readonly $ref: string readonly code: SchemaRepresentation.Code }> readonly recursives?: { readonly [$ref: string]: SchemaRepresentation.Code } } readonly artifacts?: ReadonlyArray } function assertSchema(input: { readonly schema: Schema.Constraint readonly reviver?: SchemaRepresentation.Reviver | undefined }, expected: Expected) { const multiDocument = SchemaRepresentation.fromASTs([input.schema.ast]) assertMultiDocument({ multiDocument }, expected) } function assertJsonSchema(input: { readonly schema: JsonSchema.JsonSchema readonly reviver?: SchemaRepresentation.Reviver | undefined }, expected: Expected) { const multiDocument = SchemaRepresentation.toMultiDocument( SchemaRepresentation.fromJsonSchemaDocument(JsonSchema.fromSchemaDraft2020_12(input.schema), { onEnter: (js) => { if (js.type === "object" && js.additionalProperties === undefined) { return { ...js, additionalProperties: false } } return js } }) ) assertMultiDocument({ multiDocument }, expected) } function assertMultiDocument(input: { readonly multiDocument: SchemaRepresentation.MultiDocument readonly reviver?: SchemaRepresentation.Reviver | undefined }, expected: Expected) { const codeDocument = SchemaRepresentation.toCodeDocument(input.multiDocument, { reviver: input.reviver }) deepStrictEqual(codeDocument, { codes: Array.isArray(expected.codes) ? expected.codes : [expected.codes], references: { nonRecursives: expected.references?.nonRecursives ?? [], recursives: expected.references?.recursives ?? {} }, artifacts: expected.artifacts ?? [] }) } const makeCode = SchemaRepresentation.makeCode describe("options", () => { it("reviver can override declaration code and recur into type parameters", () => { }) }) describe("Declaration", () => { it("declaration without typeConstructor annotation", () => { assertSchema({ schema: Schema.instanceOf(URL) }, { codes: makeCode("Schema.Null", "null") }) }) it("Error", () => { assertSchema({ schema: Schema.Error() }, { codes: makeCode(`Schema.Error()`, "globalThis.Error") }) }) it("Error with stack", () => { assertSchema({ schema: Schema.Error({ includeStack: true }) }, { codes: makeCode(`Schema.Error({"includeStack":true})`, "globalThis.Error") }) }) it("Error with excluded cause", () => { assertSchema({ schema: Schema.Error({ excludeCause: true }) }, { codes: makeCode(`Schema.Error({"excludeCause":true})`, "globalThis.Error") }) }) it("RegExp", () => { assertSchema({ schema: Schema.RegExp }, { codes: makeCode(`Schema.RegExp`, "globalThis.RegExp") }) }) it("URL", () => { assertSchema({ schema: Schema.URL }, { codes: makeCode(`Schema.URL`, "globalThis.URL") }) }) it("Uint8Array", () => { assertSchema({ schema: Schema.Uint8Array }, { codes: makeCode(`Schema.Uint8Array`, "globalThis.Uint8Array") }) }) it("URLSearchParams", () => { assertSchema({ schema: Schema.URLSearchParams }, { codes: makeCode(`Schema.URLSearchParams`, "globalThis.URLSearchParams") }) }) it("File", () => { assertSchema({ schema: Schema.File }, { codes: makeCode(`Schema.File`, "globalThis.File") }) }) it("FormData", () => { assertSchema({ schema: Schema.FormData }, { codes: makeCode(`Schema.FormData`, "globalThis.FormData") }) }) it("URLSearchParams", () => { assertSchema({ schema: Schema.URLSearchParams }, { codes: makeCode(`Schema.URLSearchParams`, "globalThis.URLSearchParams") }) }) describe("Date", () => { it("Date", () => { assertSchema({ schema: Schema.Date }, { codes: makeCode(`Schema.Date`, "globalThis.Date") }) }) it("Date & check", () => { assertSchema( { schema: Schema.Date.check(Schema.isGreaterThanDate(new Date(0))) }, { codes: makeCode(`Schema.Date.check(Schema.isGreaterThanDate(new Date(0)))`, "globalThis.Date") } ) }) }) it("Option(String)", () => { assertSchema( { schema: Schema.Option(Schema.String) }, { codes: makeCode("Schema.Option(Schema.String)", "Option.Option"), artifacts: [{ _tag: "Import", importDeclaration: `import * as Option from "effect/Option"` }] } ) }) it("Result(String, Number)", () => { assertSchema( { schema: Schema.Result(Schema.String, Schema.Number) }, { codes: makeCode("Schema.Result(Schema.String, Schema.Number)", "Result.Result"), artifacts: [{ _tag: "Import", importDeclaration: `import * as Result from "effect/Result"` }] } ) }) it("Redacted options", () => { assertSchema( { schema: Schema.Redacted(Schema.String, { label: "password", disallowJsonEncode: true }) }, { codes: makeCode( `Schema.Redacted(Schema.String, {"label":"password","disallowJsonEncode":true})`, "Redacted.Redacted" ), artifacts: [{ _tag: "Import", importDeclaration: `import * as Redacted from "effect/Redacted"` }] } ) }) it("CauseReason(String, Number)", () => { assertSchema({ schema: Schema.CauseReason(Schema.String, Schema.Number) }, { codes: makeCode("Schema.CauseReason(Schema.String, Schema.Number)", "Cause.Failure"), artifacts: [{ _tag: "Import", importDeclaration: `import * as Cause from "effect/Cause"` }] }) }) it("Cause(String, Number)", () => { assertSchema({ schema: Schema.Cause(Schema.String, Schema.Number) }, { codes: makeCode("Schema.Cause(Schema.String, Schema.Number)", "Cause.Cause"), artifacts: [{ _tag: "Import", importDeclaration: `import * as Cause from "effect/Cause"` }] }) }) it("Exit(String, Number, String)", () => { assertSchema({ schema: Schema.Exit(Schema.String, Schema.Number, Schema.Boolean) }, { codes: makeCode( "Schema.Exit(Schema.String, Schema.Number, Schema.Boolean)", "Exit.Exit" ), artifacts: [{ _tag: "Import", importDeclaration: `import * as Exit from "effect/Exit"` }] }) }) }) it("Null", () => { assertSchema({ schema: Schema.Null }, { codes: makeCode("Schema.Null", "null") }) assertSchema( { schema: Schema.Null.annotate({ "description": "a" }) }, { codes: makeCode(`Schema.Null.annotate({ "description": "a" })`, "null") } ) assertSchema({ schema: Schema.Null.annotate({}) }, { codes: makeCode("Schema.Null", "null") }) }) it("Undefined", () => { assertSchema({ schema: Schema.Undefined }, { codes: makeCode("Schema.Undefined", "undefined") }) assertSchema( { schema: Schema.Undefined.annotate({ "description": "a" }) }, { codes: makeCode(`Schema.Undefined.annotate({ "description": "a" })`, "undefined") } ) }) it("Void", () => { assertSchema({ schema: Schema.Void }, { codes: makeCode("Schema.Void", "void") }) assertSchema( { schema: Schema.Void.annotate({ "description": "a" }) }, { codes: makeCode(`Schema.Void.annotate({ "description": "a" })`, "void") } ) }) it("Never", () => { assertSchema({ schema: Schema.Never }, { codes: makeCode("Schema.Never", "never") }) assertSchema( { schema: Schema.Never.annotate({ "description": "a" }) }, { codes: makeCode(`Schema.Never.annotate({ "description": "a" })`, "never") } ) }) it("Unknown", () => { assertSchema({ schema: Schema.Unknown }, { codes: makeCode("Schema.Unknown", "unknown") }) assertSchema( { schema: Schema.Unknown.annotate({ "description": "a" }) }, { codes: makeCode(`Schema.Unknown.annotate({ "description": "a" })`, "unknown") } ) }) it("Any", () => { assertSchema({ schema: Schema.Any }, { codes: makeCode("Schema.Any", "any") }) assertSchema( { schema: Schema.Any.annotate({ "description": "a" }) }, { codes: makeCode(`Schema.Any.annotate({ "description": "a" })`, "any") } ) }) describe("String", () => { it("String", () => { assertSchema({ schema: Schema.String }, { codes: makeCode("Schema.String", "string") }) }) it("String & identifier", () => { assertSchema({ schema: Schema.String.annotate({ identifier: "ID" }) }, { codes: makeCode("ID", "ID"), references: { nonRecursives: [ { $ref: "ID", code: makeCode(`Schema.String.annotate({ "identifier": "ID" })`, "string") } ] } }) }) it("String & annotations", () => { assertSchema( { schema: Schema.String.annotate({ "description": "a" }) }, { codes: makeCode(`Schema.String.annotate({ "description": "a" })`, "string") } ) }) it("String & check", () => { assertSchema( { schema: Schema.String.check(Schema.isMinLength(1)) }, { codes: makeCode(`Schema.String.check(Schema.isMinLength(1))`, "string") } ) }) it("String & annotations & check", () => { assertSchema( { schema: Schema.String.annotate({ "description": "a" }).check(Schema.isMinLength(1)) }, { codes: makeCode( `Schema.String.annotate({ "description": "a" }).check(Schema.isMinLength(1))`, "string" ) } ) }) it("String & check + annotations", () => { assertSchema( { schema: Schema.String.check(Schema.isMinLength(1, { description: "a" })) }, { codes: makeCode(`Schema.String.check(Schema.isMinLength(1, { "description": "a" }))`, "string") } ) }) it("String & check & annotations", () => { assertSchema( { schema: Schema.String.check(Schema.isMinLength(1)).annotate({ "description": "a" }) }, { codes: makeCode(`Schema.String.check(Schema.isMinLength(1, { "description": "a" }))`, "string") } ) }) describe("checks", () => { it("isStartsWith", () => { assertSchema( { schema: Schema.String.check(Schema.isStartsWith("a")) }, { codes: makeCode(`Schema.String.check(Schema.isStartsWith("a"))`, "string") } ) }) it("isEndsWith", () => { assertSchema( { schema: Schema.String.check(Schema.isEndsWith("a")) }, { codes: makeCode(`Schema.String.check(Schema.isEndsWith("a"))`, "string") } ) }) it("isIncludes", () => { assertSchema( { schema: Schema.String.check(Schema.isIncludes("a")) }, { codes: makeCode(`Schema.String.check(Schema.isIncludes("a"))`, "string") } ) }) it("isGUID with annotations", () => { assertSchema( { schema: Schema.String.check(Schema.isGUID({ message: "message" })) }, { codes: makeCode(`Schema.String.check(Schema.isGUID({ "message": "message" }))`, "string") } ) }) }) }) describe("Number", () => { it("Number", () => { assertSchema({ schema: Schema.Number }, { codes: makeCode("Schema.Number", "number") }) assertSchema( { schema: Schema.Number.annotate({ "description": "a" }) }, { codes: makeCode(`Schema.Number.annotate({ "description": "a" })`, "number") } ) }) it("Number & check", () => { assertSchema( { schema: Schema.Number.check(Schema.isGreaterThan(10)) }, { codes: makeCode(`Schema.Number.check(Schema.isGreaterThan(10))`, "number") } ) }) }) it("Boolean", () => { assertSchema({ schema: Schema.Boolean }, { codes: makeCode("Schema.Boolean", "boolean") }) assertSchema( { schema: Schema.Boolean.annotate({ "description": "a" }) }, { codes: makeCode(`Schema.Boolean.annotate({ "description": "a" })`, "boolean") } ) }) describe("BigInt", () => { it("BigInt", () => { assertSchema({ schema: Schema.BigInt }, { codes: makeCode("Schema.BigInt", "bigint") }) assertSchema( { schema: Schema.BigInt.annotate({ "description": "a" }) }, { codes: makeCode(`Schema.BigInt.annotate({ "description": "a" })`, "bigint") } ) }) it("BigInt & check", () => { assertSchema( { schema: Schema.BigInt.check(Schema.isGreaterThanBigInt(10n)) }, { codes: makeCode(`Schema.BigInt.check(Schema.isGreaterThanBigInt(10n))`, "bigint") } ) }) }) it("Symbol", () => { assertSchema({ schema: Schema.Symbol }, { codes: makeCode("Schema.Symbol", "symbol") }) assertSchema( { schema: Schema.Symbol.annotate({ "description": "a" }) }, { codes: makeCode(`Schema.Symbol.annotate({ "description": "a" })`, "symbol") } ) }) it("ObjectKeyword", () => { assertSchema({ schema: Schema.ObjectKeyword }, { codes: makeCode("Schema.ObjectKeyword", "object") }) assertSchema( { schema: Schema.ObjectKeyword.annotate({ "description": "a" }) }, { codes: makeCode(`Schema.ObjectKeyword.annotate({ "description": "a" })`, "object") } ) }) describe("Literal", () => { it("string literal", () => { assertSchema({ schema: Schema.Literal("a") }, { codes: makeCode(`Schema.Literal("a")`, `"a"`) }) assertSchema( { schema: Schema.Literal("a").annotate({ "description": "a" }) }, { codes: makeCode(`Schema.Literal("a").annotate({ "description": "a" })`, `"a"`) } ) }) it("number literal", () => { assertSchema({ schema: Schema.Literal(1) }, { codes: makeCode(`Schema.Literal(1)`, "1") }) assertSchema( { schema: Schema.Literal(1).annotate({ "description": "a" }) }, { codes: makeCode(`Schema.Literal(1).annotate({ "description": "a" })`, "1") } ) }) it("boolean literal", () => { assertSchema({ schema: Schema.Literal(true) }, { codes: makeCode(`Schema.Literal(true)`, "true") }) assertSchema( { schema: Schema.Literal(true).annotate({ "description": "a" }) }, { codes: makeCode(`Schema.Literal(true).annotate({ "description": "a" })`, "true") } ) }) it("bigint literal", () => { assertSchema({ schema: Schema.Literal(100n) }, { codes: makeCode(`Schema.Literal(100n)`, "100n") }) assertSchema( { schema: Schema.Literal(100n).annotate({ "description": "a" }) }, { codes: makeCode(`Schema.Literal(100n).annotate({ "description": "a" })`, "100n") } ) }) }) describe("UniqueSymbol", () => { it("should create a Symbol artifact", () => { assertSchema( { schema: Schema.UniqueSymbol(Symbol("a")) }, { codes: makeCode(`Schema.UniqueSymbol(_symbol)`, "typeof _symbol"), artifacts: [{ _tag: "Symbol", identifier: "_symbol", generation: makeCode(`Symbol("a")`, `typeof _symbol`) }] } ) assertSchema( { schema: Schema.UniqueSymbol(Symbol()) }, { codes: makeCode(`Schema.UniqueSymbol(_symbol)`, "typeof _symbol"), artifacts: [{ _tag: "Symbol", identifier: "_symbol", generation: makeCode(`Symbol()`, `typeof _symbol`) }] } ) }) it("should create a global Symbol artifact", () => { assertSchema( { schema: Schema.UniqueSymbol(Symbol.for("a")) }, { codes: makeCode(`Schema.UniqueSymbol(_symbol)`, "typeof _symbol"), artifacts: [{ _tag: "Symbol", identifier: "_symbol", generation: makeCode(`Symbol.for("a")`, `typeof _symbol`) }] } ) assertSchema( { schema: Schema.UniqueSymbol(Symbol.for("a")).annotate({ "description": "a" }) }, { codes: makeCode( `Schema.UniqueSymbol(_symbol).annotate({ "description": "a" })`, "typeof _symbol" ), artifacts: [{ _tag: "Symbol", identifier: "_symbol", generation: makeCode(`Symbol.for("a")`, `typeof _symbol`) }] } ) }) }) describe("Enum", () => { it("string values", () => { assertSchema( { schema: Schema.Enum({ A: "a", B: "b" }) }, { codes: makeCode(`Schema.Enum(_Enum)`, `typeof _Enum`), artifacts: [{ _tag: "Enum", identifier: "_Enum", generation: makeCode(`enum _Enum { "A": "a", "B": "b" }`, `typeof _Enum`) }] } ) assertSchema( { schema: Schema.Enum({ A: "a", B: "b" }).annotate({ "description": "a" }) }, { codes: makeCode( `Schema.Enum(_Enum).annotate({ "description": "a" })`, `typeof _Enum` ), artifacts: [{ _tag: "Enum", identifier: "_Enum", generation: makeCode(`enum _Enum { "A": "a", "B": "b" }`, `typeof _Enum`) }] } ) }) it("number values", () => { assertSchema( { schema: Schema.Enum({ One: 1, Two: 2 }) }, { codes: makeCode(`Schema.Enum(_Enum)`, `typeof _Enum`), artifacts: [{ _tag: "Enum", identifier: "_Enum", generation: makeCode(`enum _Enum { "One": 1, "Two": 2 }`, `typeof _Enum`) }] } ) assertSchema( { schema: Schema.Enum({ One: 1, Two: 2 }).annotate({ "description": "a" }) }, { codes: makeCode( `Schema.Enum(_Enum).annotate({ "description": "a" })`, `typeof _Enum` ), artifacts: [{ _tag: "Enum", identifier: "_Enum", generation: makeCode(`enum _Enum { "One": 1, "Two": 2 }`, `typeof _Enum`) }] } ) }) it("mixed values", () => { assertSchema( { schema: Schema.Enum({ A: "a", One: 1 }) }, { codes: makeCode(`Schema.Enum(_Enum)`, `typeof _Enum`), artifacts: [{ _tag: "Enum", identifier: "_Enum", generation: makeCode(`enum _Enum { "A": "a", "One": 1 }`, `typeof _Enum`) }] } ) assertSchema( { schema: Schema.Enum({ A: "a", One: 1 }).annotate({ "description": "a" }) }, { codes: makeCode( `Schema.Enum(_Enum).annotate({ "description": "a" })`, `typeof _Enum` ), artifacts: [{ _tag: "Enum", identifier: "_Enum", generation: makeCode(`enum _Enum { "A": "a", "One": 1 }`, `typeof _Enum`) }] } ) }) }) describe("TemplateLiteral", () => { it("empty template literal", () => { assertSchema( { schema: Schema.TemplateLiteral([]) }, { codes: makeCode(`Schema.TemplateLiteral([])`, "``") } ) }) it("string literal", () => { assertSchema( { schema: Schema.TemplateLiteral([Schema.Literal("a")]) }, { codes: makeCode(`Schema.TemplateLiteral([Schema.Literal("a")])`, "`a`") } ) }) it("number literal", () => { assertSchema( { schema: Schema.TemplateLiteral([Schema.Literal(1)]) }, { codes: makeCode(`Schema.TemplateLiteral([Schema.Literal(1)])`, "`1`") } ) }) it("bigint literal", () => { assertSchema( { schema: Schema.TemplateLiteral([Schema.Literal(1n)]) }, { codes: makeCode(`Schema.TemplateLiteral([Schema.Literal(1n)])`, "`1`") } ) }) it("multiple consecutive literals", () => { assertSchema( { schema: Schema.TemplateLiteral([Schema.Literal("a"), Schema.Literal("b"), Schema.Literal("c")]) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.Literal("a"), Schema.Literal("b"), Schema.Literal("c")])`, "`abc`" ) } ) }) it("special characters in literals", () => { assertSchema( { schema: Schema.TemplateLiteral([Schema.Literal("a b"), Schema.String]) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.Literal("a b"), Schema.String])`, "`a b${string}`" ) } ) assertSchema( { schema: Schema.TemplateLiteral([Schema.Literal("\n"), Schema.String]) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.Literal("\\n"), Schema.String])`, "`\n${string}`" ) } ) }) it("only schemas", () => { assertSchema( { schema: Schema.TemplateLiteral([Schema.String]) }, { codes: makeCode(`Schema.TemplateLiteral([Schema.String])`, "`${string}`") } ) assertSchema( { schema: Schema.TemplateLiteral([Schema.Number]) }, { codes: makeCode(`Schema.TemplateLiteral([Schema.Number])`, "`${number}`") } ) assertSchema( { schema: Schema.TemplateLiteral([Schema.BigInt]) }, { codes: makeCode(`Schema.TemplateLiteral([Schema.BigInt])`, "`${bigint}`") } ) assertSchema( { schema: Schema.TemplateLiteral([Schema.String, Schema.Number]) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.String, Schema.Number])`, "`${string}${number}`" ) } ) }) it("schema & literal", () => { assertSchema( { schema: Schema.TemplateLiteral([Schema.String, Schema.Literal("a")]) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.String, Schema.Literal("a")])`, "`${string}a`" ) } ) assertSchema( { schema: Schema.TemplateLiteral([Schema.Number, Schema.Literal("a")]) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.Number, Schema.Literal("a")])`, "`${number}a`" ) } ) assertSchema( { schema: Schema.TemplateLiteral([Schema.BigInt, Schema.Literal("a")]) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.BigInt, Schema.Literal("a")])`, "`${bigint}a`" ) } ) }) it("literal & schema", () => { assertSchema( { schema: Schema.TemplateLiteral([Schema.Literal("a"), Schema.String]) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.Literal("a"), Schema.String])`, "`a${string}`" ) } ) assertSchema( { schema: Schema.TemplateLiteral([Schema.Literal("a"), Schema.Number]) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.Literal("a"), Schema.Number])`, "`a${number}`" ) } ) assertSchema( { schema: Schema.TemplateLiteral([Schema.Literal("a"), Schema.BigInt]) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.Literal("a"), Schema.BigInt])`, "`a${bigint}`" ) } ) }) it("schema & literal & schema", () => { assertSchema( { schema: Schema.TemplateLiteral([Schema.String, Schema.Literal("-"), Schema.Number]) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.String, Schema.Literal("-"), Schema.Number])`, "`${string}-${number}`" ) } ) assertSchema( { schema: Schema.TemplateLiteral([Schema.String, Schema.Literal("-"), Schema.Number]).annotate({ "description": "ad" }) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.String, Schema.Literal("-"), Schema.Number]).annotate({ "description": "ad" })`, "`${string}-${number}`" ) } ) }) it("TemplateLiteral as part", () => { assertSchema( { schema: Schema.TemplateLiteral([ Schema.Literal("a"), Schema.TemplateLiteral([Schema.String, Schema.Literals(["-", "+"]), Schema.Number]) ]) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.Literal("a"), Schema.TemplateLiteral([Schema.String, Schema.Literals(["-", "+"]), Schema.Number])])`, "`a${string}-${number}` | `a${string}+${number}`" ) } ) }) it("Union as part", () => { assertSchema( { schema: Schema.TemplateLiteral([Schema.Literal("a"), Schema.Union([Schema.String, Schema.Number])]) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.Literal("a"), Schema.Union([Schema.String, Schema.Number])])`, "`a${string}` | `a${number}`" ) } ) }) it("Literals as part", () => { assertSchema( { schema: Schema.TemplateLiteral([Schema.Literals(["a", "b"]), Schema.String]) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.Literals(["a", "b"]), Schema.String])`, "`a${string}` | `b${string}`" ) } ) }) it("multiple unions", () => { assertSchema( { schema: Schema.TemplateLiteral([ Schema.Union([Schema.Literal("a"), Schema.Literal("b")]), Schema.String, Schema.Union([Schema.Number, Schema.BigInt]) ]) }, { codes: makeCode( `Schema.TemplateLiteral([Schema.Literals(["a", "b"]), Schema.String, Schema.Union([Schema.BigInt, Schema.Number])])`, "`a${string}${bigint}` | `a${string}${number}` | `b${string}${bigint}` | `b${string}${number}`" ) } ) }) }) describe("Tuple", () => { it("empty tuple", () => { assertSchema({ schema: Schema.Tuple([]) }, { codes: makeCode("Schema.Tuple([])", "readonly []") }) assertSchema( { schema: Schema.Tuple([]).annotate({ "description": "a" }) }, { codes: makeCode(`Schema.Tuple([]).annotate({ "description": "a" })`, "readonly []") } ) }) it("required element", () => { assertSchema( { schema: Schema.Tuple([Schema.String]) }, { codes: makeCode(`Schema.Tuple([Schema.String])`, "readonly [string]") } ) assertSchema( { schema: Schema.Tuple([Schema.String]).annotate({ "description": "a" }) }, { codes: makeCode( `Schema.Tuple([Schema.String]).annotate({ "description": "a" })`, "readonly [string]" ) } ) }) it("optional element", () => { assertSchema( { schema: Schema.Tuple([Schema.optionalKey(Schema.String)]) }, { codes: makeCode(`Schema.Tuple([Schema.optionalKey(Schema.String)])`, "readonly [string?]") } ) assertSchema( { schema: Schema.Tuple([Schema.optionalKey(Schema.String)]).annotate({ "description": "a" }) }, { codes: makeCode( `Schema.Tuple([Schema.optionalKey(Schema.String)]).annotate({ "description": "a" })`, "readonly [string?]" ) } ) }) it("annotateKey", () => { assertSchema( { schema: Schema.Tuple([Schema.String.annotateKey({ "description": "a" })]) }, { codes: makeCode( `Schema.Tuple([Schema.String.annotateKey({ "description": "a" })])`, "readonly [string]" ) } ) }) }) it("Array", () => { assertSchema( { schema: Schema.Array(Schema.String) }, { codes: makeCode("Schema.Array(Schema.String)", "ReadonlyArray") } ) assertSchema( { schema: Schema.Array(Schema.String).annotate({ "description": "a" }) }, { codes: makeCode( `Schema.Array(Schema.String).annotate({ "description": "a" })`, "ReadonlyArray" ) } ) }) it("TupleWithRest", () => { assertSchema( { schema: Schema.TupleWithRest(Schema.Tuple([Schema.String]), [Schema.Number]) }, { codes: makeCode( `Schema.TupleWithRest(Schema.Tuple([Schema.String]), [Schema.Number])`, "readonly [string, ...Array]" ) } ) assertSchema( { schema: Schema.TupleWithRest(Schema.Tuple([Schema.String]), [Schema.Number]).annotate({ "description": "a" }) }, { codes: makeCode( `Schema.TupleWithRest(Schema.Tuple([Schema.String]), [Schema.Number]).annotate({ "description": "a" })`, "readonly [string, ...Array]" ) } ) assertSchema( { schema: Schema.TupleWithRest(Schema.Tuple([Schema.String]), [Schema.Number, Schema.Boolean]) }, { codes: makeCode( `Schema.TupleWithRest(Schema.Tuple([Schema.String]), [Schema.Number, Schema.Boolean])`, "readonly [string, ...Array, boolean]" ) } ) }) describe("Struct", () => { it("empty struct", () => { assertSchema({ schema: Schema.Struct({}) }, { codes: makeCode("Schema.Struct({ })", "{ }") }) assertSchema( { schema: Schema.Struct({}).annotate({ "description": "a" }) }, { codes: makeCode(`Schema.Struct({ }).annotate({ "description": "a" })`, "{ }") } ) }) it("required properties", () => { assertSchema( { schema: Schema.Struct({ a: Schema.String }) }, { codes: makeCode( `Schema.Struct({ "a": Schema.String })`, `{ readonly "a": string }` ) } ) assertSchema( { schema: Schema.Struct({ a: Schema.String, b: Schema.Number }) }, { codes: makeCode( `Schema.Struct({ "a": Schema.String, "b": Schema.Number })`, `{ readonly "a": string, readonly "b": number }` ) } ) }) it("optional properties", () => { assertSchema( { schema: Schema.Struct({ a: Schema.optionalKey(Schema.String) }) }, { codes: makeCode( `Schema.Struct({ "a": Schema.optionalKey(Schema.String) })`, `{ readonly "a"?: string }` ) } ) }) it("mutable properties", () => { assertSchema( { schema: Schema.Struct({ a: Schema.mutableKey(Schema.String) }) }, { codes: makeCode( `Schema.Struct({ "a": Schema.mutableKey(Schema.String) })`, `{ "a": string }` ) } ) }) it("optional and mutable properties", () => { assertSchema( { schema: Schema.Struct({ a: Schema.optionalKey(Schema.mutableKey(Schema.String)) }) }, { codes: makeCode( `Schema.Struct({ "a": Schema.optionalKey(Schema.mutableKey(Schema.String)) })`, `{ "a"?: string }` ) } ) }) it("annotateKey", () => { assertSchema( { schema: Schema.Struct({ a: Schema.String.annotateKey({ "description": "a" }) }) }, { codes: makeCode( `Schema.Struct({ "a": Schema.String.annotateKey({ "description": "a" }) })`, `{ readonly "a": string }` ) } ) }) it("struct with symbol property key", () => { const sym = Symbol.for("a") assertSchema( { schema: Schema.Struct({ [sym]: Schema.String }) }, { codes: makeCode( `Schema.Struct({ [_symbol]: Schema.String })`, `{ readonly [typeof _symbol]: string }` ), artifacts: [{ _tag: "Symbol", identifier: "_symbol", generation: makeCode(`Symbol.for("a")`, `typeof _symbol`) }] } ) }) }) it("Record(String, Number)", () => { assertSchema( { schema: Schema.Record(Schema.String, Schema.Number) }, { codes: makeCode("Schema.Record(Schema.String, Schema.Number)", "{ readonly [x: string]: number }") } ) assertSchema( { schema: Schema.Record(Schema.String, Schema.Number).annotate({ "description": "a" }) }, { codes: makeCode( `Schema.Record(Schema.String, Schema.Number).annotate({ "description": "a" })`, "{ readonly [x: string]: number }" ) } ) }) it("StructWithRest", () => { assertSchema( { schema: Schema.StructWithRest(Schema.Struct({ a: Schema.Number }), [ Schema.Record(Schema.String, Schema.Number) ]) }, { codes: makeCode( `Schema.StructWithRest(Schema.Struct({ "a": Schema.Number }), [Schema.Record(Schema.String, Schema.Number)])`, `{ readonly "a": number, readonly [x: string]: number }` ) } ) assertSchema( { schema: Schema.StructWithRest(Schema.Struct({ a: Schema.Number }), [ Schema.Record(Schema.String, Schema.Number) ]).annotate({ description: "a" }) }, { codes: makeCode( `Schema.StructWithRest(Schema.Struct({ "a": Schema.Number }), [Schema.Record(Schema.String, Schema.Number)]).annotate({ "description": "a" })`, `{ readonly "a": number, readonly [x: string]: number }` ) } ) }) describe("Union", () => { it("union with anyOf mode (default)", () => { assertSchema( { schema: Schema.Union([Schema.String, Schema.Number]) }, { codes: makeCode("Schema.Union([Schema.String, Schema.Number])", "string | number") } ) assertSchema( { schema: Schema.Union([Schema.String, Schema.Number]).annotate({ "description": "z" }) }, { codes: makeCode( `Schema.Union([Schema.String, Schema.Number]).annotate({ "description": "z" })`, "string | number" ) } ) }) it("union with oneOf mode", () => { assertSchema( { schema: Schema.Union([Schema.String, Schema.Number], { mode: "oneOf" }) }, { codes: makeCode( `Schema.Union([Schema.String, Schema.Number], { mode: "oneOf" })`, "string | number" ) } ) assertSchema( { schema: Schema.Union([Schema.String, Schema.Number], { mode: "oneOf" }).annotate({ "description": "aa" }) }, { codes: makeCode( `Schema.Union([Schema.String, Schema.Number], { mode: "oneOf" }).annotate({ "description": "aa" })`, "string | number" ) } ) }) it("union with multiple types", () => { assertSchema( { schema: Schema.Union([Schema.String, Schema.Number, Schema.Boolean]) }, { codes: makeCode( "Schema.Union([Schema.String, Schema.Number, Schema.Boolean])", "string | number | boolean" ) } ) assertSchema( { schema: Schema.Union([Schema.String, Schema.Number, Schema.Boolean]).annotate({ "description": "a" }) }, { codes: makeCode( `Schema.Union([Schema.String, Schema.Number, Schema.Boolean]).annotate({ "description": "a" })`, "string | number | boolean" ) } ) }) }) describe("suspend", () => { it("non-recursive", () => { assertSchema( { schema: Schema.suspend(() => Schema.String) }, { codes: makeCode(`Schema.suspend((): Schema.Codec => Schema.String)`, "string") } ) }) it("no identifier annotation", () => { type A = { readonly a?: A } const A = Schema.Struct({ a: Schema.optionalKey(Schema.suspend((): Schema.Codec => A)) }) assertSchema({ schema: A }, { codes: makeCode(`Objects_`, `Objects_`), references: { recursives: { Objects_: makeCode( `Schema.Struct({ "a": Schema.optionalKey(Schema.suspend((): Schema.Codec => Objects_)) })`, `{ readonly "a"?: Objects_ }` ) } } }) }) it("outer identifier annotation", () => { type A = { readonly a?: A } const A = Schema.Struct({ a: Schema.optionalKey(Schema.suspend((): Schema.Codec => A)) }).annotate({ identifier: "A" }) // outer identifier annotation assertSchema({ schema: A }, { codes: makeCode(`A`, `A`), references: { recursives: { A: makeCode( `Schema.Struct({ "a": Schema.optionalKey(Schema.suspend((): Schema.Codec => A)) }).annotate({ "identifier": "A" })`, `{ readonly "a"?: A }` ) } } }) }) it("inner identifier annotation", () => { type A = { readonly a?: A } const A = Schema.Struct({ a: Schema.optionalKey(Schema.suspend((): Schema.Codec => A.annotate({ identifier: "A" }))) }) assertSchema({ schema: A }, { codes: makeCode( `Schema.Struct({ "a": Schema.optionalKey(Suspend_) })`, `{ readonly "a"?: Suspend_ }` ), references: { recursives: { A: makeCode( `Schema.Struct({ "a": Schema.optionalKey(Suspend_) }).annotate({ "identifier": "A" })`, `{ readonly "a"?: Suspend_ }` ), Suspend_: makeCode( `Schema.suspend((): Schema.Codec => A)`, `A` ) } } }) }) it("suspend identifier annotation", () => { type A = { readonly a?: A } const A = Schema.Struct({ a: Schema.optionalKey(Schema.suspend((): Schema.Codec => A).annotate({ identifier: "A" })) }) assertSchema({ schema: A }, { codes: makeCode(`Objects_`, `Objects_`), references: { recursives: { A: makeCode( `Schema.suspend((): Schema.Codec => Objects_).annotate({ "identifier": "A" })`, `Objects_` ), Objects_: makeCode( `Schema.Struct({ "a": Schema.optionalKey(A) })`, `{ readonly "a"?: A }` ) } } }) }) }) describe("brand", () => { it("brand", () => { assertSchema( { schema: Schema.String.pipe(Schema.brand("a")) }, { codes: makeCode( `Schema.String.pipe(Schema.brand("a"))`, `string & Brand.Brand<"a">` ), artifacts: [{ _tag: "Import", importDeclaration: `import type * as Brand from "effect/Brand"` }] } ) }) it("brand & brand", () => { assertSchema( { schema: Schema.String.pipe(Schema.brand("a"), Schema.brand("b")) }, { codes: makeCode( `Schema.String.pipe(Schema.brand("a"), Schema.brand("b"))`, `string & Brand.Brand<"a"> & Brand.Brand<"b">` ), artifacts: [{ _tag: "Import", importDeclaration: `import type * as Brand from "effect/Brand"` }] } ) }) it("check & brand", () => { assertSchema( { schema: Schema.String.check(Schema.isMinLength(1)).pipe(Schema.brand("b")) }, { codes: makeCode( `Schema.String.check(Schema.isMinLength(1)).pipe(Schema.brand("b"))`, `string & Brand.Brand<"b">` ), artifacts: [{ _tag: "Import", importDeclaration: `import type * as Brand from "effect/Brand"` }] } ) }) it("brand & check & brand", () => { assertSchema( { schema: Schema.String.pipe(Schema.brand("a")).check(Schema.isMinLength(1)).pipe(Schema.brand("b")) }, { codes: makeCode( `Schema.String.pipe(Schema.brand("a")).check(Schema.isMinLength(1)).pipe(Schema.brand("b"))`, `string & Brand.Brand<"a"> & Brand.Brand<"b">` ), artifacts: [{ _tag: "Import", importDeclaration: `import type * as Brand from "effect/Brand"` }] } ) }) it("check & brand & check", () => { assertSchema( { schema: Schema.String.check(Schema.isMinLength(1)).pipe(Schema.brand("b")).check(Schema.isMaxLength(2)) }, { codes: makeCode( `Schema.String.check(Schema.isMinLength(1)).pipe(Schema.brand("b")).check(Schema.isMaxLength(2))`, `string & Brand.Brand<"b">` ), artifacts: [{ _tag: "Import", importDeclaration: `import type * as Brand from "effect/Brand"` }] } ) }) }) describe("Date", () => { it("Date", () => { assertSchema({ schema: Schema.Date }, { codes: makeCode(`Schema.Date`, "globalThis.Date") }) }) describe("checks", () => { it("isDateValid", () => { assertSchema( { schema: Schema.Date.check(Schema.isDateValid()) }, { codes: makeCode(`Schema.Date.check(Schema.isDateValid())`, "globalThis.Date") } ) }) it("isGreaterThanDate", () => { assertSchema( { schema: Schema.Date.check(Schema.isGreaterThanDate(new Date(0))) }, { codes: makeCode(`Schema.Date.check(Schema.isGreaterThanDate(new Date(0)))`, "globalThis.Date") } ) }) it("isGreaterThanOrEqualToDate", () => { assertSchema( { schema: Schema.Date.check(Schema.isGreaterThanOrEqualToDate(new Date(0))) }, { codes: makeCode(`Schema.Date.check(Schema.isGreaterThanOrEqualToDate(new Date(0)))`, "globalThis.Date") } ) }) it("isLessThanDate", () => { assertSchema( { schema: Schema.Date.check(Schema.isLessThanDate(new Date(0))) }, { codes: makeCode(`Schema.Date.check(Schema.isLessThanDate(new Date(0)))`, "globalThis.Date") } ) }) it("isLessThanOrEqualToDate", () => { assertSchema( { schema: Schema.Date.check(Schema.isLessThanOrEqualToDate(new Date(0))) }, { codes: makeCode(`Schema.Date.check(Schema.isLessThanOrEqualToDate(new Date(0)))`, "globalThis.Date") } ) }) it("isBetweenDate", () => { assertSchema( { schema: Schema.Date.check(Schema.isBetweenDate({ minimum: new Date(0), maximum: new Date(1) })) }, { codes: makeCode( `Schema.Date.check(Schema.isBetweenDate({ minimum: new Date(0), maximum: new Date(1), exclusiveMinimum: undefined, exclusiveMaximum: undefined))`, "globalThis.Date" ) } ) }) }) }) describe("ReadonlySet", () => { it("ReadonlySet(String)", () => { assertSchema({ schema: Schema.ReadonlySet(Schema.String) }, { codes: makeCode( `Schema.ReadonlySet(Schema.String)`, "globalThis.ReadonlySet" ) }) }) describe("checks", () => { it("isMinSize", () => { assertSchema( { schema: Schema.ReadonlySet(Schema.String).check(Schema.isMinSize(2)) }, { codes: makeCode( `Schema.ReadonlySet(Schema.String).check(Schema.isMinSize(2))`, "globalThis.ReadonlySet" ) } ) }) it("isMaxSize", () => { assertSchema( { schema: Schema.ReadonlySet(Schema.String).check(Schema.isMaxSize(2)) }, { codes: makeCode( `Schema.ReadonlySet(Schema.String).check(Schema.isMaxSize(2))`, "globalThis.ReadonlySet" ) } ) }) }) it("isSizeBetween", () => { assertSchema( { schema: Schema.ReadonlySet(Schema.String).check(Schema.isSizeBetween(2, 2)) }, { codes: makeCode( `Schema.ReadonlySet(Schema.String).check(Schema.isSizeBetween(2, 2))`, "globalThis.ReadonlySet" ) } ) }) }) describe("HashMap", () => { it("HashMap(String, Number)", () => { assertSchema({ schema: Schema.HashMap(Schema.String, Schema.Number) }, { codes: makeCode( `Schema.HashMap(Schema.String, Schema.Number)`, "HashMap.HashMap" ), artifacts: [{ _tag: "Import", importDeclaration: `import * as HashMap from "effect/HashMap"` }] }) }) }) describe("allOf", () => { it("should resolve references", () => { assertJsonSchema({ schema: { allOf: [ { $ref: "#/$defs/A" } ], $defs: { A: { type: "string" } } } }, { codes: makeCode(`Schema.String`, "string") }) }) it("should resolve references in definitions", () => { assertJsonSchema({ schema: { $ref: "#/$defs/A", $defs: { A: { allOf: [ { $ref: "#/$defs/B" }, { type: "object", properties: { a: { type: "string" } }, required: ["a"] } ] }, B: { type: "object", properties: { b: { type: "number" } }, required: ["b"] } } } }, { codes: makeCode(`A`, "A"), references: { nonRecursives: [ { $ref: "A", code: makeCode( `Schema.Struct({ "b": Schema.Number.check(Schema.isFinite()), "a": Schema.String })`, `{ readonly "b": number, readonly "a": string }` ) } ] } }) }) }) }) describe("sanitizeJavaScriptIdentifier", () => { const sanitizeJavaScriptIdentifier = SchemaRepresentation.sanitizeJavaScriptIdentifier it("returns '_' for empty input", () => { strictEqual(sanitizeJavaScriptIdentifier(""), "_") }) it("returns input when already a valid uppercase-start identifier", () => { strictEqual(sanitizeJavaScriptIdentifier("Abc"), "Abc") strictEqual(sanitizeJavaScriptIdentifier("_"), "_") strictEqual(sanitizeJavaScriptIdentifier("$"), "$") strictEqual(sanitizeJavaScriptIdentifier("$a_b9"), "$a_b9") strictEqual(sanitizeJavaScriptIdentifier("A1b2"), "A1b2") }) it("uppercases a leading ASCII letter", () => { strictEqual(sanitizeJavaScriptIdentifier("abc"), "Abc") strictEqual(sanitizeJavaScriptIdentifier("a0"), "A0") strictEqual(sanitizeJavaScriptIdentifier("a1b2c3"), "A1b2c3") strictEqual(sanitizeJavaScriptIdentifier("class"), "Class") }) it("prefixes '_' when starting with a digit", () => { strictEqual(sanitizeJavaScriptIdentifier("1"), "_1") strictEqual(sanitizeJavaScriptIdentifier("1a"), "_1a") strictEqual(sanitizeJavaScriptIdentifier("9lives"), "_9lives") }) it("replaces invalid leading characters with '_'", () => { strictEqual(sanitizeJavaScriptIdentifier(" abc"), "_abc") strictEqual(sanitizeJavaScriptIdentifier("-a"), "_a") strictEqual(sanitizeJavaScriptIdentifier(".a"), "_a") strictEqual(sanitizeJavaScriptIdentifier(" a"), "_a") strictEqual(sanitizeJavaScriptIdentifier("\ta"), "_a") }) it("replaces invalid characters with '_'", () => { strictEqual(sanitizeJavaScriptIdentifier("a-b"), "A_b") strictEqual(sanitizeJavaScriptIdentifier("a b"), "A_b") strictEqual(sanitizeJavaScriptIdentifier("a.b"), "A_b") strictEqual(sanitizeJavaScriptIdentifier("a/b"), "A_b") }) it("replaces multiple invalid characters with '_'", () => { strictEqual(sanitizeJavaScriptIdentifier("a-b c"), "A_b_c") strictEqual(sanitizeJavaScriptIdentifier("a..b"), "A__b") strictEqual(sanitizeJavaScriptIdentifier("a--b"), "A__b") strictEqual(sanitizeJavaScriptIdentifier("a b\tc"), "A_b_c") }) it("replaces non-ascii characters with '_' under ASCII rules", () => { strictEqual(sanitizeJavaScriptIdentifier("café"), "Caf_") strictEqual(sanitizeJavaScriptIdentifier("你好"), "__") strictEqual(sanitizeJavaScriptIdentifier("🤖"), "_") strictEqual(sanitizeJavaScriptIdentifier("a🤖b"), "A_b") }) it("allows '$' and '_' anywhere", () => { strictEqual(sanitizeJavaScriptIdentifier("a$b"), "A$b") strictEqual(sanitizeJavaScriptIdentifier("a_b"), "A_b") strictEqual(sanitizeJavaScriptIdentifier("$a_b9"), "$a_b9") }) it("keeps already-sanitized results stable (idempotent)", () => { const cases = [ "", "abc", "_", "$", "a1b2", "a-b", "a b", "1a", "-a", "class", "café", "a🤖b" ] as const for (const input of cases) { const once = sanitizeJavaScriptIdentifier(input) const twice = sanitizeJavaScriptIdentifier(once) strictEqual(twice, once) } }) it("preserves length when only replacements are needed", () => { strictEqual(sanitizeJavaScriptIdentifier("a-b").length, "a-b".length) strictEqual(sanitizeJavaScriptIdentifier("a b").length, "a b".length) strictEqual(sanitizeJavaScriptIdentifier("..").length, "..".length) }) it("increases length only when prefixing is required", () => { strictEqual(sanitizeJavaScriptIdentifier("1a"), "_1a") strictEqual(sanitizeJavaScriptIdentifier("1a").length, "1a".length + 1) }) }) describe("topologicalSort", () => { function assertTopologicalSort( definitions: Record, expected: SchemaRepresentation.TopologicalSort ) { deepStrictEqual(SchemaRepresentation.topologicalSort(definitions), expected) } it("empty definitions", () => { assertTopologicalSort( {}, { nonRecursives: [], recursives: {} } ) }) it("single definition with no dependencies", () => { assertTopologicalSort( { A: { _tag: "String", checks: [] } }, { nonRecursives: [ { $ref: "A", representation: { _tag: "String", checks: [] } } ], recursives: {} } ) }) it("multiple independent definitions", () => { assertTopologicalSort({ A: { _tag: "String", checks: [] }, B: { _tag: "Number", checks: [] }, C: { _tag: "Boolean" } }, { nonRecursives: [ { $ref: "A", representation: { _tag: "String", checks: [] } }, { $ref: "B", representation: { _tag: "Number", checks: [] } }, { $ref: "C", representation: { _tag: "Boolean" } } ], recursives: {} }) }) it("A -> B -> C", () => { assertTopologicalSort({ A: { _tag: "String", checks: [] }, B: { _tag: "Reference", $ref: "A" }, C: { _tag: "Reference", $ref: "B" } }, { nonRecursives: [ { $ref: "A", representation: { _tag: "String", checks: [] } }, { $ref: "B", representation: { _tag: "Reference", $ref: "A" } }, { $ref: "C", representation: { _tag: "Reference", $ref: "B" } } ], recursives: {} }) }) it("A -> B, A -> C", () => { assertTopologicalSort({ A: { _tag: "String", checks: [] }, B: { _tag: "Reference", $ref: "A" }, C: { _tag: "Reference", $ref: "A" } }, { nonRecursives: [ { $ref: "A", representation: { _tag: "String", checks: [] } }, { $ref: "B", representation: { _tag: "Reference", $ref: "A" } }, { $ref: "C", representation: { _tag: "Reference", $ref: "A" } } ], recursives: {} }) }) it("A -> B -> C, A -> D", () => { assertTopologicalSort({ A: { _tag: "String", checks: [] }, B: { _tag: "Reference", $ref: "A" }, C: { _tag: "Reference", $ref: "B" }, D: { _tag: "Reference", $ref: "A" } }, { nonRecursives: [ { $ref: "A", representation: { _tag: "String", checks: [] } }, { $ref: "B", representation: { _tag: "Reference", $ref: "A" } }, { $ref: "D", representation: { _tag: "Reference", $ref: "A" } }, { $ref: "C", representation: { _tag: "Reference", $ref: "B" } } ], recursives: {} }) }) it("self-referential definition (A -> A)", () => { assertTopologicalSort({ A: { _tag: "Reference", $ref: "A" } }, { nonRecursives: [], recursives: { A: { _tag: "Reference", $ref: "A" } } }) }) it("mutual recursion (A -> B -> A)", () => { assertTopologicalSort({ A: { _tag: "Reference", $ref: "B" }, B: { _tag: "Reference", $ref: "A" } }, { nonRecursives: [], recursives: { A: { _tag: "Reference", $ref: "B" }, B: { _tag: "Reference", $ref: "A" } } }) }) it("complex cycle (A -> B -> C -> A)", () => { assertTopologicalSort({ A: { _tag: "Reference", $ref: "B" }, B: { _tag: "Reference", $ref: "C" }, C: { _tag: "Reference", $ref: "A" } }, { nonRecursives: [], recursives: { A: { _tag: "Reference", $ref: "B" }, B: { _tag: "Reference", $ref: "C" }, C: { _tag: "Reference", $ref: "A" } } }) }) it("mixed recursive and non-recursive definitions", () => { assertTopologicalSort({ A: { _tag: "String", checks: [] }, B: { _tag: "Reference", $ref: "A" }, C: { _tag: "Reference", $ref: "C" }, D: { _tag: "Reference", $ref: "E" }, E: { _tag: "Reference", $ref: "D" } }, { nonRecursives: [ { $ref: "A", representation: { _tag: "String", checks: [] } }, { $ref: "B", representation: { _tag: "Reference", $ref: "A" } } ], recursives: { C: { _tag: "Reference", $ref: "C" }, D: { _tag: "Reference", $ref: "E" }, E: { _tag: "Reference", $ref: "D" } } }) }) it("nested $ref in object properties", () => { assertTopologicalSort({ A: { _tag: "String", checks: [] }, B: { _tag: "Objects", propertySignatures: [{ name: "value", type: { _tag: "Reference", $ref: "A" }, isOptional: false, isMutable: false }], indexSignatures: [], checks: [] } }, { nonRecursives: [ { $ref: "A", representation: { _tag: "String", checks: [] } }, { $ref: "B", representation: { _tag: "Objects", propertySignatures: [{ name: "value", type: { _tag: "Reference", $ref: "A" }, isOptional: false, isMutable: false }], indexSignatures: [], checks: [] } } ], recursives: {} }) }) it("nested $ref in array rest", () => { assertTopologicalSort({ A: { _tag: "String", checks: [] }, B: { _tag: "Arrays", elements: [], rest: [{ _tag: "Reference", $ref: "A" }], checks: [] } }, { nonRecursives: [ { $ref: "A", representation: { _tag: "String", checks: [] } }, { $ref: "B", representation: { _tag: "Arrays", elements: [], rest: [{ _tag: "Reference", $ref: "A" }], checks: [] } } ], recursives: {} }) }) it("external $ref (not in definitions) should be ignored", () => { assertTopologicalSort({ A: { _tag: "Reference", $ref: "#/definitions/External" }, B: { _tag: "Reference", $ref: "A" } }, { nonRecursives: [ { $ref: "A", representation: { _tag: "Reference", $ref: "#/definitions/External" } }, { $ref: "B", representation: { _tag: "Reference", $ref: "A" } } ], recursives: {} }) }) it("multiple cycles with independent definitions", () => { assertTopologicalSort({ Independent: { _tag: "String", checks: [] }, A: { _tag: "Reference", $ref: "B" }, B: { _tag: "Reference", $ref: "A" }, C: { _tag: "Reference", $ref: "D" }, D: { _tag: "Reference", $ref: "C" } }, { nonRecursives: [ { $ref: "Independent", representation: { _tag: "String", checks: [] } } ], recursives: { A: { _tag: "Reference", $ref: "B" }, B: { _tag: "Reference", $ref: "A" }, C: { _tag: "Reference", $ref: "D" }, D: { _tag: "Reference", $ref: "C" } } }) }) it("definition depending on recursive definition", () => { assertTopologicalSort({ A: { _tag: "Reference", $ref: "A" }, B: { _tag: "Reference", $ref: "A" } }, { nonRecursives: [ { $ref: "B", representation: { _tag: "Reference", $ref: "A" } } ], recursives: { A: { _tag: "Reference", $ref: "A" } } }) }) })