1import { assert, describe, it } from "@effect/vitest"
2import { Schema, type SchemaRepresentation } from "effect"
3
4describe("Schema.toJsonSchemaDocument", () => {
5 it("uses the encoded side for representations and JSON Schema", () => {
6 const representation = Schema.toRepresentation(Schema.FiniteFromString)
7 assert.strictEqual(representation.representation._tag, "String")
8
9 const typeRepresentation = Schema.toRepresentation(Schema.toType(Schema.FiniteFromString))
10 assert.strictEqual(typeRepresentation.representation._tag, "Number")
11
12 assert.deepStrictEqual(Schema.toJsonSchemaDocument(Schema.FiniteFromString), {
13 dialect: "draft-2020-12",
14 schema: { type: "string" },
15 definitions: {}
16 })
17 })
18
19 it("inlines repeated anonymous schemas by default", () => {
20 const shared = Schema.Struct({ value: Schema.String })
21
22 assert.deepStrictEqual(
23 Schema.toJsonSchemaDocument(Schema.Struct({ first: shared, second: shared })),
24 {
25 dialect: "draft-2020-12",
26 schema: {
27 type: "object",
28 properties: {
29 first: {
30 type: "object",
31 properties: { value: { type: "string" } },
32 required: ["value"],
33 additionalProperties: true
34 },
35 second: {
36 type: "object",
37 properties: { value: { type: "string" } },
38 required: ["value"],
39 additionalProperties: true
40 }
41 },
42 required: ["first", "second"],
43 additionalProperties: true
44 },
45 definitions: {}
46 }
47 )
48 })
49
50 it("forwards the reference policy to representation and JSON Schema generation", () => {
51 const shared = Schema.String.annotate({ identifier: "Shared" })
52 const options = { referencePolicy: () => undefined }
53
54 assert.deepStrictEqual(Schema.toRepresentation(shared, options).references, {})
55 assert.deepStrictEqual(Schema.toJsonSchemaDocument(shared, options), {
56 dialect: "draft-2020-12",
57 schema: { type: "string" },
58 definitions: {}
59 })
60
61 const inputs: Array<SchemaRepresentation.ReferencePolicyInput> = []
62 Schema.toJsonSchemaDocument(Schema.Date.annotate({ identifier: "Date" }), {
63 referencePolicy: (input) => {
64 inputs.push(input)
65 return undefined
66 }
67 })
68 assert.strictEqual(inputs[0].ast._tag, "String")
69 assert.strictEqual(inputs[0].identifier, "DateEncoded")
70 })
71
72 it("projects encoded tuple elements for JSON Schema", () => {
73 assert.deepStrictEqual(Schema.toJsonSchemaDocument(Schema.Tuple([Schema.NumberFromString])).schema, {
74 type: "array",
75 prefixItems: [{ type: "string" }],
76 minItems: 1,
77 maxItems: 1
78 })
79 })
80
81 it("does not project checks through an artificial JSON encoding", () => {
82 assert.deepStrictEqual(
83 Schema.toJsonSchemaDocument(Schema.Number.check(Schema.isGreaterThan(0))),
84 {
85 dialect: "draft-2020-12",
86 schema: {
87 anyOf: [
88 { type: "number" },
89 {
90 type: "string",
91 enum: ["Infinity", "-Infinity", "NaN"]
92 }
93 ]
94 },
95 definitions: {}
96 }
97 )
98 })
99
100 it("preserves checks when no artificial JSON encoding is needed", () => {
101 assert.deepStrictEqual(
102 Schema.toJsonSchemaDocument(Schema.Number.check(Schema.isFinite(), Schema.isGreaterThan(0))),
103 {
104 dialect: "draft-2020-12",
105 schema: {
106 type: "number",
107 exclusiveMinimum: 0
108 },
109 definitions: {}
110 }
111 )
112 })
113
114 it("does not project annotations through an artificial JSON encoding", () => {
115 const schema = Schema.Number.annotate({ description: "source description" })
116 const expected = {
117 dialect: "draft-2020-12" as const,
118 schema: {
119 anyOf: [
120 { type: "number" },
121 { type: "string", enum: ["Infinity", "-Infinity", "NaN"] }
122 ]
123 },
124 definitions: {}
125 }
126
127 assert.deepStrictEqual(Schema.toJsonSchemaDocument(schema), expected)
128 assert.deepStrictEqual(Schema.toJsonSchemaDocument(Schema.toCodecJson(schema)), expected)
129 })
130
131 it("preserves output, references and generation options", () => {
132 const shared = Schema.String.check(Schema.isMinLength(2)).annotate({
133 identifier: "Shared",
134 description: "shared text",
135 "x-consumer": "kept"
136 })
137 const schema = Schema.Struct({
138 first: shared,
139 second: shared,
140 count: Schema.FiniteFromString
141 }).annotate({ description: "root" })
142 const options: Schema.ToJsonSchemaOptions = {
143 onExcessProperty: "ignore",
144 generateDescriptions: true,
145 includeAnnotationKey: (key) => key === "x-consumer"
146 }
147
148 assert.deepStrictEqual(Schema.toJsonSchemaDocument(schema, options), {
149 dialect: "draft-2020-12",
150 schema: {
151 type: "object",
152 properties: {
153 first: { $ref: "#/$defs/Shared" },
154 second: { $ref: "#/$defs/Shared" },
155 count: {
156 type: "string",
157 description: "a string that will be decoded as a finite number"
158 }
159 },
160 required: ["first", "second", "count"],
161 additionalProperties: true,
162 description: "root"
163 },
164 definitions: {
165 Shared: {
166 type: "string",
167 allOf: [{
168 minLength: 1,
169 description: "shared text",
170 "x-consumer": "kept"
171 }]
172 }
173 }
174 })
175 })
176
177 it("uses custom compiler annotations without a central built-in switch", () => {
178 const custom = Schema.String.check(Schema.makeFilter<string>((value) => value.length >= 2, {
179 representation: {
180 id: "test/schema/minTwoCharacters",
181 payload: null
182 },
183 toJsonSchema: () => ({ minLength: 2 })
184 }))
185
186 assert.deepStrictEqual(Schema.toJsonSchemaDocument(custom), {
187 dialect: "draft-2020-12",
188 schema: {
189 type: "string",
190 minLength: 2
191 },
192 definitions: {}
193 })
194 })
195
196 it("emits JSON content media types after encoded projection", () => {
197 const schema = Schema.fromJsonString(Schema.Struct({
198 value: Schema.FiniteFromString
199 }))
200
201 assert.deepStrictEqual(Schema.toJsonSchemaDocument(schema).schema, {
202 type: "string",
203 contentMediaType: "application/json"
204 })
205 })
206
207 it("approximates declarations without a JSON codec", () => {
208 const schema = Schema.declare((input): input is string => typeof input === "string", {
209 representation: {
210 id: "test/schema/opaqueString",
211 payload: null
212 }
213 })
214
215 assert.deepStrictEqual(Schema.toJsonSchemaDocument(schema), {
216 dialect: "draft-2020-12",
217 schema: {},
218 definitions: {}
219 })
220 })
221})