1110 lines
31 KiB
TypeScript
1110 lines
31 KiB
TypeScript
import { Array as Arr, Option, Predicate, Schema, SchemaGetter, SchemaRepresentation } from "effect"
|
|
import { describe, it } from "vitest"
|
|
import { deepStrictEqual } from "../../utils/assert.ts"
|
|
|
|
describe("fromASTs", () => {
|
|
function assertFromASTs(schemas: readonly [Schema.Constraint, ...Array<Schema.Constraint>], expected: {
|
|
readonly representations: readonly [
|
|
SchemaRepresentation.Representation,
|
|
...Array<SchemaRepresentation.Representation>
|
|
]
|
|
readonly references?: SchemaRepresentation.References
|
|
}) {
|
|
const document = SchemaRepresentation.fromASTs(Arr.map(schemas, (s) => s.ast))
|
|
deepStrictEqual(document, {
|
|
representations: expected.representations,
|
|
references: expected.references ?? {}
|
|
})
|
|
}
|
|
|
|
it("should handle multiple schemas", () => {
|
|
const A = Schema.String.annotate({ identifier: "id", description: "a" })
|
|
const B = Schema.String.annotate({ identifier: "id", description: "b" })
|
|
const C = Schema.Tuple([A, B])
|
|
assertFromASTs([A, B, C], {
|
|
representations: [
|
|
{ _tag: "Reference", $ref: "id" },
|
|
{ _tag: "Reference", $ref: "id1" },
|
|
{
|
|
_tag: "Arrays",
|
|
elements: [
|
|
{
|
|
isOptional: false,
|
|
type: { _tag: "Reference", $ref: "id" }
|
|
},
|
|
{
|
|
isOptional: false,
|
|
type: { _tag: "Reference", $ref: "id1" }
|
|
}
|
|
],
|
|
rest: [],
|
|
checks: []
|
|
}
|
|
],
|
|
references: {
|
|
id: {
|
|
_tag: "String",
|
|
checks: [],
|
|
annotations: { identifier: "id", description: "a" }
|
|
},
|
|
id1: {
|
|
_tag: "String",
|
|
checks: [],
|
|
annotations: { identifier: "id", description: "b" }
|
|
}
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("fromAST", () => {
|
|
function assertFromAST(schema: Schema.Constraint, expected: {
|
|
readonly representation: SchemaRepresentation.Representation
|
|
readonly references?: SchemaRepresentation.References
|
|
}) {
|
|
const document = SchemaRepresentation.fromAST(schema.ast)
|
|
deepStrictEqual(document, {
|
|
representation: expected.representation,
|
|
references: expected.references ?? {}
|
|
})
|
|
}
|
|
|
|
describe("String", () => {
|
|
it("String", () => {
|
|
assertFromAST(Schema.String, {
|
|
representation: {
|
|
_tag: "String",
|
|
checks: []
|
|
}
|
|
})
|
|
})
|
|
|
|
it("String & brand", () => {
|
|
assertFromAST(Schema.String.pipe(Schema.brand("a")), {
|
|
representation: {
|
|
_tag: "String",
|
|
checks: [],
|
|
annotations: { brands: ["a"] }
|
|
}
|
|
})
|
|
})
|
|
|
|
it("String & brand & brand", () => {
|
|
assertFromAST(Schema.String.pipe(Schema.brand("a"), Schema.brand("b")), {
|
|
representation: {
|
|
_tag: "String",
|
|
checks: [],
|
|
annotations: { brands: ["a", "b"] }
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
it("URL", () => {
|
|
assertFromAST(Schema.URL, {
|
|
representation: {
|
|
_tag: "Declaration",
|
|
annotations: {
|
|
expected: "URL",
|
|
typeConstructor: { _tag: "URL" },
|
|
generation: {
|
|
runtime: "Schema.URL",
|
|
Type: "globalThis.URL"
|
|
}
|
|
},
|
|
checks: [],
|
|
typeParameters: [],
|
|
encodedSchema: {
|
|
_tag: "String",
|
|
annotations: {
|
|
expected: "a string that will be decoded as a URL"
|
|
},
|
|
checks: []
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
it("RegExp", () => {
|
|
assertFromAST(Schema.RegExp, {
|
|
representation: {
|
|
_tag: "Declaration",
|
|
annotations: {
|
|
expected: "RegExp",
|
|
typeConstructor: { _tag: "RegExp" },
|
|
generation: {
|
|
runtime: "Schema.RegExp",
|
|
Type: "globalThis.RegExp"
|
|
}
|
|
},
|
|
checks: [],
|
|
typeParameters: [],
|
|
encodedSchema: {
|
|
_tag: "Objects",
|
|
propertySignatures: [
|
|
{
|
|
name: "source",
|
|
type: { _tag: "String", checks: [] },
|
|
isOptional: false,
|
|
isMutable: false
|
|
},
|
|
{
|
|
name: "flags",
|
|
type: { _tag: "String", checks: [] },
|
|
isOptional: false,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
it("URLSearchParams", () => {
|
|
assertFromAST(Schema.URLSearchParams, {
|
|
representation: {
|
|
_tag: "Declaration",
|
|
annotations: {
|
|
expected: "URLSearchParams",
|
|
typeConstructor: { _tag: "URLSearchParams" },
|
|
generation: {
|
|
runtime: "Schema.URLSearchParams",
|
|
Type: "globalThis.URLSearchParams"
|
|
}
|
|
},
|
|
checks: [],
|
|
typeParameters: [],
|
|
encodedSchema: {
|
|
_tag: "String",
|
|
annotations: {
|
|
expected: "a query string that will be decoded as URLSearchParams"
|
|
},
|
|
checks: []
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
it("Option(Number)", () => {
|
|
assertFromAST(Schema.Option(Schema.Number), {
|
|
representation: {
|
|
_tag: "Declaration",
|
|
annotations: {
|
|
expected: "Option",
|
|
typeConstructor: { _tag: "effect/Option" },
|
|
generation: {
|
|
runtime: "Schema.Option(?)",
|
|
Type: "Option.Option<?>",
|
|
importDeclaration: `import * as Option from "effect/Option"`
|
|
}
|
|
},
|
|
checks: [],
|
|
typeParameters: [
|
|
{ _tag: "Number", checks: [] }
|
|
],
|
|
encodedSchema: {
|
|
_tag: "Union",
|
|
types: [
|
|
{
|
|
_tag: "Objects",
|
|
propertySignatures: [
|
|
{
|
|
name: "_tag",
|
|
type: { _tag: "Literal", literal: "Some" },
|
|
isOptional: false,
|
|
isMutable: false
|
|
},
|
|
{
|
|
name: "value",
|
|
type: { _tag: "Number", checks: [] },
|
|
isOptional: false,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
},
|
|
{
|
|
_tag: "Objects",
|
|
propertySignatures: [
|
|
{
|
|
name: "_tag",
|
|
type: { _tag: "Literal", literal: "None" },
|
|
isOptional: false,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
}
|
|
],
|
|
mode: "anyOf"
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
describe("node kinds", () => {
|
|
it("primitive nodes", () => {
|
|
assertFromAST(
|
|
Schema.Tuple([
|
|
Schema.Null,
|
|
Schema.Undefined,
|
|
Schema.Void,
|
|
Schema.Never,
|
|
Schema.Unknown,
|
|
Schema.Any,
|
|
Schema.Boolean,
|
|
Schema.BigInt,
|
|
Schema.Symbol,
|
|
Schema.ObjectKeyword
|
|
]),
|
|
{
|
|
representation: {
|
|
_tag: "Arrays",
|
|
elements: [
|
|
{ isOptional: false, type: { _tag: "Null" } },
|
|
{ isOptional: false, type: { _tag: "Undefined" } },
|
|
{ isOptional: false, type: { _tag: "Void" } },
|
|
{ isOptional: false, type: { _tag: "Never" } },
|
|
{ isOptional: false, type: { _tag: "Unknown" } },
|
|
{ isOptional: false, type: { _tag: "Any" } },
|
|
{ isOptional: false, type: { _tag: "Boolean" } },
|
|
{ isOptional: false, type: { _tag: "BigInt", checks: [] } },
|
|
{ isOptional: false, type: { _tag: "Symbol" } },
|
|
{ isOptional: false, type: { _tag: "ObjectKeyword" } }
|
|
],
|
|
rest: [],
|
|
checks: []
|
|
}
|
|
}
|
|
)
|
|
})
|
|
|
|
it("literal-like nodes", () => {
|
|
const symbol = Symbol.for("a")
|
|
assertFromAST(
|
|
Schema.Tuple([
|
|
Schema.Literal("a"),
|
|
Schema.UniqueSymbol(symbol),
|
|
Schema.Enum({ A: "a", B: "b", One: 1 }),
|
|
Schema.TemplateLiteral(["a", Schema.String, Schema.Number])
|
|
]),
|
|
{
|
|
representation: {
|
|
_tag: "Arrays",
|
|
elements: [
|
|
{ isOptional: false, type: { _tag: "Literal", literal: "a" } },
|
|
{ isOptional: false, type: { _tag: "UniqueSymbol", symbol } },
|
|
{
|
|
isOptional: false,
|
|
type: {
|
|
_tag: "Enum",
|
|
enums: [["A", "a"], ["B", "b"], ["One", 1]]
|
|
}
|
|
},
|
|
{
|
|
isOptional: false,
|
|
type: {
|
|
_tag: "TemplateLiteral",
|
|
parts: [
|
|
{ _tag: "Literal", literal: "a" },
|
|
{ _tag: "String", checks: [] },
|
|
{ _tag: "Number", checks: [] }
|
|
]
|
|
}
|
|
}
|
|
],
|
|
rest: [],
|
|
checks: []
|
|
}
|
|
}
|
|
)
|
|
})
|
|
|
|
it("string content schema", () => {
|
|
const document = SchemaRepresentation.fromAST(
|
|
Schema.fromJsonString(Schema.Struct({ a: Schema.String })).ast
|
|
)
|
|
const representation = document.representation as SchemaRepresentation.String
|
|
deepStrictEqual(representation.contentSchema, {
|
|
_tag: "Objects",
|
|
propertySignatures: [
|
|
{
|
|
name: "a",
|
|
type: { _tag: "String", checks: [] },
|
|
isOptional: false,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
})
|
|
deepStrictEqual(representation.annotations?.expected, "a string that will be decoded as JSON")
|
|
deepStrictEqual(representation.annotations?.contentMediaType, "application/json")
|
|
})
|
|
|
|
it("tuple rest and mutable properties", () => {
|
|
assertFromAST(
|
|
Schema.Tuple([
|
|
Schema.TupleWithRest(Schema.Tuple([Schema.String, Schema.optionalKey(Schema.Number)]), [Schema.Boolean]),
|
|
Schema.Struct({ a: Schema.mutableKey(Schema.String) })
|
|
]),
|
|
{
|
|
representation: {
|
|
_tag: "Arrays",
|
|
elements: [
|
|
{
|
|
isOptional: false,
|
|
type: {
|
|
_tag: "Arrays",
|
|
elements: [
|
|
{ isOptional: false, type: { _tag: "String", checks: [] } },
|
|
{ isOptional: true, type: { _tag: "Number", checks: [] } }
|
|
],
|
|
rest: [{ _tag: "Boolean" }],
|
|
checks: []
|
|
}
|
|
},
|
|
{
|
|
isOptional: false,
|
|
type: {
|
|
_tag: "Objects",
|
|
propertySignatures: [
|
|
{
|
|
name: "a",
|
|
type: { _tag: "String", checks: [] },
|
|
isOptional: false,
|
|
isMutable: true
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
}
|
|
}
|
|
],
|
|
rest: [],
|
|
checks: []
|
|
}
|
|
}
|
|
)
|
|
})
|
|
|
|
it("declaration without an encoded schema", () => {
|
|
assertFromAST(
|
|
Schema.declare((u): u is string => typeof u === "string", { expected: "string declaration" }),
|
|
{
|
|
representation: {
|
|
_tag: "Declaration",
|
|
typeParameters: [],
|
|
encodedSchema: { _tag: "Null" },
|
|
checks: [],
|
|
annotations: { expected: "string declaration" }
|
|
}
|
|
}
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("checks", () => {
|
|
it("array and object checks", () => {
|
|
assertFromAST(
|
|
Schema.Tuple([
|
|
Schema.Array(Schema.String).check(Schema.isMinLength(1), Schema.isUnique()),
|
|
Schema.Record(Schema.String, Schema.Number).check(
|
|
Schema.isMinProperties(1),
|
|
Schema.isMaxProperties(2)
|
|
)
|
|
]),
|
|
{
|
|
representation: {
|
|
_tag: "Arrays",
|
|
elements: [
|
|
{
|
|
isOptional: false,
|
|
type: {
|
|
_tag: "Arrays",
|
|
elements: [],
|
|
rest: [{ _tag: "String", checks: [] }],
|
|
checks: [
|
|
{
|
|
_tag: "Filter",
|
|
meta: { _tag: "isMinLength", minLength: 1 },
|
|
annotations: { expected: "a value with a length of at least 1" }
|
|
},
|
|
{
|
|
_tag: "Filter",
|
|
meta: { _tag: "isUnique" },
|
|
annotations: { expected: "an array with unique items" }
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
isOptional: false,
|
|
type: {
|
|
_tag: "Objects",
|
|
propertySignatures: [],
|
|
indexSignatures: [
|
|
{
|
|
parameter: { _tag: "String", checks: [] },
|
|
type: { _tag: "Number", checks: [] }
|
|
}
|
|
],
|
|
checks: [
|
|
{
|
|
_tag: "Filter",
|
|
meta: { _tag: "isMinProperties", minProperties: 1 },
|
|
annotations: { expected: "a value with at least 1 entry" }
|
|
},
|
|
{
|
|
_tag: "Filter",
|
|
meta: { _tag: "isMaxProperties", maxProperties: 2 },
|
|
annotations: { expected: "a value with at most 2 entries" }
|
|
}
|
|
]
|
|
}
|
|
}
|
|
],
|
|
rest: [],
|
|
checks: []
|
|
}
|
|
}
|
|
)
|
|
})
|
|
|
|
it("filter groups", () => {
|
|
assertFromAST(
|
|
Schema.String.check(
|
|
Schema.makeFilterGroup([
|
|
Schema.isMinLength(1),
|
|
Schema.isMaxLength(2)
|
|
], { description: "range" })
|
|
),
|
|
{
|
|
representation: {
|
|
_tag: "String",
|
|
checks: [
|
|
{
|
|
_tag: "FilterGroup",
|
|
checks: [
|
|
{
|
|
_tag: "Filter",
|
|
meta: { _tag: "isMinLength", minLength: 1 },
|
|
annotations: { expected: "a value with a length of at least 1" }
|
|
},
|
|
{
|
|
_tag: "Filter",
|
|
meta: { _tag: "isMaxLength", maxLength: 2 },
|
|
annotations: { expected: "a value with a length of at most 2" }
|
|
}
|
|
],
|
|
annotations: { description: "range" }
|
|
}
|
|
]
|
|
}
|
|
}
|
|
)
|
|
})
|
|
|
|
it("drops checks without representation metadata", () => {
|
|
assertFromAST(
|
|
Schema.String.check(Schema.makeFilter((s) => s.length > 0, { expected: "custom" })),
|
|
{
|
|
representation: {
|
|
_tag: "String",
|
|
checks: []
|
|
}
|
|
}
|
|
)
|
|
assertFromAST(
|
|
Schema.String.check(
|
|
Schema.makeFilterGroup([
|
|
Schema.makeFilter((s) => s.length > 0, { expected: "custom" })
|
|
], { description: "group" })
|
|
),
|
|
{
|
|
representation: {
|
|
_tag: "String",
|
|
checks: []
|
|
}
|
|
}
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("Record", () => {
|
|
describe("checks", () => {
|
|
it("isPropertyNames", () => {
|
|
assertFromAST(
|
|
Schema.Record(Schema.String, Schema.Number)
|
|
.check(Schema.isPropertyNames(Schema.String.check(Schema.isPattern(/^[A-Z]/)))),
|
|
{
|
|
representation: {
|
|
_tag: "Objects",
|
|
propertySignatures: [],
|
|
indexSignatures: [
|
|
{
|
|
parameter: { _tag: "String", checks: [] },
|
|
type: { _tag: "Number", checks: [] }
|
|
}
|
|
],
|
|
checks: [
|
|
{
|
|
_tag: "Filter",
|
|
meta: {
|
|
_tag: "isPropertyNames",
|
|
propertyNames: {
|
|
_tag: "String",
|
|
checks: [
|
|
{
|
|
_tag: "Filter",
|
|
meta: { _tag: "isPattern", regExp: new RegExp("^[A-Z]") },
|
|
annotations: { expected: "a string matching the RegExp ^[A-Z]" }
|
|
}
|
|
]
|
|
}
|
|
},
|
|
annotations: { expected: "an object with property names matching the schema" }
|
|
}
|
|
]
|
|
},
|
|
references: {}
|
|
}
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("Class", () => {
|
|
it("Class", () => {
|
|
class A extends Schema.Class<A>("A")({
|
|
a: Schema.String
|
|
}) {}
|
|
assertFromAST(A, {
|
|
representation: { _tag: "Reference", $ref: "A" },
|
|
references: {
|
|
A: {
|
|
_tag: "Objects",
|
|
propertySignatures: [
|
|
{
|
|
name: "a",
|
|
type: {
|
|
_tag: "String",
|
|
checks: []
|
|
},
|
|
isOptional: false,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
it("toType(Class)", () => {
|
|
class A extends Schema.Class<A>("A")({
|
|
a: Schema.String
|
|
}) {}
|
|
assertFromAST(Schema.toType(A), {
|
|
representation: { _tag: "Reference", $ref: "A" },
|
|
references: {
|
|
A: {
|
|
_tag: "Declaration",
|
|
annotations: {
|
|
identifier: "A"
|
|
},
|
|
checks: [],
|
|
typeParameters: [
|
|
{ _tag: "Reference", $ref: "A1" }
|
|
],
|
|
encodedSchema: { _tag: "Reference", $ref: "A1" }
|
|
},
|
|
A1: {
|
|
_tag: "Objects",
|
|
propertySignatures: [
|
|
{
|
|
name: "a",
|
|
type: {
|
|
_tag: "String",
|
|
checks: []
|
|
},
|
|
isOptional: false,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
it("the type side and the class used together", () => {
|
|
class A extends Schema.Class<A>("A")({
|
|
a: Schema.String
|
|
}) {}
|
|
assertFromAST(Schema.Tuple([Schema.toType(A), A]), {
|
|
representation: {
|
|
_tag: "Arrays",
|
|
elements: [
|
|
{
|
|
isOptional: false,
|
|
type: { _tag: "Reference", $ref: "A" }
|
|
},
|
|
{
|
|
isOptional: false,
|
|
type: { _tag: "Reference", $ref: "A1" }
|
|
}
|
|
],
|
|
rest: [],
|
|
checks: []
|
|
},
|
|
references: {
|
|
A: {
|
|
_tag: "Declaration",
|
|
annotations: { identifier: "A" },
|
|
checks: [],
|
|
typeParameters: [
|
|
{ _tag: "Reference", $ref: "A1" }
|
|
],
|
|
encodedSchema: { _tag: "Reference", $ref: "A1" }
|
|
},
|
|
A1: {
|
|
_tag: "Objects",
|
|
propertySignatures: [
|
|
{
|
|
name: "a",
|
|
type: {
|
|
_tag: "String",
|
|
checks: []
|
|
},
|
|
isOptional: false,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
}
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("reference handling", () => {
|
|
it("using a schema with an identifier twice should point to the identifier as a reference", () => {
|
|
const S = Schema.String.annotate({ identifier: "id" })
|
|
assertFromAST(Schema.Tuple([S, S]), {
|
|
representation: {
|
|
_tag: "Arrays",
|
|
elements: [
|
|
{
|
|
isOptional: false,
|
|
type: { _tag: "Reference", $ref: "id" }
|
|
},
|
|
{
|
|
isOptional: false,
|
|
type: { _tag: "Reference", $ref: "id" }
|
|
}
|
|
],
|
|
rest: [],
|
|
checks: []
|
|
},
|
|
references: {
|
|
id: {
|
|
_tag: "String",
|
|
checks: [],
|
|
annotations: { identifier: "id" }
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
it("should handle duplicate identifiers on different schemas with different representations", () => {
|
|
assertFromAST(
|
|
Schema.Union([
|
|
Schema.String.annotate({ identifier: "id", description: "a" }),
|
|
Schema.String.annotate({ identifier: "id", description: "b" })
|
|
]),
|
|
{
|
|
representation: {
|
|
_tag: "Union",
|
|
mode: "anyOf",
|
|
types: [
|
|
{ _tag: "Reference", $ref: "id" },
|
|
{ _tag: "Reference", $ref: "id1" }
|
|
]
|
|
},
|
|
references: {
|
|
id: {
|
|
_tag: "String",
|
|
checks: [],
|
|
annotations: { identifier: "id", description: "a" }
|
|
},
|
|
id1: {
|
|
_tag: "String",
|
|
checks: [],
|
|
annotations: { identifier: "id", description: "b" }
|
|
}
|
|
}
|
|
}
|
|
)
|
|
})
|
|
|
|
it("should handle duplicate identifiers on different schemas with the same representation", () => {
|
|
const X = Schema.String.annotate({ title: "X", identifier: "X" })
|
|
assertFromAST(
|
|
Schema.Struct({
|
|
a: X,
|
|
b: Schema.NullOr(X),
|
|
c: Schema.optionalKey(X),
|
|
d: Schema.optionalKey(Schema.NullOr(X)),
|
|
e: Schema.NullOr(X).pipe(
|
|
Schema.encodeTo(Schema.optionalKey(X), {
|
|
decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
|
|
encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
|
|
})
|
|
)
|
|
}),
|
|
{
|
|
representation: {
|
|
_tag: "Objects",
|
|
propertySignatures: [
|
|
{
|
|
name: "a",
|
|
type: { _tag: "Reference", $ref: "X" },
|
|
isOptional: false,
|
|
isMutable: false
|
|
},
|
|
{
|
|
name: "b",
|
|
type: {
|
|
_tag: "Union",
|
|
mode: "anyOf",
|
|
types: [
|
|
{ _tag: "Reference", $ref: "X" },
|
|
{ _tag: "Null" }
|
|
]
|
|
},
|
|
isOptional: false,
|
|
isMutable: false
|
|
},
|
|
{
|
|
name: "c",
|
|
type: { _tag: "Reference", $ref: "X" },
|
|
isOptional: true,
|
|
isMutable: false
|
|
},
|
|
{
|
|
name: "d",
|
|
type: {
|
|
_tag: "Union",
|
|
mode: "anyOf",
|
|
types: [
|
|
{ _tag: "Reference", $ref: "X" },
|
|
{ _tag: "Null" }
|
|
]
|
|
},
|
|
isOptional: true,
|
|
isMutable: false
|
|
},
|
|
{
|
|
name: "e",
|
|
type: { _tag: "Reference", $ref: "X" },
|
|
isOptional: true,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
},
|
|
references: {
|
|
X: {
|
|
_tag: "String",
|
|
checks: [],
|
|
annotations: { identifier: "X", title: "X" }
|
|
}
|
|
}
|
|
}
|
|
)
|
|
})
|
|
|
|
describe("suspend", () => {
|
|
it("non-recursive", () => {
|
|
assertFromAST(Schema.suspend(() => Schema.String), {
|
|
representation: {
|
|
_tag: "Suspend",
|
|
checks: [],
|
|
thunk: {
|
|
_tag: "String",
|
|
checks: []
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
it("no identifier annotation", () => {
|
|
type A = {
|
|
readonly a?: A
|
|
}
|
|
const A = Schema.Struct({
|
|
a: Schema.optionalKey(Schema.suspend((): Schema.Codec<A> => A))
|
|
})
|
|
|
|
assertFromAST(A, {
|
|
representation: { _tag: "Reference", $ref: "Objects_" },
|
|
references: {
|
|
Objects_: {
|
|
_tag: "Objects",
|
|
propertySignatures: [
|
|
{
|
|
name: "a",
|
|
type: {
|
|
_tag: "Suspend",
|
|
checks: [],
|
|
thunk: { _tag: "Reference", $ref: "Objects_" }
|
|
},
|
|
isOptional: true,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
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
|
|
|
|
assertFromAST(A, {
|
|
representation: { _tag: "Reference", $ref: "A" },
|
|
references: {
|
|
A: {
|
|
_tag: "Objects",
|
|
annotations: { identifier: "A" },
|
|
propertySignatures: [
|
|
{
|
|
name: "a",
|
|
type: {
|
|
_tag: "Suspend",
|
|
checks: [],
|
|
thunk: { _tag: "Reference", $ref: "A" }
|
|
},
|
|
isOptional: true,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
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" })))
|
|
})
|
|
|
|
assertFromAST(A, {
|
|
representation: {
|
|
_tag: "Objects",
|
|
propertySignatures: [
|
|
{
|
|
name: "a",
|
|
type: { _tag: "Reference", $ref: "Suspend_" },
|
|
isOptional: true,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
},
|
|
references: {
|
|
A: {
|
|
_tag: "Objects",
|
|
annotations: { identifier: "A" },
|
|
propertySignatures: [
|
|
{
|
|
name: "a",
|
|
type: { _tag: "Reference", $ref: "Suspend_" },
|
|
isOptional: true,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
},
|
|
Suspend_: {
|
|
_tag: "Suspend",
|
|
checks: [],
|
|
thunk: { _tag: "Reference", $ref: "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" }))
|
|
})
|
|
|
|
assertFromAST(A, {
|
|
representation: { _tag: "Reference", $ref: "Objects_" },
|
|
references: {
|
|
A: {
|
|
_tag: "Suspend",
|
|
annotations: { identifier: "A" },
|
|
checks: [],
|
|
thunk: { _tag: "Reference", $ref: "Objects_" }
|
|
},
|
|
Objects_: {
|
|
_tag: "Objects",
|
|
propertySignatures: [
|
|
{
|
|
name: "a",
|
|
type: { _tag: "Reference", $ref: "A" },
|
|
isOptional: true,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
it("duplicate identifiers", () => {
|
|
type A = {
|
|
readonly a?: A
|
|
}
|
|
const A = Schema.Struct({
|
|
a: Schema.optionalKey(Schema.suspend((): Schema.Codec<A> => A))
|
|
}).annotate({ identifier: "A" })
|
|
|
|
type A1 = {
|
|
readonly a?: A1
|
|
}
|
|
const A1 = Schema.Struct({
|
|
a: Schema.optionalKey(Schema.suspend((): Schema.Codec<A1> => A1))
|
|
}).annotate({ identifier: "A" })
|
|
|
|
const schema = Schema.Tuple([A, A1])
|
|
assertFromAST(schema, {
|
|
representation: {
|
|
_tag: "Arrays",
|
|
elements: [
|
|
{
|
|
isOptional: false,
|
|
type: { _tag: "Reference", $ref: "A" }
|
|
},
|
|
{
|
|
isOptional: false,
|
|
type: { _tag: "Reference", $ref: "A1" }
|
|
}
|
|
],
|
|
rest: [],
|
|
checks: []
|
|
},
|
|
references: {
|
|
A: {
|
|
_tag: "Objects",
|
|
annotations: { identifier: "A" },
|
|
propertySignatures: [
|
|
{
|
|
name: "a",
|
|
type: {
|
|
_tag: "Suspend",
|
|
checks: [],
|
|
thunk: { _tag: "Reference", $ref: "A" }
|
|
},
|
|
isOptional: true,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
},
|
|
A1: {
|
|
_tag: "Objects",
|
|
annotations: { identifier: "A" },
|
|
propertySignatures: [
|
|
{
|
|
name: "a",
|
|
type: {
|
|
_tag: "Suspend",
|
|
checks: [],
|
|
thunk: { _tag: "Reference", $ref: "A1" }
|
|
},
|
|
isOptional: true,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
}
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("transformation schemas with identifiers", () => {
|
|
it("Class", () => {
|
|
class A extends Schema.Class<A>("A")({
|
|
a: Schema.String
|
|
}) {}
|
|
assertFromAST(Schema.Tuple([A, A]), {
|
|
representation: {
|
|
_tag: "Arrays",
|
|
elements: [
|
|
{
|
|
isOptional: false,
|
|
type: { _tag: "Reference", $ref: "A" }
|
|
},
|
|
{
|
|
isOptional: false,
|
|
type: { _tag: "Reference", $ref: "A" }
|
|
}
|
|
],
|
|
rest: [],
|
|
checks: []
|
|
},
|
|
references: {
|
|
A: {
|
|
_tag: "Objects",
|
|
propertySignatures: [
|
|
{
|
|
name: "a",
|
|
type: { _tag: "String", checks: [] },
|
|
isOptional: false,
|
|
isMutable: false
|
|
}
|
|
],
|
|
indexSignatures: [],
|
|
checks: []
|
|
}
|
|
}
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|