import type { Options as AjvOptions } from "ajv" import { Effect, JsonSchema, Option, Predicate, Schema, SchemaGetter } from "effect" // import { FastCheck } from "effect/testing" import { describe, it } from "vitest" import { assertTrue, deepStrictEqual, throws } from "../utils/assert.ts" // oxlint-disable-next-line @typescript-eslint/no-require-imports const Ajv2020 = require("ajv/dist/2020") const baseAjvOptions: AjvOptions = { allErrors: true, strict: false, // warns/throws on unknown keywords depending on Ajv version validateSchema: true, code: { esm: true } // optional } const ajvDraft2020_12 = new Ajv2020.default(baseAjvOptions) function assertUnsupportedSchema( schema: Schema.Constraint, message: string, options?: Schema.ToJsonSchemaOptions ) { throws(() => Schema.toJsonSchemaDocument(schema, options), message) } function assertJsonSchemaDocument( schema: Schema.Codec, expected: { schema: JsonSchema.JsonSchema; definitions?: JsonSchema.Definitions }, options?: Schema.ToJsonSchemaOptions ) { const document = Schema.toJsonSchemaDocument(schema, options) deepStrictEqual(document, { dialect: "draft-2020-12", schema: expected.schema, definitions: expected.definitions ?? {} }) const jsonSchema = { $schema: JsonSchema.META_SCHEMA_URI_DRAFT_2020_12, ...document.schema, $defs: document.definitions } const valid = ajvDraft2020_12.validateSchema(jsonSchema) assertTrue(valid) // const validate = ajvDraft2020_12.compile(jsonSchema) // const arb = Schema.toArbitrary(schema) // const codec = Schema.toCodecJson(schema) // const encode = Schema.encodeSync(codec) // FastCheck.assert(FastCheck.property(arb, (t) => { // const e = encode(t) // return validate(e) // })) } describe("toJsonSchemaDocument", () => { describe("Unsupported schemas", () => { it("Tuple: unsupported post-rest elements", () => { assertUnsupportedSchema( Schema.TupleWithRest(Schema.Tuple([]), [Schema.Finite, Schema.String]), "Generating a JSON Schema for post-rest elements is not supported" ) }) it("Struct: unsupported property signature name", () => { const a = Symbol.for("effect/Schema/test/a") assertUnsupportedSchema( Schema.Struct({ [a]: Schema.String }), `Unsupported property signature name: Symbol(effect/Schema/test/a)` ) }) it("Record: unsupported index signature parameter", () => { assertUnsupportedSchema( Schema.Record(Schema.Symbol, Schema.Finite), `Unsupported index signature parameter: Symbol` ) }) }) describe("options", () => { it("generateDescriptions: true", () => { assertJsonSchemaDocument( Schema.String.annotate({ expected: "b" }), { schema: { "type": "string", "description": "b" } }, { generateDescriptions: true } ) assertJsonSchemaDocument( Schema.String.annotate({ description: "a", expected: "b" }), { schema: { "type": "string", "description": "a" } }, { generateDescriptions: true } ) }) describe("additionalProperties", () => { it(`false (default)`, () => { const schema = Schema.Struct({ a: Schema.String }) assertJsonSchemaDocument(schema, { schema: { "type": "object", "properties": { "a": { "type": "string" } }, "required": ["a"], "additionalProperties": false } }, { additionalProperties: false }) }) it(`true`, () => { const schema = Schema.Struct({ a: Schema.String }) assertJsonSchemaDocument(schema, { schema: { "type": "object", "properties": { "a": { "type": "string" } }, "required": ["a"], "additionalProperties": true } }, { additionalProperties: true }) }) it(`schema`, () => { const schema = Schema.Struct({ a: Schema.String }) assertJsonSchemaDocument(schema, { schema: { "type": "object", "properties": { "a": { "type": "string" } }, "required": ["a"], "additionalProperties": { "type": "string" } } }, { additionalProperties: { "type": "string" } }) }) }) describe("includeAnnotationKey", () => { it("passthroughs matching annotation keys at schema level", () => { assertJsonSchemaDocument( Schema.String.annotate({ title: "Name", description: "A name", "markdownDescription": "The **name** field" }), { schema: { "type": "string", "title": "Name", "description": "A name", "markdownDescription": "The **name** field" } }, { includeAnnotationKey: (key) => key === "markdownDescription" } ) }) it("does not include keys not matching the predicate", () => { assertJsonSchemaDocument( Schema.String.annotate({ title: "Name", description: "A name", "markdownDescription": "The **name** field", "customKey": "value" }), { schema: { "type": "string", "title": "Name", "description": "A name", "markdownDescription": "The **name** field" } }, { includeAnnotationKey: (key) => key === "markdownDescription" } ) }) it("does not include matching keys with undefined values", () => { assertJsonSchemaDocument( Schema.String.annotate({ "x-missing": undefined, "x-value": "value" }), { schema: { "type": "string", "x-value": "value" } }, { includeAnnotationKey: (key) => key.startsWith("x-") } ) }) it("standard keys are always included regardless of predicate", () => { assertJsonSchemaDocument( Schema.String.annotate({ title: "Name", description: "A name", default: "hello" }), { schema: { "type": "string", "title": "Name", "description": "A name", "default": "hello" } }, { includeAnnotationKey: (_key) => false } ) }) it("does not overwrite generated contentSchema with the raw annotation", () => { assertJsonSchemaDocument( Schema.fromJsonString(Schema.Struct({ a: Schema.String })), { schema: { "type": "string", "contentMediaType": "application/json", "contentSchema": { "type": "object", "properties": { "a": { "type": "string" } }, "required": ["a"], "additionalProperties": false } } }, { includeAnnotationKey: (key) => key === "contentSchema" } ) }) it("passthroughs at property level in structs", () => { const schema = Schema.Struct({ name: Schema.String.annotate({ description: "A name", "markdownDescription": "The **name** field" }), tag: Schema.String.annotate({ description: "A tag", "defaultSnippets": [{ label: "v1", body: "v1" }] }) }) assertJsonSchemaDocument( schema, { schema: { "type": "object", "properties": { "name": { "type": "string", "description": "A name", "markdownDescription": "The **name** field" }, "tag": { "type": "string", "description": "A tag", "defaultSnippets": [{ label: "v1", body: "v1" }] } }, "required": ["name", "tag"], "additionalProperties": false } }, { includeAnnotationKey: (key) => key === "markdownDescription" || key === "defaultSnippets" } ) }) it("passthroughs at check level", () => { const schema = Schema.String .annotate({ description: "A string" }) .pipe( Schema.check( Schema.isMinLength(1, { "x-check-annotation": true }) ) ) assertJsonSchemaDocument( schema, { schema: { "type": "string", "description": "A string", "allOf": [{ "minLength": 1, "x-check-annotation": true }] } }, { includeAnnotationKey: (key) => key.startsWith("x-") } ) }) it("passthroughs x- prefixed vendor extensions", () => { assertJsonSchemaDocument( Schema.String.annotate({ description: "A value", "x-custom": true, "x-extension": { foo: "bar" } }), { schema: { "type": "string", "description": "A value", "x-custom": true, "x-extension": { foo: "bar" } } }, { includeAnnotationKey: (key) => key.startsWith("x-") } ) }) }) }) it("should support JSON Schema annotations", () => { const schema = Schema.String.annotate({ title: "a", description: "b", default: "c", examples: ["d"], readOnly: true, writeOnly: true }) assertJsonSchemaDocument(schema, { schema: { "type": "string", "title": "a", "description": "b", "default": "c", "examples": ["d"], "readOnly": true, "writeOnly": true } }) }) describe("identifier handling", () => { it(`refs should escape "~" and "/"`, () => { const S = Schema.String.annotate({ identifier: "id~a/b" }) assertJsonSchemaDocument( S, { schema: { "$ref": "#/$defs/id~0a~1b" }, definitions: { "id~a/b": { "type": "string" } } } ) }) it("using the same identifier annotated schema twice", () => { const S = Schema.String.annotate({ identifier: "id" }) assertJsonSchemaDocument( Schema.Union([S, S]), { schema: { "anyOf": [ { "$ref": "#/$defs/id" }, { "$ref": "#/$defs/id" } ] }, definitions: { id: { "type": "string" } } } ) }) it("should handle duplicate identifiers on different schemas with different representations", () => { const S = Schema.Union([ Schema.String.annotate({ identifier: "id", description: "a" }), Schema.String.annotate({ identifier: "id", description: "b" }) ]) assertJsonSchemaDocument(S, { schema: { "anyOf": [ { "$ref": "#/$defs/id" }, { "$ref": "#/$defs/id1" } ] }, definitions: { id: { "type": "string", "description": "a" }, id1: { "type": "string", "description": "b" } } }) }) it("should handle duplicate identifiers on different schemas with the same representation", () => { const X = Schema.String.annotate({ title: "X", identifier: "X" }) const S = 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)) }) ) }) assertJsonSchemaDocument(S, { schema: { "type": "object", "properties": { "a": { "$ref": "#/$defs/X" }, "b": { "anyOf": [ { "$ref": "#/$defs/X" }, { "type": "null" } ] }, "c": { "$ref": "#/$defs/X" }, "d": { "anyOf": [ { "$ref": "#/$defs/X" }, { "type": "null" } ] }, "e": { "$ref": "#/$defs/X" } }, "required": [ "a", "b" ], "additionalProperties": false }, definitions: { "X": { "type": "string", "title": "X" } } }) }) }) describe("Declaration", () => { it("Date", () => { const schema = Schema.Date assertJsonSchemaDocument(schema, { schema: { "type": "string" } }) }) it("DateValid", () => { const schema = Schema.DateValid assertJsonSchemaDocument(schema, { schema: { "type": "string", "allOf": [ { "format": "date-time" } ] } }) }) it("URL", () => { const schema = Schema.URL assertJsonSchemaDocument(schema, { schema: { "type": "string" } }) }) it("Error", () => { const schema = Schema.Error() assertJsonSchemaDocument(schema, { schema: { "type": "object", "properties": { "name": { "type": "string" }, "message": { "type": "string" }, "stack": { "type": "string" }, "cause": {} }, "required": ["message"], "additionalProperties": false } }) }) it("RegExp", () => { const schema = Schema.RegExp assertJsonSchemaDocument(schema, { schema: { "type": "object", "properties": { "source": { "type": "string" }, "flags": { "type": "string" } }, "required": ["source", "flags"], "additionalProperties": false } }) }) it("Uint8Array", () => { const schema = Schema.Uint8Array assertJsonSchemaDocument(schema, { schema: { "type": "string", "format": "byte", "contentEncoding": "base64" } }) }) it("Duration", () => { const schema = Schema.Duration assertJsonSchemaDocument(schema, { schema: { "anyOf": [ { "type": "object", "properties": { "_tag": { "type": "string", "enum": ["Infinity"] } }, "required": ["_tag"], "additionalProperties": false }, { "type": "object", "properties": { "_tag": { "type": "string", "enum": ["NegativeInfinity"] } }, "required": ["_tag"], "additionalProperties": false }, { "type": "object", "properties": { "_tag": { "type": "string", "enum": ["Nanos"] }, "value": { "type": "string", "allOf": [ { "pattern": "^-?\\d+$" } ] } }, "required": ["_tag", "value"], "additionalProperties": false }, { "type": "object", "properties": { "_tag": { "type": "string", "enum": ["Millis"] }, "value": { "type": "integer" } }, "required": ["_tag", "value"], "additionalProperties": false } ] } }) }) it("Option(String)", () => { const schema = Schema.Option(Schema.String) assertJsonSchemaDocument(schema, { schema: { "anyOf": [ { "type": "object", "properties": { "_tag": { "type": "string", "enum": ["Some"] }, "value": { "type": "string" } }, "required": ["_tag", "value"], "additionalProperties": false }, { "type": "object", "properties": { "_tag": { "type": "string", "enum": ["None"] } }, "required": ["_tag"], "additionalProperties": false } ] } }) }) }) it("Any", () => { const schema = Schema.Any assertJsonSchemaDocument( schema, { schema: {} } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "description": "a" } } ) }) it("Json", () => { const schema = Schema.Json assertJsonSchemaDocument( schema, { schema: {} } ) }) it("MutableJson", () => { const schema = Schema.MutableJson assertJsonSchemaDocument( schema, { schema: {} } ) }) it("Unknown", () => { const schema = Schema.Unknown assertJsonSchemaDocument( schema, { schema: {} } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "description": "a" } } ) }) it("Void", () => { const schema = Schema.Void assertJsonSchemaDocument( schema, { schema: { "type": "null" } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "type": "null", "description": "a" } } ) }) it("Undefined", () => { const schema = Schema.Undefined assertJsonSchemaDocument( schema, { schema: { "type": "null" } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "type": "null", "description": "a" } } ) }) it("BigInt", () => { const schema = Schema.BigInt assertJsonSchemaDocument( schema, { schema: { "type": "string", "allOf": [ { "pattern": "^-?\\d+$" } ] } } ) }) it("Symbol", () => { const schema = Schema.Symbol assertJsonSchemaDocument( schema, { schema: { "type": "string", "allOf": [ { "pattern": "^Symbol\\((.*)\\)$" } ] } } ) }) it("UniqueSymbol", () => { const schema = Schema.UniqueSymbol(Symbol.for("a")) assertJsonSchemaDocument( schema, { schema: { "type": "string", "allOf": [ { "pattern": "^Symbol\\((.*)\\)$" } ] } } ) }) it("Never", () => { const schema = Schema.Never assertJsonSchemaDocument( schema, { schema: { "not": {} } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "description": "a", "not": {} } } ) }) it("Null", () => { const schema = Schema.Null assertJsonSchemaDocument( schema, { schema: { "type": "null" } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "type": "null", "description": "a" } } ) }) describe("String", () => { it("String", () => { assertJsonSchemaDocument( Schema.String, { schema: { "type": "string" } } ) }) it("String & annotate", () => { assertJsonSchemaDocument( Schema.String.annotate({ description: "a" }), { schema: { "type": "string", "description": "a" } } ) }) it("should ignore annotateKey annotations if the schema is not contextual", () => { assertJsonSchemaDocument( Schema.String.annotateKey({ description: "a" }), { schema: { "type": "string" } } ) }) it("String & check", () => { assertJsonSchemaDocument( Schema.String.check(Schema.isMinLength(2)), { schema: { "type": "string", "allOf": [ { "minLength": 2 } ] } } ) }) it("String & custom check without annotation", () => { assertJsonSchemaDocument( Schema.String.check(Schema.makeFilter(() => true)), { schema: { "type": "string" } } ) }) it("String & annotate & check", () => { assertJsonSchemaDocument( Schema.String.annotate({ description: "a" }).check(Schema.isMinLength(2)), { schema: { "type": "string", "description": "a", "allOf": [ { "minLength": 2 } ] } } ) }) it("String & check & annotate", () => { assertJsonSchemaDocument( Schema.String.check(Schema.isMinLength(2)).annotate({ description: "a" }), { schema: { "type": "string", "allOf": [ { "minLength": 2, "description": "a" } ] } } ) }) it("String & check & check", () => { assertJsonSchemaDocument( Schema.String.check(Schema.isMinLength(2), Schema.isMaxLength(3)), { schema: { "type": "string", "allOf": [ { "minLength": 2 }, { "maxLength": 3 } ] } } ) }) it("String & annotate & check & check", () => { assertJsonSchemaDocument( Schema.String.annotate({ description: "a" }).check(Schema.isMinLength(2), Schema.isMaxLength(3)), { schema: { "type": "string", "description": "a", "allOf": [ { "minLength": 2 }, { "maxLength": 3 } ] } } ) }) it("String & check & check & annotate", () => { assertJsonSchemaDocument( Schema.String.check(Schema.isMinLength(2), Schema.isMaxLength(3)).annotate({ description: "a" }), { schema: { "type": "string", "allOf": [ { "minLength": 2 }, { "maxLength": 3, "description": "a" } ] } } ) }) it("String & annotate & check & check & annotate", () => { assertJsonSchemaDocument( Schema.String.annotate({ description: "a" }).check( Schema.isMinLength(2), Schema.isMaxLength(3, { description: "c" }) ), { schema: { "type": "string", "description": "a", "allOf": [ { "minLength": 2 }, { "maxLength": 3, "description": "c" } ] } } ) }) it("String & check & annotations & check & annotations", () => { assertJsonSchemaDocument( Schema.String.check( Schema.isMinLength(2, { description: "b" }), Schema.isMaxLength(3, { description: "c" }) ), { schema: { "type": "string", "allOf": [ { "minLength": 2, "description": "b" }, { "maxLength": 3, "description": "c" } ] } } ) }) it("String & annotations & check & annotations & check & annotations", () => { assertJsonSchemaDocument( Schema.String.annotate({ description: "a" }).check( Schema.isMinLength(2, { description: "b" }), Schema.isMaxLength(3, { description: "c" }) ), { schema: { "type": "string", "description": "a", "allOf": [ { "minLength": 2, "description": "b" }, { "maxLength": 3, "description": "c" } ] } } ) }) describe("checks", () => { it("isPattern", () => { assertJsonSchemaDocument(Schema.String.check(Schema.isPattern(/^abb+$/)), { schema: { "type": "string", "allOf": [ { "pattern": "^abb+$" } ] } }) }) it("isTrimmed", () => { const schema = Schema.Trimmed assertJsonSchemaDocument(schema, { schema: { "type": "string", "allOf": [ { "pattern": "^\\S[\\s\\S]*\\S$|^\\S$|^$" } ] } }) }) it("isLowercased", () => { const schema = Schema.String.check(Schema.isLowercased()) assertJsonSchemaDocument(schema, { schema: { "type": "string", "allOf": [ { "pattern": "^[^A-Z]*$" } ] } }) }) it("isUppercased", () => { const schema = Schema.String.check(Schema.isUppercased()) assertJsonSchemaDocument(schema, { schema: { "type": "string", "allOf": [ { "pattern": "^[^a-z]*$" } ] } }) }) it("isCapitalized", () => { const schema = Schema.String.check(Schema.isCapitalized()) assertJsonSchemaDocument(schema, { schema: { "type": "string", "allOf": [ { "pattern": "^[^a-z]?.*$" } ] } }) }) it("isUncapitalized", () => { const schema = Schema.String.check(Schema.isUncapitalized()) assertJsonSchemaDocument(schema, { schema: { "type": "string", "allOf": [ { "pattern": "^[^A-Z]?.*$" } ] } }) }) describe("isLengthBetween", () => { it("String", () => { assertJsonSchemaDocument( Schema.String.check(Schema.isLengthBetween(2, 2)), { schema: { "type": "string", "allOf": [ { "minLength": 2 }, { "maxLength": 2 } ] } } ) }) it("Array", () => { assertJsonSchemaDocument( Schema.Array(Schema.String).check(Schema.isLengthBetween(2, 2)), { schema: { "type": "array", "items": { "type": "string" }, "allOf": [ { "minItems": 2 }, { "maxItems": 2 } ] } } ) }) it("NonEmptyArray", () => { assertJsonSchemaDocument( Schema.NonEmptyArray(Schema.String).check(Schema.isLengthBetween(2, 2)), { schema: { "type": "array", "prefixItems": [{ "type": "string" }], "items": { "type": "string" }, "minItems": 1, "allOf": [ { "minItems": 2 }, { "maxItems": 2 } ] } } ) }) }) describe("isMinLength", () => { it("String", () => { assertJsonSchemaDocument( Schema.String.check(Schema.isMinLength(2)), { schema: { "type": "string", "allOf": [ { "minLength": 2 } ] } } ) }) it("Array", () => { assertJsonSchemaDocument( Schema.Array(Schema.String).check(Schema.isMinLength(2)), { schema: { "type": "array", "items": { "type": "string" }, "allOf": [ { "minItems": 2 } ] } } ) }) it("NonEmptyArray", () => { assertJsonSchemaDocument( Schema.NonEmptyArray(Schema.String).check(Schema.isMinLength(2)), { schema: { "type": "array", "prefixItems": [{ "type": "string" }], "items": { "type": "string" }, "minItems": 1, "allOf": [ { "minItems": 2 } ] } } ) }) }) describe("isMaxLength", () => { it("String", () => { assertJsonSchemaDocument( Schema.String.check(Schema.isMaxLength(2)), { schema: { "type": "string", "allOf": [ { "maxLength": 2 } ] } } ) }) it("Array", () => { assertJsonSchemaDocument( Schema.Array(Schema.String).check(Schema.isMaxLength(2)), { schema: { "type": "array", "items": { "type": "string" }, "allOf": [ { "maxItems": 2 } ] } } ) }) it("NonEmptyArray", () => { assertJsonSchemaDocument( Schema.NonEmptyArray(Schema.String).check(Schema.isMaxLength(2)), { schema: { "type": "array", "minItems": 1, "prefixItems": [{ "type": "string" }], "items": { "type": "string" }, "allOf": [ { "maxItems": 2 } ] } } ) }) }) it("isUUID", () => { assertJsonSchemaDocument( Schema.String.annotate({ description: "description" }).check(Schema.isUUID()), { schema: { "type": "string", "description": "description", "allOf": [ { "format": "uuid", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|[fF]{8}-[fF]{4}-[fF]{4}-[fF]{4}-[fF]{12})$" } ] } } ) }) it("isGUID", () => { assertJsonSchemaDocument( Schema.String.annotate({ description: "description" }).check(Schema.isGUID()), { schema: { "type": "string", "description": "description", "allOf": [ { "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$" } ] } } ) }) it("isBase64", () => { assertJsonSchemaDocument( Schema.String.annotate({ description: "description" }).check(Schema.isBase64()), { schema: { "type": "string", "description": "description", "allOf": [ { "pattern": "^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$" } ] } } ) }) it("isBase64Url", () => { assertJsonSchemaDocument( Schema.String.annotate({ description: "description" }).check(Schema.isBase64Url()), { schema: { "type": "string", "description": "description", "allOf": [ { "pattern": "^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$" } ] } } ) }) }) }) describe("Number", () => { it("Number", () => { const schema = Schema.Number assertJsonSchemaDocument( schema, { schema: { "anyOf": [ { "type": "number" }, { "type": "string", "enum": ["NaN"] }, { "type": "string", "enum": ["Infinity"] }, { "type": "string", "enum": ["-Infinity"] } ] } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "anyOf": [ { "type": "number" }, { "type": "string", "enum": ["NaN"] }, { "type": "string", "enum": ["Infinity"] }, { "type": "string", "enum": ["-Infinity"] } ], "description": "a" } } ) }) }) describe("Finite", () => { it("Finite", () => { assertJsonSchemaDocument( Schema.Finite, { schema: { "type": "number" } } ) assertJsonSchemaDocument( Schema.Finite.annotate({ description: "a" }), { schema: { "type": "number", "allOf": [{ "description": "a" }] } } ) }) describe("checks", () => { it("isInt", () => { assertJsonSchemaDocument( Schema.Finite.check(Schema.isInt()), { schema: { "type": "integer" } } ) }) it("isInt32", () => { assertJsonSchemaDocument( Schema.Finite.check(Schema.isInt32()), { schema: { "type": "integer", "allOf": [ { "maximum": 2147483647, "minimum": -2147483648 } ] } } ) }) it("isUint32", () => { assertJsonSchemaDocument( Schema.Finite.check(Schema.isUint32()), { schema: { "type": "integer", "allOf": [ { "maximum": 4294967295, "minimum": 0 } ] } } ) assertJsonSchemaDocument( Schema.Finite.check(Schema.isUint32({ description: "a" })), { schema: { "type": "integer", "allOf": [ { "description": "a", "allOf": [ { "maximum": 4294967295, "minimum": 0 } ] } ] } } ) assertJsonSchemaDocument( Schema.Finite.check( Schema.isUint32({ description: "a" }) ), { schema: { "type": "integer", "allOf": [ { "description": "a", "allOf": [ { "maximum": 4294967295, "minimum": 0 } ] } ] } } ) }) it("isGreaterThan", () => { assertJsonSchemaDocument( Schema.Finite.check(Schema.isGreaterThan(1)), { schema: { "type": "number", "allOf": [ { "exclusiveMinimum": 1 } ] } } ) }) it("isGreaterThanOrEqualTo", () => { assertJsonSchemaDocument( Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1)), { schema: { "type": "number", "allOf": [ { "minimum": 1 } ] } } ) }) it("isLessThan", () => { assertJsonSchemaDocument(Schema.Finite.check(Schema.isLessThan(1)), { schema: { "type": "number", "allOf": [ { "exclusiveMaximum": 1 } ] } }) }) it("isLessThanOrEqualTo", () => { assertJsonSchemaDocument(Schema.Finite.check(Schema.isLessThanOrEqualTo(1)), { schema: { "type": "number", "allOf": [ { "maximum": 1 } ] } }) }) it("isBetween", () => { assertJsonSchemaDocument( Schema.Finite.check(Schema.isBetween({ minimum: 1, maximum: 10 })), { schema: { "type": "number", "allOf": [ { "minimum": 1, "maximum": 10 } ] } } ) assertJsonSchemaDocument( Schema.Finite.check( Schema.isBetween({ minimum: 1, maximum: 10, exclusiveMinimum: true }) ), { schema: { "type": "number", "allOf": [ { "exclusiveMinimum": 1, "maximum": 10 } ] } } ) assertJsonSchemaDocument( Schema.Finite.check( Schema.isBetween({ minimum: 1, maximum: 10, exclusiveMaximum: true }) ), { schema: { "type": "number", "allOf": [ { "minimum": 1, "exclusiveMaximum": 10 } ] } } ) assertJsonSchemaDocument( Schema.Finite.check( Schema.isBetween({ minimum: 1, maximum: 10, exclusiveMinimum: true, exclusiveMaximum: true }) ), { schema: { "type": "number", "allOf": [ { "exclusiveMinimum": 1, "exclusiveMaximum": 10 } ] } } ) }) it("isMultipleOf", () => { assertJsonSchemaDocument( Schema.Int.check(Schema.isMultipleOf(2)), { schema: { "type": "integer", "allOf": [ { "multipleOf": 2 } ] } } ) }) }) }) it("Boolean", () => { const schema = Schema.Boolean assertJsonSchemaDocument( schema, { schema: { "type": "boolean" } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "type": "boolean", "description": "a" } } ) }) it("ObjectKeyword", () => { const schema = Schema.ObjectKeyword assertJsonSchemaDocument( schema, { schema: { anyOf: [{ type: "object" }, { type: "array" }] } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "anyOf": [ { "type": "object" }, { "type": "array" } ], "description": "a" } } ) }) describe("Literal", () => { it("string", () => { const schema = Schema.Literal("a") assertJsonSchemaDocument( schema, { schema: { "type": "string", "enum": ["a"] } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "type": "string", "enum": ["a"], "description": "a" } } ) }) it("number", () => { const schema = Schema.Literal(1) assertJsonSchemaDocument( schema, { schema: { "type": "number", "enum": [1] } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "type": "number", "enum": [1], "description": "a" } } ) }) it("boolean", () => { const schema = Schema.Literal(true) assertJsonSchemaDocument( schema, { schema: { "type": "boolean", "enum": [true] } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "type": "boolean", "enum": [true], "description": "a" } } ) }) it("bigint", () => { const schema = Schema.Literal(1n) assertJsonSchemaDocument( schema, { schema: { "type": "string", "enum": ["1"] } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "type": "string", "enum": ["1"], "description": "a" } } ) }) }) describe("Literals", () => { it("empty literals", () => { const schema = Schema.Literals([]) assertJsonSchemaDocument( schema, { schema: { "not": {} } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "not": {}, "description": "a" } } ) }) it("strings", () => { const schema = Schema.Literals(["a", "b"]) assertJsonSchemaDocument( schema, { schema: { "type": "string", "enum": ["a", "b"] } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "type": "string", "enum": ["a", "b"], "description": "a" } } ) }) it("numbers", () => { const schema = Schema.Literals([1, 2]) assertJsonSchemaDocument( schema, { schema: { "type": "number", "enum": [1, 2] } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "type": "number", "enum": [1, 2], "description": "a" } } ) }) it("booleans", () => { const schema = Schema.Literals([true, false]) assertJsonSchemaDocument( schema, { schema: { "type": "boolean", "enum": [true, false] } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "type": "boolean", "enum": [true, false], "description": "a" } } ) }) it("strings & numbers", () => { const schema = Schema.Literals(["a", 1]) assertJsonSchemaDocument( schema, { schema: { "anyOf": [ { "type": "string", "enum": ["a"] }, { "type": "number", "enum": [1] } ] } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "anyOf": [ { "type": "string", "enum": ["a"] }, { "type": "number", "enum": [1] } ], "description": "a" } } ) }) }) describe("Union of literals", () => { it("strings", () => { const schema = Schema.Union([ Schema.Literal("a"), Schema.Literal("b") ]) assertJsonSchemaDocument( schema, { schema: { "type": "string", "enum": ["a", "b"] } } ) const jsonAnnotations = { "title": "title", "description": "description", "default": "a" as const, "examples": ["a", "b"] as const } assertJsonSchemaDocument( schema.annotate({ ...jsonAnnotations }), { schema: { "type": "string", "enum": ["a", "b"], ...jsonAnnotations } } ) }) it("nested literals", () => { const schema = Schema.Union([ Schema.Literal("a"), Schema.Literals(["b", "c"]) ]) assertJsonSchemaDocument( schema, { schema: { "type": "string", "enum": ["a", "b", "c"] } } ) }) it("strings & inner annotate", () => { const schema = Schema.Union([ Schema.Literal("a"), Schema.Literal("b").annotate({ description: "b-description" }) ]) assertJsonSchemaDocument( schema, { schema: { "anyOf": [ { "type": "string", "enum": ["a"] }, { "type": "string", "enum": ["b"], "description": "b-description" } ] } } ) const jsonAnnotations = { "title": "title", "description": "description", "default": "a" as const, "examples": ["a", "b"] as const } assertJsonSchemaDocument( schema.annotate({ ...jsonAnnotations }), { schema: { "anyOf": [ { "type": "string", "enum": ["a"] }, { "type": "string", "enum": ["b"], "description": "b-description" } ], ...jsonAnnotations } } ) }) it("numbers", () => { const schema = Schema.Union([ Schema.Literal(1), Schema.Literal(2) ]) assertJsonSchemaDocument( schema, { schema: { "type": "number", "enum": [1, 2] } } ) const jsonAnnotations = { "title": "title", "description": "description", "default": 1 as const, "examples": [1, 2] as const } assertJsonSchemaDocument( schema.annotate({ ...jsonAnnotations }), { schema: { "type": "number", "enum": [1, 2], ...jsonAnnotations } } ) }) it("booleans", () => { const schema = Schema.Union([ Schema.Literal(true), Schema.Literal(false) ]) assertJsonSchemaDocument( schema, { schema: { "type": "boolean", "enum": [true, false] } } ) const jsonAnnotations = { "title": "title", "description": "description", "default": true as const, "examples": [true, false] as const } assertJsonSchemaDocument( schema.annotate({ ...jsonAnnotations }), { schema: { "type": "boolean", "enum": [true, false], ...jsonAnnotations } } ) }) it("strings & numbers", () => { const schema = Schema.Union([ Schema.Literal("a"), Schema.Literal(1) ]) assertJsonSchemaDocument( schema, { schema: { "anyOf": [ { "type": "string", "enum": ["a"] }, { "type": "number", "enum": [1] } ] } } ) const jsonAnnotations = { "title": "title", "description": "description", "default": "a" as const, "examples": ["a", 1] as const } assertJsonSchemaDocument( schema.annotate({ ...jsonAnnotations }), { schema: { "anyOf": [ { "type": "string", "enum": ["a"] }, { "type": "number", "enum": [1] } ], ...jsonAnnotations } } ) }) }) describe("Enum", () => { it("empty enum", () => { enum Empty {} const schema = Schema.Enum(Empty) assertJsonSchemaDocument( schema, { schema: { "not": {} } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "not": {}, "description": "a" } } ) }) it("single enum", () => { enum Fruits { Apple } const schema = Schema.Enum(Fruits) assertJsonSchemaDocument( schema, { schema: { "anyOf": [ { "type": "number", "enum": [0], "title": "Apple" } ] } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "description": "a", "anyOf": [ { "type": "number", "enum": [0], "title": "Apple" } ] } } ) }) it("mixed enums (number & string)", () => { enum Fruits { Apple, Banana, Orange = "orange" } const schema = Schema.Enum(Fruits) assertJsonSchemaDocument( schema, { schema: { "anyOf": [ { "type": "number", "enum": [0], "title": "Apple" }, { "type": "number", "enum": [1], "title": "Banana" }, { "type": "string", "enum": ["orange"], "title": "Orange" } ] } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "anyOf": [ { "type": "number", "enum": [0], "title": "Apple" }, { "type": "number", "enum": [1], "title": "Banana" }, { "type": "string", "enum": ["orange"], "title": "Orange" } ], "description": "a" } } ) }) it("const enum", () => { const Fruits = { Apple: "apple", Banana: "banana", Cantaloupe: 3 } as const const schema = Schema.Enum(Fruits) assertJsonSchemaDocument( schema, { schema: { "anyOf": [ { "type": "string", "title": "Apple", "enum": ["apple"] }, { "type": "string", "title": "Banana", "enum": ["banana"] }, { "type": "number", "title": "Cantaloupe", "enum": [3] } ] } } ) }) }) it("TemplateLiteral", () => { const schema = Schema.TemplateLiteral(["a", Schema.String]) assertJsonSchemaDocument(schema, { schema: { "type": "string", "pattern": "^a[\\s\\S]*?$" } }) assertJsonSchemaDocument(schema.annotate({ description: "a" }), { schema: { "type": "string", "pattern": "^a[\\s\\S]*?$", "description": "a" } }) }) describe("Struct", () => { it("empty struct", () => { const schema = Schema.Struct({}) assertJsonSchemaDocument( schema, { schema: { "anyOf": [ { "type": "object" }, { "type": "array" } ] } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "anyOf": [ { "type": "object" }, { "type": "array" } ], "description": "a" } } ) }) describe("required property", () => { it("String", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.String }), { schema: { "type": "object", "properties": { "a": { "type": "string" } }, "required": ["a"], "additionalProperties": false } } ) }) it("String & annotate", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.String.annotate({ description: "a" }) }), { schema: { "type": "object", "properties": { "a": { "type": "string", "description": "a" } }, "required": ["a"], "additionalProperties": false } } ) }) it("String & annotateKey", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.String.annotateKey({ description: "a-key" }) }), { schema: { "type": "object", "properties": { "a": { "type": "string", "allOf": [{ "description": "a-key" }] } }, "required": ["a"], "additionalProperties": false } } ) }) it("String & annotate & annotateKey", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.String.annotate({ description: "a" }).annotateKey({ description: "a-key" }) }), { schema: { "type": "object", "properties": { "a": { "type": "string", "description": "a", "allOf": [{ "description": "a-key" }] } }, "required": ["a"], "additionalProperties": false } } ) }) }) describe("optionalKey", () => { it("String", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.optionalKey(Schema.String) }), { schema: { "type": "object", "properties": { "a": { "type": "string" } }, "additionalProperties": false } } ) }) it("String & annotate", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.optionalKey(Schema.String.annotate({ description: "a" })), b: Schema.optionalKey(Schema.String).annotate({ description: "b" }) }), { schema: { "type": "object", "properties": { "a": { "type": "string", "description": "a" }, "b": { "type": "string", "description": "b" } }, "additionalProperties": false } } ) }) it("String & annotateKey", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.optionalKey(Schema.String.annotateKey({ description: "a-key" })), b: Schema.optionalKey(Schema.String).annotateKey({ description: "b-key" }) }), { schema: { "type": "object", "properties": { "a": { "type": "string", "allOf": [{ "description": "a-key" }] }, "b": { "type": "string", "allOf": [{ "description": "b-key" }] } }, "additionalProperties": false } } ) }) it("String & annotate & annotateKey", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.optionalKey(Schema.String.annotate({ description: "a" }).annotateKey({ description: "a-key" })), b: Schema.optionalKey(Schema.String).annotate({ description: "b" }).annotateKey({ description: "b-key" }), c: Schema.optionalKey(Schema.String.annotate({ description: "a" }).annotateKey({ description: "a-key" })) .annotate({ description: "c-outer" }).annotateKey({ description: "c-outer-key" }) }), { schema: { "type": "object", "properties": { "a": { "type": "string", "description": "a", "allOf": [{ "description": "a-key" }] }, "b": { "type": "string", "description": "b", "allOf": [{ "description": "b-key" }] }, "c": { "type": "string", "description": "c-outer", "allOf": [{ "description": "c-outer-key" }] } }, "additionalProperties": false } } ) }) it("optionalKey(String) to String", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.optionalKey(Schema.String).pipe(Schema.encodeTo(Schema.String, { decode: SchemaGetter.passthrough(), encode: SchemaGetter.withDefault(Effect.succeed("")) })) }), { schema: { "type": "object", "properties": { "a": { "type": "string" } }, "required": ["a"], "additionalProperties": false } } ) }) }) describe("optional", () => { it("String", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.optional(Schema.String) }), { schema: { "type": "object", "properties": { "a": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "additionalProperties": false } } ) }) it("String & annotate", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.optional(Schema.String.annotate({ description: "a" })), b: Schema.optional(Schema.String).annotate({ description: "b" }) }), { schema: { "type": "object", "properties": { "a": { "anyOf": [ { "type": "string", "description": "a" }, { "type": "null" } ] }, "b": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "b" } }, "additionalProperties": false } } ) }) it("String & annotateKey", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.optional(Schema.String).annotateKey({ description: "a-key" }), b: Schema.optional(Schema.String.annotate({ description: "b" })).annotateKey({ description: "b-key" }) }), { schema: { "type": "object", "properties": { "a": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "allOf": [{ "description": "a-key" }] }, "b": { "anyOf": [ { "type": "string", "description": "b" }, { "type": "null" } ], "allOf": [{ "description": "b-key" }] } }, "additionalProperties": false } } ) }) it("optional(String) to String", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.optional(Schema.String).pipe(Schema.encodeTo(Schema.String, { decode: SchemaGetter.passthrough(), encode: SchemaGetter.withDefault(Effect.succeed("")) })) }), { schema: { "type": "object", "properties": { "a": { "type": "string" } }, "required": ["a"], "additionalProperties": false } } ) }) }) describe("UndefinedOr", () => { it("String", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.UndefinedOr(Schema.String) }), { schema: { "type": "object", "properties": { "a": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } }, "required": ["a"], "additionalProperties": false } } ) }) it("String & annotate", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.UndefinedOr(Schema.String.annotate({ description: "a" })), b: Schema.UndefinedOr(Schema.String).annotate({ description: "b" }) }), { schema: { "type": "object", "properties": { "a": { "anyOf": [ { "type": "string", "description": "a" }, { "type": "null" } ] }, "b": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "description": "b" } }, "required": ["a", "b"], "additionalProperties": false } } ) }) it("String & annotateKey", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.UndefinedOr(Schema.String).annotateKey({ description: "a-key" }), b: Schema.UndefinedOr(Schema.String.annotate({ description: "b" })).annotateKey({ description: "b-key" }) }), { schema: { "type": "object", "properties": { "a": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "allOf": [{ "description": "a-key" }] }, "b": { "anyOf": [ { "type": "string", "description": "b" }, { "type": "null" } ], "allOf": [{ "description": "b-key" }] } }, "required": ["a", "b"], "additionalProperties": false } } ) }) it("UndefinedOr(String) to String", () => { assertJsonSchemaDocument( Schema.Struct({ a: Schema.UndefinedOr(Schema.String).pipe(Schema.encodeTo(Schema.String, { decode: SchemaGetter.passthrough(), encode: SchemaGetter.transform((s) => s ?? "") })) }), { schema: { "type": "object", "properties": { "a": { "type": "string" } }, "required": ["a"], "additionalProperties": false } } ) }) }) }) describe("Record", () => { it("Record(String, Never)", () => { assertJsonSchemaDocument( Schema.Record(Schema.String, Schema.Never), { schema: { "type": "object", "additionalProperties": false } } ) assertJsonSchemaDocument( Schema.Record(Schema.String, Schema.Never.annotate({ description: "a" })), { schema: { "type": "object", "additionalProperties": { "description": "a", "not": {} } } } ) }) it("Record(String, Finite)", () => { assertJsonSchemaDocument( Schema.Record(Schema.String, Schema.Finite), { schema: { "type": "object", "additionalProperties": { "type": "number" } } } ) assertJsonSchemaDocument( Schema.Record( Schema.String.annotate({ description: "k" }), // TODO: where can we attach the description? Schema.Finite.annotate({ description: "v" }) ).annotate({ description: "r" }), { schema: { "type": "object", "additionalProperties": { "type": "number", "allOf": [{ "description": "v" }] }, "description": "r" } } ) }) it("Record(String, Json)", () => { const schema = Schema.Record(Schema.String, Schema.Json) assertJsonSchemaDocument( schema, { schema: { "type": "object" } } ) }) it("Record(`a${string}`, Number) & annotate", () => { assertJsonSchemaDocument( Schema.Record(Schema.TemplateLiteral(["a", Schema.String]), Schema.Finite), { schema: { "type": "object", "patternProperties": { "^a[\\s\\S]*?$": { "type": "number" } } } } ) }) it("Record(Literals(['a', 'b']), Finite)", () => { assertJsonSchemaDocument( Schema.Record(Schema.Literals(["a", "b"]), Schema.Finite), { schema: { "type": "object", "properties": { "a": { "type": "number" }, "b": { "type": "number" } }, "required": ["a", "b"], "additionalProperties": false } } ) }) it("Record(isUppercased, Number)", () => { assertJsonSchemaDocument( Schema.Record(Schema.String.check(Schema.isUppercased()), Schema.Finite), { schema: { "type": "object", "patternProperties": { "^[^a-z]*$": { "type": "number" } } } } ) }) describe("checks", () => { it("isMinProperties", () => { assertJsonSchemaDocument( Schema.Record(Schema.String, Schema.Finite).check(Schema.isMinProperties(2)), { schema: { "type": "object", "additionalProperties": { "type": "number" }, "allOf": [ { "minProperties": 2 } ] } } ) }) it("isMaxProperties", () => { assertJsonSchemaDocument( Schema.Record(Schema.String, Schema.Finite).check(Schema.isMaxProperties(2)), { schema: { "type": "object", "additionalProperties": { "type": "number" }, "allOf": [{ "maxProperties": 2 }] } } ) }) it("isPropertiesLengthBetween", () => { assertJsonSchemaDocument( Schema.Record(Schema.String, Schema.Finite).check(Schema.isPropertiesLengthBetween(2, 2)), { schema: { "type": "object", "additionalProperties": { "type": "number" }, "allOf": [{ "minProperties": 2, "maxProperties": 2 }] } } ) }) }) }) it("StructWithRest", () => { assertJsonSchemaDocument( Schema.StructWithRest(Schema.Struct({ a: Schema.String }), [ Schema.Record(Schema.String, Schema.Union([Schema.Finite, Schema.String])) ]), { schema: { "type": "object", "properties": { "a": { "type": "string" } }, "additionalProperties": { "anyOf": [ { "type": "number" }, { "type": "string" } ] }, "required": ["a"] } } ) }) describe("Tuple", () => { it("empty tuple", () => { const schema = Schema.Tuple([]) assertJsonSchemaDocument( schema, { schema: { "type": "array", "items": false } } ) assertJsonSchemaDocument( schema.annotate({ description: "a" }), { schema: { "type": "array", "items": false, "description": "a" } } ) }) describe("required element", () => { it("String", () => { assertJsonSchemaDocument( Schema.Tuple([ Schema.String ]), { schema: { "type": "array", "prefixItems": [{ "type": "string" }], "minItems": 1, "maxItems": 1 } } ) }) it("String & annotate", () => { assertJsonSchemaDocument( Schema.Tuple([ Schema.String.annotate({ description: "a" }) ]), { schema: { "type": "array", "prefixItems": [{ "type": "string", "description": "a" }], "minItems": 1, "maxItems": 1 } } ) }) it("String & annotateKey", () => { assertJsonSchemaDocument( Schema.Tuple([ Schema.String.annotateKey({ description: "a-key" }) ]), { schema: { "type": "array", "prefixItems": [{ "type": "string", "allOf": [{ "description": "a-key" }] }], "minItems": 1, "maxItems": 1 } } ) }) it("String & annotate & annotateKey", () => { assertJsonSchemaDocument( Schema.Tuple([ Schema.String.annotate({ description: "a" }).annotateKey({ description: "a-key" }) ]), { schema: { "type": "array", "prefixItems": [{ "type": "string", "description": "a", "allOf": [{ "description": "a-key" }] }], "minItems": 1, "maxItems": 1 } } ) }) }) describe("optionalKey", () => { it("String", () => { assertJsonSchemaDocument( Schema.Tuple([ Schema.optionalKey(Schema.String) ]), { schema: { "type": "array", "prefixItems": [ { "type": "string" } ], "maxItems": 1 } } ) }) it("String & annotate", () => { assertJsonSchemaDocument( Schema.Tuple([ Schema.optionalKey(Schema.String.annotate({ description: "a" })), Schema.optionalKey(Schema.String).annotate({ description: "b" }) ]), { schema: { "type": "array", "prefixItems": [ { "type": "string", "description": "a" }, { "type": "string", "description": "b" } ], "maxItems": 2 } } ) }) it("String & annotateKey", () => { assertJsonSchemaDocument( Schema.Tuple([ Schema.optionalKey(Schema.String.annotateKey({ description: "a-key" })), Schema.optionalKey(Schema.String).annotateKey({ description: "b-key" }) ]), { schema: { "type": "array", "prefixItems": [ { "type": "string", "allOf": [{ "description": "a-key" }] }, { "type": "string", "allOf": [{ "description": "b-key" }] } ], "maxItems": 2 } } ) }) it("String & annotate & annotateKey", () => { assertJsonSchemaDocument( Schema.Tuple([ Schema.optionalKey(Schema.String.annotate({ description: "a" }).annotateKey({ description: "a-key" })), Schema.optionalKey(Schema.String).annotate({ description: "b" }).annotateKey({ description: "b-key" }), Schema.optionalKey(Schema.String.annotate({ description: "a" }).annotateKey({ description: "a-key" })) .annotate({ description: "c-outer" }).annotateKey({ description: "c-outer-key" }) ]), { schema: { "type": "array", "prefixItems": [ { "type": "string", "description": "a", "allOf": [{ "description": "a-key" }] }, { "type": "string", "description": "b", "allOf": [{ "description": "b-key" }] }, { "type": "string", "description": "c-outer", "allOf": [{ "description": "c-outer-key" }] } ], "maxItems": 3 } } ) }) it("optionalKey(String) to String", () => { assertJsonSchemaDocument( Schema.Tuple([ Schema.optionalKey(Schema.String).pipe(Schema.encodeTo(Schema.String, { decode: SchemaGetter.passthrough(), encode: SchemaGetter.withDefault(Effect.succeed("")) })) ]), { schema: { "type": "array", "prefixItems": [{ "type": "string" }], "minItems": 1, "maxItems": 1 } } ) }) }) it("optionalKey to required key", () => { assertJsonSchemaDocument( Schema.Tuple([ Schema.optionalKey(Schema.String).pipe(Schema.encodeTo(Schema.String, { decode: SchemaGetter.passthrough(), encode: SchemaGetter.withDefault(Effect.succeed("")) })) ]), { schema: { "type": "array", "prefixItems": [ { "type": "string" } ], "minItems": 1, "maxItems": 1 } } ) }) }) describe("Array", () => { it("Array(String)", () => { assertJsonSchemaDocument( Schema.Array(Schema.String), { schema: { "type": "array", "items": { "type": "string" } } } ) assertJsonSchemaDocument( Schema.Array(Schema.String).annotate({ description: "a" }), { schema: { "type": "array", "items": { "type": "string" }, "description": "a" } } ) }) describe("checks", () => { it("isMinLength", () => { assertJsonSchemaDocument( Schema.Array(Schema.String).check(Schema.isMinLength(2)), { schema: { "type": "array", "items": { "type": "string" }, "allOf": [ { "minItems": 2 } ] } } ) }) it("isMaxLength", () => { assertJsonSchemaDocument( Schema.Array(Schema.String).check(Schema.isMaxLength(2)), { schema: { "type": "array", "items": { "type": "string" }, "allOf": [ { "maxItems": 2 } ] } } ) }) it("isLengthBetween", () => { assertJsonSchemaDocument( Schema.Array(Schema.String).check(Schema.isLengthBetween(2, 2)), { schema: { "type": "array", "items": { "type": "string" }, "allOf": [ { "minItems": 2 }, { "maxItems": 2 } ] } } ) }) it("UniqueArray", () => { assertJsonSchemaDocument( Schema.UniqueArray(Schema.String), { schema: { "type": "array", "items": { "type": "string" }, "allOf": [ { "uniqueItems": true } ] } } ) }) }) }) it("TupleWithRest", () => { assertJsonSchemaDocument( Schema.TupleWithRest(Schema.Tuple([Schema.String]), [Schema.Finite]), { schema: { "type": "array", "prefixItems": [ { "type": "string" } ], "items": { "type": "number" }, "minItems": 1 } } ) }) describe("Union", () => { it("empty union", () => { const schema = Schema.Union([]) assertJsonSchemaDocument(schema, { schema: { "not": {} } }) assertJsonSchemaDocument(Schema.Union([]).annotate({ description: "a" }), { schema: { "not": {}, "description": "a" } }) }) it("single member", () => { const schema = Schema.Union([Schema.String]) assertJsonSchemaDocument(schema, { schema: { "anyOf": [ { "type": "string" } ] } }) assertJsonSchemaDocument(Schema.Union([Schema.String]).annotate({ description: "a" }), { schema: { "anyOf": [ { "type": "string" } ], "description": "a" } }) assertJsonSchemaDocument( Schema.Union([Schema.String.annotate({ description: "inner" })]).annotate({ description: "outer" }), { schema: { "anyOf": [ { "type": "string", "description": "inner" } ], "description": "outer" } } ) }) it("String | Number", () => { assertJsonSchemaDocument( Schema.Union([ Schema.String, Schema.Finite ]), { schema: { "anyOf": [ { "type": "string" }, { "type": "number" } ] } } ) assertJsonSchemaDocument( Schema.Union([ Schema.String, Schema.Finite ]).annotate({ description: "description" }), { schema: { "anyOf": [ { "type": "string" }, { "type": "number" } ], "description": "description" } } ) }) it("String | BigInt", () => { assertJsonSchemaDocument( Schema.Union([ Schema.String, Schema.BigInt ]), { schema: { "anyOf": [ { "type": "string", "allOf": [ { "pattern": "^-?\\d+$" } ] }, { "type": "string" } ] } } ) assertJsonSchemaDocument( Schema.Union([ Schema.String, Schema.Finite ]).annotate({ description: "description" }), { schema: { "anyOf": [ { "type": "string" }, { "type": "number" } ], "description": "description" } } ) }) }) it("mutually recursive schemas", () => { interface Expression { readonly type: "expression" readonly value: number | Operation } interface Operation { readonly type: "operation" readonly operator: "+" | "-" readonly left: Expression readonly right: Expression } const Expression = Schema.Struct({ type: Schema.Literal("expression"), value: Schema.Union([Schema.Finite, Schema.suspend((): Schema.Codec => Operation)]) }).annotate({ identifier: "Expression" }) const Operation = Schema.Struct({ type: Schema.Literal("operation"), operator: Schema.Literals(["+", "-"]), left: Expression, right: Expression }).annotate({ identifier: "Operation" }) assertJsonSchemaDocument( Operation, { schema: { "$ref": "#/$defs/Operation" }, definitions: { Operation: { "type": "object", "properties": { "type": { "type": "string", "enum": [ "operation" ] }, "operator": { "type": "string", "enum": [ "+", "-" ] }, "left": { "$ref": "#/$defs/Expression" }, "right": { "$ref": "#/$defs/Expression" } }, "required": [ "type", "operator", "left", "right" ], "additionalProperties": false }, Expression: { "type": "object", "properties": { "type": { "type": "string", "enum": [ "expression" ] }, "value": { "anyOf": [ { "type": "number" }, { "$ref": "#/$defs/Operation" } ] } }, "required": [ "type", "value" ], "additionalProperties": false } } } ) assertJsonSchemaDocument( Expression, { schema: { "$ref": "#/$defs/Expression" }, definitions: { Operation: { "type": "object", "properties": { "type": { "type": "string", "enum": [ "operation" ] }, "operator": { "type": "string", "enum": [ "+", "-" ] }, "left": { "$ref": "#/$defs/Expression" }, "right": { "$ref": "#/$defs/Expression" } }, "required": [ "type", "operator", "left", "right" ], "additionalProperties": false }, Expression: { "type": "object", "properties": { "type": { "type": "string", "enum": [ "expression" ] }, "value": { "anyOf": [ { "type": "number" }, { "$ref": "#/$defs/Operation" } ] } }, "required": [ "type", "value" ], "additionalProperties": false } } } ) }) describe("fromJsonString", () => { it("top level fromJsonString", () => { assertJsonSchemaDocument( Schema.fromJsonString(Schema.FiniteFromString), { schema: { "type": "string", "contentMediaType": "application/json", "contentSchema": { "type": "string" } } } ) }) it("preserves the content schema identifier", () => { const MyEvent = Schema.Struct({ value: Schema.String }).annotate({ identifier: "MyEvent" }) assertJsonSchemaDocument( Schema.fromJsonString(MyEvent), { schema: { "$ref": "#/$defs/MyEventJsonString" }, definitions: { "MyEvent": { "type": "object", "properties": { "value": { "type": "string" } }, "required": [ "value" ], "additionalProperties": false }, "MyEventJsonString": { "type": "string", "contentMediaType": "application/json", "contentSchema": { "$ref": "#/$defs/MyEvent" } } } } ) }) it("respects an explicit encoded-side identifier", () => { const MyEvent = Schema.Struct({ value: Schema.String }).annotate({ identifier: "MyEvent" }) const MyWireEvent = Schema.flip( Schema.flip(Schema.fromJsonString(MyEvent)).annotate({ identifier: "MyWireEvent" }) ) assertJsonSchemaDocument( MyWireEvent, { schema: { "$ref": "#/$defs/MyWireEvent" }, definitions: { "MyEvent": { "type": "object", "properties": { "value": { "type": "string" } }, "required": [ "value" ], "additionalProperties": false }, "MyWireEvent": { "type": "string", "contentMediaType": "application/json", "contentSchema": { "$ref": "#/$defs/MyEvent" } } } } ) }) it("nested fromJsonString", () => { assertJsonSchemaDocument( Schema.fromJsonString(Schema.Struct({ a: Schema.fromJsonString(Schema.FiniteFromString) })), { schema: { "type": "string", "contentMediaType": "application/json", "contentSchema": { "additionalProperties": false, "properties": { "a": { "contentMediaType": "application/json", "contentSchema": { "type": "string" }, "type": "string" } }, "required": [ "a" ], "type": "object" } } } ) }) }) it("Class", () => { class A extends Schema.Class("A")({ a: Schema.String }) {} assertJsonSchemaDocument( A, { schema: { "$ref": "#/$defs/A" }, definitions: { A: { "type": "object", "properties": { "a": { "type": "string" } }, "required": ["a"], "additionalProperties": false } } } ) }) it("ErrorClass", () => { class E extends Schema.ErrorClass("E")({ a: Schema.String }) {} assertJsonSchemaDocument(E, { schema: { "$ref": "#/$defs/E" }, definitions: { E: { "type": "object", "properties": { "a": { "type": "string" } }, "required": ["a"], "additionalProperties": false } } }) }) })