1import { assert } from "@effect/vitest"
2import {
3 Cause,
4 DateTime,
5 Duration,
6 Effect,
7 HashMap,
8 Option,
9 Redacted,
10 Result,
11 Schema,
12 SchemaAST,
13 SchemaGetter,
14 SchemaIssue,
15 SchemaParser,
16 SchemaTransformation
17} from "effect"
18import { TestSchema } from "effect/testing"
19import { describe, it } from "vitest"
20import { assertTrue, deepStrictEqual, strictEqual, throws } from "../utils/assert.ts"
21
22const isDeno = "Deno" in globalThis
23const formatIssue = SchemaIssue.makeFormatterDefault()
24
25const FiniteFromDate = Schema.Date.pipe(Schema.decodeTo(
26 Schema.Number,
27 SchemaTransformation.transform({
28 decode: (date) => date.getTime(),
29 encode: (n) => new Date(n)
30 })
31))
32
33describe("Serializers", () => {
34 for (
35 const [name, toCodec] of [
36 ["toCodecJson", Schema.toCodecJson],
37 ["toCodecStringTree", Schema.toCodecStringTree]
38 ] as const
39 ) {
40 it(`${name} preserves union order when branded members share an AST`, () => {
41 const A = Schema.Struct({ a: Schema.String })
42 const First = A.pipe(Schema.brand("First"))
43 const schema = Schema.Union([
44 First,
45 Schema.Struct({ b: Schema.String }),
46 A.pipe(Schema.brand("Last"))
47 ])
48 const codec = toCodec(schema)
49 const input = { a: "a", b: "b" }
50
51 assert.deepStrictEqual(Schema.decodeUnknownSync(codec)(input), First.make({ a: "a" }))
52 assert.deepStrictEqual(Schema.encodeUnknownSync(codec)(input), { a: "a" })
53 })
54 }
55
56 describe("toCodecJson", () => {
57 it("exposes the source schema", () => {
58 const schema = Schema.FiniteFromString
59 const serializer = Schema.toCodecJson(schema)
60 strictEqual(serializer.schema, schema)
61 })
62
63 it("treats Json as canonical", () => {
64 strictEqual(Schema.toCodecJson(Schema.Json).ast, Schema.Json.ast)
65 strictEqual(Schema.toCodecJson(Schema.MutableJson).ast, Schema.MutableJson.ast)
66 })
67
68 it("is idempotent", () => {
69 const once = Schema.toCodecJson(Schema.suspend(() => Schema.Struct({ value: Schema.Number })))
70 strictEqual(Schema.toCodecJson(once).ast, once.ast)
71 })
72
73 it("should reorder the types in the Union based on the encoded side", async () => {
74 const schema = Schema.Union([
75 Schema.String,
76 Schema.String.pipe(Schema.encodeTo(Schema.BigInt, {
77 decode: SchemaGetter.transform((n: bigint) => String(n) + "a"),
78 encode: SchemaGetter.transform(() => 0n)
79 }))
80 ])
81 const serializer = Schema.toCodecJson(schema)
82 const asserts = new TestSchema.Asserts(Schema.toCodecJson(serializer))
83
84 const decoding = asserts.decoding()
85 await decoding.succeed("1", "1a")
86 })
87
88 describe("schemas without encoding", () => {
89 describe("Unsupported schemas", () => {
90 it("Struct with Symbol property name", () => {
91 const a = Symbol.for("a")
92 const schema = Schema.Struct({
93 [a]: Schema.String
94 })
95 throws(
96 () => Schema.toCodecJson(schema),
97 "Objects property names must be strings"
98 )
99 })
100 })
101
102 describe("Declaration", () => {
103 it("instanceOf without annotations", async () => {
104 const schema = Schema.instanceOf(URL)
105 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
106
107 await asserts.encoding().fail(
108 new URL("https://example.com"),
109 "Expected JSON value"
110 )
111 await asserts.decoding().fail({}, "Expected <Declaration>")
112 })
113
114 describe("instanceOf with annotation", () => {
115 it("arg: message: string", async () => {
116 class MyError extends Error {
117 constructor(message?: string) {
118 super(message)
119 this.name = "MyError"
120 Object.setPrototypeOf(this, MyError.prototype)
121 }
122 }
123
124 const schema = Schema.instanceOf(
125 MyError,
126 {
127 title: "MyError",
128 toCodec: () =>
129 Schema.link<MyError>()(
130 Schema.String,
131 SchemaTransformation.transform({
132 decode: (message) => new MyError(message),
133 encode: (e) => e.message
134 })
135 )
136 }
137 )
138 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
139
140 const encoding = asserts.encoding()
141 await encoding.succeed(new MyError("a"), "a")
142
143 const decoding = asserts.decoding()
144 await decoding.succeed("a", new MyError("a"))
145 })
146
147 it("arg: struct", async () => {
148 class MyError extends Error {
149 static Props = Schema.Struct({
150 message: Schema.String,
151 cause: Schema.String
152 })
153
154 constructor(props: typeof MyError.Props["Type"]) {
155 super(props.message, { cause: props.cause })
156 this.name = "MyError"
157 Object.setPrototypeOf(this, MyError.prototype)
158 }
159
160 static schema = Schema.instanceOf(
161 MyError,
162 {
163 title: "MyError",
164 toCodec: () =>
165 Schema.link<MyError>()(
166 MyError.Props,
167 SchemaTransformation.transform({
168 decode: (props) => new MyError(props),
169 encode: (e) => ({
170 message: e.message,
171 cause: typeof e.cause === "string" ? e.cause : String(e.cause)
172 })
173 })
174 )
175 }
176 )
177 }
178
179 const schema = MyError.schema
180 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
181
182 const encoding = asserts.encoding()
183 await encoding.succeed(new MyError({ message: "a", cause: "b" }), {
184 message: "a",
185 cause: "b"
186 })
187
188 const decoding = asserts.decoding()
189 await decoding.succeed({ message: "a", cause: "b" }, new MyError({ message: "a", cause: "b" }))
190 })
191 })
192 })
193
194 describe("should return the same reference if nothing changed", () => {
195 it("Struct", async () => {
196 const schema = Schema.Struct({
197 a: Schema.String,
198 b: Schema.Boolean
199 })
200 const serializer = Schema.toCodecJson(schema)
201 strictEqual(serializer.ast, schema.ast)
202 })
203
204 it("Record", async () => {
205 const schema = Schema.Record(Schema.String, Schema.Boolean)
206 const serializer = Schema.toCodecJson(schema)
207 strictEqual(serializer.ast, schema.ast)
208 })
209
210 it("Tuple", async () => {
211 const schema = Schema.Tuple([Schema.String, Schema.Boolean])
212 const serializer = Schema.toCodecJson(schema)
213 strictEqual(serializer.ast, schema.ast)
214 })
215
216 it("Array", async () => {
217 const schema = Schema.Array(Schema.String)
218 const serializer = Schema.toCodecJson(schema)
219 strictEqual(serializer.ast, schema.ast)
220 })
221
222 it("Union", async () => {
223 const schema = Schema.Union([Schema.String, Schema.Boolean])
224 const serializer = Schema.toCodecJson(schema)
225 strictEqual(serializer.ast, schema.ast)
226 })
227 })
228
229 it("should apply the construction process to the provided link in the serializer annotation", async () => {
230 const schema = Schema.Struct({
231 a: Schema.Date.annotate({
232 toCodec: () =>
233 Schema.link<Date>()(
234 Schema.Date,
235 SchemaTransformation.passthrough()
236 )
237 }),
238 b: Schema.Number
239 })
240 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
241
242 const encoding = asserts.encoding()
243 await encoding.succeed({ a: new Date("2021-01-01"), b: 1 }, {
244 a: "2021-01-01T00:00:00.000Z",
245 b: 1
246 })
247 })
248
249 it("Never", async () => {
250 const schema = Schema.Never
251 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
252
253 const encoding = asserts.encoding()
254 await encoding.fail({}, "Expected never")
255 })
256
257 it("Any", async () => {
258 const schema = Schema.Any
259 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
260
261 const encoding = asserts.encoding()
262 await encoding.succeed(() => {})
263
264 const decoding = asserts.decoding()
265 await decoding.succeed(() => {})
266 })
267
268 it("Unknown", async () => {
269 const schema = Schema.Unknown
270 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
271
272 const encoding = asserts.encoding()
273 await encoding.succeed("a")
274 await encoding.succeed(1)
275 await encoding.succeed(true)
276 await encoding.succeed(null)
277 await encoding.succeed({ a: "a", b: 1, c: true })
278 await encoding.succeed(["a", 1, true])
279 await encoding.fail({ a: 1n }, `Expected JSON value`)
280
281 const decoding = asserts.decoding()
282 await decoding.succeed("a")
283 await decoding.succeed(1)
284 await decoding.succeed(true)
285 await decoding.succeed(null)
286 await decoding.succeed({ a: "a", b: 1, c: true })
287 await decoding.succeed(["a", 1, true])
288 await decoding.fail({ a: 1n }, `Expected JSON value`)
289 })
290
291 it("ObjectKeyword", async () => {
292 const schema = Schema.ObjectKeyword
293 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
294
295 const encoding = asserts.encoding()
296 await encoding.succeed({ a: "a", b: 1, c: true })
297 await encoding.succeed(["a", 1, true])
298 await encoding.fail("a", `Expected object | array | function`)
299 await encoding.fail({ a: 1n }, `Expected JSON value\n at ["a"]`)
300
301 const decoding = asserts.decoding()
302 await decoding.succeed({ a: "a", b: 1, c: true })
303 await decoding.succeed(["a", 1, true])
304 await decoding.fail("a", `Expected array | object`)
305 await decoding.fail({ a: 1n }, `Expected JSON value\n at ["a"]`)
306 })
307
308 it("Undefined", async () => {
309 const schema = Schema.Undefined
310 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
311
312 const encoding = asserts.encoding()
313 await encoding.succeed(undefined, null)
314 })
315
316 it("Void", async () => {
317 const schema = Schema.Void
318 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
319
320 const encoding = asserts.encoding()
321 await encoding.succeed(undefined, null)
322 })
323
324 it("Null", async () => {
325 const schema = Schema.Null
326 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
327
328 const encoding = asserts.encoding()
329 await encoding.succeed(null)
330 })
331
332 it("String", async () => {
333 const schema = Schema.String
334 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
335
336 const encoding = asserts.encoding()
337 await encoding.succeed("a")
338
339 const decoding = asserts.decoding()
340 await decoding.succeed("a", "a")
341 })
342
343 describe("Number", () => {
344 it("reuses the Finite AST in the canonical encoding", () => {
345 const encoded = SchemaAST.getLastEncoding(Schema.toCodecJson(Schema.Number).ast)
346 strictEqual(encoded._tag, "Union")
347 if (encoded._tag === "Union") {
348 strictEqual(encoded.types[0], SchemaAST.finite)
349 strictEqual(encoded.types[0], Schema.Finite.ast)
350 }
351 })
352
353 it("does not propagate constructor defaults to the canonical encoding", () => {
354 const schema = Schema.Struct({
355 a: Schema.Number.pipe(
356 Schema.optionalKey,
357 Schema.mutableKey,
358 Schema.annotateKey({ description: "a" }),
359 Schema.withConstructorDefault(Effect.succeed(0))
360 )
361 })
362 const ast = Schema.toCodecJson(schema).ast
363 strictEqual(ast._tag, "Objects")
364 if (ast._tag === "Objects") {
365 const type = ast.propertySignatures[0].type
366 assertTrue(type.context?.constructorDefault !== undefined)
367 const encoded = SchemaAST.getLastEncoding(type)
368 strictEqual(encoded.context?.isOptional, true)
369 strictEqual(encoded.context?.isMutable, true)
370 strictEqual(encoded.context?.constructorDefault, undefined)
371 deepStrictEqual(encoded.context?.annotations, { description: "a" })
372 }
373 })
374
375 it("Number", async () => {
376 const schema = Schema.Number
377 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
378
379 const encoding = asserts.encoding()
380 await encoding.succeed(1)
381 await encoding.succeed(-1)
382 await encoding.succeed(1.2)
383 await encoding.succeed(Infinity, "Infinity")
384 await encoding.succeed(-Infinity, "-Infinity")
385 await encoding.succeed(NaN, "NaN")
386
387 const decoding = asserts.decoding()
388 await decoding.succeed(1)
389 await decoding.succeed(-1)
390 await decoding.succeed(1.2)
391 await decoding.succeed("Infinity", Infinity)
392 await decoding.succeed("-Infinity", -Infinity)
393 await decoding.succeed("NaN", NaN)
394 await decoding.fail(Infinity, "Expected a finite number")
395 await decoding.fail(-Infinity, "Expected a finite number")
396 await decoding.fail(NaN, "Expected a finite number")
397 await decoding.fail(null, `Expected number | "Infinity" | "-Infinity" | "NaN"`)
398 await decoding.fail("a", `Expected "Infinity" | "-Infinity" | "NaN"`)
399 })
400
401 describe("checks", () => {
402 it("runs source checks once per direction", () => {
403 let executions = 0
404 const codec = Schema.toCodecJson(Schema.Number.check(Schema.makeFilter<number>(() => {
405 executions++
406 return undefined
407 })))
408
409 Schema.decodeUnknownSync(codec)(1)
410 strictEqual(executions, 1)
411
412 executions = 0
413 Schema.encodeUnknownSync(codec)(1)
414 strictEqual(executions, 1)
415 })
416
417 it("Finite", async () => {
418 const schema = Schema.Finite
419 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
420
421 const encoding = asserts.encoding()
422 await encoding.succeed(1)
423 await encoding.succeed(-1)
424 await encoding.succeed(1.2)
425 await encoding.fail(Infinity, "Expected a finite number")
426 await encoding.fail(-Infinity, "Expected a finite number")
427 await encoding.fail(NaN, "Expected a finite number")
428
429 const decoding = asserts.decoding()
430 await decoding.succeed(1)
431 await decoding.succeed(-1)
432 await decoding.succeed(1.2)
433 await decoding.fail("Infinity", `Expected number`)
434 await decoding.fail("-Infinity", `Expected number`)
435 await decoding.fail("NaN", `Expected number`)
436 await decoding.fail(Infinity, `Expected a finite number`)
437 await decoding.fail(-Infinity, `Expected a finite number`)
438 await decoding.fail(NaN, `Expected a finite number`)
439 await decoding.fail(null, `Expected number`)
440 await decoding.fail("a", `Expected number`)
441 })
442
443 it("Int", async () => {
444 const schema = Schema.Int
445 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
446
447 const encoding = asserts.encoding()
448 await encoding.succeed(1)
449 await encoding.succeed(-1)
450 await encoding.fail(1.2, `Expected an integer`)
451 await encoding.fail(Infinity, "Expected an integer")
452 await encoding.fail(-Infinity, "Expected an integer")
453 await encoding.fail(NaN, "Expected an integer")
454
455 const decoding = asserts.decoding()
456 await decoding.succeed(1)
457 await decoding.succeed(-1)
458 await decoding.fail(1.2, `Expected an integer`)
459 await decoding.fail("Infinity", `Expected number`)
460 await decoding.fail("-Infinity", `Expected number`)
461 await decoding.fail("NaN", `Expected number`)
462 await decoding.fail(Infinity, `Expected an integer`)
463 await decoding.fail(-Infinity, `Expected an integer`)
464 await decoding.fail(NaN, `Expected an integer`)
465 await decoding.fail(null, `Expected number`)
466 await decoding.fail("a", `Expected number`)
467 })
468
469 it("isGreaterThanOrEqualTo", async () => {
470 const schema = Schema.Number.check(Schema.isGreaterThanOrEqualTo(1))
471 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
472
473 const encoding = asserts.encoding()
474 await encoding.succeed(1)
475 await encoding.fail(-1, `Expected a value greater than or equal to 1`)
476 await encoding.succeed(1.2)
477 await encoding.succeed(Infinity, "Infinity")
478 await encoding.fail(-Infinity, "Expected a value greater than or equal to 1")
479 await encoding.fail(NaN, "Expected a value greater than or equal to 1")
480
481 const decoding = asserts.decoding()
482 await decoding.succeed(1)
483 await encoding.fail(-1, `Expected a value greater than or equal to 1`)
484 await decoding.succeed(1.2)
485 await decoding.succeed("Infinity", Infinity)
486 await decoding.fail("-Infinity", `Expected a value greater than or equal to 1`)
487 await decoding.fail("NaN", `Expected a value greater than or equal to 1`)
488 await decoding.fail(Infinity, "Expected a finite number")
489 await decoding.fail(-Infinity, "Expected a finite number")
490 await decoding.fail(NaN, "Expected a finite number")
491 await decoding.fail(null, `Expected number | "Infinity" | "-Infinity" | "NaN"`)
492 await decoding.fail("a", `Expected "Infinity" | "-Infinity" | "NaN"`)
493 })
494 })
495 })
496
497 it("Boolean", async () => {
498 const schema = Schema.Boolean
499 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
500
501 const encoding = asserts.encoding()
502 await encoding.succeed(true)
503
504 const decoding = asserts.decoding()
505 await decoding.succeed(true, true)
506 })
507
508 it("Symbol", async () => {
509 const schema = Schema.Symbol
510 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
511
512 const encoding = asserts.encoding()
513 await encoding.succeed(Symbol.for("a"), "Symbol(a)")
514 await encoding.fail(
515 Symbol("a"),
516 "cannot serialize to string, Symbol is not registered"
517 )
518 await encoding.fail(
519 Symbol(),
520 "cannot serialize to string, Symbol is not registered"
521 )
522
523 const decoding = asserts.decoding()
524 await decoding.succeed("Symbol(a)", Symbol.for("a"))
525 })
526
527 it("UniqueSymbol", async () => {
528 const schema = Schema.UniqueSymbol(Symbol.for("a"))
529 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
530
531 const encoding = asserts.encoding()
532 await encoding.succeed(Symbol.for("a"), "Symbol(a)")
533
534 const decoding = asserts.decoding()
535 await decoding.succeed("Symbol(a)", Symbol.for("a"))
536 await decoding.fail("Symbol(b)", `Expected "Symbol(a)"`)
537 })
538
539 it("Symbol with a multiline registry key", async () => {
540 const symbol = Symbol.for("a\nb")
541 const asserts = new TestSchema.Asserts(Schema.toCodecJson(Schema.Symbol))
542
543 await asserts.encoding().succeed(symbol, "Symbol(a\nb)")
544 await asserts.decoding().succeed("Symbol(a\nb)", symbol)
545 })
546
547 it("local UniqueSymbol", async () => {
548 const symbol = Symbol("a")
549 const asserts = new TestSchema.Asserts(Schema.toCodecJson(Schema.UniqueSymbol(symbol)))
550
551 const encoding = asserts.encoding()
552 await encoding.fail(symbol, "cannot serialize to string, Symbol is not registered")
553
554 const decoding = asserts.decoding()
555 await decoding.fail("Symbol(a)", "Expected never")
556 })
557
558 it("BigInt", async () => {
559 const schema = Schema.BigInt
560 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
561
562 const encoding = asserts.encoding()
563 await encoding.succeed(1n, "1")
564
565 const decoding = asserts.decoding()
566 await decoding.succeed("1", 1n)
567 })
568
569 it("PropertyKey", async () => {
570 const schema = Schema.PropertyKey
571 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
572
573 const encoding = asserts.encoding()
574 await encoding.succeed("a")
575 await encoding.succeed(1)
576 await encoding.succeed(Symbol.for("a"), "Symbol(a)")
577
578 const decoding = asserts.decoding()
579 await decoding.succeed("a")
580 await decoding.succeed(1)
581 await decoding.succeed("Symbol(a)", Symbol.for("a"))
582 })
583
584 describe("Literal", () => {
585 it("string", async () => {
586 const schema = Schema.Literal("a")
587 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
588
589 const encoding = asserts.encoding()
590 await encoding.succeed("a")
591
592 const decoding = asserts.decoding()
593 await decoding.succeed("a")
594 })
595
596 it("number", async () => {
597 const schema = Schema.Literal(1)
598 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
599
600 const encoding = asserts.encoding()
601 await encoding.succeed(1)
602
603 const decoding = asserts.decoding()
604 await decoding.succeed(1)
605 })
606
607 it("boolean", async () => {
608 const schema = Schema.Literal(true)
609 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
610
611 const encoding = asserts.encoding()
612 await encoding.succeed(true)
613
614 const decoding = asserts.decoding()
615 await decoding.succeed(true)
616 })
617
618 it("bigint", async () => {
619 const schema = Schema.Literal(1n)
620 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
621
622 const encoding = asserts.encoding()
623 await encoding.succeed(1n, "1")
624
625 const decoding = asserts.decoding()
626 await decoding.succeed("1", 1n)
627 })
628 })
629
630 it("Literals", async () => {
631 const schema = Schema.Literals(["a", 1, 2n, true])
632 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
633
634 const decoding = asserts.decoding()
635 await decoding.fail(
636 "-",
637 `Expected "a" | 1 | "2" | true`
638 )
639 })
640
641 describe("TemplateLiteral", () => {
642 it("1n + string", async () => {
643 const schema = Schema.TemplateLiteral([1n, Schema.String])
644 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
645
646 const encoding = asserts.encoding()
647 await encoding.succeed("1a")
648
649 const decoding = asserts.decoding()
650 await decoding.succeed("1a")
651 })
652
653 it(`"a" + bigint`, async () => {
654 const schema = Schema.TemplateLiteral(["a", Schema.BigInt])
655 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
656
657 const encoding = asserts.encoding()
658 await encoding.succeed("a1")
659
660 const decoding = asserts.decoding()
661 await decoding.succeed("a1")
662 })
663 })
664
665 it("Enum", async () => {
666 enum Fruits {
667 Apple,
668 Banana
669 }
670 const schema = Schema.Enum(Fruits)
671 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
672
673 const encoding = asserts.encoding()
674 await encoding.succeed(Fruits.Apple, 0)
675 await encoding.succeed(Fruits.Banana, 1)
676
677 const decoding = asserts.decoding()
678 await decoding.succeed(0, Fruits.Apple)
679 await decoding.succeed(1, Fruits.Banana)
680 })
681
682 describe("Struct", () => {
683 it("Date", async () => {
684 const schema = Schema.Struct({
685 a: Schema.Date
686 })
687 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
688
689 const encoding = asserts.encoding()
690 await encoding.succeed(
691 { a: new Date("2021-01-01") },
692 { a: "2021-01-01T00:00:00.000Z" }
693 )
694 })
695
696 it("UndefinedOr(Date)", async () => {
697 const schema = Schema.Struct({
698 a: Schema.UndefinedOr(Schema.Date)
699 })
700 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
701
702 const encoding = asserts.encoding()
703 await encoding.succeed({ a: new Date("2021-01-01") }, {
704 a: "2021-01-01T00:00:00.000Z"
705 })
706 await encoding.succeed({ a: undefined }, { a: null })
707
708 const decoding = asserts.decoding()
709 await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
710 a: new Date("2021-01-01")
711 })
712 await decoding.succeed({ a: null }, { a: undefined })
713 })
714
715 it("NullOr(Date)", async () => {
716 const schema = Schema.Struct({
717 a: Schema.NullOr(Schema.Date)
718 })
719 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
720
721 const encoding = asserts.encoding()
722 await encoding.succeed({ a: new Date("2021-01-01") }, {
723 a: "2021-01-01T00:00:00.000Z"
724 })
725 await encoding.succeed({ a: null }, { a: null })
726
727 const decoding = asserts.decoding()
728 await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
729 a: new Date("2021-01-01")
730 })
731 await decoding.succeed({ a: null }, { a: null })
732 })
733
734 it("optionalKey(Date)", async () => {
735 const schema = Schema.Struct({
736 a: Schema.optionalKey(Schema.Date)
737 })
738 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
739
740 const encoding = asserts.encoding()
741 await encoding.succeed({ a: new Date("2021-01-01") }, {
742 a: "2021-01-01T00:00:00.000Z"
743 })
744 await encoding.succeed({}, {})
745
746 const decoding = asserts.decoding()
747 await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
748 a: new Date("2021-01-01")
749 })
750 await decoding.succeed({}, {})
751 })
752
753 it("optional(Date)", async () => {
754 const schema = Schema.Struct({
755 a: Schema.optional(Schema.Date)
756 })
757 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
758
759 const encoding = asserts.encoding()
760 await encoding.succeed({ a: new Date("2021-01-01") }, {
761 a: "2021-01-01T00:00:00.000Z"
762 })
763 await encoding.succeed({}, {})
764 await encoding.succeed({ a: undefined }, { a: null })
765
766 const decoding = asserts.decoding()
767 await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
768 a: new Date("2021-01-01")
769 })
770 await decoding.succeed({}, {})
771 await decoding.succeed({ a: null }, { a: undefined })
772 })
773 })
774
775 it("Record(Symbol, Date)", async () => {
776 const schema = Schema.Record(Schema.Symbol, Schema.Date)
777 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
778
779 const encoding = asserts.encoding()
780 await encoding.succeed(
781 { [Symbol.for("a")]: new Date("2021-01-01"), [Symbol.for("b")]: new Date("2021-01-01") },
782 { "Symbol(a)": "2021-01-01T00:00:00.000Z", "Symbol(b)": "2021-01-01T00:00:00.000Z" }
783 )
784
785 const decoding = asserts.decoding()
786 await decoding.succeed(
787 { "Symbol(a)": "2021-01-01T00:00:00.000Z", "Symbol(b)": "2021-01-01T00:00:00.000Z" },
788 { [Symbol.for("a")]: new Date("2021-01-01"), [Symbol.for("b")]: new Date("2021-01-01") }
789 )
790 })
791
792 it("Struct with an explicitly encoded Symbol property name", async () => {
793 const field = Symbol.for("field")
794 const schema = Schema.Struct({
795 [field]: Schema.String
796 }).pipe(Schema.encodeKeys({ [field]: "field" }))
797 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
798
799 await asserts.encoding().succeed({ [field]: "a" }, { field: "a" })
800 await asserts.decoding().succeed({ field: "a" }, { [field]: "a" })
801 })
802
803 describe("Tuple", () => {
804 it("Date", async () => {
805 const schema = Schema.Tuple([Schema.Date])
806 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
807
808 const encoding = asserts.encoding()
809 await encoding.succeed(
810 [new Date("2021-01-01")],
811 ["2021-01-01T00:00:00.000Z"]
812 )
813
814 const decoding = asserts.decoding()
815 await decoding.succeed(
816 ["2021-01-01T00:00:00.000Z"],
817 [new Date("2021-01-01")]
818 )
819 })
820
821 it("UndefinedOr(Date)", async () => {
822 const schema = Schema.Tuple([Schema.UndefinedOr(Schema.Date)])
823 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
824
825 const encoding = asserts.encoding()
826 await encoding.succeed(
827 [new Date("2021-01-01")],
828 ["2021-01-01T00:00:00.000Z"]
829 )
830 await encoding.succeed([undefined], [null])
831
832 const decoding = asserts.decoding()
833 await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
834 new Date("2021-01-01")
835 ])
836 await decoding.succeed([null], [undefined])
837 })
838
839 it("NullOr(Date)", async () => {
840 const schema = Schema.Tuple([Schema.NullOr(Schema.Date)])
841 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
842
843 const encoding = asserts.encoding()
844 await encoding.succeed([new Date("2021-01-01")], [
845 "2021-01-01T00:00:00.000Z"
846 ])
847 await encoding.succeed([null], [null])
848
849 const decoding = asserts.decoding()
850 await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
851 new Date("2021-01-01")
852 ])
853 await decoding.succeed([null], [null])
854 })
855
856 it("optionalKey(Date)", async () => {
857 const schema = Schema.Tuple([Schema.optionalKey(Schema.Date)])
858 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
859
860 const encoding = asserts.encoding()
861 await encoding.succeed([new Date("2021-01-01")], [
862 "2021-01-01T00:00:00.000Z"
863 ])
864 await encoding.succeed([], [])
865
866 const decoding = asserts.decoding()
867 await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
868 new Date("2021-01-01")
869 ])
870 await decoding.succeed([], [])
871 })
872
873 it("optional(Date)", async () => {
874 const schema = Schema.Tuple([Schema.optional(Schema.Date)])
875 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
876
877 const encoding = asserts.encoding()
878 await encoding.succeed([new Date("2021-01-01")], [
879 "2021-01-01T00:00:00.000Z"
880 ])
881 await encoding.succeed([], [])
882 await encoding.succeed([undefined], [null])
883
884 const decoding = asserts.decoding()
885 await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
886 new Date("2021-01-01")
887 ])
888 await decoding.succeed([], [])
889 await decoding.succeed([null], [undefined])
890 })
891 })
892
893 it("Array(Date)", async () => {
894 const schema = Schema.Array(Schema.Date)
895 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
896
897 const encoding = asserts.encoding()
898 await encoding.succeed(
899 [new Date("2021-01-01"), new Date("2021-01-02")],
900 ["2021-01-01T00:00:00.000Z", "2021-01-02T00:00:00.000Z"]
901 )
902 })
903
904 describe("Union", () => {
905 it("NullOr(String)", async () => {
906 const schema = Schema.NullOr(Schema.String)
907 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
908
909 const encoding = asserts.encoding()
910 await encoding.succeed("a")
911 await encoding.succeed(null)
912
913 const decoding = asserts.decoding()
914 await decoding.succeed(null)
915 await decoding.succeed("a")
916 })
917
918 it("NullOr(Number)", async () => {
919 const schema = Schema.NullOr(Schema.Number)
920 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
921
922 const encoding = asserts.encoding()
923 await encoding.succeed(1)
924 await encoding.succeed(null)
925
926 const decoding = asserts.decoding()
927 await decoding.succeed(null)
928 await decoding.succeed(1)
929 })
930
931 it("Array(NullOr(Number))", async () => {
932 const schema = Schema.Array(Schema.NullOr(Schema.Number))
933 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
934
935 const encoding = asserts.encoding()
936 await encoding.succeed([1, null])
937
938 const decoding = asserts.decoding()
939 await decoding.succeed([1, null])
940 })
941
942 it("Union(Schema.Date, Finite)", async () => {
943 const schema = Schema.Union([Schema.Date, Schema.Finite])
944 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
945
946 const encoding = asserts.encoding()
947 await encoding.succeed(
948 new Date("2021-01-01"),
949 "2021-01-01T00:00:00.000Z"
950 )
951 await encoding.succeed(0)
952 })
953 })
954
955 it("Suspend", async () => {
956 interface Category {
957 readonly a: number
958 readonly categories: ReadonlyArray<Category>
959 }
960
961 const schema = Schema.Struct({
962 a: Schema.Finite.check(Schema.isGreaterThan(0)),
963 categories: Schema.Array(Schema.suspend((): Schema.Codec<Category> => schema))
964 })
965 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
966
967 const encoding = asserts.encoding()
968 await encoding.succeed({ a: 1, categories: [] }, {
969 a: 1,
970 categories: []
971 })
972 await encoding.succeed({
973 a: 1,
974 categories: [{ a: 2, categories: [] }]
975 }, {
976 a: 1,
977 categories: [
978 { a: 2, categories: [] }
979 ]
980 })
981
982 const decoding = asserts.decoding()
983 await decoding.succeed({
984 a: 1,
985 categories: []
986 }, { a: 1, categories: [] })
987 await decoding.succeed({
988 a: 1,
989 categories: [
990 { a: 2, categories: [] }
991 ]
992 }, { a: 1, categories: [{ a: 2, categories: [] }] })
993 })
994
995 it("Class", async () => {
996 class A extends Schema.Class<A>("A")(Schema.Struct({
997 a: Schema.Finite
998 })) {}
999 const asserts = new TestSchema.Asserts(Schema.toCodecJson(Schema.toType(A)))
1000
1001 const encoding = asserts.encoding()
1002 await encoding.succeed(new A({ a: 0 }), { a: 0 })
1003
1004 const decoding = asserts.decoding()
1005 await decoding.succeed({ a: 0 }, new A({ a: 0 }))
1006 })
1007
1008 it("Error", async () => {
1009 class E extends Schema.Error<E>("E")({
1010 a: Schema.Finite
1011 }) {}
1012 const asserts = new TestSchema.Asserts(Schema.toCodecJson(Schema.toType(E)))
1013
1014 const encoding = asserts.encoding()
1015 await encoding.succeed(new E({ a: 0 }), { a: 0 })
1016
1017 const decoding = asserts.decoding()
1018 await decoding.succeed({ a: 0 }, new E({ a: 0 }))
1019 })
1020
1021 it("Date", async () => {
1022 const schema = Schema.Date
1023 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1024
1025 const encoding = asserts.encoding()
1026 await encoding.succeed(
1027 new Date("2021-01-01"),
1028 "2021-01-01T00:00:00.000Z"
1029 )
1030 })
1031
1032 it("Error", async () => {
1033 const schema = Schema.ErrorInstance()
1034 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1035
1036 const encoding = asserts.encoding()
1037 await encoding.succeed(
1038 new Error("a"),
1039 { name: "Error", message: "a" }
1040 )
1041 await encoding.succeed(
1042 new Error("a", { cause: new Error("b") }),
1043 { name: "Error", message: "a", cause: { name: "Error", message: "b" } }
1044 )
1045 await encoding.succeed(
1046 new Error("a", { cause: "b" }),
1047 { name: "Error", message: "a", cause: "b" }
1048 )
1049 const selfCause = new Error("a")
1050 selfCause.stack = "stack"
1051 selfCause.cause = selfCause
1052 await encoding.succeed(
1053 selfCause,
1054 {
1055 name: "Error",
1056 message: "a",
1057 cause: "[Circular]"
1058 }
1059 )
1060 const cyclicCause: Record<string, unknown> = {}
1061 cyclicCause.self = cyclicCause
1062 await encoding.succeed(
1063 new Error("a", { cause: cyclicCause }),
1064 { name: "Error", message: "a", cause: {} }
1065 )
1066
1067 const decoding = asserts.decoding()
1068 // Error: message only
1069 await decoding.succeed(
1070 { message: "a" },
1071 new Error("a")
1072 )
1073 // Error: message and name
1074 await decoding.succeed(
1075 { name: "b", message: "a" },
1076 (() => {
1077 const err = new Error("a")
1078 err.name = "b"
1079 return err
1080 })()
1081 )
1082 // Error: message, name, and stack
1083 await decoding.succeed(
1084 { name: "b", message: "a", stack: "c" },
1085 (() => {
1086 const err = new Error("a")
1087 err.name = "b"
1088 err.stack = "c"
1089 return err
1090 })()
1091 )
1092 // Error: message and cause
1093 await decoding.succeed(
1094 { message: "a", cause: { message: "b" } },
1095 new Error("a", { cause: new Error("b") })
1096 )
1097 // Error: explicit null cause
1098 await decoding.succeed(
1099 { message: "a", cause: null },
1100 new Error("a", { cause: null })
1101 )
1102 })
1103
1104 it("Error with stack", async () => {
1105 const schema = Schema.ErrorInstance({ includeStack: true })
1106 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1107 const error = new Error("a")
1108 error.stack = "stack"
1109 const customError = new Error("b")
1110 customError.name = "CustomError"
1111 customError.stack = "custom stack"
1112
1113 const encoding = asserts.encoding()
1114 await encoding.succeed(error, { name: "Error", message: "a", stack: "stack" })
1115 await encoding.succeed(customError, { name: "CustomError", message: "b", stack: "custom stack" })
1116
1117 const decoding = asserts.decoding()
1118 await decoding.succeed(
1119 { message: "a", stack: "stack" },
1120 error
1121 )
1122 await decoding.succeed(
1123 { name: "CustomError", message: "b", stack: "custom stack" },
1124 customError
1125 )
1126 })
1127
1128 it("Error with excluded cause", async () => {
1129 const schema = Schema.ErrorInstance({ excludeCause: true })
1130 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1131
1132 const encoding = asserts.encoding()
1133 await encoding.succeed(
1134 new Error("a", { cause: new Error("b") }),
1135 { name: "Error", message: "a" }
1136 )
1137 })
1138
1139 it("URL", async () => {
1140 const schema = Schema.URL
1141 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1142
1143 const encoding = asserts.encoding()
1144 await encoding.succeed(
1145 new URL("https://example.com"),
1146 "https://example.com/"
1147 )
1148
1149 const decoding = asserts.decoding()
1150 await decoding.succeed(
1151 "https://example.com",
1152 new URL("https://example.com")
1153 )
1154 await decoding.succeed(
1155 "https://example.com/",
1156 new URL("https://example.com")
1157 )
1158 await decoding.fail(
1159 "not a url",
1160 "Expected a valid URL string"
1161 )
1162 })
1163
1164 it("URLSearchParams", async () => {
1165 const schema = Schema.URLSearchParams
1166 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1167
1168 const encoding = asserts.encoding()
1169 await encoding.succeed(
1170 new URLSearchParams("a=1&b=two"),
1171 "a=1&b=two"
1172 )
1173
1174 const decoding = asserts.decoding()
1175 await decoding.succeed(
1176 "a=1&b=two",
1177 new URLSearchParams("a=1&b=two")
1178 )
1179 })
1180
1181 it.skipIf(isDeno)("File", async () => {
1182 const schema = Schema.File
1183 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1184
1185 const encoding = asserts.encoding()
1186 await encoding.succeed(
1187 new File(["hi"], "note.txt", { type: "text/plain", lastModified: 123 }),
1188 { data: "aGk=", type: "text/plain", name: "note.txt", lastModified: 123 }
1189 )
1190
1191 const decoding = asserts.decoding()
1192 await decoding.succeed(
1193 { data: "aGk=", type: "text/plain", name: "note.txt", lastModified: 123 },
1194 new File(["hi"], "note.txt", { type: "text/plain", lastModified: 123 })
1195 )
1196 })
1197
1198 it.skipIf(isDeno)("FormData", async () => {
1199 const schema = Schema.FormData
1200 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1201
1202 const encoding = asserts.encoding()
1203 const formData = new FormData()
1204 formData.append("a", "1")
1205 formData.append("b", new File(["hi"], "note.txt", { type: "text/plain", lastModified: 123 }))
1206 await encoding.succeed(
1207 formData,
1208 [
1209 ["a", { _tag: "String", value: "1" }],
1210 ["b", { _tag: "File", value: { data: "aGk=", type: "text/plain", name: "note.txt", lastModified: 123 } }]
1211 ]
1212 )
1213
1214 const decoding = asserts.decoding()
1215 const expected = new FormData()
1216 expected.append("a", "1")
1217 expected.append("b", new File(["hi"], "note.txt", { type: "text/plain", lastModified: 123 }))
1218 await decoding.succeed(
1219 [
1220 ["a", { _tag: "String", value: "1" }],
1221 ["b", { _tag: "File", value: { data: "aGk=", type: "text/plain", name: "note.txt", lastModified: 123 } }]
1222 ],
1223 expected
1224 )
1225 })
1226
1227 it("RegExp", async () => {
1228 const schema = Schema.RegExp
1229 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1230
1231 const encoding = asserts.encoding()
1232 await encoding.succeed(new RegExp("a"), { source: "a", flags: "" })
1233 await encoding.succeed(new RegExp("a", "i"), { source: "a", flags: "i" })
1234
1235 const decoding = asserts.decoding()
1236 await decoding.succeed({ source: "a", flags: "" }, new RegExp("a"))
1237 await decoding.succeed({ source: "a", flags: "i" }, new RegExp("a", "i"))
1238 await decoding.fail(
1239 { source: "(", flags: "" },
1240 "Expected valid RegExp source and flags"
1241 )
1242 await decoding.fail(
1243 { source: "a", flags: "x" },
1244 "Expected valid RegExp source and flags"
1245 )
1246 })
1247
1248 it("Uint8Array", async () => {
1249 const schema = Schema.Uint8Array
1250 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1251
1252 const encoding = asserts.encoding()
1253 await encoding.succeed(new Uint8Array([1, 2, 3]), "AQID")
1254
1255 const decoding = asserts.decoding()
1256 await decoding.succeed("AQID", new Uint8Array([1, 2, 3]))
1257 await decoding.fail(
1258 "not a base64 string",
1259 "Expected a valid Base64 string"
1260 )
1261 })
1262
1263 it("Duration", async () => {
1264 const schema = Schema.Duration
1265 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1266
1267 const encoding = asserts.encoding()
1268 await encoding.succeed(Duration.infinity, { _tag: "Infinity" })
1269 await encoding.succeed(Duration.negativeInfinity, { _tag: "NegativeInfinity" })
1270 await encoding.succeed(Duration.nanos(1000n), { _tag: "Nanos", value: "1000" })
1271 await encoding.succeed(Duration.nanos(-1000n), { _tag: "Nanos", value: "-1000" })
1272 await encoding.succeed(Duration.millis(1), { _tag: "Millis", value: 1 })
1273 await encoding.succeed(Duration.millis(-1), { _tag: "Millis", value: -1 })
1274 await encoding.succeed(Duration.zero, { _tag: "Millis", value: 0 })
1275
1276 const decoding = asserts.decoding()
1277 await decoding.succeed({ _tag: "Infinity" }, Duration.infinity)
1278 await decoding.succeed({ _tag: "NegativeInfinity" }, Duration.negativeInfinity)
1279 await decoding.succeed({ _tag: "Millis", value: 1 }, Duration.millis(1))
1280 await decoding.succeed({ _tag: "Millis", value: -1 }, Duration.millis(-1))
1281 await decoding.succeed({ _tag: "Nanos", value: "1000" }, Duration.nanos(1000n))
1282 await decoding.succeed({ _tag: "Nanos", value: "-1000" }, Duration.nanos(-1000n))
1283 })
1284
1285 it("DateTimeUtc", async () => {
1286 const schema = Schema.DateTimeUtc
1287 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1288
1289 const encoding = asserts.encoding()
1290 await encoding.succeed(
1291 DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"),
1292 "2021-01-01T00:00:00.000Z"
1293 )
1294
1295 const decoding = asserts.decoding()
1296 await decoding.succeed(
1297 "2021-01-01T00:00:00.000Z",
1298 DateTime.makeUnsafe("2021-01-01T00:00:00.000Z")
1299 )
1300 })
1301
1302 it("Option(Date)", async () => {
1303 const schema = Schema.Option(Schema.Date)
1304 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1305
1306 const encoding = asserts.encoding()
1307 await encoding.succeed(Option.some(new Date("2021-01-01")), {
1308 _tag: "Some",
1309 value: "2021-01-01T00:00:00.000Z"
1310 })
1311 await encoding.succeed(Option.none(), { _tag: "None" })
1312 })
1313
1314 describe("Redacted", () => {
1315 it("Redacted(Option(String))", async () => {
1316 const schema = Schema.Redacted(Schema.Option(Schema.String))
1317 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1318
1319 const encoding = asserts.encoding()
1320 await encoding.succeed(
1321 Redacted.make(Option.none()),
1322 { _tag: "None" }
1323 )
1324 await encoding.succeed(
1325 Redacted.make(Option.some("a")),
1326 { _tag: "Some", value: "a" }
1327 )
1328
1329 const decoding = asserts.decoding()
1330 await decoding.succeed(
1331 { _tag: "None" },
1332 Redacted.make(Option.none())
1333 )
1334 await decoding.succeed(
1335 { _tag: "Some", value: "a" },
1336 Redacted.make(Option.some("a"))
1337 )
1338 })
1339
1340 it("Redacted(Option(String)) disallowJsonEncode", async () => {
1341 const schema = Schema.Redacted(Schema.Option(Schema.String), {
1342 disallowJsonEncode: true
1343 })
1344 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1345
1346 const encoding = asserts.encoding()
1347 await encoding.fail(
1348 Redacted.make(Option.none()),
1349 `Cannot serialize Redacted`
1350 )
1351 await encoding.fail(
1352 Redacted.make(Option.some("a")),
1353 `Cannot serialize Redacted`
1354 )
1355
1356 const decoding = asserts.decoding()
1357 await decoding.succeed(
1358 { _tag: "None" },
1359 Redacted.make(Option.none())
1360 )
1361 await decoding.succeed(
1362 { _tag: "Some", value: "a" },
1363 Redacted.make(Option.some("a"))
1364 )
1365 })
1366
1367 it("encoding a Redacted with a label", async () => {
1368 const schema = Schema.Redacted(Schema.String, { label: "password" })
1369 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1370
1371 const encoding = asserts.encoding()
1372 await encoding.fail(
1373 Redacted.make("a", { label: "API key" }),
1374 `Expected "password"
1375 at ["label"]`
1376 )
1377 })
1378
1379 it("encoding a Redacted with a different label", async () => {
1380 const schema = Schema.Redacted(Schema.String)
1381 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1382
1383 const encoding = asserts.encoding()
1384 await encoding.succeed(
1385 Redacted.make("a", { label: "API key" }),
1386 "a"
1387 )
1388 })
1389 })
1390
1391 it("ReadonlySet", async () => {
1392 const schema = Schema.ReadonlySet(Schema.Option(Schema.Date))
1393 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1394
1395 const encoding = asserts.encoding()
1396 await encoding.succeed(new Set([Option.some(new Date("2021-01-01"))]), [{
1397 _tag: "Some",
1398 value: "2021-01-01T00:00:00.000Z"
1399 }])
1400
1401 const decoding = asserts.decoding()
1402 await decoding.succeed(
1403 [{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }],
1404 new Set([Option.some(new Date("2021-01-01"))])
1405 )
1406 })
1407
1408 it("ReadonlyMap", async () => {
1409 const schema = Schema.ReadonlyMap(Schema.Option(Schema.Date), Schema.Finite)
1410 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1411
1412 const encoding = asserts.encoding()
1413 await encoding.succeed(
1414 new Map([[Option.some(new Date("2021-01-01")), 0]]),
1415 [[
1416 { _tag: "Some", value: "2021-01-01T00:00:00.000Z" },
1417 0
1418 ]]
1419 )
1420
1421 const decoding = asserts.decoding()
1422 await decoding.succeed(
1423 [[{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, 0]],
1424 new Map([[Option.some(new Date("2021-01-01")), 0]])
1425 )
1426 })
1427
1428 it("HashMap", async () => {
1429 const schema = Schema.HashMap(Schema.Option(Schema.Date), Schema.Finite)
1430 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1431
1432 const encoding = asserts.encoding()
1433 await encoding.succeed(
1434 HashMap.make([Option.some(new Date("2021-01-01")), 0]),
1435 [[
1436 { _tag: "Some", value: "2021-01-01T00:00:00.000Z" },
1437 0
1438 ]]
1439 )
1440
1441 const decoding = asserts.decoding()
1442 await decoding.succeed(
1443 [[{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, 0]],
1444 HashMap.make([Option.some(new Date("2021-01-01")), 0])
1445 )
1446 })
1447 })
1448
1449 describe("schemas with encoding", () => {
1450 it("FiniteFromDate", async () => {
1451 const schema = FiniteFromDate
1452 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1453
1454 const encoding = asserts.encoding()
1455 await encoding.succeed(0, "1970-01-01T00:00:00.000Z")
1456 })
1457
1458 it("Struct", async () => {
1459 const schema = Schema.Struct({
1460 a: FiniteFromDate,
1461 b: FiniteFromDate
1462 })
1463 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1464
1465 const encoding = asserts.encoding()
1466 await encoding.succeed(
1467 { a: 0, b: 0 },
1468 { a: "1970-01-01T00:00:00.000Z", b: "1970-01-01T00:00:00.000Z" }
1469 )
1470 })
1471
1472 it("Tuple(Schema.Date, Schema.Date)", async () => {
1473 const schema = Schema.Tuple([FiniteFromDate, FiniteFromDate])
1474 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1475
1476 const encoding = asserts.encoding()
1477 await encoding.succeed(
1478 [0, 0],
1479 ["1970-01-01T00:00:00.000Z", "1970-01-01T00:00:00.000Z"]
1480 )
1481 })
1482
1483 it("Class", async () => {
1484 class A extends Schema.Class<A>("A")(Schema.Struct({
1485 a: FiniteFromDate
1486 })) {}
1487 const asserts = new TestSchema.Asserts(Schema.toCodecJson(A))
1488
1489 const encoding = asserts.encoding()
1490 await encoding.succeed(new A({ a: 0 }), { a: "1970-01-01T00:00:00.000Z" })
1491
1492 const decoding = asserts.decoding()
1493 await decoding.succeed({ a: "1970-01-01T00:00:00.000Z" }, new A({ a: 0 }))
1494 })
1495
1496 it("Error", async () => {
1497 class E extends Schema.Error<E>("E")({
1498 a: FiniteFromDate
1499 }) {}
1500 const asserts = new TestSchema.Asserts(Schema.toCodecJson(E))
1501
1502 const encoding = asserts.encoding()
1503 await encoding.succeed(new E({ a: 0 }), { a: "1970-01-01T00:00:00.000Z" })
1504
1505 const decoding = asserts.decoding()
1506 await decoding.succeed({ a: "1970-01-01T00:00:00.000Z" }, new E({ a: 0 }))
1507 })
1508
1509 it("Enum", async () => {
1510 enum Fruits {
1511 Apple,
1512 Banana = "banana"
1513 }
1514 const schema = Schema.Enum(Fruits)
1515 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1516
1517 const encoding = asserts.encoding()
1518 await encoding.succeed(Fruits.Apple, 0)
1519 await encoding.succeed(Fruits.Banana, "banana")
1520
1521 const decoding = asserts.decoding()
1522 await decoding.succeed(0, Fruits.Apple)
1523 })
1524
1525 it("Option(Option(FiniteFromDate))", async () => {
1526 const schema = Schema.Option(Schema.Option(FiniteFromDate))
1527 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1528
1529 const encoding = asserts.encoding()
1530 await encoding.succeed(Option.some(Option.some(0)), {
1531 _tag: "Some",
1532 value: {
1533 _tag: "Some",
1534 value: "1970-01-01T00:00:00.000Z"
1535 }
1536 })
1537 })
1538
1539 it("ReadonlyMap(Option(Symbol), Date)", async () => {
1540 const schema = Schema.ReadonlyMap(Schema.Option(Schema.Symbol), Schema.Date)
1541 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1542
1543 const encoding = asserts.encoding()
1544 await encoding.succeed(
1545 new Map([[Option.some(Symbol.for("a")), new Date("2021-01-01")]]),
1546 [[
1547 { _tag: "Some", value: "Symbol(a)" },
1548 "2021-01-01T00:00:00.000Z"
1549 ]]
1550 )
1551
1552 const decoding = asserts.decoding()
1553 await decoding.succeed(
1554 [[{ _tag: "Some", value: "Symbol(a)" }, "2021-01-01T00:00:00.000Z"]],
1555 new Map([[Option.some(Symbol.for("a")), new Date("2021-01-01")]])
1556 )
1557 })
1558
1559 it("Defect", async () => {
1560 const schema = Schema.Defect()
1561 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1562
1563 const encoding = asserts.encoding()
1564 await encoding.succeed(new Error("a"), { name: "Error", message: "a" })
1565 await encoding.succeed(
1566 new Error("a", { cause: new Error("b") }),
1567 { name: "Error", message: "a", cause: { name: "Error", message: "b" } }
1568 )
1569 await encoding.succeed(
1570 new Error("a", { cause: "b" }),
1571 { name: "Error", message: "a", cause: "b" }
1572 )
1573 await encoding.succeed(
1574 new Cause.NoSuchElementError(),
1575 { name: "NoSuchElementError", message: "" }
1576 )
1577 const cyclicDefect: Record<string, unknown> = {}
1578 cyclicDefect.self = cyclicDefect
1579 await encoding.succeed(cyclicDefect, {})
1580 await encoding.succeed("a")
1581 await encoding.succeed({ a: 1 })
1582
1583 const decoding = asserts.decoding()
1584 await decoding.succeed(
1585 { message: "a", cause: { message: "b" } },
1586 new Error("a", { cause: new Error("b") })
1587 )
1588 await decoding.succeed(
1589 { message: "a", cause: null },
1590 new Error("a", { cause: null })
1591 )
1592 })
1593
1594 it("Defect with excluded cause", async () => {
1595 const schema = Schema.Defect({ excludeCause: true })
1596 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1597
1598 const encoding = asserts.encoding()
1599 await encoding.succeed(
1600 new Error("a", { cause: new Error("b") }),
1601 { name: "Error", message: "a" }
1602 )
1603 })
1604
1605 it("Cause(Option(Finite), Option(String))", async () => {
1606 const schema = Schema.Cause(Schema.Option(Schema.Finite), Schema.Option(Schema.String))
1607 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1608
1609 const encoding = asserts.encoding()
1610 await encoding.succeed(Cause.fail(Option.some(1)), [{
1611 _tag: "Fail",
1612 error: { _tag: "Some", value: 1 }
1613 }])
1614 await encoding.succeed(Cause.die(Option.some("a")), [{
1615 _tag: "Die",
1616 defect: { _tag: "Some", value: "a" }
1617 }])
1618 await encoding.succeed(Cause.interrupt(1), [{
1619 _tag: "Interrupt",
1620 fiberId: 1
1621 }])
1622 await encoding.succeed(Cause.interrupt(), [{
1623 _tag: "Interrupt",
1624 fiberId: null
1625 }])
1626 })
1627
1628 it("DateTimeUtcFromValidDate", async () => {
1629 const schema = Schema.DateTimeUtcFromDate
1630 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
1631
1632 const encoding = asserts.encoding()
1633 await encoding.succeed(
1634 DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"),
1635 "2021-01-01T00:00:00.000Z"
1636 )
1637
1638 const decoding = asserts.decoding()
1639 await decoding.succeed(
1640 "2021-01-01T00:00:00.000Z",
1641 DateTime.makeUnsafe("2021-01-01T00:00:00.000Z")
1642 )
1643 })
1644 })
1645
1646 it("StandardSchemaV1FailureResult", async () => {
1647 const b = Symbol.for("b")
1648
1649 const schema = Schema.Struct({
1650 a: Schema.NonEmptyString,
1651 [b]: Schema.Finite,
1652 c: Schema.Tuple([Schema.String])
1653 })
1654
1655 const r = SchemaParser.decodeUnknownExit(schema)({ a: "", c: [] }, { errors: "all" })
1656
1657 assertTrue(r._tag === "Failure")
1658 assertTrue(r.cause.reasons.length === 1)
1659 const failure = r.cause.reasons[0]
1660 assertTrue(failure._tag === "Fail")
1661
1662 const failureResult = SchemaIssue.makeFormatterStandardSchemaV1({
1663 leafHook: SchemaIssue.defaultLeafHook
1664 })(failure.error)
1665
1666 const asserts = new TestSchema.Asserts(Schema.toCodecJson(Schema.StandardSchemaV1FailureResult))
1667
1668 const encoding = asserts.encoding()
1669 await encoding.succeed(failureResult, {
1670 issues: [
1671 { path: ["a"], message: `Expected a value with a length of at least 1` },
1672 { path: ["c", 0], message: "Missing key" },
1673 { path: ["Symbol(b)"], message: "Missing key" }
1674 ]
1675 })
1676
1677 const decoding = asserts.decoding()
1678 await decoding.succeed({
1679 issues: [
1680 { path: ["a"], message: `Expected a value with a length of at least 1` },
1681 { path: ["c", 0], message: "Missing key" },
1682 { path: ["Symbol(b)"], message: "Missing key" }
1683 ]
1684 }, failureResult)
1685 })
1686 })
1687
1688 describe("toCodecIso", () => {
1689 it("does not propagate constructor defaults to the canonical encoding", () => {
1690 const schema = Schema.Struct({
1691 a: Schema.URL.pipe(
1692 Schema.overrideToCodecIso(Schema.String, SchemaTransformation.urlFromString),
1693 Schema.optionalKey,
1694 Schema.mutableKey,
1695 Schema.annotateKey({ description: "a" }),
1696 Schema.withConstructorDefault(Effect.succeed(new URL("https://example.com")))
1697 )
1698 })
1699 const ast = Schema.toCodecIso(schema).ast
1700 strictEqual(ast._tag, "Objects")
1701 if (ast._tag === "Objects") {
1702 const type = ast.propertySignatures[0].type
1703 assertTrue(type.context?.constructorDefault !== undefined)
1704 const encoded = SchemaAST.getLastEncoding(type)
1705 strictEqual(encoded.context?.isOptional, true)
1706 strictEqual(encoded.context?.isMutable, true)
1707 strictEqual(encoded.context?.constructorDefault, undefined)
1708 deepStrictEqual(encoded.context?.annotations, { description: "a" })
1709 }
1710 })
1711 })
1712
1713 describe("toCodecStringTree", () => {
1714 it("exposes the source schema", () => {
1715 const schema = Schema.FiniteFromString
1716 const serializer = Schema.toCodecStringTree(schema)
1717 strictEqual(serializer.schema, schema)
1718 })
1719
1720 it("does not propagate constructor defaults to the canonical encoding", () => {
1721 const schema = Schema.Struct({
1722 a: Schema.Number.pipe(
1723 Schema.optionalKey,
1724 Schema.mutableKey,
1725 Schema.annotateKey({ description: "a" }),
1726 Schema.withConstructorDefault(Effect.succeed(0))
1727 )
1728 })
1729 const ast = Schema.toCodecStringTree(schema).ast
1730 strictEqual(ast._tag, "Objects")
1731 if (ast._tag === "Objects") {
1732 const type = ast.propertySignatures[0].type
1733 assertTrue(type.context?.constructorDefault !== undefined)
1734 const encoded = SchemaAST.getLastEncoding(type)
1735 strictEqual(encoded.context?.isOptional, true)
1736 strictEqual(encoded.context?.isMutable, true)
1737 strictEqual(encoded.context?.constructorDefault, undefined)
1738 deepStrictEqual(encoded.context?.annotations, { description: "a" })
1739 }
1740 })
1741
1742 it("should reorder the types in the Union based on the encoded side", async () => {
1743 const schema = Schema.Union([
1744 Schema.String,
1745 Schema.String.pipe(Schema.encodeTo(Schema.BigInt, {
1746 decode: SchemaGetter.transform((n: bigint) => String(n) + "a"),
1747 encode: SchemaGetter.transform(() => 0n)
1748 }))
1749 ])
1750 const serializer = Schema.toCodecStringTree(schema)
1751 const asserts = new TestSchema.Asserts(Schema.toCodecJson(serializer))
1752
1753 const decoding = asserts.decoding()
1754 await decoding.succeed("1", "1a")
1755 })
1756
1757 describe("should return the same reference if nothing changed", () => {
1758 it("String", async () => {
1759 const schema = Schema.String
1760 const serializer = Schema.toCodecStringTree(schema)
1761 strictEqual(serializer.ast, schema.ast)
1762 })
1763
1764 it("Struct({ a: String })", async () => {
1765 const schema = Schema.Struct({
1766 a: Schema.String
1767 })
1768 const serializer = Schema.toCodecStringTree(schema)
1769 strictEqual(serializer.ast, schema.ast)
1770 })
1771 })
1772
1773 describe("should memoize the result", () => {
1774 it("Struct", async () => {
1775 const schema = Schema.Struct({
1776 a: Schema.Finite
1777 })
1778 const serializer = Schema.toCodecStringTree(schema)
1779 strictEqual(serializer.ast, Schema.toCodecStringTree(serializer).ast)
1780 })
1781
1782 it("Array", async () => {
1783 const schema = Schema.Array(Schema.Finite)
1784 const serializer = Schema.toCodecStringTree(schema)
1785 strictEqual(serializer.ast, Schema.toCodecStringTree(serializer).ast)
1786 })
1787
1788 it("Unknown", () => {
1789 const serializer = Schema.toCodecStringTree(Schema.Unknown)
1790 strictEqual(serializer.ast, Schema.toCodecStringTree(serializer).ast)
1791 })
1792
1793 it("Suspend", () => {
1794 const serializer = Schema.toCodecStringTree(Schema.suspend(() => Schema.Array(Schema.Finite)))
1795 strictEqual(serializer.ast, Schema.toCodecStringTree(serializer).ast)
1796 })
1797 })
1798
1799 describe("schemas without encoding", () => {
1800 describe("Unsupported schemas", () => {
1801 it("Struct with Symbol property name", () => {
1802 const a = Symbol.for("a")
1803 const schema = Schema.Struct({
1804 [a]: Schema.String
1805 })
1806 throws(
1807 () => Schema.toCodecStringTree(schema),
1808 "Objects property names must be strings"
1809 )
1810 })
1811 })
1812
1813 it("Declaration", () => {
1814 const schema = Schema.instanceOf(URL)
1815 throws(
1816 () => Schema.toCodecStringTree(schema),
1817 "Missing structural codec for StringTree"
1818 )
1819 })
1820
1821 it("Json", async () => {
1822 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(Schema.Json))
1823
1824 const encoding = asserts.encoding()
1825 await encoding.succeed("a")
1826 await encoding.succeed(["a"])
1827 await encoding.succeed({ a: "a" })
1828 await encoding.fail(1, `Expected StringTree`)
1829 await encoding.fail(true, `Expected StringTree`)
1830 await encoding.fail(null, `Expected StringTree`)
1831 await encoding.fail({ a: 1 }, `Expected StringTree`)
1832
1833 const decoding = asserts.decoding()
1834 await decoding.succeed("a")
1835 await decoding.succeed(["a"])
1836 await decoding.succeed({ a: "a" })
1837 await decoding.fail(undefined, `Expected JSON value`)
1838 })
1839
1840 it("Unknown", async () => {
1841 const schema = Schema.Unknown
1842 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
1843
1844 const encoding = asserts.encoding()
1845 await encoding.succeed("a")
1846 await encoding.fail(1, `Expected StringTree`)
1847 await encoding.succeed({ a: "a" })
1848 await encoding.succeed(["a"])
1849 await encoding.fail({ a: 1 }, `Expected StringTree`)
1850
1851 const decoding = asserts.decoding()
1852 await decoding.succeed("a")
1853 await decoding.fail(1, `Expected StringTree`)
1854 await decoding.succeed({ a: "a" })
1855 await decoding.succeed(["a"])
1856 await decoding.fail({ a: 1 }, `Expected StringTree`)
1857 })
1858
1859 it("ObjectKeyword", async () => {
1860 const schema = Schema.ObjectKeyword
1861 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
1862
1863 const encoding = asserts.encoding()
1864 await encoding.fail("a", `Expected object | array | function`)
1865 await encoding.fail(1, `Expected object | array | function`)
1866 await encoding.succeed({ a: "a" })
1867 await encoding.succeed(["a"])
1868 await encoding.fail({ a: 1 }, `Expected StringTree`)
1869
1870 const decoding = asserts.decoding()
1871 await decoding.fail("a", `Expected object | array | function`)
1872 await decoding.fail(1, `Expected StringTree`)
1873 await decoding.succeed({ a: "a" })
1874 await decoding.succeed(["a"])
1875 await decoding.fail({ a: 1 }, `Expected StringTree`)
1876 })
1877
1878 it("Never", async () => {
1879 const schema = Schema.Never
1880 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
1881
1882 const encoding = asserts.encoding()
1883 await encoding.fail({}, "Expected never")
1884 })
1885
1886 it("Any should be an escape hatch", async () => {
1887 const schema = Schema.Any
1888 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
1889
1890 const encoding = asserts.encoding()
1891 await encoding.succeed(() => {})
1892
1893 const decoding = asserts.decoding()
1894 await decoding.succeed(() => {})
1895 })
1896
1897 it("Undefined", async () => {
1898 const schema = Schema.Undefined
1899 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
1900
1901 const encoding = asserts.encoding()
1902 await encoding.succeed(undefined)
1903
1904 const decoding = asserts.decoding()
1905 await decoding.succeed(undefined)
1906 })
1907
1908 it("Void", async () => {
1909 const schema = Schema.Void
1910 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
1911
1912 const encoding = asserts.encoding()
1913 await encoding.succeed(undefined)
1914
1915 const decoding = asserts.decoding()
1916 await decoding.succeed(undefined)
1917 })
1918
1919 it("Null", async () => {
1920 const schema = Schema.Null
1921 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
1922
1923 const encoding = asserts.encoding()
1924 await encoding.succeed(null, "null")
1925
1926 const decoding = asserts.decoding()
1927 await decoding.succeed("null", null)
1928 })
1929
1930 it("String", async () => {
1931 const schema = Schema.String
1932 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
1933
1934 const encoding = asserts.encoding()
1935 await encoding.succeed("a")
1936
1937 const decoding = asserts.decoding()
1938 await decoding.succeed("a")
1939 })
1940
1941 describe("Number", () => {
1942 it("Number", async () => {
1943 const schema = Schema.Number
1944 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
1945
1946 const encoding = asserts.encoding()
1947 await encoding.succeed(1, "1")
1948 await encoding.succeed(-1, "-1")
1949 await encoding.succeed(1.2, "1.2")
1950 await encoding.succeed(Infinity, "Infinity")
1951 await encoding.succeed(-Infinity, "-Infinity")
1952 await encoding.succeed(NaN, "NaN")
1953
1954 const decoding = asserts.decoding()
1955 await decoding.succeed("1", 1)
1956 await decoding.succeed("-1", -1)
1957 await decoding.succeed("1.2", 1.2)
1958 await decoding.succeed("Infinity", Infinity)
1959 await decoding.succeed("-Infinity", -Infinity)
1960 await decoding.succeed("NaN", NaN)
1961 await decoding.fail(Infinity, `Expected string | "Infinity" | "-Infinity" | "NaN"`)
1962 await decoding.fail(-Infinity, `Expected string | "Infinity" | "-Infinity" | "NaN"`)
1963 await decoding.fail(NaN, `Expected string | "Infinity" | "-Infinity" | "NaN"`)
1964 await decoding.fail(null, `Expected string | "Infinity" | "-Infinity" | "NaN"`)
1965 await decoding.fail(
1966 "a",
1967 `Expected a string representing a finite number
1968Expected "Infinity" | "-Infinity" | "NaN"`
1969 )
1970 })
1971
1972 describe("checks", () => {
1973 it("Finite", async () => {
1974 const schema = Schema.Finite
1975 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
1976
1977 const encoding = asserts.encoding()
1978 await encoding.succeed(1, "1")
1979 await encoding.succeed(-1, "-1")
1980 await encoding.succeed(1.2, "1.2")
1981 await encoding.fail(Infinity, "Expected a finite number")
1982 await encoding.fail(-Infinity, "Expected a finite number")
1983 await encoding.fail(NaN, "Expected a finite number")
1984
1985 const decoding = asserts.decoding()
1986 await decoding.succeed("1", 1)
1987 await decoding.succeed("-1", -1)
1988 await decoding.succeed("1.2", 1.2)
1989 await decoding.fail("Infinity", `Expected a string representing a finite number`)
1990 await decoding.fail("-Infinity", `Expected a string representing a finite number`)
1991 await decoding.fail("NaN", `Expected a string representing a finite number`)
1992 await decoding.fail(Infinity, `Expected string`)
1993 await decoding.fail(-Infinity, `Expected string`)
1994 await decoding.fail(NaN, `Expected string`)
1995 await decoding.fail(null, `Expected string`)
1996 await decoding.fail("a", `Expected a string representing a finite number`)
1997 })
1998
1999 it("Int", async () => {
2000 const schema = Schema.Int
2001 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2002
2003 const encoding = asserts.encoding()
2004 await encoding.succeed(1, "1")
2005 await encoding.succeed(-1, "-1")
2006 await encoding.fail(1.2, `Expected an integer`)
2007 await encoding.fail(Infinity, `Expected an integer`)
2008 await encoding.fail(-Infinity, `Expected an integer`)
2009 await encoding.fail(NaN, `Expected an integer`)
2010
2011 const decoding = asserts.decoding()
2012 await decoding.succeed("1", 1)
2013 await decoding.succeed("-1", -1)
2014 await decoding.fail("1.2", `Expected an integer`)
2015 await decoding.fail("Infinity", `Expected a string representing a finite number`)
2016 await decoding.fail("-Infinity", `Expected a string representing a finite number`)
2017 await decoding.fail("NaN", `Expected a string representing a finite number`)
2018 await decoding.fail(Infinity, `Expected string`)
2019 await decoding.fail(-Infinity, `Expected string`)
2020 await decoding.fail(NaN, `Expected string`)
2021 await decoding.fail(null, `Expected string`)
2022 await decoding.fail("a", `Expected a string representing a finite number`)
2023 })
2024
2025 it("isGreaterThanOrEqualTo", async () => {
2026 const schema = Schema.Number.check(Schema.isGreaterThanOrEqualTo(1))
2027 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2028
2029 const encoding = asserts.encoding()
2030 await encoding.succeed(1, "1")
2031 await encoding.fail(-1, `Expected a value greater than or equal to 1`)
2032 await encoding.succeed(1.2, "1.2")
2033 await encoding.succeed(Infinity, "Infinity")
2034 await encoding.fail(-Infinity, "Expected a value greater than or equal to 1")
2035 await encoding.fail(NaN, "Expected a value greater than or equal to 1")
2036
2037 const decoding = asserts.decoding()
2038 await decoding.succeed("1", 1)
2039 await decoding.fail("-1", `Expected a value greater than or equal to 1`)
2040 await decoding.succeed("1.2", 1.2)
2041 await decoding.succeed("Infinity", Infinity)
2042 await decoding.fail("-Infinity", `Expected a value greater than or equal to 1`)
2043 await decoding.fail("NaN", `Expected a value greater than or equal to 1`)
2044 await decoding.fail(Infinity, `Expected string | "Infinity" | "-Infinity" | "NaN"`)
2045 await decoding.fail(-Infinity, `Expected string | "Infinity" | "-Infinity" | "NaN"`)
2046 await decoding.fail(NaN, `Expected string | "Infinity" | "-Infinity" | "NaN"`)
2047 await decoding.fail(null, `Expected string | "Infinity" | "-Infinity" | "NaN"`)
2048 await decoding.fail(
2049 "a",
2050 `Expected a string representing a finite number
2051Expected "Infinity" | "-Infinity" | "NaN"`
2052 )
2053 })
2054 })
2055 })
2056
2057 it("Boolean", async () => {
2058 const schema = Schema.Boolean
2059 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2060
2061 const encoding = asserts.encoding()
2062 await encoding.succeed(true, "true")
2063 await encoding.succeed(false, "false")
2064
2065 const decoding = asserts.decoding()
2066 await decoding.succeed("true", true)
2067 await decoding.succeed("false", false)
2068 })
2069
2070 it("Symbol", async () => {
2071 const schema = Schema.Symbol
2072 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2073
2074 const encoding = asserts.encoding()
2075 await encoding.succeed(Symbol.for("a"), "Symbol(a)")
2076 await encoding.fail(
2077 Symbol("a"),
2078 "cannot serialize to string, Symbol is not registered"
2079 )
2080 await encoding.fail(
2081 Symbol(),
2082 "cannot serialize to string, Symbol is not registered"
2083 )
2084
2085 const decoding = asserts.decoding()
2086 await decoding.succeed("Symbol(a)", Symbol.for("a"))
2087 await decoding.fail("a", `Expected a string representing a symbol`)
2088 })
2089
2090 it("UniqueSymbol", async () => {
2091 const schema = Schema.UniqueSymbol(Symbol.for("a"))
2092 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2093
2094 const encoding = asserts.encoding()
2095 await encoding.succeed(Symbol.for("a"), "Symbol(a)")
2096
2097 const decoding = asserts.decoding()
2098 await decoding.succeed("Symbol(a)", Symbol.for("a"))
2099 await decoding.fail("a", `Expected "Symbol(a)"`)
2100 })
2101
2102 it("BigInt", async () => {
2103 const schema = Schema.BigInt
2104 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2105
2106 const encoding = asserts.encoding()
2107 await encoding.succeed(1n, "1")
2108
2109 const decoding = asserts.decoding()
2110 await decoding.succeed("1", 1n)
2111 await decoding.fail("a", `Expected a string representing a bigint`)
2112 })
2113
2114 it("PropertyKey", async () => {
2115 const schema = Schema.PropertyKey
2116 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2117
2118 const encoding = asserts.encoding()
2119 await encoding.succeed("a", "a")
2120 await encoding.succeed(1, "1")
2121 await encoding.succeed(Symbol.for("a"), "Symbol(a)")
2122
2123 const decoding = asserts.decoding()
2124 await decoding.succeed("a", "a")
2125 await decoding.succeed("1", 1)
2126 await decoding.succeed("Symbol(a)", Symbol.for("a"))
2127 })
2128
2129 describe("Literal", () => {
2130 it("string", async () => {
2131 const schema = Schema.Literal("a")
2132 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2133
2134 const encoding = asserts.encoding()
2135 await encoding.succeed("a", "a")
2136
2137 const decoding = asserts.decoding()
2138 await decoding.succeed("a", "a")
2139 })
2140
2141 it("number", async () => {
2142 const schema = Schema.Literal(1)
2143 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2144
2145 const encoding = asserts.encoding()
2146 await encoding.succeed(1, "1")
2147
2148 const decoding = asserts.decoding()
2149 await decoding.succeed("1", 1)
2150 })
2151
2152 it("boolean", async () => {
2153 const schema = Schema.Literal(true)
2154 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2155
2156 const encoding = asserts.encoding()
2157 await encoding.succeed(true, "true")
2158
2159 const decoding = asserts.decoding()
2160 await decoding.succeed("true", true)
2161 })
2162
2163 it("bigint", async () => {
2164 const schema = Schema.Literal(1n)
2165 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2166
2167 const encoding = asserts.encoding()
2168 await encoding.succeed(1n, "1")
2169
2170 const decoding = asserts.decoding()
2171 await decoding.succeed("1", 1n)
2172 })
2173 })
2174
2175 it("Literals", async () => {
2176 const schema = Schema.Literals(["a", 1, 2n, true])
2177 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2178
2179 const decoding = asserts.decoding()
2180 await decoding.fail(
2181 "-",
2182 `Expected "a" | "1" | "2" | "true"`
2183 )
2184 })
2185
2186 describe("TemplateLiteral", () => {
2187 it("1n + string", async () => {
2188 const schema = Schema.TemplateLiteral([1n, Schema.String])
2189 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2190
2191 const encoding = asserts.encoding()
2192 await encoding.succeed("1a")
2193
2194 const decoding = asserts.decoding()
2195 await decoding.succeed("1a")
2196 })
2197
2198 it(`"a" + bigint`, async () => {
2199 const schema = Schema.TemplateLiteral(["a", Schema.BigInt])
2200 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2201
2202 const encoding = asserts.encoding()
2203 await encoding.succeed("a1")
2204
2205 const decoding = asserts.decoding()
2206 await decoding.succeed("a1")
2207 })
2208 })
2209
2210 it("Enum", async () => {
2211 enum Fruits {
2212 Apple,
2213 Banana
2214 }
2215 const schema = Schema.Enum(Fruits)
2216 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2217
2218 const encoding = asserts.encoding()
2219 await encoding.succeed(Fruits.Apple, "0")
2220 await encoding.succeed(Fruits.Banana, "1")
2221
2222 const decoding = asserts.decoding()
2223 await decoding.succeed("0", Fruits.Apple)
2224 await decoding.succeed("1", Fruits.Banana)
2225 })
2226
2227 describe("Struct", () => {
2228 it("Date", async () => {
2229 const schema = Schema.Struct({
2230 a: Schema.Date
2231 })
2232 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2233
2234 const encoding = asserts.encoding()
2235 await encoding.succeed({ a: new Date("2021-01-01") }, {
2236 a: "2021-01-01T00:00:00.000Z"
2237 })
2238
2239 const decoding = asserts.decoding()
2240 await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
2241 a: new Date("2021-01-01")
2242 })
2243 })
2244
2245 it("UndefinedOr(Date)", async () => {
2246 const schema = Schema.Struct({
2247 a: Schema.UndefinedOr(Schema.Date)
2248 })
2249 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2250
2251 const encoding = asserts.encoding()
2252 await encoding.succeed({ a: new Date("2021-01-01") }, {
2253 a: "2021-01-01T00:00:00.000Z"
2254 })
2255 await encoding.succeed({ a: undefined }, { a: undefined })
2256
2257 const decoding = asserts.decoding()
2258 await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
2259 a: new Date("2021-01-01")
2260 })
2261 await decoding.succeed({ a: undefined }, { a: undefined })
2262 })
2263
2264 it("NullOr(Date)", async () => {
2265 const schema = Schema.Struct({
2266 a: Schema.NullOr(Schema.Date)
2267 })
2268 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2269
2270 const encoding = asserts.encoding()
2271 await encoding.succeed({ a: new Date("2021-01-01") }, {
2272 a: "2021-01-01T00:00:00.000Z"
2273 })
2274 await encoding.succeed({ a: null }, { a: "null" })
2275
2276 const decoding = asserts.decoding()
2277 await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
2278 a: new Date("2021-01-01")
2279 })
2280 await decoding.succeed({ a: "null" }, { a: null })
2281 })
2282
2283 it("optionalKey(Date)", async () => {
2284 const schema = Schema.Struct({
2285 a: Schema.optionalKey(Schema.Date)
2286 })
2287 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2288
2289 const encoding = asserts.encoding()
2290 await encoding.succeed({ a: new Date("2021-01-01") }, {
2291 a: "2021-01-01T00:00:00.000Z"
2292 })
2293 await encoding.succeed({}, {})
2294
2295 const decoding = asserts.decoding()
2296 await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
2297 a: new Date("2021-01-01")
2298 })
2299 await decoding.succeed({}, {})
2300 })
2301
2302 it("optional(Date)", async () => {
2303 const schema = Schema.Struct({
2304 a: Schema.optional(Schema.Date)
2305 })
2306 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2307
2308 const encoding = asserts.encoding()
2309 await encoding.succeed({ a: new Date("2021-01-01") }, {
2310 a: "2021-01-01T00:00:00.000Z"
2311 })
2312 await encoding.succeed({}, {})
2313 await encoding.succeed({ a: undefined }, { a: undefined })
2314
2315 const decoding = asserts.decoding()
2316 await decoding.succeed({ a: "2021-01-01T00:00:00.000Z" }, {
2317 a: new Date("2021-01-01")
2318 })
2319 await decoding.succeed({}, {})
2320 await decoding.succeed({ a: undefined }, { a: undefined })
2321 })
2322 })
2323
2324 it("Record(Symbol, Date)", async () => {
2325 const schema = Schema.Record(Schema.Symbol, Schema.Date)
2326 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2327
2328 const encoding = asserts.encoding()
2329 await encoding.succeed(
2330 { [Symbol.for("a")]: new Date("2021-01-01"), [Symbol.for("b")]: new Date("2021-01-01") },
2331 { "Symbol(a)": "2021-01-01T00:00:00.000Z", "Symbol(b)": "2021-01-01T00:00:00.000Z" }
2332 )
2333
2334 const decoding = asserts.decoding()
2335 await decoding.succeed(
2336 { "Symbol(a)": "2021-01-01T00:00:00.000Z", "Symbol(b)": "2021-01-01T00:00:00.000Z" },
2337 { [Symbol.for("a")]: new Date("2021-01-01"), [Symbol.for("b")]: new Date("2021-01-01") }
2338 )
2339 })
2340
2341 it("Struct with an explicitly encoded Symbol property name", async () => {
2342 const field = Symbol.for("field")
2343 const schema = Schema.Struct({
2344 [field]: Schema.String
2345 }).pipe(Schema.encodeKeys({ [field]: "field" }))
2346 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2347
2348 await asserts.encoding().succeed({ [field]: "a" }, { field: "a" })
2349 await asserts.decoding().succeed({ field: "a" }, { [field]: "a" })
2350 })
2351
2352 describe("Tuple", () => {
2353 it("Date", async () => {
2354 const schema = Schema.Tuple([Schema.Date])
2355 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2356
2357 const encoding = asserts.encoding()
2358 await encoding.succeed(
2359 [new Date("2021-01-01")],
2360 ["2021-01-01T00:00:00.000Z"]
2361 )
2362
2363 const decoding = asserts.decoding()
2364 await decoding.succeed(
2365 ["2021-01-01T00:00:00.000Z"],
2366 [new Date("2021-01-01")]
2367 )
2368 })
2369
2370 it("UndefinedOr(Date)", async () => {
2371 const schema = Schema.Tuple([Schema.UndefinedOr(Schema.Date)])
2372 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2373
2374 const encoding = asserts.encoding()
2375 await encoding.succeed(
2376 [new Date("2021-01-01")],
2377 ["2021-01-01T00:00:00.000Z"]
2378 )
2379 await encoding.succeed([undefined])
2380
2381 const decoding = asserts.decoding()
2382 await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
2383 new Date("2021-01-01")
2384 ])
2385 await decoding.succeed([undefined])
2386 })
2387
2388 it("NullOr(Date)", async () => {
2389 const schema = Schema.Tuple([Schema.NullOr(Schema.Date)])
2390 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2391
2392 const encoding = asserts.encoding()
2393 await encoding.succeed([new Date("2021-01-01")], [
2394 "2021-01-01T00:00:00.000Z"
2395 ])
2396 await encoding.succeed([null], ["null"])
2397
2398 const decoding = asserts.decoding()
2399 await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
2400 new Date("2021-01-01")
2401 ])
2402 await decoding.succeed(["null"], [null])
2403 })
2404
2405 it("optionalKey(Date)", async () => {
2406 const schema = Schema.Tuple([Schema.optionalKey(Schema.Date)])
2407 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2408
2409 const encoding = asserts.encoding()
2410 await encoding.succeed([new Date("2021-01-01")], [
2411 "2021-01-01T00:00:00.000Z"
2412 ])
2413 await encoding.succeed([], [])
2414
2415 const decoding = asserts.decoding()
2416 await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
2417 new Date("2021-01-01")
2418 ])
2419 await decoding.succeed([], [])
2420 })
2421
2422 it("optional(Date)", async () => {
2423 const schema = Schema.Tuple([Schema.optional(Schema.Date)])
2424 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2425
2426 const encoding = asserts.encoding()
2427 await encoding.succeed([new Date("2021-01-01")], [
2428 "2021-01-01T00:00:00.000Z"
2429 ])
2430 await encoding.succeed([], [])
2431 await encoding.succeed([undefined])
2432
2433 const decoding = asserts.decoding()
2434 await decoding.succeed(["2021-01-01T00:00:00.000Z"], [
2435 new Date("2021-01-01")
2436 ])
2437 await decoding.succeed([], [])
2438 await decoding.succeed([undefined])
2439 })
2440 })
2441
2442 it("Array(Date)", async () => {
2443 const schema = Schema.Array(Schema.Date)
2444 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2445
2446 const encoding = asserts.encoding()
2447 await encoding.succeed(
2448 [new Date("2021-01-01"), new Date("2021-01-02")],
2449 ["2021-01-01T00:00:00.000Z", "2021-01-02T00:00:00.000Z"]
2450 )
2451 })
2452
2453 it("Array(Finite) preserves the top-level AST", async () => {
2454 const serializer = Schema.toCodecStringTree(Schema.Array(Schema.Finite))
2455 strictEqual(serializer.ast._tag, "Arrays")
2456
2457 const asserts = new TestSchema.Asserts(serializer)
2458
2459 const encoding = asserts.encoding()
2460 await encoding.succeed([1, 2], ["1", "2"])
2461
2462 const decoding = asserts.decoding()
2463 await decoding.fail("1,2", `Expected array`)
2464 await decoding.succeed(["1", "2"], [1, 2])
2465 })
2466
2467 describe("Union", () => {
2468 it("NullOr(Date)", async () => {
2469 const schema = Schema.NullOr(Schema.String)
2470 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2471
2472 const encoding = asserts.encoding()
2473 await encoding.succeed("a", "a")
2474 await encoding.succeed(null, "null")
2475
2476 const decoding = asserts.decoding()
2477 await decoding.succeed("null", null)
2478 await decoding.succeed("a", "a")
2479 })
2480
2481 it("NullOr(Number)", async () => {
2482 const schema = Schema.NullOr(Schema.Number)
2483 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2484
2485 const encoding = asserts.encoding()
2486 await encoding.succeed(1, "1")
2487 await encoding.succeed(null, "null")
2488
2489 const decoding = asserts.decoding()
2490 await decoding.succeed("null", null)
2491 await decoding.succeed("1", 1)
2492 })
2493
2494 it("Array(NullOr(Number))", async () => {
2495 const schema = Schema.Array(Schema.NullOr(Schema.Number))
2496 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2497
2498 const encoding = asserts.encoding()
2499 await encoding.succeed([1, null], ["1", "null"])
2500
2501 const decoding = asserts.decoding()
2502 await decoding.succeed(["1", "null"], [1, null])
2503 })
2504
2505 it("Union(Date, Finite)", async () => {
2506 const schema = Schema.Union([Schema.Date, Schema.Finite])
2507 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2508
2509 const encoding = asserts.encoding()
2510 await encoding.succeed(
2511 new Date("2021-01-01"),
2512 "2021-01-01T00:00:00.000Z"
2513 )
2514 await encoding.succeed(0, "0")
2515 })
2516 })
2517
2518 it("Suspend", async () => {
2519 interface Category {
2520 readonly a: number
2521 readonly categories: ReadonlyArray<Category>
2522 }
2523
2524 const schema = Schema.Struct({
2525 a: Schema.Finite.check(Schema.isGreaterThan(0)),
2526 categories: Schema.Array(Schema.suspend((): Schema.Codec<Category> => schema))
2527 })
2528 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2529
2530 const encoding = asserts.encoding()
2531 await encoding.succeed({ a: 1, categories: [] }, {
2532 a: "1",
2533 categories: []
2534 })
2535 await encoding.succeed({
2536 a: 1,
2537 categories: [{ a: 2, categories: [] }]
2538 }, {
2539 a: "1",
2540 categories: [
2541 { a: "2", categories: [] }
2542 ]
2543 })
2544
2545 const decoding = asserts.decoding()
2546 await decoding.succeed({
2547 a: "1",
2548 categories: []
2549 }, { a: 1, categories: [] })
2550 await decoding.succeed({
2551 a: "1",
2552 categories: [
2553 { a: "2", categories: [] }
2554 ]
2555 }, { a: 1, categories: [{ a: 2, categories: [] }] })
2556 })
2557
2558 it("Class", async () => {
2559 class A extends Schema.Class<A>("A")(Schema.Struct({
2560 a: Schema.Finite
2561 })) {}
2562 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(Schema.toType(A)))
2563
2564 const encoding = asserts.encoding()
2565 await encoding.succeed(new A({ a: 0 }), { a: "0" })
2566
2567 const decoding = asserts.decoding()
2568 await decoding.succeed({ a: "0" }, new A({ a: 0 }))
2569 })
2570
2571 it("Error", async () => {
2572 class E extends Schema.Error<E>("E")({
2573 a: Schema.Finite
2574 }) {}
2575 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(Schema.toType(E)))
2576
2577 const encoding = asserts.encoding()
2578 await encoding.succeed(new E({ a: 0 }), { a: "0" })
2579
2580 const decoding = asserts.decoding()
2581 await decoding.succeed({ a: "0" }, new E({ a: 0 }))
2582 })
2583
2584 it("Date", async () => {
2585 const schema = Schema.Date
2586 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2587
2588 const encoding = asserts.encoding()
2589 await encoding.succeed(
2590 new Date("2021-01-01"),
2591 "2021-01-01T00:00:00.000Z"
2592 )
2593 })
2594
2595 it("Error", async () => {
2596 const schema = Schema.ErrorInstance()
2597 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2598
2599 const encoding = asserts.encoding()
2600 await encoding.succeed(
2601 new Error("a"),
2602 { name: "Error", message: "a" }
2603 )
2604
2605 const decoding = asserts.decoding()
2606 await decoding.succeed(
2607 { message: "a" },
2608 new Error("a")
2609 )
2610 await decoding.succeed(
2611 { name: "b", message: "a" },
2612 (() => {
2613 const err = new Error("a")
2614 err.name = "b"
2615 return err
2616 })()
2617 )
2618 await decoding.succeed(
2619 { name: "b", message: "a", stack: "c" },
2620 (() => {
2621 const err = new Error("a")
2622 err.name = "b"
2623 err.stack = "c"
2624 return err
2625 })()
2626 )
2627 })
2628
2629 it("URL", async () => {
2630 const schema = Schema.URL
2631 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2632
2633 const encoding = asserts.encoding()
2634 await encoding.succeed(
2635 new URL("https://example.com"),
2636 "https://example.com/"
2637 )
2638
2639 const decoding = asserts.decoding()
2640 await decoding.succeed(
2641 "https://example.com",
2642 new URL("https://example.com")
2643 )
2644 await decoding.succeed(
2645 "https://example.com/",
2646 new URL("https://example.com")
2647 )
2648 await decoding.fail(
2649 "not a url",
2650 "Expected a valid URL string"
2651 )
2652 })
2653
2654 it("RegExp", async () => {
2655 const schema = Schema.RegExp
2656 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2657
2658 const encoding = asserts.encoding()
2659 await encoding.succeed(new RegExp("a"), { source: "a", flags: "" })
2660 await encoding.succeed(new RegExp("a", "i"), { source: "a", flags: "i" })
2661
2662 const decoding = asserts.decoding()
2663 await decoding.succeed({ source: "a", flags: "" }, new RegExp("a"))
2664 await decoding.succeed({ source: "a", flags: "i" }, new RegExp("a", "i"))
2665 await decoding.fail(
2666 { source: "a", flags: "x" },
2667 "Expected valid RegExp source and flags"
2668 )
2669 })
2670
2671 it("Option(Date)", async () => {
2672 const schema = Schema.Option(Schema.Date)
2673 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2674
2675 const encoding = asserts.encoding()
2676 await encoding.succeed(Option.some(new Date("2021-01-01")), {
2677 _tag: "Some",
2678 value: "2021-01-01T00:00:00.000Z"
2679 })
2680 await encoding.succeed(Option.none(), { _tag: "None" })
2681 })
2682
2683 it("Redacted(Option(String))", async () => {
2684 const schema = Schema.Redacted(Schema.Option(Schema.String))
2685 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2686
2687 const encoding = asserts.encoding()
2688 await encoding.succeed(
2689 Redacted.make(Option.none()),
2690 { _tag: "None" }
2691 )
2692 await encoding.succeed(
2693 Redacted.make(Option.some("a")),
2694 { _tag: "Some", value: "a" }
2695 )
2696
2697 const decoding = asserts.decoding()
2698 await decoding.succeed(
2699 { _tag: "None" },
2700 Redacted.make(Option.none())
2701 )
2702 await decoding.succeed(
2703 { _tag: "Some", value: "a" },
2704 Redacted.make(Option.some("a"))
2705 )
2706 })
2707
2708 it("Redacted(Option(String)) disallowJsonEncode", async () => {
2709 const schema = Schema.Redacted(Schema.Option(Schema.String), {
2710 disallowJsonEncode: true
2711 })
2712 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2713
2714 const encoding = asserts.encoding()
2715 await encoding.fail(
2716 Redacted.make(Option.none()),
2717 "Cannot serialize Redacted"
2718 )
2719 await encoding.fail(
2720 Redacted.make(Option.some("a")),
2721 "Cannot serialize Redacted"
2722 )
2723
2724 const decoding = asserts.decoding()
2725 await decoding.succeed(
2726 { _tag: "None" },
2727 Redacted.make(Option.none())
2728 )
2729 await decoding.succeed(
2730 { _tag: "Some", value: "a" },
2731 Redacted.make(Option.some("a"))
2732 )
2733 })
2734
2735 it("ReadonlySet", async () => {
2736 const schema = Schema.ReadonlySet(Schema.Option(Schema.Date))
2737 const asserts = new TestSchema.Asserts(Schema.toCodecJson(schema))
2738
2739 const encoding = asserts.encoding()
2740 await encoding.succeed(new Set([Option.some(new Date("2021-01-01"))]), [{
2741 _tag: "Some",
2742 value: "2021-01-01T00:00:00.000Z"
2743 }])
2744
2745 const decoding = asserts.decoding()
2746 await decoding.succeed(
2747 [{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }],
2748 new Set([Option.some(new Date("2021-01-01"))])
2749 )
2750 })
2751
2752 it("ReadonlyMap", async () => {
2753 const schema = Schema.ReadonlyMap(Schema.Option(Schema.Date), Schema.Finite)
2754 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2755
2756 const encoding = asserts.encoding()
2757 await encoding.succeed(
2758 new Map([[Option.some(new Date("2021-01-01")), 0]]),
2759 [[
2760 { _tag: "Some", value: "2021-01-01T00:00:00.000Z" },
2761 "0"
2762 ]]
2763 )
2764
2765 const decoding = asserts.decoding()
2766 await decoding.succeed(
2767 [[{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, "0"]],
2768 new Map([[Option.some(new Date("2021-01-01")), 0]])
2769 )
2770 })
2771
2772 it("HashMap", async () => {
2773 const schema = Schema.HashMap(Schema.Option(Schema.Date), Schema.Finite)
2774 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(schema))
2775
2776 const encoding = asserts.encoding()
2777 await encoding.succeed(
2778 HashMap.make([Option.some(new Date("2021-01-01")), 0]),
2779 [[
2780 { _tag: "Some", value: "2021-01-01T00:00:00.000Z" },
2781 "0"
2782 ]]
2783 )
2784
2785 const decoding = asserts.decoding()
2786 await decoding.succeed(
2787 [[{ _tag: "Some", value: "2021-01-01T00:00:00.000Z" }, "0"]],
2788 HashMap.make([Option.some(new Date("2021-01-01")), 0])
2789 )
2790 })
2791 })
2792
2793 it("should handle optional keys", async () => {
2794 const schema = Schema.Struct({
2795 a: Schema.optionalKey(Schema.NonEmptyArray(Schema.String))
2796 })
2797 const serializer = Schema.toCodecStringTree(schema)
2798 const asserts = new TestSchema.Asserts(Schema.toCodecStringTree(serializer))
2799
2800 const decoding = asserts.decoding()
2801 await decoding.succeed({})
2802 await decoding.succeed({ a: ["a"] })
2803 await decoding.fail({ a: "a" }, `Expected array\n at ["a"]`)
2804 })
2805 })
2806
2807 describe("toCodecArrayFromSingle", () => {
2808 it("preserves union fallback when a singleton fails array element checks", async () => {
2809 const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Union([
2810 Schema.Array(Schema.String.check(Schema.isMinLength(2))),
2811 Schema.String
2812 ])))
2813 const asserts = new TestSchema.Asserts(schema)
2814
2815 const decoding = asserts.decoding()
2816 await decoding.succeed("a", "a")
2817 await decoding.succeed("ab", ["ab"])
2818 })
2819
2820 it("accepts string and array inputs for a top-level array", async () => {
2821 const serializer = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Array(Schema.Finite)))
2822 strictEqual(serializer.ast._tag, "Arrays")
2823
2824 const asserts = new TestSchema.Asserts(serializer)
2825
2826 const encoding = asserts.encoding()
2827 await encoding.succeed([1, 2], ["1", "2"])
2828 await encoding.fail(1 as any, "Expected array")
2829
2830 const decoding = asserts.decoding()
2831 await decoding.succeed("1", [1])
2832 await decoding.fail("1,2", `Expected a string representing a finite number\n at [0]`)
2833 await decoding.succeed(["1", "2"], [1, 2])
2834 })
2835
2836 it("accepts string and array inputs for required array fields", async () => {
2837 const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Struct({
2838 a: Schema.Array(Schema.Finite)
2839 })))
2840 const asserts = new TestSchema.Asserts(schema)
2841
2842 const decoding = asserts.decoding()
2843 await decoding.succeed({ a: "1" }, { a: [1] })
2844 await decoding.succeed({ a: ["1", "2"] }, { a: [1, 2] })
2845 })
2846
2847 it("accepts string and array inputs for nested optional array fields", async () => {
2848 const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Struct({
2849 a: Schema.optionalKey(Schema.NonEmptyArray(Schema.String))
2850 })))
2851 const asserts = new TestSchema.Asserts(schema)
2852
2853 const decoding = asserts.decoding()
2854 await decoding.succeed({})
2855 await decoding.succeed({ a: ["a"] })
2856 await decoding.succeed({ a: "a" }, { a: ["a"] })
2857 })
2858
2859 it("accepts string and array inputs for tuples with optional elements", async () => {
2860 const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Tuple([
2861 Schema.optionalKey(Schema.String)
2862 ])))
2863 const asserts = new TestSchema.Asserts(schema)
2864
2865 const decoding = asserts.decoding()
2866 await decoding.succeed([])
2867 await decoding.succeed("a", ["a"])
2868 await decoding.succeed(["a"])
2869 })
2870
2871 it("applies recursively to nested arrays", async () => {
2872 const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Array(Schema.Array(Schema.Finite))))
2873 const asserts = new TestSchema.Asserts(schema)
2874
2875 const decoding = asserts.decoding()
2876 await decoding.succeed("1", [[1]])
2877 await decoding.succeed(["1", "2"], [[1], [2]])
2878 await decoding.succeed([["1", "2"]], [[1, 2]])
2879 })
2880
2881 it("preserves array-from-single encoding when converting to StringTree again", () => {
2882 const schema = Schema.toCodecArrayFromSingle(Schema.toCodecStringTree(Schema.Array(Schema.Finite)))
2883 strictEqual(Schema.toCodecStringTree(schema).ast, schema.ast)
2884 })
2885
2886 it("is idempotent", () => {
2887 const schema = Schema.toCodecArrayFromSingle(
2888 Schema.toCodecStringTree(Schema.suspend(() => Schema.Array(Schema.Finite)))
2889 )
2890 strictEqual(schema.ast, Schema.toCodecArrayFromSingle(schema).ast)
2891 })
2892 })
2893
2894 describe("toEncoderXml", () => {
2895 async function assertXml<T, E, RD>(schema: Schema.Codec<T, E, RD>, value: T, expected: string) {
2896 const serializer = Schema.toEncoderXml(Schema.toCodecStringTree(schema))
2897 strictEqual(await Effect.runPromise(serializer(value)), expected)
2898 }
2899
2900 async function assertXmlFailure<T, E, RD>(schema: Schema.Codec<T, E, RD>, value: T, message: string) {
2901 const serializer = Schema.toEncoderXml(Schema.toCodecStringTree(schema))
2902 const r = await serializer(value).pipe(
2903 Effect.mapError(formatIssue),
2904 Effect.result,
2905 Effect.runPromise
2906 )
2907 deepStrictEqual(r, Result.fail(message))
2908 }
2909
2910 describe("Schemas without annotations", () => {
2911 it("Declaration", () => {
2912 throws(
2913 () => Schema.toEncoderXml(Schema.instanceOf(URL)),
2914 "Missing structural codec for StringTree"
2915 )
2916 })
2917
2918 it("Unknown", async () => {
2919 await assertXml(Schema.Unknown, "value", "<root>value</root>")
2920 })
2921
2922 it("ObjectKeyword", async () => {
2923 await assertXml(Schema.ObjectKeyword, { a: "value" }, "<root>\n <a>value</a>\n</root>")
2924 })
2925 })
2926
2927 it("should use the identifier as the root name", async () => {
2928 await assertXml(Schema.String.annotate({ identifier: "a" }), "value", "<a>value</a>")
2929 await assertXml(Schema.String.annotate({ identifier: "a b" }), "value", `<a_b data-name="a b">value</a_b>`)
2930 await assertXml(Schema.String.annotate({ identifier: "a", title: "b" }), "value", "<a>value</a>")
2931 })
2932
2933 it("should use the title as the root name", async () => {
2934 await assertXml(Schema.String.annotate({ title: "a" }), "value", "<a>value</a>")
2935 await assertXml(Schema.String.annotate({ title: "a b" }), "value", `<a_b data-name="a b">value</a_b>`)
2936 })
2937
2938 it("should escape the text", async () => {
2939 await assertXml(Schema.String, "value&", `<root>value&amp;</root>`)
2940 await assertXml(Schema.String, "<value/>", `<root>&lt;value/&gt;</root>`)
2941 })
2942
2943 it("should escape the attributes", async () => {
2944 await assertXml(Schema.String.annotate({ title: "value&" }), "", `<value_ data-name="value&amp;"></value_>`)
2945 await assertXml(
2946 Schema.String.annotate({ title: "<value/>" }),
2947 "",
2948 `<__value__ data-name="&lt;value/&gt;"></__value__>`
2949 )
2950 })
2951
2952 it("Never", async () => {
2953 await assertXmlFailure(Schema.Never, "test", `Expected never`)
2954 })
2955
2956 it("Any", async () => {
2957 await assertXml(Schema.Any, "test", "<root>test</root>")
2958 await assertXml(Schema.Any, 42, "<root>42</root>")
2959 await assertXml(
2960 Schema.Any,
2961 { a: 1 },
2962 `<root>
2963 <a>1</a>
2964</root>`
2965 )
2966 })
2967
2968 it("Void", async () => {
2969 await assertXml(Schema.Void, undefined, "<root/>")
2970 })
2971
2972 it("Undefined", async () => {
2973 await assertXml(Schema.Undefined, undefined, "<root/>")
2974 })
2975
2976 it("Null", async () => {
2977 await assertXml(Schema.Null, null, "<root>null</root>")
2978 })
2979
2980 it("Number", async () => {
2981 await assertXml(Schema.Number, 1, "<root>1</root>")
2982 await assertXml(Schema.Number, 0, "<root>0</root>")
2983 await assertXml(Schema.Number, -1.5, "<root>-1.5</root>")
2984 await assertXml(Schema.Number, Infinity, "<root>Infinity</root>")
2985 await assertXml(Schema.Number, -Infinity, "<root>-Infinity</root>")
2986 await assertXml(Schema.Number, NaN, "<root>NaN</root>")
2987 })
2988
2989 it("Boolean", async () => {
2990 await assertXml(Schema.Boolean, true, "<root>true</root>")
2991 await assertXml(Schema.Boolean, false, "<root>false</root>")
2992 })
2993
2994 it("BigInt", async () => {
2995 await assertXml(Schema.BigInt, BigInt(42), "<root>42</root>")
2996 await assertXml(Schema.BigInt, BigInt(0), "<root>0</root>")
2997 await assertXml(Schema.BigInt, BigInt(-123), "<root>-123</root>")
2998 })
2999
3000 it("Symbol", async () => {
3001 const sym = Symbol.for("test")
3002 await assertXml(Schema.Symbol, sym, "<root>Symbol(test)</root>")
3003 })
3004
3005 it("TemplateLiteral", async () => {
3006 const schema = Schema.TemplateLiteral([Schema.Literal("Hello "), Schema.String, Schema.Literal("!")])
3007 await assertXml(schema, "Hello World!", "<root>Hello World!</root>")
3008 })
3009
3010 it("Struct", async () => {
3011 await assertXml(
3012 Schema.Struct({
3013 a: Schema.Number,
3014 "a b": Schema.Number
3015 }),
3016 { a: 1, "a b": 2 },
3017 `<root>
3018 <a>1</a>
3019 <a_b data-name="a b">2</a_b>
3020</root>`
3021 )
3022 })
3023
3024 it("Array", async () => {
3025 await assertXml(Schema.Array(Schema.Number), [], "<root/>")
3026 await assertXml(
3027 Schema.Array(Schema.Number),
3028 [1, 2, 3],
3029 `<root>
3030 <item>1</item>
3031 <item>2</item>
3032 <item>3</item>
3033</root>`
3034 )
3035 })
3036
3037 it("Array with custom item name", async () => {
3038 const serializer = Schema.toEncoderXml(Schema.toCodecStringTree(Schema.Array(Schema.Number)), {
3039 arrayItemName: "number"
3040 })
3041 strictEqual(
3042 await Effect.runPromise(serializer([1, 2, 3])),
3043 `<root>
3044 <number>1</number>
3045 <number>2</number>
3046 <number>3</number>
3047</root>`
3048 )
3049 })
3050
3051 it("Union", async () => {
3052 await assertXml(Schema.Union([Schema.String, Schema.Number]), "test", "<root>test</root>")
3053 await assertXml(Schema.Union([Schema.String, Schema.Number]), 42, "<root>42</root>")
3054 })
3055
3056 it("Tuple", async () => {
3057 await assertXml(Schema.Tuple([]), [], "<root/>")
3058 await assertXml(
3059 Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]),
3060 ["a", 1, true],
3061 `<root>
3062 <item>a</item>
3063 <item>1</item>
3064 <item>true</item>
3065</root>`
3066 )
3067 })
3068
3069 it("Record", async () => {
3070 await assertXml(Schema.Record(Schema.String, Schema.Number), {}, "<root/>")
3071 await assertXml(
3072 Schema.Record(Schema.String, Schema.Number),
3073 { a: 1, b: 2 },
3074 `<root>
3075 <a>1</a>
3076 <b>2</b>
3077</root>`
3078 )
3079 })
3080
3081 it("NullOr", async () => {
3082 await assertXml(Schema.NullOr(Schema.String), "test", "<root>test</root>")
3083 await assertXml(Schema.NullOr(Schema.String), null, "<root>null</root>")
3084 })
3085
3086 it("TaggedUnion", async () => {
3087 const schema = Schema.TaggedUnion({
3088 A: { value: Schema.String },
3089 B: { value: Schema.Number }
3090 })
3091 await assertXml(
3092 schema,
3093 { _tag: "A", value: "test" },
3094 `<root>
3095 <_tag>A</_tag>
3096 <value>test</value>
3097</root>`
3098 )
3099 await assertXml(
3100 schema,
3101 { _tag: "B", value: 42 },
3102 `<root>
3103 <_tag>B</_tag>
3104 <value>42</value>
3105</root>`
3106 )
3107 })
3108
3109 it("TaggedStruct", async () => {
3110 const schema = Schema.TaggedStruct("User", {
3111 name: Schema.String,
3112 age: Schema.Number
3113 })
3114 await assertXml(
3115 schema,
3116 { _tag: "User", name: "John", age: 30 },
3117 `<root>
3118 <_tag>User</_tag>
3119 <age>30</age>
3120 <name>John</name>
3121</root>`
3122 )
3123 })
3124
3125 it("Enum", async () => {
3126 const schema = Schema.Enum({
3127 A: "a",
3128 B: "b"
3129 })
3130 await assertXml(schema, "a", "<root>a</root>")
3131 await assertXml(schema, "b", "<root>b</root>")
3132 })
3133
3134 it("Literals", async () => {
3135 const schema = Schema.Literals(["a", 1, true, 1n])
3136 await assertXml(schema, "a", "<root>a</root>")
3137 await assertXml(schema, 1, "<root>1</root>")
3138 await assertXml(schema, true, "<root>true</root>")
3139 await assertXml(schema, 1n, "<root>1</root>")
3140 })
3141
3142 it("Nested Structures", async () => {
3143 const schema = Schema.Struct({
3144 user: Schema.Struct({
3145 name: Schema.String,
3146 age: Schema.Number
3147 }),
3148 tags: Schema.Array(Schema.String)
3149 })
3150 await assertXml(
3151 schema,
3152 { user: { name: "John", age: 30 }, tags: ["admin", "user"] },
3153 `<root>
3154 <tags>
3155 <item>admin</item>
3156 <item>user</item>
3157 </tags>
3158 <user>
3159 <age>30</age>
3160 <name>John</name>
3161 </user>
3162</root>`
3163 )
3164 })
3165
3166 it("Special Characters in Text", async () => {
3167 await assertXml(Schema.String, "&<>\"'", `<root>&amp;&lt;&gt;"'</root>`)
3168 await assertXml(
3169 Schema.String,
3170 "line1\nline2",
3171 `<root>line1
3172line2</root>`
3173 )
3174 await assertXml(Schema.String, "tab\there", "<root>tab here</root>")
3175 })
3176
3177 it("Special Characters in Attributes", async () => {
3178 await assertXml(
3179 Schema.String.annotate({ title: "test&value" }),
3180 "content",
3181 `<test_value data-name="test&amp;value">content</test_value>`
3182 )
3183 await assertXml(
3184 Schema.String.annotate({ title: "test<value>" }),
3185 "content",
3186 `<test_value_ data-name="test&lt;value&gt;">content</test_value_>`
3187 )
3188 await assertXml(
3189 Schema.String.annotate({ title: "test\"value" }),
3190 "content",
3191 `<test_value data-name="test&quot;value">content</test_value>`
3192 )
3193 })
3194
3195 it("XML Reserved Names", async () => {
3196 await assertXml(
3197 Schema.String.annotate({ title: "xml" }),
3198 "content",
3199 `<_xml data-name="xml">content</_xml>`
3200 )
3201 await assertXml(
3202 Schema.String.annotate({ title: "XML" }),
3203 "content",
3204 `<_XML data-name="XML">content</_XML>`
3205 )
3206 await assertXml(
3207 Schema.String.annotate({ title: "xmlns" }),
3208 "content",
3209 `<_xmlns data-name="xmlns">content</_xmlns>`
3210 )
3211 })
3212
3213 it("Invalid XML Tag Names", async () => {
3214 await assertXml(
3215 Schema.String.annotate({ title: "123invalid" }),
3216 "content",
3217 `<_123invalid data-name="123invalid">content</_123invalid>`
3218 )
3219 await assertXml(
3220 Schema.String.annotate({ title: "invalid name" }),
3221 "content",
3222 `<invalid_name data-name="invalid name">content</invalid_name>`
3223 )
3224 })
3225
3226 it("Empty String", async () => {
3227 await assertXml(Schema.String, "", "<root></root>")
3228 })
3229
3230 it("Whitespace Only String", async () => {
3231 await assertXml(Schema.String, " ", "<root> </root>")
3232 await assertXml(Schema.String, "\n\t", "<root>\n\t</root>")
3233 })
3234
3235 it("Unicode Characters", async () => {
3236 await assertXml(Schema.String, "Hello δΈ–η•Œ", "<root>Hello δΈ–η•Œ</root>")
3237 await assertXml(Schema.String, "πŸš€πŸŒŸβœ¨", "<root>πŸš€πŸŒŸβœ¨</root>")
3238 await assertXml(Schema.String, "αβγδε", "<root>αβγδε</root>")
3239 })
3240
3241 it("XML Encoder Options - rootName", async () => {
3242 const serializer = Schema.toEncoderXml(Schema.toCodecStringTree(Schema.String), {
3243 rootName: "custom"
3244 })
3245 strictEqual(await Effect.runPromise(serializer("test")), "<custom>test</custom>")
3246 })
3247
3248 it("XML Encoder Options - pretty: false", async () => {
3249 const serializer = Schema.toEncoderXml(
3250 Schema.toCodecStringTree(Schema.Struct({
3251 a: Schema.Number,
3252 b: Schema.String
3253 })),
3254 {
3255 pretty: false
3256 }
3257 )
3258 strictEqual(await Effect.runPromise(serializer({ a: 1, b: "test" })), "<root><a>1</a><b>test</b></root>")
3259 })
3260
3261 it("XML Encoder Options - custom indent", async () => {
3262 const serializer = Schema.toEncoderXml(
3263 Schema.toCodecStringTree(Schema.Struct({
3264 a: Schema.Number
3265 })),
3266 {
3267 indent: " "
3268 }
3269 )
3270 strictEqual(
3271 await Effect.runPromise(serializer({ a: 1 })),
3272 `<root>
3273 <a>1</a>
3274</root>`
3275 )
3276 })
3277
3278 it("XML Encoder Options - sortKeys: false", async () => {
3279 const serializer = Schema.toEncoderXml(
3280 Schema.toCodecStringTree(Schema.Struct({
3281 z: Schema.Number,
3282 a: Schema.Number,
3283 m: Schema.Number
3284 })),
3285 {
3286 sortKeys: false
3287 }
3288 )
3289 strictEqual(
3290 await Effect.runPromise(serializer({ z: 3, a: 1, m: 2 })),
3291 `<root>
3292 <z>3</z>
3293 <a>1</a>
3294 <m>2</m>
3295</root>`
3296 )
3297 })
3298
3299 it("Circular Reference Detection", async () => {
3300 const obj: any = { name: "test" }
3301 obj.self = obj
3302
3303 const serializer = Schema.toEncoderXml(Schema.toCodecStringTree(Schema.Any))
3304 try {
3305 await Effect.runPromise(serializer(obj))
3306 throw new Error("Expected error")
3307 } catch (error: any) {
3308 strictEqual(error.message, "Cycle detected while serializing to XML.")
3309 }
3310 })
3311
3312 it("Nested Arrays", async () => {
3313 const schema = Schema.Array(Schema.Array(Schema.Number))
3314 await assertXml(
3315 schema,
3316 [[1, 2], [3, 4]],
3317 `<root>
3318 <item>
3319 <item>1</item>
3320 <item>2</item>
3321 </item>
3322 <item>
3323 <item>3</item>
3324 <item>4</item>
3325 </item>
3326</root>`
3327 )
3328 })
3329
3330 it("Record with Number Keys", async () => {
3331 const schema = Schema.Record(Schema.Number, Schema.String)
3332 await assertXml(
3333 schema,
3334 { 1: "one", 2: "two" },
3335 `<root>
3336 <_1 data-name="1">one</_1>
3337 <_2 data-name="2">two</_2>
3338</root>`
3339 )
3340 })
3341
3342 it("Record with Symbol Keys", async () => {
3343 const sym1 = Symbol.for("key1")
3344 const sym2 = Symbol.for("key2")
3345 const schema = Schema.Record(Schema.Symbol, Schema.String)
3346 await assertXml(
3347 schema,
3348 { [sym1]: "value1", [sym2]: "value2" },
3349 `<root>
3350 <Symbol_key1_ data-name="Symbol(key1)">value1</Symbol_key1_>
3351 <Symbol_key2_ data-name="Symbol(key2)">value2</Symbol_key2_>
3352</root>`
3353 )
3354 })
3355
3356 it("Tuple with Rest", async () => {
3357 const schema = Schema.TupleWithRest(Schema.Tuple([Schema.String, Schema.Number]), [Schema.Boolean])
3358 await assertXml(
3359 schema,
3360 ["test", 42, true, false],
3361 `<root>
3362 <item>test</item>
3363 <item>42</item>
3364 <item>true</item>
3365 <item>false</item>
3366</root>`
3367 )
3368 })
3369
3370 it("Suspend (Recursive Types)", async () => {
3371 interface Tree {
3372 readonly value: number
3373 readonly children: ReadonlyArray<Tree>
3374 }
3375
3376 const Tree: Schema.Codec<Tree> = Schema.Struct({
3377 value: Schema.Number,
3378 children: Schema.Array(Schema.suspend(() => Tree))
3379 })
3380
3381 const tree: Tree = {
3382 value: 1,
3383 children: [
3384 { value: 2, children: [] },
3385 { value: 3, children: [] }
3386 ]
3387 }
3388
3389 await assertXml(
3390 Tree,
3391 tree,
3392 `<root>
3393 <children>
3394 <item>
3395 <children/>
3396 <value>2</value>
3397 </item>
3398 <item>
3399 <children/>
3400 <value>3</value>
3401 </item>
3402 </children>
3403 <value>1</value>
3404</root>`
3405 )
3406 })
3407 })
3408})