1import { assert, describe, it } from "@effect/vitest"
2import { Effect, Schema, SchemaAST, SchemaRepresentation } from "effect"
3
4describe("SchemaRepresentation.toRepresentations", () => {
5 describe("root identity and sharing", () => {
6 it("preserves root order", () => {
7 const document = SchemaRepresentation.toRepresentations([
8 Schema.String.ast,
9 Schema.Number.ast,
10 Schema.Boolean.ast
11 ])
12
13 assert.deepStrictEqual(document, {
14 representations: [
15 { _tag: "String", checks: [] },
16 { _tag: "Number", checks: [] },
17 { _tag: "Boolean", checks: [] }
18 ],
19 references: {}
20 })
21 })
22
23 it("shares a named reference between roots", () => {
24 const shared = Schema.String.annotate({ identifier: "Shared" })
25 const document = SchemaRepresentation.toRepresentations([shared.ast, shared.ast])
26
27 assert.deepStrictEqual(document, {
28 representations: [
29 { _tag: "Reference", $ref: "Shared" },
30 { _tag: "Reference", $ref: "Shared" }
31 ],
32 references: {
33 Shared: {
34 _tag: "String",
35 annotations: { identifier: "Shared" },
36 checks: []
37 }
38 }
39 })
40 })
41
42 it("shares a fallback reference across contextual copies of the same encoded AST", () => {
43 const Content = Schema.Struct({ text: Schema.String }).annotate({ identifier: "Tool.Content" })
44 const first = Schema.toCodecJson(Schema.fromJsonString(Content))
45 const second = Schema.toCodecJson(Schema.fromJsonString(Content))
46 const document = SchemaRepresentation.toRepresentations([first.ast, second.ast])
47
48 assert.notStrictEqual(first.ast, second.ast)
49 assert.deepStrictEqual(document, {
50 representations: [
51 { _tag: "Reference", $ref: "Tool.ContentEncoded" },
52 { _tag: "Reference", $ref: "Tool.ContentEncoded" }
53 ],
54 references: {
55 "Tool.ContentEncoded": {
56 _tag: "String",
57 annotations: {
58 expected: "a string that will be decoded as JSON",
59 contentMediaType: "application/json",
60 "~identifier": "Tool.Content"
61 },
62 checks: []
63 }
64 }
65 })
66 })
67
68 it("shares copies of the same AST with the same Context", () => {
69 const ast = Schema.String.annotate({ identifier: "Value" }).ast
70 const context = new SchemaAST.Context(false, false)
71 const first = SchemaAST.replaceContext(ast, context)
72 const second = SchemaAST.replaceContext(ast, context)
73
74 assert.notStrictEqual(first, second)
75 assert.deepStrictEqual(SchemaRepresentation.toRepresentations([first, second]), {
76 representations: [
77 { _tag: "Reference", $ref: "Value" },
78 { _tag: "Reference", $ref: "Value" }
79 ],
80 references: {
81 Value: {
82 _tag: "String",
83 annotations: { identifier: "Value" },
84 checks: []
85 }
86 }
87 })
88 })
89
90 it("preserves the original owner through repeated Context replacements", () => {
91 const ast = Schema.String.annotate({ identifier: "Value" }).ast
92 const first = SchemaAST.replaceContext(ast, new SchemaAST.Context(true, false))
93 const chained = SchemaAST.replaceContext(first, new SchemaAST.Context(false, true))
94 const direct = SchemaAST.replaceContext(ast, new SchemaAST.Context(false, true))
95
96 assert.strictEqual(SchemaAST.getContextOwner(first), ast)
97 assert.strictEqual(SchemaAST.getContextOwner(chained), ast)
98 assert.deepStrictEqual(SchemaRepresentation.toRepresentations([chained, direct]), {
99 representations: [
100 { _tag: "Reference", $ref: "Value" },
101 { _tag: "Reference", $ref: "Value" }
102 ],
103 references: {
104 Value: {
105 _tag: "String",
106 annotations: { identifier: "Value" },
107 checks: []
108 }
109 }
110 })
111 })
112
113 it("keeps a checked derivative distinct from its identified source", () => {
114 const base = Schema.String.annotate({ identifier: "Text" })
115 const refined = base.pipe(Schema.check(Schema.isMinLength(1)))
116 const forward = SchemaRepresentation.toRepresentations([base.ast, refined.ast])
117 const reversed = SchemaRepresentation.toRepresentations([refined.ast, base.ast])
118
119 assert.deepStrictEqual(forward.representations[0], { _tag: "Reference", $ref: "Text" })
120 assert.strictEqual(forward.representations[1]._tag, "String")
121 if (forward.representations[1]._tag === "String") {
122 assert.strictEqual(forward.representations[1].checks.length, 1)
123 }
124 assert.strictEqual(reversed.representations[0]._tag, "String")
125 if (reversed.representations[0]._tag === "String") {
126 assert.strictEqual(reversed.representations[0].checks.length, 1)
127 }
128 assert.deepStrictEqual(reversed.representations[1], { _tag: "Reference", $ref: "Text" })
129 assert.deepStrictEqual(forward.references, {
130 Text: {
131 _tag: "String",
132 annotations: { identifier: "Text" },
133 checks: []
134 }
135 })
136 assert.deepStrictEqual(reversed.references, forward.references)
137 })
138 })
139
140 describe.each([
141 { name: "toType", project: SchemaAST.toType },
142 { name: "toEncoded", project: SchemaAST.toEncoded },
143 { name: "flip", project: SchemaAST.flip }
144 ])("contextual projections with $name", ({ project }) => {
145 it.each([
146 {
147 name: "primitive",
148 schema: Schema.NumberFromString.pipe(Schema.annotateEncoded({ identifier: "Wire" }))
149 },
150 { name: "struct", schema: Schema.Struct({ value: Schema.NumberFromString }) },
151 { name: "array", schema: Schema.Array(Schema.NumberFromString) },
152 { name: "tuple", schema: Schema.Tuple([Schema.NumberFromString, Schema.String]) },
153 { name: "union", schema: Schema.Union([Schema.NumberFromString, Schema.Boolean]) },
154 { name: "declaration", schema: Schema.Option(Schema.NumberFromString) },
155 { name: "suspend", schema: Schema.suspend(() => Schema.Struct({ value: Schema.NumberFromString })) },
156 {
157 name: "encoding chain",
158 schema: Schema.String.pipe(
159 Schema.decodeTo(Schema.NumberFromString),
160 Schema.annotateEncoded({ identifier: "Wire" })
161 )
162 }
163 ])("shares the $name reference while preserving each field's context", ({ schema }) => {
164 const value = schema.annotate({ identifier: "Value" })
165 const struct = Schema.Struct({
166 optional: Schema.optionalKey(value),
167 mutable: Schema.mutableKey(value),
168 both: Schema.optionalKey(Schema.mutableKey(value)),
169 required: value
170 })
171 const expected = SchemaRepresentation.toRepresentation(project(value.ast))
172 const document = SchemaRepresentation.toRepresentations([project(struct.ast), project(value.ast)])
173
174 assert.deepStrictEqual(document.references, expected.references)
175 assert.deepStrictEqual(document.representations, [
176 {
177 _tag: "Objects",
178 propertySignatures: [
179 { name: "optional", type: expected.representation, isOptional: true, isMutable: false },
180 { name: "mutable", type: expected.representation, isOptional: false, isMutable: true },
181 { name: "both", type: expected.representation, isOptional: true, isMutable: true },
182 { name: "required", type: expected.representation, isOptional: false, isMutable: false }
183 ],
184 indexSignatures: [],
185 checks: []
186 },
187 expected.representation
188 ])
189 })
190
191 it("shares recursive references across contextual copies", () => {
192 const node: SchemaAST.Suspend = new SchemaAST.Suspend(
193 () =>
194 new SchemaAST.Objects([
195 new SchemaAST.PropertySignature("value", Schema.NumberFromString.ast),
196 new SchemaAST.PropertySignature("next", SchemaAST.optionalKey(node))
197 ], []),
198 { identifier: "Node" }
199 )
200 const document = SchemaRepresentation.toRepresentations([
201 project(SchemaAST.mutableKey(node)),
202 project(node)
203 ])
204
205 assert.deepStrictEqual(document.representations, [
206 { _tag: "Reference", $ref: "Node" },
207 { _tag: "Reference", $ref: "Node" }
208 ])
209 assert.deepStrictEqual(Object.keys(document.references), ["Node"])
210 const representation = document.references.Node
211 assert.strictEqual(representation._tag, "Suspend")
212 if (representation._tag !== "Suspend") return
213 assert.strictEqual(representation.thunk._tag, "Objects")
214 if (representation.thunk._tag !== "Objects") return
215 assert.deepStrictEqual(representation.thunk.propertySignatures[1], {
216 name: "next",
217 type: { _tag: "Reference", $ref: "Node" },
218 isOptional: true,
219 isMutable: false
220 })
221 })
222 })
223
224 it("shares type references without losing key annotations or constructor defaults", () => {
225 const value = Schema.NumberFromString.annotate({ identifier: "Value" })
226 const schema = Schema.toType(Schema.Struct({
227 annotated: value.annotateKey({ description: "field" }),
228 defaulted: value.pipe(Schema.withConstructorDefault(Effect.succeed(1))),
229 required: value
230 }))
231 const document = Schema.toRepresentation(schema)
232
233 assert.deepStrictEqual(Object.keys(document.references), ["Value"])
234 assert.strictEqual(document.representation._tag, "Objects")
235 if (document.representation._tag !== "Objects") return
236 assert.deepStrictEqual(document.representation.propertySignatures[0], {
237 name: "annotated",
238 type: { _tag: "Reference", $ref: "Value" },
239 isOptional: false,
240 isMutable: false,
241 annotations: { description: "field" }
242 })
243 assert.deepStrictEqual(schema.make({ annotated: 2, required: 3 }), { annotated: 2, defaulted: 1, required: 3 })
244 })
245
246 it("keeps projected checks and value annotations distinct from the source", () => {
247 const value = Schema.optionalKey(Schema.NumberFromString).annotate({ identifier: "Value" })
248 const checked = value.check(Schema.isGreaterThan(0))
249 const annotated = value.annotate({ description: "another value" })
250 const document = SchemaRepresentation.toRepresentations([
251 SchemaAST.toType(value.ast),
252 SchemaAST.toType(checked.ast),
253 SchemaAST.toType(annotated.ast)
254 ])
255
256 assert.deepStrictEqual(document.representations[0], { _tag: "Reference", $ref: "Value" })
257 assert.strictEqual(document.representations[1]._tag, "Number")
258 if (document.representations[1]._tag !== "Number") return
259 assert.strictEqual(document.representations[1].checks.length, 1)
260 assert.deepStrictEqual(document.representations[2], { _tag: "Reference", $ref: "Value_1" })
261 assert.deepStrictEqual(document.references.Value, {
262 _tag: "Number",
263 annotations: { identifier: "Value" },
264 checks: []
265 })
266 assert.deepStrictEqual(document.references.Value_1, {
267 _tag: "Number",
268 annotations: { identifier: "Value_1", description: "another value" },
269 checks: []
270 })
271 })
272
273 describe("reference policies", () => {
274 it("exposes the projected body and counts contextual copies together", () => {
275 const value = Schema.NumberFromString.annotate({ identifier: "Value" })
276 const inputs: Array<SchemaRepresentation.ReferencePolicyInput> = []
277 const document = SchemaRepresentation.toRepresentations([
278 SchemaAST.toType(Schema.optionalKey(value).ast),
279 SchemaAST.toType(value.ast)
280 ], {
281 referencePolicy: (input) => {
282 inputs.push(input)
283 return SchemaAST.getLastEncoding(input.ast)._tag
284 }
285 })
286
287 assert.strictEqual(inputs.length, 1)
288 assert.strictEqual(inputs[0].occurrences, 2)
289 assert.strictEqual(inputs[0].ast.encoding, undefined)
290 assert.deepStrictEqual(document, {
291 representations: [
292 { _tag: "Reference", $ref: "Number" },
293 { _tag: "Reference", $ref: "Number" }
294 ],
295 references: {
296 Number: { _tag: "Number", annotations: { identifier: "Number" }, checks: [] }
297 }
298 })
299 })
300
301 it("supports policies based on occurrence counts", () => {
302 const shared = Schema.Struct({ value: Schema.String })
303 const equivalent = Schema.Struct({ value: Schema.String })
304 const referencePolicy: SchemaRepresentation.ReferencePolicy = ({ ast, occurrences }) =>
305 occurrences > 1 ? `${ast._tag}_` : undefined
306
307 const single = SchemaRepresentation.toRepresentations([shared.ast], { referencePolicy })
308 const distinct = SchemaRepresentation.toRepresentations([shared.ast, equivalent.ast], {
309 referencePolicy: ({ ast, occurrences }) =>
310 ast._tag === "Objects" && occurrences > 1 ? `${ast._tag}_` : undefined
311 })
312 const repeated = SchemaRepresentation.toRepresentations([shared.ast, shared.ast], { referencePolicy })
313
314 assert.deepStrictEqual(single.references, {})
315 assert.deepStrictEqual(distinct.references, {})
316 assert.deepStrictEqual(repeated.representations, [
317 { _tag: "Reference", $ref: "Objects_" },
318 { _tag: "Reference", $ref: "Objects_" }
319 ])
320 assert.deepStrictEqual(Object.keys(repeated.references), ["Objects_"])
321 })
322
323 it("suffixes policy name collisions", () => {
324 const first = Schema.Struct({ first: Schema.String })
325 const second = Schema.Struct({ second: Schema.String })
326 const document = SchemaRepresentation.toRepresentations([first.ast, second.ast], {
327 referencePolicy: ({ ast }) => ast._tag === "Objects" ? "Model" : undefined
328 })
329
330 assert.deepStrictEqual(document.representations, [
331 { _tag: "Reference", $ref: "Model" },
332 { _tag: "Reference", $ref: "Model_1" }
333 ])
334 assert.deepStrictEqual(Object.keys(document.references), ["Model", "Model_1"])
335 })
336
337 it("distinguishes identifiers that share the same encoded AST owner", () => {
338 const first = Schema.NumberFromString.annotate({ identifier: "First" })
339 const second = Schema.NumberFromString.annotate({ identifier: "Second" })
340 const inputs: Array<SchemaRepresentation.ReferencePolicyInput> = []
341 const document = SchemaRepresentation.toRepresentations([first.ast, second.ast], {
342 referencePolicy: (input) => {
343 inputs.push(input)
344 return input.identifier
345 }
346 })
347
348 assert.deepStrictEqual(document.representations, [
349 { _tag: "Reference", $ref: "FirstEncoded" },
350 { _tag: "Reference", $ref: "SecondEncoded" }
351 ])
352 assert.deepStrictEqual(Object.keys(document.references), ["FirstEncoded", "SecondEncoded"])
353 assert.deepStrictEqual(inputs.map(({ identifier, occurrences }) => ({ identifier, occurrences })), [
354 { identifier: "FirstEncoded", occurrences: 1 },
355 { identifier: "SecondEncoded", occurrences: 1 }
356 ])
357 assert.strictEqual(inputs[0].ast, inputs[1].ast)
358 })
359 })
360
361 describe("identifier collisions", () => {
362 it("suffixes different schemas with the same identifier", () => {
363 const first = Schema.String.annotate({ identifier: "Value", description: "first" })
364 const second = Schema.Number.annotate({ identifier: "Value", description: "second" })
365
366 assert.deepStrictEqual(
367 SchemaRepresentation.toRepresentations([first.ast, second.ast]),
368 {
369 representations: [
370 { _tag: "Reference", $ref: "Value" },
371 { _tag: "Reference", $ref: "Value_1" }
372 ],
373 references: {
374 Value: {
375 _tag: "String",
376 annotations: { identifier: "Value", description: "first" },
377 checks: []
378 },
379 Value_1: {
380 _tag: "Number",
381 annotations: { identifier: "Value_1", description: "second" },
382 checks: []
383 }
384 }
385 }
386 )
387 })
388
389 it("suffixes referentially distinct ASTs with equal representations", () => {
390 const first = Schema.String.annotate({ identifier: "Value" })
391 const second = Schema.String.annotate({ identifier: "Value" })
392
393 assert.deepStrictEqual(
394 SchemaRepresentation.toRepresentations([first.ast, second.ast]),
395 {
396 representations: [
397 { _tag: "Reference", $ref: "Value" },
398 { _tag: "Reference", $ref: "Value_1" }
399 ],
400 references: {
401 Value: {
402 _tag: "String",
403 annotations: { identifier: "Value" },
404 checks: []
405 },
406 Value_1: {
407 _tag: "String",
408 annotations: { identifier: "Value_1" },
409 checks: []
410 }
411 }
412 }
413 )
414 })
415
416 it("suffixes fallback and explicit identifier collisions in encounter order", () => {
417 const first = Schema.String.annotate({ "~identifier": "Person" })
418 const second = Schema.Number.annotate({ "~identifier": "Person" })
419 const explicit = Schema.Boolean.annotate({ identifier: "PersonEncoded" })
420
421 assert.deepStrictEqual(
422 SchemaRepresentation.toRepresentations([first.ast, second.ast, explicit.ast]),
423 {
424 representations: [
425 { _tag: "Reference", $ref: "PersonEncoded" },
426 { _tag: "Reference", $ref: "PersonEncoded_1" },
427 { _tag: "Reference", $ref: "PersonEncoded_2" }
428 ],
429 references: {
430 PersonEncoded: {
431 _tag: "String",
432 annotations: { "~identifier": "Person" },
433 checks: []
434 },
435 PersonEncoded_1: {
436 _tag: "Number",
437 annotations: { "~identifier": "Person" },
438 checks: []
439 },
440 PersonEncoded_2: {
441 _tag: "Boolean",
442 annotations: { identifier: "PersonEncoded_2" },
443 checks: []
444 }
445 }
446 }
447 )
448 })
449 })
450})