2090 lines
59 KiB
TypeScript
2090 lines
59 KiB
TypeScript
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<Category>
|
|
}
|
|
|
|
const OuterCategory = Schema.Struct({
|
|
name: Schema.String,
|
|
children: Schema.Array(Schema.suspend((): Schema.Codec<Category> => OuterCategory))
|
|
}).annotate({ identifier: "Category" })
|
|
|
|
const InnerCategory = Schema.Struct({
|
|
name: Schema.String,
|
|
children: Schema.Array(
|
|
Schema.suspend((): Schema.Codec<Category> => InnerCategory.annotate({ identifier: "Category" }))
|
|
)
|
|
})
|
|
|
|
describe("toCodeDocument", () => {
|
|
type Expected = {
|
|
readonly codes: SchemaRepresentation.Code | ReadonlyArray<SchemaRepresentation.Code>
|
|
readonly references?: {
|
|
readonly nonRecursives?: ReadonlyArray<{
|
|
readonly $ref: string
|
|
readonly code: SchemaRepresentation.Code
|
|
}>
|
|
readonly recursives?: {
|
|
readonly [$ref: string]: SchemaRepresentation.Code
|
|
}
|
|
}
|
|
readonly artifacts?: ReadonlyArray<SchemaRepresentation.Artifact>
|
|
}
|
|
|
|
function assertSchema(input: {
|
|
readonly schema: Schema.Constraint
|
|
readonly reviver?: SchemaRepresentation.Reviver<SchemaRepresentation.Code> | undefined
|
|
}, expected: Expected) {
|
|
const multiDocument = SchemaRepresentation.fromASTs([input.schema.ast])
|
|
assertMultiDocument({ multiDocument }, expected)
|
|
}
|
|
|
|
function assertJsonSchema(input: {
|
|
readonly schema: JsonSchema.JsonSchema
|
|
readonly reviver?: SchemaRepresentation.Reviver<SchemaRepresentation.Code> | 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<SchemaRepresentation.Code> | 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<string>"),
|
|
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<string, number>"),
|
|
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<string>"
|
|
),
|
|
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<string, number>"),
|
|
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<string, number>"),
|
|
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<string, number, boolean>"
|
|
),
|
|
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<string>")
|
|
}
|
|
)
|
|
assertSchema(
|
|
{ schema: Schema.Array(Schema.String).annotate({ "description": "a" }) },
|
|
{
|
|
codes: makeCode(
|
|
`Schema.Array(Schema.String).annotate({ "description": "a" })`,
|
|
"ReadonlyArray<string>"
|
|
)
|
|
}
|
|
)
|
|
})
|
|
|
|
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<number>]"
|
|
)
|
|
}
|
|
)
|
|
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<number>]"
|
|
)
|
|
}
|
|
)
|
|
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<number>, 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<string> => Schema.String)`, "string")
|
|
}
|
|
)
|
|
})
|
|
|
|
it("no identifier annotation", () => {
|
|
type A = {
|
|
readonly a?: A
|
|
}
|
|
const A = Schema.Struct({
|
|
a: Schema.optionalKey(Schema.suspend((): Schema.Codec<A> => A))
|
|
})
|
|
|
|
assertSchema({ schema: A }, {
|
|
codes: makeCode(`Objects_`, `Objects_`),
|
|
references: {
|
|
recursives: {
|
|
Objects_: makeCode(
|
|
`Schema.Struct({ "a": Schema.optionalKey(Schema.suspend((): Schema.Codec<Objects_> => 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> => 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> => 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> => 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)`,
|
|
`A`
|
|
)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
it("suspend identifier annotation", () => {
|
|
type A = {
|
|
readonly a?: A
|
|
}
|
|
const A = Schema.Struct({
|
|
a: Schema.optionalKey(Schema.suspend((): Schema.Codec<A> => A).annotate({ identifier: "A" }))
|
|
})
|
|
|
|
assertSchema({ schema: A }, {
|
|
codes: makeCode(`Objects_`, `Objects_`),
|
|
references: {
|
|
recursives: {
|
|
A: makeCode(
|
|
`Schema.suspend((): Schema.Codec<Objects_> => 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<string>"
|
|
)
|
|
})
|
|
})
|
|
|
|
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<string>"
|
|
)
|
|
}
|
|
)
|
|
})
|
|
|
|
it("isMaxSize", () => {
|
|
assertSchema(
|
|
{ schema: Schema.ReadonlySet(Schema.String).check(Schema.isMaxSize(2)) },
|
|
{
|
|
codes: makeCode(
|
|
`Schema.ReadonlySet(Schema.String).check(Schema.isMaxSize(2))`,
|
|
"globalThis.ReadonlySet<string>"
|
|
)
|
|
}
|
|
)
|
|
})
|
|
})
|
|
|
|
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<string>"
|
|
)
|
|
}
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("HashMap", () => {
|
|
it("HashMap(String, Number)", () => {
|
|
assertSchema({ schema: Schema.HashMap(Schema.String, Schema.Number) }, {
|
|
codes: makeCode(
|
|
`Schema.HashMap(Schema.String, Schema.Number)`,
|
|
"HashMap.HashMap<string, number>"
|
|
),
|
|
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<string, SchemaRepresentation.Representation>,
|
|
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" }
|
|
}
|
|
})
|
|
})
|
|
})
|