1import { assert, describe, it } from "@effect/vitest"
2import {
3 BigDecimal,
4 Brand,
5 ByteSize,
6 Cause,
7 Chunk,
8 Context,
9 DateTime,
10 Deferred,
11 Duration,
12 Effect,
13 Equal,
14 Exit,
15 Fiber,
16 flow,
17 HashMap,
18 HashSet,
19 Option,
20 Order,
21 pipe,
22 Predicate,
23 Redacted,
24 Result,
25 Schema,
26 SchemaAST,
27 SchemaGetter,
28 SchemaIssue,
29 SchemaParser,
30 SchemaTransformation,
31 String as Str,
32 Struct,
33 Tuple
34} from "effect"
35import { TestSchema } from "effect/testing"
36import { produce } from "immer"
37import { deepStrictEqual, fail, strictEqual } from "node:assert"
38import {
39 assertExitSuccess,
40 assertFalse,
41 assertInclude,
42 assertSchemaIssueError,
43 assertTrue,
44 throws
45} from "../utils/assert.ts"
46
47const verifyGeneration = true
48
49const equals = TestSchema.Asserts.ast.fields.equals
50const formatIssue = SchemaIssue.makeFormatterDefault()
51
52const SnakeToCamel = Schema.String.pipe(
53 Schema.decode(
54 SchemaTransformation.snakeToCamel()
55 )
56)
57
58describe("Schema", () => {
59 it("keeps synchronous decode and encode effects eager", () => {
60 // A synchronous parser already produces an `Exit`, so the adapter must hand
61 // it back as-is instead of wrapping it in something a fiber has to run.
62 const eagerExit = <A>(effect: Effect.Effect<A, Schema.SchemaError>): Exit.Exit<A, Schema.SchemaError> => {
63 assertTrue(Exit.isExit(effect), "expected the adapter to return an Exit")
64 return effect as Exit.Exit<A, Schema.SchemaError>
65 }
66 const schemaError = <A>(exit: Exit.Exit<A, Schema.SchemaError>) =>
67 Exit.isFailure(exit) ? Cause.findErrorOption(exit.cause) : Option.none()
68
69 assertExitSuccess(eagerExit(Schema.decodeUnknownEffect(Schema.String)("a")), "a")
70 assertExitSuccess(eagerExit(Schema.encodeUnknownEffect(Schema.String)("a")), "a")
71
72 for (
73 const effect of [
74 Schema.decodeUnknownEffect(Schema.String)(null),
75 Schema.encodeUnknownEffect(Schema.String)(null)
76 ]
77 ) {
78 const error = schemaError(eagerExit(effect))
79 assertTrue(Option.isSome(error) && Schema.isSchemaError(error.value))
80 }
81 })
82
83 it("isSchema", () => {
84 class A extends Schema.Class<A>("A")(Schema.Struct({
85 a: Schema.String
86 })) {}
87 class B extends Schema.Opaque<B>()(Schema.Struct({ a: Schema.String })) {}
88 assertTrue(Schema.isSchema(Schema.String))
89 assertTrue(Schema.isSchema(A))
90 assertTrue(Schema.isSchema(B))
91 assertFalse(Schema.isSchema({}))
92 })
93
94 it("toString", () => {
95 const schema = Schema.String
96 const result = Schema.decodeUnknownExit(schema)(null)
97 assertTrue(Exit.isFailure(result))
98 strictEqual(String(result.cause.reasons[0]), "Fail(SchemaError(Expected string))")
99 })
100
101 describe("SchemaError", () => {
102 it("extends Error and exposes the issue", () => {
103 const result = SchemaParser.decodeUnknownResult(Schema.String)(null)
104 assertTrue(Result.isFailure(result))
105 const error = new Schema.SchemaError(result.failure)
106
107 assertTrue(error instanceof Error)
108 assertTrue(Schema.isSchemaError(error))
109 assertFalse(Schema.isSchemaError({ "~effect/Schema/SchemaError": false }))
110 strictEqual(error._tag, "SchemaError")
111 strictEqual(error.name, "SchemaError")
112 strictEqual(error.issue, result.failure)
113 strictEqual(error.message, "Expected string")
114 strictEqual(String(error), "SchemaError(Expected string)")
115 })
116
117 it("does not capture stack frames", () => {
118 const result = SchemaParser.decodeUnknownResult(Schema.String)(null)
119 assertTrue(Result.isFailure(result))
120 const ErrorWithLimit = Error as typeof Error & { stackTraceLimit?: number | undefined }
121 const stackTraceLimit = ErrorWithLimit.stackTraceLimit
122
123 const error = new Schema.SchemaError(result.failure)
124
125 strictEqual(error.stack, "SchemaError: Expected string")
126 strictEqual(ErrorWithLimit.stackTraceLimit, stackTraceLimit)
127 })
128 })
129
130 describe("parse options", () => {
131 it("does not interpret custom parseOptions metadata", async () => {
132 const schema = Schema.Number.check(Schema.isGreaterThan(0), Schema.isInt()).annotate({
133 parseOptions: { errors: "all" }
134 })
135 const asserts = new TestSchema.Asserts(schema)
136
137 const decoding = asserts.decoding()
138 await decoding.fail(
139 -1.2,
140 "Expected a value greater than 0"
141 )
142 })
143
144 it("applies errors to nested schemas without annotation overrides", async () => {
145 const schema = Schema.Struct({
146 a: Schema.String,
147 b: Schema.Struct({
148 c: Schema.String,
149 d: Schema.String
150 }).annotate({ parseOptions: { errors: "first" } })
151 })
152 const asserts = new TestSchema.Asserts(schema)
153
154 const decoding = asserts.decoding({ parseOptions: { errors: "all" } })
155 await decoding.fail(
156 { a: "a", b: {} },
157 `Missing key
158 at ["b"]["c"]
159Missing key
160 at ["b"]["d"]`
161 )
162 })
163
164 it("applies errors with encoding checks", async () => {
165 const schema = Schema.Struct({
166 a: Schema.String,
167 b: Schema.String
168 }).pipe(
169 Schema.flip,
170 Schema.check(Schema.isMaxProperties(1)),
171 Schema.flip
172 )
173 assertTrue(SchemaAST.isObjects(schema.ast))
174 strictEqual(schema.ast.checks, undefined)
175 strictEqual(schema.ast.encodingChecks?.length, 1)
176 const asserts = new TestSchema.Asserts(schema)
177
178 const decoding = asserts.decoding({ parseOptions: { errors: "all" } })
179 await decoding.fail(
180 {},
181 `Missing key
182 at ["a"]
183Missing key
184 at ["b"]`
185 )
186 })
187 it("decoders can receive options when they are created", () => {
188 const schema = Schema.Struct({
189 a: Schema.String
190 })
191 const decode = Schema.decodeUnknownExit(schema, { onExcessProperty: "error" })
192
193 const failure = decode({ a: "a", b: "b" })
194 assertTrue(Exit.isFailure(failure))
195
196 const success = decode({ a: "a", b: "b" }, { onExcessProperty: "ignore" })
197 assertTrue(Exit.isSuccess(success))
198 deepStrictEqual(success.value, { a: "a" })
199 assertTrue(Exit.isFailure(decode({ a: "a", b: "b" })))
200 })
201
202 it("encoders can receive options when they are created", () => {
203 const schema = Schema.Struct({
204 a: Schema.String
205 })
206 const encode = Schema.encodeUnknownExit(schema, { onExcessProperty: "error" })
207
208 const failure = encode({ a: "a", b: "b" })
209 assertTrue(Exit.isFailure(failure))
210
211 const success = encode({ a: "a", b: "b" }, { onExcessProperty: "ignore" })
212 assertTrue(Exit.isSuccess(success))
213 deepStrictEqual(success.value, { a: "a" })
214 assertTrue(Exit.isFailure(encode({ a: "a", b: "b" })))
215 })
216 })
217
218 describe("Literal", () => {
219 it("should throw an error if the literal is not a finite number", () => {
220 throws(
221 // @effect-diagnostics-next-line schemaLiteralNonFinite:off
222 () => Schema.Literal(Infinity),
223 new Error("A numeric literal must be finite, got Infinity")
224 )
225 throws(
226 // @effect-diagnostics-next-line schemaLiteralNonFinite:off
227 () => Schema.Literal(-Infinity),
228 new Error("A numeric literal must be finite, got -Infinity")
229 )
230 throws(
231 // @effect-diagnostics-next-line schemaLiteralNonFinite:off
232 () => Schema.Literal(NaN),
233 new Error("A numeric literal must be finite, got NaN")
234 )
235 })
236
237 it("should expose the literal", () => {
238 const schema = Schema.Literal("a")
239 strictEqual(schema.literal, "a")
240 strictEqual(schema.annotate({}).literal, "a")
241 })
242
243 it(`"a"`, async () => {
244 const schema = Schema.Literal("a")
245 const asserts = new TestSchema.Asserts(schema)
246
247 const make = asserts.make()
248 await make.succeed("a")
249 await make.fail(null, `Expected "a"`)
250
251 const decoding = asserts.decoding()
252 await decoding.succeed("a")
253 await decoding.fail(1, `Expected "a"`)
254
255 const encoding = asserts.encoding()
256 await encoding.succeed("a")
257 await encoding.fail(1, `Expected "a"`)
258 })
259
260 it(`1`, async () => {
261 const schema = Schema.Literal(1)
262 const asserts = new TestSchema.Asserts(schema)
263
264 const make = asserts.make()
265 await make.succeed(1)
266 await make.fail(null, `Expected 1`)
267
268 const decoding = asserts.decoding()
269 await decoding.succeed(1)
270 await decoding.fail("1", `Expected 1`)
271
272 const encoding = asserts.encoding()
273 await encoding.succeed(1)
274 await encoding.fail("1", `Expected 1`)
275 })
276
277 it("accepts either signed zero and preserves the input sign", () => {
278 for (const literal of [0, -0]) {
279 const schema = Schema.Literal(literal)
280 for (const input of [0, -0]) {
281 assertTrue(Object.is(Schema.decodeUnknownSync(schema)(input), input))
282 assertTrue(Object.is(Schema.encodeUnknownSync(schema)(input), input))
283 }
284 }
285 })
286
287 it("transform", async () => {
288 const schema = Schema.Literal(0).transform("a")
289 const asserts = new TestSchema.Asserts(schema)
290
291 const decoding = asserts.decoding()
292 await decoding.succeed(0, "a")
293 await decoding.fail(1, `Expected 0`)
294
295 const encoding = asserts.encoding()
296 await encoding.succeed("a", 0)
297 await encoding.fail("b", `Expected "a"`)
298 })
299 })
300
301 describe("Literals", () => {
302 it("red, green, blue", async () => {
303 const schema = Schema.Literals(["red", "green", "blue"])
304
305 deepStrictEqual(schema.literals, ["red", "green", "blue"])
306
307 const asserts = new TestSchema.Asserts(schema)
308 const make = asserts.make()
309 await make.succeed("red")
310 await make.succeed("green")
311 await make.succeed("blue")
312 await make.fail("yellow", `Expected "red" | "green" | "blue"`)
313 })
314
315 it("transform", async () => {
316 const schema = Schema.Literals([0, 1]).transform(["a", "b"])
317 const asserts = new TestSchema.Asserts(schema)
318
319 const decoding = asserts.decoding()
320 await decoding.succeed(0, "a")
321 await decoding.succeed(1, "b")
322 await decoding.fail(2, `Expected 0 | 1`)
323
324 const encoding = asserts.encoding()
325 await encoding.succeed("a", 0)
326 await encoding.succeed("b", 1)
327 await encoding.fail("c", `Expected "a" | "b"`)
328 })
329
330 it("pick", () => {
331 const schema = Schema.Literals(["a", "b", "c"]).pick(["a", "b"])
332
333 deepStrictEqual(schema.literals, ["a", "b"])
334 })
335 })
336
337 it("Never", async () => {
338 const schema = Schema.Never
339 const asserts = new TestSchema.Asserts(schema)
340
341 const make = asserts.make()
342 await make.fail(null as never, `Expected never`)
343
344 const decoding = asserts.decoding()
345 await decoding.fail("a", `Expected never`)
346
347 const encoding = asserts.encoding()
348 await encoding.fail("a", `Expected never`)
349 })
350
351 it("Any", async () => {
352 const schema = Schema.Any
353 const asserts = new TestSchema.Asserts(schema)
354
355 const make = asserts.make()
356 await make.succeed("a")
357
358 const decoding = asserts.decoding()
359 await decoding.succeed("a")
360 })
361
362 it("Unknown", async () => {
363 const schema = Schema.Unknown
364 const asserts = new TestSchema.Asserts(schema)
365
366 const make = asserts.make()
367 await make.succeed("a")
368
369 const decoding = asserts.decoding()
370 await decoding.succeed("a")
371 })
372
373 it("Null", async () => {
374 const schema = Schema.Null
375 const asserts = new TestSchema.Asserts(schema)
376
377 const make = asserts.make()
378 await make.succeed(null)
379 await make.fail(undefined, `Expected null`)
380 })
381
382 it("Undefined", async () => {
383 const schema = Schema.Undefined
384 const asserts = new TestSchema.Asserts(schema)
385
386 const make = asserts.make()
387 await make.succeed(undefined)
388 await make.fail(null, `Expected undefined`)
389 })
390
391 it("String", async () => {
392 const schema = Schema.String
393 const asserts = new TestSchema.Asserts(schema)
394
395 const make = asserts.make()
396 await make.succeed("a")
397 await make.fail(null, `Expected string`)
398
399 const decoding = asserts.decoding()
400 await decoding.succeed("a")
401 await decoding.fail(1, `Expected string`)
402
403 const encoding = asserts.encoding()
404 await encoding.succeed("a")
405 await encoding.fail(1, `Expected string`)
406 })
407
408 it("StringForLiteralAutocomplete", async () => {
409 const schema = Schema.StringForLiteralAutocomplete
410 const asserts = new TestSchema.Asserts(schema)
411
412 strictEqual(schema.ast, SchemaAST.string)
413
414 const make = asserts.make()
415 await make.succeed("a")
416 await make.fail(null, `Expected string`)
417
418 const decoding = asserts.decoding()
419 await decoding.succeed("a")
420 await decoding.fail(1, `Expected string`)
421
422 const encoding = asserts.encoding()
423 await encoding.succeed("a")
424 await encoding.fail(1, `Expected string`)
425 })
426
427 it("StringForLiteralAutocomplete | Literals", async () => {
428 const schema = Schema.Union([
429 Schema.StringForLiteralAutocomplete,
430 Schema.Literals(["GET", "POST"])
431 ])
432 const asserts = new TestSchema.Asserts(schema)
433
434 deepStrictEqual(schema.members[1].literals, ["GET", "POST"])
435
436 const make = asserts.make()
437 await make.succeed("GET")
438 await make.succeed("PATCH")
439 await make.fail(null, `Expected string | "GET" | "POST"`)
440
441 const decoding = asserts.decoding()
442 await decoding.succeed("GET")
443 await decoding.succeed("PATCH")
444 await decoding.fail(1, `Expected string | "GET" | "POST"`)
445
446 const encoding = asserts.encoding()
447 await encoding.succeed("GET")
448 await encoding.succeed("PATCH")
449 await encoding.fail(1, `Expected string | "GET" | "POST"`)
450 })
451
452 it("Number", async () => {
453 const schema = Schema.Number
454 const asserts = new TestSchema.Asserts(schema)
455
456 const make = asserts.make()
457 await make.succeed(1)
458 await make.fail(null, `Expected number`)
459
460 const decoding = asserts.decoding()
461 await decoding.succeed(1)
462 await decoding.fail("a", `Expected number`)
463
464 const encoding = asserts.encoding()
465 await encoding.succeed(1)
466 await encoding.fail("a", `Expected number`)
467 })
468
469 it("Boolean", async () => {
470 const schema = Schema.Boolean
471 const asserts = new TestSchema.Asserts(schema)
472
473 const make = asserts.make()
474 await make.succeed(true)
475 await make.succeed(false)
476 await make.fail(null, `Expected boolean`)
477
478 const decoding = asserts.decoding()
479 await decoding.succeed(true)
480 await decoding.succeed(false)
481 await decoding.fail("a", `Expected boolean`)
482
483 const encoding = asserts.encoding()
484 await encoding.succeed(true)
485 await encoding.succeed(false)
486 await encoding.fail("a", `Expected boolean`)
487 })
488
489 it("Symbol", async () => {
490 const schema = Schema.Symbol
491 const asserts = new TestSchema.Asserts(schema)
492
493 const make = asserts.make()
494 await make.succeed(Symbol("a"))
495 await make.fail(null, `Expected symbol`)
496
497 const decoding = asserts.decoding()
498 await decoding.succeed(Symbol("a"))
499 await decoding.fail("a", `Expected symbol`)
500
501 const encoding = asserts.encoding()
502 await encoding.succeed(Symbol("a"))
503 await encoding.fail("a", `Expected symbol`)
504 })
505
506 it("UniqueSymbol", async () => {
507 const a = Symbol("a")
508 const schema = Schema.UniqueSymbol(a)
509 const asserts = new TestSchema.Asserts(schema)
510
511 const make = asserts.make()
512 await make.succeed(a)
513 await make.fail(Symbol("b"), `Expected Symbol(a)`)
514
515 const decoding = asserts.decoding()
516 await decoding.succeed(a)
517 await decoding.fail(Symbol("b"), `Expected Symbol(a)`)
518 })
519
520 it("BigInt", async () => {
521 const schema = Schema.BigInt
522 const asserts = new TestSchema.Asserts(schema)
523
524 const make = asserts.make()
525 await make.succeed(1n)
526 await make.fail(null, `Expected bigint`)
527
528 const decoding = asserts.decoding()
529 await decoding.succeed(1n)
530 await decoding.fail("1", `Expected bigint`)
531
532 const encoding = asserts.encoding()
533 await encoding.succeed(1n)
534 await encoding.fail("1", `Expected bigint`)
535 })
536
537 it("Void", async () => {
538 const schema = Schema.Void
539 const asserts = new TestSchema.Asserts(schema)
540
541 // The public make input stays typed as void; these callbacks exercise runtime parser behavior.
542 let fn: () => void
543
544 const make = asserts.make()
545 await make.succeed()
546 await make.succeed(undefined)
547 fn = () => undefined
548 await make.succeed(fn())
549 fn = () => null
550 await make.succeed(fn(), undefined)
551 fn = () => "a"
552 await make.succeed(fn(), undefined)
553
554 const decoding = asserts.decoding()
555 await decoding.succeed(undefined)
556 await decoding.succeed(null, undefined)
557 await decoding.succeed("a", undefined)
558
559 const encoding = asserts.encoding()
560 await encoding.succeed(undefined)
561 await encoding.succeed(null, undefined)
562 await encoding.succeed("1", undefined)
563 })
564
565 it("ObjectKeyword", async () => {
566 const schema = Schema.ObjectKeyword
567 const asserts = new TestSchema.Asserts(schema)
568
569 const make = asserts.make()
570 await make.succeed({})
571 await make.succeed([])
572 await make.fail(null, `Expected object | array | function`)
573
574 const decoding = asserts.decoding()
575 await decoding.succeed({})
576 await decoding.succeed([])
577 await decoding.fail("1", `Expected object | array | function`)
578
579 const encoding = asserts.encoding()
580 await encoding.succeed({})
581 await encoding.succeed([])
582 await encoding.fail("1", `Expected object | array | function`)
583 })
584
585 it("optionalKey", () => {
586 const schema = Schema.optionalKey(Schema.String)
587 strictEqual(schema.ast.context?.isOptional, true)
588 strictEqual(Schema.optionalKey(Schema.String).ast, schema.ast)
589 strictEqual(Schema.optionalKey(schema).ast, schema.ast)
590 })
591
592 it("optionalKey & mutableKey", () => {
593 const schema = Schema.String.pipe(Schema.optionalKey, Schema.mutableKey)
594 strictEqual(schema.ast.context?.isOptional, true)
595 strictEqual(schema.ast.context?.isMutable, true)
596 })
597
598 it("optional", () => {
599 const schema = Schema.optional(Schema.String)
600 strictEqual(schema.ast.context?.isOptional, true)
601 strictEqual(Schema.optional(Schema.String).ast, schema.ast)
602 const nested = Schema.optional(schema)
603 strictEqual(Schema.required(nested), schema)
604 })
605
606 it("mutableKey", () => {
607 const schema = Schema.mutableKey(Schema.String)
608 strictEqual(schema.ast.context?.isMutable, true)
609 strictEqual(Schema.mutableKey(Schema.String).ast, schema.ast)
610 strictEqual(Schema.mutableKey(schema).ast, schema.ast)
611 })
612
613 it("mutableKey & optionalKey", () => {
614 const schema = Schema.String.pipe(Schema.mutableKey, Schema.optionalKey)
615 strictEqual(schema.ast.context?.isOptional, true)
616 strictEqual(schema.ast.context?.isMutable, true)
617 })
618
619 it("readonlyKey", () => {
620 const schema = Schema.readonlyKey(Schema.mutableKey(Schema.String))
621 strictEqual(schema.ast.context?.isMutable, undefined)
622 })
623
624 describe("Struct", () => {
625 it("allows duplicate property signatures in the low-level AST constructor", () => {
626 const ast = new SchemaAST.Objects(
627 [
628 new SchemaAST.PropertySignature("a", Schema.String.ast),
629 new SchemaAST.PropertySignature("b", Schema.String.ast),
630 new SchemaAST.PropertySignature("a", Schema.Number.ast)
631 ],
632 []
633 )
634 deepStrictEqual(ast.propertySignatures.map((propertySignature) => propertySignature.name), ["a", "b", "a"])
635 })
636
637 describe("onExcessProperty", () => {
638 it("error", async () => {
639 const schema = Schema.Struct({
640 a: Schema.String
641 })
642 const asserts = new TestSchema.Asserts(schema)
643 const decoding = asserts.decoding({ parseOptions: { onExcessProperty: "error" } })
644 await decoding.fail(
645 { a: "a", b: "b" },
646 `Expected no excess property
647 at ["b"]`
648 )
649 const sym = Symbol("sym")
650 await decoding.fail(
651 { a: "a", [sym]: "sym" },
652 `Expected no excess property
653 at [Symbol(sym)]`
654 )
655
656 const decodingAll = asserts.decoding({ parseOptions: { onExcessProperty: "error", errors: "all" } })
657 await decodingAll.fail(
658 { a: "a", b: "b", c: "c" },
659 `Expected no excess property
660 at ["b"]
661Expected no excess property
662 at ["c"]`
663 )
664 })
665
666 it("ignore strips undeclared string and symbol properties", async () => {
667 const schema = Schema.Struct({
668 a: Schema.String
669 })
670 const asserts = new TestSchema.Asserts(schema)
671
672 const decoding = asserts.decoding({ parseOptions: { onExcessProperty: "ignore" } })
673 const sym = Symbol("sym")
674 await decoding.succeed(
675 { a: "a", b: "b", c: "c", [sym]: "sym" },
676 { a: "a" }
677 )
678 })
679
680 it("error ignores non-enumerable undeclared own properties without reading them", async () => {
681 const schema = Schema.Struct({
682 a: Schema.String
683 })
684 const asserts = new TestSchema.Asserts(schema)
685 const sym = Symbol("sym")
686 const input: Record<PropertyKey, unknown> = { a: "a" }
687 for (const key of ["stack", sym]) {
688 Object.defineProperty(input, key, {
689 get() {
690 throw new Error("Non-enumerable excess properties must not be read")
691 },
692 enumerable: false
693 })
694 }
695
696 for (const errors of ["first", "all"] as const) {
697 const parseOptions = { onExcessProperty: "error", errors } as const
698 await asserts.decoding({ parseOptions }).succeed(input, { a: "a" })
699 await asserts.encoding({ parseOptions }).succeed(input, { a: "a" })
700 }
701 })
702
703 it("error validates non-enumerable declared string and symbol properties", async () => {
704 for (const key of ["a", Symbol("a")]) {
705 const schema = Schema.Struct({ [key]: Schema.Number })
706 const input = Object.defineProperty({}, key, { value: 1, enumerable: false })
707 const invalid = Object.defineProperty({}, key, { value: "invalid", enumerable: false })
708 const asserts = new TestSchema.Asserts(schema)
709 const parseOptions = { onExcessProperty: "error" } as const
710 const message = `Expected number\n at [${typeof key === "string" ? JSON.stringify(key) : String(key)}]`
711
712 await asserts.decoding({ parseOptions }).succeed(input, { [key]: 1 })
713 await asserts.encoding({ parseOptions }).succeed(input, { [key]: 1 })
714 await asserts.decoding({ parseOptions }).fail(invalid, message)
715 await asserts.encoding({ parseOptions }).fail(invalid, message)
716 }
717 })
718
719 it("error accepts Error internals in an open record", async () => {
720 const schema = Schema.Record(Schema.String, Schema.Number)
721 const asserts = new TestSchema.Asserts(schema)
722 const decoding = asserts.decoding({ parseOptions: { onExcessProperty: "error" } })
723 await decoding.succeed(new Error("boom"), {})
724 await asserts.encoding({ parseOptions: { onExcessProperty: "error" } }).succeed(new Error("boom"), {})
725 })
726
727 it("error encodes tagged errors carrying runtime internals", async () => {
728 class NotFound extends Schema.TaggedError<NotFound>()("NotFound", { id: Schema.Number }) {}
729 const asserts = new TestSchema.Asserts(NotFound)
730 const encoding = asserts.encoding({ parseOptions: { onExcessProperty: "error" } })
731 await encoding.succeed(new NotFound({ id: 1 }), { _tag: "NotFound", id: 1 })
732 })
733 })
734
735 it("should corectly handle __proto__", async () => {
736 const schema = Schema.Struct({
737 ["__proto__"]: Schema.String
738 })
739 const asserts = new TestSchema.Asserts(schema)
740
741 const decoding = asserts.decoding()
742 await decoding.succeed({ ["__proto__"]: "a" })
743 await decoding.fail(
744 { __proto__: "a" },
745 `Missing key
746 at ["__proto__"]`
747 )
748 })
749
750 it(`{ readonly "a": string }`, async () => {
751 const schema = Schema.Struct({
752 a: Schema.String
753 })
754 const asserts = new TestSchema.Asserts(schema)
755
756 // Should be able to access the fields
757 deepStrictEqual(schema.fields, { a: Schema.String })
758
759 const make = asserts.make()
760 await make.succeed({ a: "a" })
761 await make.fail(null, `Expected object`)
762
763 const decoding = asserts.decoding()
764 await decoding.succeed({ a: "a" })
765 await decoding.fail(
766 {},
767 `Missing key
768 at ["a"]`
769 )
770 await decoding.fail(
771 { a: 1 },
772 `Expected string
773 at ["a"]`
774 )
775
776 const encoding = asserts.encoding()
777 await encoding.succeed({ a: "a" })
778 await encoding.fail(
779 {},
780 `Missing key
781 at ["a"]`
782 )
783 await encoding.fail(
784 { a: 1 },
785 `Expected string
786 at ["a"]`
787 )
788 })
789
790 it(`{ readonly "a": <transformation> }`, async () => {
791 const schema = Schema.Struct({
792 a: Schema.FiniteFromString
793 })
794 const asserts = new TestSchema.Asserts(schema)
795
796 const decoding = asserts.decoding()
797 await decoding.succeed({ a: "1" }, { a: 1 })
798 await decoding.fail(
799 { a: "a" },
800 `Expected a finite number
801 at ["a"]`
802 )
803
804 const encoding = asserts.encoding()
805 await encoding.succeed({ a: 1 }, { a: "1" })
806 await encoding.fail(
807 { a: "a" },
808 `Expected number
809 at ["a"]`
810 )
811 })
812
813 it("should use the identifier annotation as the expected message", async () => {
814 const schema = Schema.Struct({
815 a: Schema.String
816 }).annotate({ identifier: "ID" })
817 const asserts = new TestSchema.Asserts(schema)
818
819 const decoding = asserts.decoding()
820 await decoding.fail(null, `Expected ID`)
821 })
822
823 it(`Schema.optionalKey: { readonly "a"?: string }`, async () => {
824 const schema = Schema.Struct({
825 a: Schema.optionalKey(Schema.String)
826 })
827 const asserts = new TestSchema.Asserts(schema)
828
829 const make = asserts.make()
830 await make.succeed({ a: "a" })
831 await make.succeed({})
832
833 const decoding = asserts.decoding()
834 await decoding.succeed({ a: "a" })
835 await decoding.succeed({})
836 await decoding.fail(
837 { a: 1 },
838 `Expected string
839 at ["a"]`
840 )
841
842 const encoding = asserts.encoding()
843 await encoding.succeed({ a: "a" })
844 await encoding.succeed({})
845 await encoding.fail(
846 { a: 1 },
847 `Expected string
848 at ["a"]`
849 )
850 })
851
852 it(`Schema.optional: { readonly "a"?: string | undefined }`, async () => {
853 const schema = Schema.Struct({
854 a: Schema.optional(Schema.String)
855 })
856 const asserts = new TestSchema.Asserts(schema)
857
858 const make = asserts.make()
859 await make.succeed({ a: "a" })
860 await make.succeed({ a: undefined })
861 await make.succeed({})
862
863 const decoding = asserts.decoding()
864 await decoding.succeed({ a: "a" })
865 await decoding.succeed({ a: undefined })
866 await decoding.succeed({})
867 await decoding.fail(
868 { a: 1 },
869 `Expected string | undefined
870 at ["a"]`
871 )
872
873 const encoding = asserts.encoding()
874 await encoding.succeed({ a: "a" })
875 await encoding.succeed({ a: undefined })
876 await encoding.succeed({})
877 await encoding.fail(
878 { a: 1 },
879 `Expected string | undefined
880 at ["a"]`
881 )
882 })
883
884 it(`{ readonly "a"?: <transformation> }`, async () => {
885 const schema = Schema.Struct({
886 a: Schema.optionalKey(Schema.FiniteFromString)
887 })
888 const asserts = new TestSchema.Asserts(schema)
889
890 const decoding = asserts.decoding()
891 await decoding.succeed({ a: "1" }, { a: 1 })
892 await decoding.succeed({})
893 await decoding.fail(
894 { a: undefined },
895 `Expected string
896 at ["a"]`
897 )
898
899 const encoding = asserts.encoding()
900 await encoding.succeed({ a: 1 }, { a: "1" })
901 await encoding.succeed({})
902 })
903
904 describe("ParseOptions", () => {
905 it(`{ errors: "all" }`, async () => {
906 const schema = Schema.Struct({
907 a: Schema.String,
908 b: Schema.Number
909 })
910 const asserts = new TestSchema.Asserts(schema)
911
912 const make = asserts.make({ parseOptions: { errors: "all" } })
913 await make.fail(
914 {},
915 `Missing key
916 at ["a"]
917Missing key
918 at ["b"]`
919 )
920
921 const decoding = asserts.decoding({ parseOptions: { errors: "all" } })
922 await decoding.fail(
923 {},
924 `Missing key
925 at ["a"]
926Missing key
927 at ["b"]`
928 )
929
930 const encoding = asserts.encoding({ parseOptions: { errors: "all" } })
931 await encoding.fail(
932 {},
933 `Missing key
934 at ["a"]
935Missing key
936 at ["b"]`
937 )
938 })
939 })
940
941 describe("assign", () => {
942 it("Struct", async () => {
943 const from = Schema.Struct({
944 a: Schema.String
945 })
946 const schema = from.mapFields(Struct.assign({ b: Schema.String }))
947 const asserts = new TestSchema.Asserts(schema)
948
949 const decoding = asserts.decoding()
950 await decoding.succeed({ a: "a", b: "b" })
951 await decoding.fail(
952 { b: "b" },
953 `Missing key
954 at ["a"]`
955 )
956 await decoding.fail(
957 { a: "a" },
958 `Missing key
959 at ["b"]`
960 )
961 })
962
963 it("overlapping fields", async () => {
964 const from = Schema.Struct({
965 a: Schema.String,
966 b: Schema.String
967 })
968 const schema = from.mapFields(Struct.assign({ b: Schema.Number, c: Schema.Number }))
969 const asserts = new TestSchema.Asserts(schema)
970
971 const decoding = asserts.decoding()
972 await decoding.succeed({ a: "a", b: 1, c: 2 })
973 await decoding.fail(
974 { a: "a", b: "b" },
975 `Expected number
976 at ["b"]`
977 )
978 })
979
980 it("Struct & check & unsafePreserveChecks: true", async () => {
981 const from = Schema.Struct({
982 a: Schema.String,
983 b: Schema.String
984 }).check(
985 Schema.makeFilter(({ a, b }) => a === b, { expected: "a === b" })
986 )
987 const schema = from.mapFields(Struct.assign({ c: Schema.String }), { unsafePreserveChecks: true })
988 const asserts = new TestSchema.Asserts(schema)
989
990 const decoding = asserts.decoding()
991 await decoding.succeed({ a: "a", b: "a", c: "c" })
992 await decoding.fail(
993 { a: "", b: "b", c: "c" },
994 `Expected a === b`
995 )
996 })
997 })
998
999 describe("pick", () => {
1000 it("Struct", async () => {
1001 const schema = Schema.Struct({
1002 a: Schema.String,
1003 b: Schema.String
1004 }).mapFields(Struct.pick(["a"]))
1005 const asserts = new TestSchema.Asserts(schema)
1006
1007 const decoding = asserts.decoding()
1008 await decoding.succeed({ a: "a" })
1009 })
1010 })
1011
1012 describe("omit", () => {
1013 it("Struct", async () => {
1014 const schema = Schema.Struct({
1015 a: Schema.String,
1016 b: Schema.String
1017 }).mapFields(Struct.omit(["b"]))
1018 const asserts = new TestSchema.Asserts(schema)
1019
1020 const decoding = asserts.decoding()
1021 await decoding.succeed({ a: "a" })
1022 })
1023 })
1024 })
1025
1026 describe("Tuple", () => {
1027 it("A required element cannot follow an optional element", () => {
1028 throws(
1029 () => Schema.Tuple([Schema.optionalKey(Schema.String), Schema.String]),
1030 new Error("A required element cannot follow an optional element. ts(1257)")
1031 )
1032 throws(
1033 () => Schema.Tuple([Schema.optional(Schema.String), Schema.String]),
1034 new Error("A required element cannot follow an optional element. ts(1257)")
1035 )
1036 })
1037
1038 it("should fail on unexpected indexes", async () => {
1039 const schema = Schema.Tuple([Schema.String])
1040 const asserts = new TestSchema.Asserts(schema)
1041
1042 const decoding = asserts.decoding()
1043 await decoding.fail(
1044 ["a", "b"],
1045 `Expected no excess property
1046 at [1]`
1047 )
1048 const decodingAll = asserts.decoding({ parseOptions: { errors: "all" } })
1049 await decodingAll.fail(
1050 ["a", "b", "c"],
1051 `Expected no excess property
1052 at [1]
1053Expected no excess property
1054 at [2]`
1055 )
1056 })
1057
1058 it(`readonly [string]`, async () => {
1059 const schema = Schema.Tuple([Schema.NonEmptyString])
1060 const asserts = new TestSchema.Asserts(schema)
1061
1062 // should be able to access the elements
1063 deepStrictEqual(schema.elements, [Schema.NonEmptyString])
1064
1065 const make = asserts.make()
1066 await make.succeed(["a"])
1067 await make.fail(
1068 [""],
1069 `Expected a value with a length of at least 1
1070 at [0]`
1071 )
1072
1073 const decoding = asserts.decoding()
1074 await decoding.succeed(["a"])
1075 await decoding.fail(null, `Expected array`)
1076 await decoding.fail(
1077 [],
1078 `Missing key
1079 at [0]`
1080 )
1081 await decoding.fail(
1082 [1],
1083 `Expected string
1084 at [0]`
1085 )
1086
1087 const encoding = asserts.encoding()
1088 await encoding.succeed(["a"])
1089 await encoding.fail(
1090 [],
1091 `Missing key
1092 at [0]`
1093 )
1094 await encoding.fail(
1095 [1],
1096 `Expected string
1097 at [0]`
1098 )
1099 })
1100
1101 it(`readonly [string?]`, async () => {
1102 const schema = Schema.Tuple([Schema.String.pipe(Schema.optionalKey)])
1103 const asserts = new TestSchema.Asserts(schema)
1104
1105 const make = asserts.make()
1106 await make.succeed(["a"])
1107 await make.succeed([])
1108
1109 const decoding = asserts.decoding()
1110 await decoding.succeed(["a"])
1111 await decoding.succeed([])
1112
1113 const encoding = asserts.encoding()
1114 await encoding.succeed(["a"])
1115 await encoding.succeed([])
1116 })
1117 })
1118
1119 describe("Array", () => {
1120 it("should expose the element schema via .value", () => {
1121 const schema = Schema.Array(Schema.String)
1122 strictEqual(schema.value, Schema.String)
1123 strictEqual(schema.annotate({}).value, Schema.String)
1124 })
1125
1126 it("readonly string[]", async () => {
1127 const schema = Schema.Array(Schema.String)
1128 const asserts = new TestSchema.Asserts(schema)
1129
1130 const make = asserts.make()
1131 await make.succeed(["a", "b"])
1132
1133 const decoding = asserts.decoding()
1134 await decoding.succeed(["a", "b"])
1135 await decoding.fail(
1136 ["a", 1],
1137 `Expected string
1138 at [1]`
1139 )
1140
1141 const encoding = asserts.encoding()
1142 await encoding.succeed(["a", "b"])
1143 await encoding.fail(
1144 ["a", 1],
1145 `Expected string
1146 at [1]`
1147 )
1148 })
1149 })
1150
1151 describe("mutable", () => {
1152 it("supports encodings on array elements", async () => {
1153 const schema = Schema.mutable(Schema.Array(Schema.FiniteFromString))
1154 const asserts = new TestSchema.Asserts(schema)
1155
1156 await asserts.decoding().succeed(["1"], [1])
1157 await asserts.encoding().succeed([1], ["1"])
1158 })
1159
1160 it("supports withDecodingDefaultType after mutable", async () => {
1161 const schema = Schema.Struct({
1162 value: Schema.Array(Schema.String).pipe(
1163 Schema.mutable,
1164 Schema.withDecodingDefaultType(Effect.succeed(["default"]))
1165 )
1166 })
1167 const asserts = new TestSchema.Asserts(schema)
1168
1169 await asserts.decoding().succeed({}, { value: ["default"] })
1170 await asserts.decoding().succeed({ value: undefined }, { value: ["default"] })
1171 await asserts.decoding().succeed({ value: ["a"] })
1172 await asserts.encoding().succeed({ value: ["a"] })
1173 })
1174
1175 it("does not support withDecodingDefaultType before mutable", () => {
1176 throws(
1177 () =>
1178 Schema.Struct({
1179 value: Schema.Array(Schema.String).pipe(
1180 Schema.withDecodingDefaultType(Effect.succeed(["default"])),
1181 Schema.mutable
1182 )
1183 }),
1184 new Error("mutable does not support encodings")
1185 )
1186 })
1187
1188 it("preserves annotations, checks, encoding checks, and context", async () => {
1189 const schema = Schema.Array(Schema.String).annotate({ identifier: "A" }).check(
1190 Schema.isMinLength(1)
1191 ).pipe(
1192 Schema.flip,
1193 Schema.check(Schema.isMaxLength(1)),
1194 Schema.flip,
1195 Schema.optionalKey
1196 )
1197 const mutable = Schema.mutable(schema)
1198
1199 strictEqual(mutable.ast.annotations, schema.ast.annotations)
1200 strictEqual(mutable.ast.checks, schema.ast.checks)
1201 strictEqual(mutable.ast.encodingChecks, schema.ast.encodingChecks)
1202 strictEqual(mutable.ast.context, schema.ast.context)
1203
1204 const asserts = new TestSchema.Asserts(mutable)
1205 await asserts.decoding().fail([], "Expected a value with a length of at least 1")
1206 await asserts.encoding().fail(["a", "b"], "Expected a value with a length of at most 1")
1207 })
1208 })
1209
1210 it("ArrayEnsure", async () => {
1211 const schema = Schema.ArrayEnsure(Schema.FiniteFromString)
1212 const asserts = new TestSchema.Asserts(schema)
1213
1214 const decoding = asserts.decoding()
1215 await decoding.succeed("1", [1])
1216 await decoding.succeed(["1", "2"], [1, 2])
1217 await decoding.succeed([], [])
1218 await decoding.fail(null, `Expected string | array`)
1219 await decoding.fail("a", `Expected a finite number`)
1220 await decoding.fail(
1221 ["a"],
1222 `Expected a finite number
1223 at [0]`
1224 )
1225
1226 const encoding = asserts.encoding()
1227 await encoding.succeed([], [])
1228 await encoding.succeed([1], "1")
1229 await encoding.succeed([1, 2], ["1", "2"])
1230 })
1231
1232 it("ArrayEnsure roundtrips array-valued elements", () => {
1233 const schema = Schema.ArrayEnsure(Schema.Tuple([Schema.Number, Schema.Number]))
1234 const encoded = Schema.encodeSync(schema)([[1, 2]])
1235 deepStrictEqual(encoded, [1, 2])
1236 deepStrictEqual(Schema.decodeSync(schema)(encoded), [[1, 2]])
1237 })
1238
1239 it("ArrayEnsure preserves outer-array encoding", () => {
1240 const schema = Schema.ArrayEnsure(Schema.fromJsonString(Schema.Unknown))
1241 deepStrictEqual(Schema.encodeSync(schema)([1, 2]), ["1", "2"])
1242 })
1243
1244 describe("NonEmptyArray", () => {
1245 it("should expose the element schema via .value", () => {
1246 const schema = Schema.NonEmptyArray(Schema.String)
1247 strictEqual(schema.value, Schema.String)
1248 })
1249
1250 it("readonly string[]", async () => {
1251 const schema = Schema.NonEmptyArray(Schema.String)
1252 const asserts = new TestSchema.Asserts(schema)
1253
1254 const make = asserts.make()
1255 await make.succeed(["a"])
1256 await make.succeed(["a", "b"])
1257
1258 const decoding = asserts.decoding()
1259 await decoding.succeed(["a"])
1260 await decoding.succeed(["a", "b"])
1261 await decoding.fail(
1262 [],
1263 `Missing key
1264 at [0]`
1265 )
1266 await decoding.fail(
1267 ["a", 1],
1268 `Expected string
1269 at [1]`
1270 )
1271
1272 const encoding = asserts.encoding()
1273 await encoding.succeed(["a"])
1274 await encoding.succeed(["a", "b"])
1275 await encoding.fail(
1276 [],
1277 `Missing key
1278 at [0]`
1279 )
1280 await encoding.fail(
1281 ["a", 1],
1282 `Expected string
1283 at [1]`
1284 )
1285 })
1286 })
1287
1288 it("Trimmed", async () => {
1289 const schema = Schema.Trimmed
1290 const asserts = new TestSchema.Asserts(schema)
1291
1292 if (verifyGeneration) {
1293 asserts.arbitrary().verifyGeneration()
1294 }
1295
1296 const decoding = asserts.decoding()
1297 await decoding.succeed("a")
1298 await decoding.fail(
1299 " a ",
1300 `Expected a string with no leading or trailing whitespace`
1301 )
1302 })
1303
1304 describe("Checks", () => {
1305 describe("check", () => {
1306 it("single check", async () => {
1307 const schema = Schema.String.check(Schema.isMinLength(3))
1308 const asserts = new TestSchema.Asserts(schema)
1309
1310 const decoding = asserts.decoding()
1311 await decoding.succeed("abc")
1312 await decoding.fail(
1313 "ab",
1314 `Expected a value with a length of at least 3`
1315 )
1316 })
1317
1318 it("multiple checks", async () => {
1319 const schema = Schema.String.check(
1320 Schema.isMinLength(3),
1321 Schema.isIncluding("c")
1322 )
1323 const asserts = new TestSchema.Asserts(schema)
1324
1325 const decoding = asserts.decoding()
1326 await decoding.succeed("abc")
1327 await decoding.fail(
1328 "ab",
1329 `Expected a value with a length of at least 3`
1330 )
1331 const decodingAll = asserts.decoding({ parseOptions: { errors: "all" } })
1332 await decodingAll.fail(
1333 "ab",
1334 `Expected a value with a length of at least 3
1335Expected a string including "c"`
1336 )
1337 })
1338
1339 it("aborting checks", async () => {
1340 const schema = Schema.String.check(
1341 Schema.isMinLength(2).abort(),
1342 Schema.isIncluding("b")
1343 )
1344 const asserts = new TestSchema.Asserts(schema)
1345
1346 const decoding = asserts.decoding()
1347 await decoding.fail(
1348 "a",
1349 `Expected a value with a length of at least 2`
1350 )
1351 })
1352
1353 it("makeFilterGroup", async () => {
1354 const usernameGroup = Schema.makeFilterGroup(
1355 [
1356 Schema.isMinLength(3),
1357 Schema.isPattern(/^[a-zA-Z0-9]+$/, {
1358 title: "alphanumeric",
1359 description: "must contain only letters and numbers"
1360 }),
1361 Schema.isTrimmed()
1362 ],
1363 {
1364 title: "username",
1365 description: "a valid username"
1366 }
1367 )
1368
1369 const Username = Schema.String.check(usernameGroup)
1370 const asserts = new TestSchema.Asserts(Username)
1371
1372 const decoding = asserts.decoding()
1373 await decoding.succeed("abc")
1374 await decoding.fail(
1375 "",
1376 `Expected a value with a length of at least 3`
1377 )
1378 })
1379
1380 it("object-level checks are validated against the decoded value when encoding", async () => {
1381 const schema = Schema.Struct({ a: Schema.FiniteFromString }).check(
1382 Schema.makeFilter((o) => typeof o.a === "number", { expected: "a is a number" })
1383 )
1384 const asserts = new TestSchema.Asserts(schema)
1385
1386 const decoding = asserts.decoding()
1387 await decoding.succeed({ a: "1" }, { a: 1 })
1388
1389 const encoding = asserts.encoding()
1390 await encoding.succeed({ a: 1 }, { a: "1" })
1391 })
1392
1393 it("suspended checks are not supported", () => {
1394 throws(
1395 () => {
1396 Schema.suspend(() => Schema.Struct({ a: Schema.FiniteFromString })).check(
1397 Schema.makeFilter((o) => typeof o.a === "number", { expected: "a is a number" })
1398 )
1399 },
1400 "Cannot add checks to Suspend"
1401 )
1402 })
1403 })
1404
1405 it("refine", async () => {
1406 const schema = Schema.Option(Schema.String).pipe(
1407 Schema.refine(Option.isSome, { expected: "isSome" }),
1408 Schema.check(
1409 Schema.makeFilter(({ value }) => value.length > 0, { expected: "length > 0" })
1410 )
1411 )
1412 const asserts = new TestSchema.Asserts(schema)
1413
1414 const decoding = asserts.decoding()
1415 await decoding.succeed(Option.some("a"))
1416 await decoding.fail(
1417 Option.some(""),
1418 `Expected length > 0`
1419 )
1420 await decoding.fail(
1421 Option.none(),
1422 `Expected isSome`
1423 )
1424 })
1425
1426 describe("String checks", () => {
1427 it("isPattern", async () => {
1428 const schema = Schema.String.check(Schema.isPattern(/^a/))
1429 const asserts = new TestSchema.Asserts(schema)
1430
1431 const decoding = asserts.decoding()
1432 await decoding.succeed("a")
1433 await decoding.fail(
1434 "b",
1435 `Expected a string matching the RegExp ^a`
1436 )
1437
1438 const encoding = asserts.encoding()
1439 await encoding.succeed("a")
1440 await encoding.fail(
1441 "b",
1442 `Expected a string matching the RegExp ^a`
1443 )
1444 })
1445
1446 it("isPattern with stateful RegExp flags", async () => {
1447 for (const regExp of [/^a/g, /^a/y]) {
1448 const schema = Schema.String.check(Schema.isPattern(regExp))
1449 const decoding = new TestSchema.Asserts(schema).decoding()
1450
1451 await decoding.succeed("a")
1452 await decoding.succeed("a")
1453 strictEqual(regExp.lastIndex, 0)
1454 }
1455 })
1456
1457 it("isStartingWith", async () => {
1458 const schema = Schema.String.check(Schema.isStartingWith("a"))
1459 const asserts = new TestSchema.Asserts(schema)
1460
1461 const decoding = asserts.decoding()
1462 await decoding.succeed("a")
1463 await decoding.fail(
1464 "b",
1465 `Expected a string starting with "a"`
1466 )
1467
1468 const encoding = asserts.encoding()
1469 await encoding.succeed("a")
1470 await encoding.fail(
1471 "b",
1472 `Expected a string starting with "a"`
1473 )
1474 })
1475
1476 it("isEndingWith", async () => {
1477 const schema = Schema.String.check(Schema.isEndingWith("a"))
1478 const asserts = new TestSchema.Asserts(schema)
1479
1480 const decoding = asserts.decoding()
1481 await decoding.succeed("a")
1482 await decoding.fail(
1483 "b",
1484 `Expected a string ending with "a"`
1485 )
1486
1487 const encoding = asserts.encoding()
1488 await encoding.succeed("a")
1489 await encoding.fail(
1490 "b",
1491 `Expected a string ending with "a"`
1492 )
1493 })
1494
1495 it("isLowercased", async () => {
1496 const schema = Schema.String.check(Schema.isLowercased())
1497 const asserts = new TestSchema.Asserts(schema)
1498
1499 const decoding = asserts.decoding()
1500 await decoding.succeed("a")
1501 await decoding.fail(
1502 "A",
1503 `Expected a string with all characters in lowercase`
1504 )
1505
1506 const encoding = asserts.encoding()
1507 await encoding.succeed("a")
1508 await encoding.fail(
1509 "A",
1510 `Expected a string with all characters in lowercase`
1511 )
1512 })
1513
1514 it("isUppercased", async () => {
1515 const schema = Schema.String.check(Schema.isUppercased())
1516 const asserts = new TestSchema.Asserts(schema)
1517
1518 const decoding = asserts.decoding()
1519 await decoding.succeed("A")
1520 await decoding.fail(
1521 "a",
1522 `Expected a string with all characters in uppercase`
1523 )
1524
1525 const encoding = asserts.encoding()
1526 await encoding.succeed("A")
1527 await encoding.fail(
1528 "a",
1529 `Expected a string with all characters in uppercase`
1530 )
1531 })
1532
1533 it("isCapitalized", async () => {
1534 const schema = Schema.String.check(Schema.isCapitalized())
1535 const asserts = new TestSchema.Asserts(schema)
1536
1537 const decoding = asserts.decoding()
1538 await decoding.succeed("Abc")
1539 await decoding.fail(
1540 "abc",
1541 `Expected a string with the first character in uppercase`
1542 )
1543
1544 const encoding = asserts.encoding()
1545 await encoding.succeed("Abc")
1546 await encoding.fail(
1547 "abc",
1548 `Expected a string with the first character in uppercase`
1549 )
1550 })
1551
1552 it("isUncapitalized", async () => {
1553 const schema = Schema.String.check(Schema.isUncapitalized())
1554 const asserts = new TestSchema.Asserts(schema)
1555
1556 const decoding = asserts.decoding()
1557 await decoding.succeed("aBC")
1558 await decoding.fail(
1559 "ABC",
1560 `Expected a string with the first character in lowercase`
1561 )
1562
1563 const encoding = asserts.encoding()
1564 await encoding.succeed("aBC")
1565 await encoding.fail(
1566 "ABC",
1567 `Expected a string with the first character in lowercase`
1568 )
1569 })
1570
1571 it("isMinLength", async () => {
1572 const schema = Schema.String.check(Schema.isMinLength(1))
1573 const asserts = new TestSchema.Asserts(schema)
1574
1575 const decoding = asserts.decoding()
1576 await decoding.succeed("a")
1577 await decoding.fail(
1578 "",
1579 `Expected a value with a length of at least 1`
1580 )
1581
1582 const encoding = asserts.encoding()
1583 await encoding.succeed("a")
1584 await encoding.fail(
1585 "",
1586 `Expected a value with a length of at least 1`
1587 )
1588 })
1589 })
1590
1591 describe("Number checks", () => {
1592 it("isGreaterThan", async () => {
1593 const schema = Schema.Number.check(Schema.isGreaterThan(1))
1594 const asserts = new TestSchema.Asserts(schema)
1595
1596 const decoding = asserts.decoding()
1597 await decoding.succeed(2)
1598 await decoding.fail(
1599 1,
1600 `Expected a value greater than 1`
1601 )
1602
1603 const encoding = asserts.encoding()
1604 await encoding.succeed(2)
1605 await encoding.fail(
1606 1,
1607 `Expected a value greater than 1`
1608 )
1609 })
1610
1611 it("isGreaterThanOrEqualTo", async () => {
1612 const schema = Schema.Number.check(Schema.isGreaterThanOrEqualTo(1))
1613 const asserts = new TestSchema.Asserts(schema)
1614
1615 const decoding = asserts.decoding()
1616 await decoding.succeed(1)
1617 await decoding.fail(
1618 0,
1619 `Expected a value greater than or equal to 1`
1620 )
1621 })
1622
1623 it("isLessThan", async () => {
1624 const schema = Schema.Number.check(Schema.isLessThan(1))
1625 const asserts = new TestSchema.Asserts(schema)
1626
1627 const decoding = asserts.decoding()
1628 await decoding.succeed(0)
1629 await decoding.fail(
1630 1,
1631 `Expected a value less than 1`
1632 )
1633 })
1634
1635 it("isLessThanOrEqualTo", async () => {
1636 const schema = Schema.Number.check(Schema.isLessThanOrEqualTo(1))
1637 const asserts = new TestSchema.Asserts(schema)
1638
1639 const decoding = asserts.decoding()
1640 await decoding.succeed(1)
1641 await decoding.fail(
1642 2,
1643 `Expected a value less than or equal to 1`
1644 )
1645 })
1646
1647 it("isMultipleOf", async () => {
1648 const schema = Schema.Number.check(Schema.isMultipleOf(2))
1649 const asserts = new TestSchema.Asserts(schema)
1650
1651 const decoding = asserts.decoding()
1652 await decoding.succeed(4)
1653 await decoding.fail(
1654 3,
1655 `Expected a value that is a multiple of 2`
1656 )
1657 })
1658
1659 it("isMultipleOf rejects nonzero subnormal remainders", () => {
1660 const is = Schema.is(Schema.Number.check(Schema.isMultipleOf(Number("1e-323"))))
1661
1662 assertFalse(is(Number("1.042e-321")))
1663 })
1664
1665 it("isMultipleOf normalizes negative divisors", async () => {
1666 const schema = Schema.Number.check(Schema.isMultipleOf(-2))
1667 const asserts = new TestSchema.Asserts(schema)
1668
1669 const decoding = asserts.decoding()
1670 await decoding.succeed(4)
1671 await decoding.fail(
1672 3,
1673 `Expected a value that is a multiple of 2`
1674 )
1675 })
1676
1677 it("isMultipleOf rejects invalid divisors", () => {
1678 for (const divisor of [0, -0, NaN, Infinity, -Infinity]) {
1679 throws(
1680 () => Schema.isMultipleOf(divisor),
1681 new RangeError(`Expected a finite non-zero number, got ${String(divisor)}`)
1682 )
1683 }
1684 })
1685
1686 describe("isBetween", () => {
1687 it("included & included", async () => {
1688 const schema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 3 }))
1689 const asserts = new TestSchema.Asserts(schema)
1690
1691 const decoding = asserts.decoding()
1692 await decoding.succeed(1)
1693 await decoding.succeed(3)
1694 await decoding.fail(
1695 0,
1696 `Expected a value between 1 and 3`
1697 )
1698 await decoding.fail(
1699 4,
1700 `Expected a value between 1 and 3`
1701 )
1702
1703 const encoding = asserts.encoding()
1704 await encoding.succeed(1)
1705 await encoding.succeed(3)
1706 await encoding.fail(
1707 0,
1708 `Expected a value between 1 and 3`
1709 )
1710 })
1711
1712 it("included & excluded", async () => {
1713 const schema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 3, exclusiveMaximum: true }))
1714 const asserts = new TestSchema.Asserts(schema)
1715
1716 const decoding = asserts.decoding()
1717 await decoding.succeed(1)
1718 await decoding.fail(3, `Expected a value between 1 and 3 (excluded)`)
1719 await decoding.fail(
1720 0,
1721 `Expected a value between 1 and 3 (excluded)`
1722 )
1723 await decoding.fail(
1724 4,
1725 `Expected a value between 1 and 3 (excluded)`
1726 )
1727
1728 const encoding = asserts.encoding()
1729 await encoding.succeed(1)
1730 await encoding.fail(3, `Expected a value between 1 and 3 (excluded)`)
1731 await encoding.fail(
1732 0,
1733 `Expected a value between 1 and 3 (excluded)`
1734 )
1735 })
1736
1737 it("excluded & included", async () => {
1738 const schema = Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 3, exclusiveMinimum: true }))
1739 const asserts = new TestSchema.Asserts(schema)
1740
1741 const decoding = asserts.decoding()
1742 await decoding.fail(1, `Expected a value between 1 (excluded) and 3`)
1743 await decoding.succeed(3)
1744 await decoding.fail(
1745 0,
1746 `Expected a value between 1 (excluded) and 3`
1747 )
1748 await decoding.fail(
1749 4,
1750 `Expected a value between 1 (excluded) and 3`
1751 )
1752
1753 const encoding = asserts.encoding()
1754 await encoding.fail(1, `Expected a value between 1 (excluded) and 3`)
1755 await encoding.succeed(3)
1756 await encoding.fail(
1757 0,
1758 `Expected a value between 1 (excluded) and 3`
1759 )
1760 })
1761
1762 it("excluded & excluded", async () => {
1763 const schema = Schema.Int.check(
1764 Schema.isBetween({ minimum: 1, maximum: 3, exclusiveMinimum: true, exclusiveMaximum: true })
1765 )
1766 const asserts = new TestSchema.Asserts(schema)
1767
1768 const decoding = asserts.decoding()
1769 await decoding.succeed(2)
1770 await decoding.fail(1, `Expected a value between 1 (excluded) and 3 (excluded)`)
1771 await decoding.fail(3, `Expected a value between 1 (excluded) and 3 (excluded)`)
1772 await decoding.fail(
1773 0,
1774 `Expected a value between 1 (excluded) and 3 (excluded)`
1775 )
1776 await decoding.fail(
1777 4,
1778 `Expected a value between 1 (excluded) and 3 (excluded)`
1779 )
1780
1781 const encoding = asserts.encoding()
1782 await encoding.succeed(2)
1783 await encoding.fail(1, `Expected a value between 1 (excluded) and 3 (excluded)`)
1784 await encoding.fail(3, `Expected a value between 1 (excluded) and 3 (excluded)`)
1785 await encoding.fail(
1786 0,
1787 `Expected a value between 1 (excluded) and 3 (excluded)`
1788 )
1789 })
1790 })
1791
1792 it("isInt", async () => {
1793 const schema = Schema.Number.check(Schema.isInt())
1794 const asserts = new TestSchema.Asserts(schema)
1795
1796 const decoding = asserts.decoding()
1797 await decoding.succeed(1)
1798 await decoding.fail(
1799 1.1,
1800 `Expected an integer`
1801 )
1802
1803 const encoding = asserts.encoding()
1804 await encoding.succeed(1)
1805 await encoding.fail(
1806 1.1,
1807 `Expected an integer`
1808 )
1809 await decoding.fail(
1810 NaN,
1811 `Expected an integer`
1812 )
1813 await decoding.fail(
1814 Infinity,
1815 `Expected an integer`
1816 )
1817 await decoding.fail(
1818 -Infinity,
1819 `Expected an integer`
1820 )
1821 })
1822
1823 it("isInt32", async () => {
1824 const schema = Schema.Number.check(Schema.isInt32())
1825 const asserts = new TestSchema.Asserts(schema)
1826
1827 const decoding = asserts.decoding()
1828 await decoding.succeed(1)
1829 await decoding.fail(
1830 1.1,
1831 `Expected an integer`
1832 )
1833 await decoding.fail(
1834 Number.MAX_SAFE_INTEGER + 1,
1835 `Expected an integer`
1836 )
1837 await decoding.fail(
1838 1.1,
1839 `Expected an integer`
1840 )
1841 await decoding.fail(
1842 Number.MIN_SAFE_INTEGER - 1,
1843 `Expected an integer`
1844 )
1845 const decodingAll = asserts.decoding({ parseOptions: { errors: "all" } })
1846 await decodingAll.fail(
1847 Number.MAX_SAFE_INTEGER + 1,
1848 `Expected an integer
1849Expected a value between -2147483648 and 2147483647`
1850 )
1851
1852 const encoding = asserts.encoding()
1853 await encoding.succeed(1)
1854 await encoding.fail(
1855 1.1,
1856 `Expected an integer`
1857 )
1858 await encoding.fail(
1859 Number.MAX_SAFE_INTEGER + 1,
1860 `Expected an integer`
1861 )
1862 })
1863 })
1864
1865 describe("BigInt Checks", () => {
1866 const options = { order: Order.BigInt, format: (value: bigint) => `${value}n` }
1867
1868 const isBetween = Schema.makeIsBetween(options)
1869 const isGreaterThan = Schema.makeIsGreaterThan(options)
1870 const isGreaterThanOrEqualTo = Schema.makeIsGreaterThanOrEqualTo(options)
1871 const isLessThan = Schema.makeIsLessThan(options)
1872 const isLessThanOrEqualTo = Schema.makeIsLessThanOrEqualTo(options)
1873
1874 it("isBetween", async () => {
1875 const schema = Schema.BigInt.check(isBetween({ minimum: 5n, maximum: 10n }))
1876 const asserts = new TestSchema.Asserts(schema)
1877
1878 const decoding = asserts.decoding()
1879 await decoding.succeed(5n)
1880 await decoding.succeed(7n)
1881 await decoding.succeed(10n)
1882 await decoding.fail(
1883 4n,
1884 `Expected a value between 5n and 10n`
1885 )
1886 })
1887
1888 it("isGreaterThan", async () => {
1889 const schema = Schema.BigInt.check(isGreaterThan(5n))
1890 const asserts = new TestSchema.Asserts(schema)
1891
1892 const decoding = asserts.decoding()
1893 await decoding.succeed(6n)
1894 await decoding.fail(
1895 5n,
1896 `Expected a value greater than 5n`
1897 )
1898 })
1899
1900 it("isGreaterThanOrEqualTo", async () => {
1901 const schema = Schema.BigInt.check(isGreaterThanOrEqualTo(5n))
1902 const asserts = new TestSchema.Asserts(schema)
1903
1904 const decoding = asserts.decoding()
1905 await decoding.succeed(5n)
1906 await decoding.succeed(6n)
1907 await decoding.fail(
1908 4n,
1909 `Expected a value greater than or equal to 5n`
1910 )
1911 })
1912
1913 it("isLessThan", async () => {
1914 const schema = Schema.BigInt.check(isLessThan(5n))
1915 const asserts = new TestSchema.Asserts(schema)
1916
1917 const decoding = asserts.decoding()
1918 await decoding.succeed(4n)
1919 await decoding.fail(
1920 5n,
1921 `Expected a value less than 5n`
1922 )
1923 })
1924
1925 it("isLessThanOrEqualTo", async () => {
1926 const schema = Schema.BigInt.check(isLessThanOrEqualTo(5n))
1927 const asserts = new TestSchema.Asserts(schema)
1928
1929 const decoding = asserts.decoding()
1930 await decoding.succeed(5n)
1931 await decoding.succeed(4n)
1932 await decoding.fail(
1933 6n,
1934 `Expected a value less than or equal to 5n`
1935 )
1936 })
1937 })
1938
1939 describe("Record checks", () => {
1940 it("isMinProperties", async () => {
1941 const schema = Schema.Record(Schema.String, Schema.Finite).check(Schema.isMinProperties(1))
1942 const asserts = new TestSchema.Asserts(schema)
1943
1944 const decoding = asserts.decoding()
1945 await decoding.succeed({ a: 1, b: 2 })
1946 await decoding.fail(
1947 {},
1948 `Expected a value with at least 1 entry`
1949 )
1950 })
1951
1952 it("isMaxProperties", async () => {
1953 const schema = Schema.Record(Schema.String, Schema.Finite).check(Schema.isMaxProperties(2))
1954 const asserts = new TestSchema.Asserts(schema)
1955
1956 const decoding = asserts.decoding()
1957 await decoding.succeed({ a: 1, b: 2 })
1958 await decoding.fail(
1959 { a: 1, b: 2, c: 3 },
1960 `Expected a value with at most 2 entries`
1961 )
1962 await decoding.fail(
1963 { a: 1, b: 2, c: 3 },
1964 `Expected a value with at most 2 entries`
1965 )
1966 })
1967
1968 it("isMinProperties with symbol keys", async () => {
1969 const sym = Symbol("test")
1970 const schema = Schema.Record(Schema.Union([Schema.String, Schema.Symbol]), Schema.Finite).check(
1971 Schema.isMinProperties(2)
1972 )
1973 const asserts = new TestSchema.Asserts(schema)
1974
1975 const decoding = asserts.decoding()
1976 await decoding.succeed({ a: 1, [sym]: 2 })
1977 await decoding.fail(
1978 { [sym]: 1 },
1979 `Expected a value with at least 2 entries`
1980 )
1981 })
1982
1983 it("isMaxProperties with symbol keys", async () => {
1984 const sym1 = Symbol("test1")
1985 const sym2 = Symbol("test2")
1986 const sym3 = Symbol("test3")
1987 const schema = Schema.Record(Schema.Union([Schema.String, Schema.Symbol]), Schema.Finite).check(
1988 Schema.isMaxProperties(2)
1989 )
1990 const asserts = new TestSchema.Asserts(schema)
1991
1992 const decoding = asserts.decoding()
1993 await decoding.succeed({ [sym1]: 1, [sym2]: 2 })
1994 await decoding.fail(
1995 { [sym1]: 1, [sym2]: 2, [sym3]: 3 },
1996 `Expected a value with at most 2 entries`
1997 )
1998 })
1999
2000 it("isBetweenProperties", async () => {
2001 const schema = Schema.Record(Schema.String, Schema.Number).check(Schema.isBetweenProperties(2, 2))
2002 const asserts = new TestSchema.Asserts(schema)
2003
2004 const decoding = asserts.decoding()
2005 await decoding.succeed({ a: 1, b: 2 })
2006 await decoding.succeed({ ["__proto__"]: 0, "": 0 })
2007 await decoding.fail(
2008 { a: 1 },
2009 `Expected a value with exactly 2 entries`
2010 )
2011 await decoding.fail(
2012 { a: 1, b: 2, c: 3 },
2013 `Expected a value with exactly 2 entries`
2014 )
2015 })
2016
2017 it("isBetweenProperties with symbol keys", async () => {
2018 const sym1 = Symbol("test1")
2019 const sym2 = Symbol("test2")
2020 const schema = Schema.Record(Schema.Union([Schema.String, Schema.Symbol]), Schema.Number).check(
2021 Schema.isBetweenProperties(2, 2)
2022 )
2023 const asserts = new TestSchema.Asserts(schema)
2024
2025 const decoding = asserts.decoding()
2026 await decoding.succeed({ [sym1]: 1, [sym2]: 2 })
2027 await decoding.succeed({ a: 1, [sym1]: 2 })
2028 await decoding.fail(
2029 { [sym1]: 1 },
2030 `Expected a value with exactly 2 entries`
2031 )
2032 await decoding.fail(
2033 { [sym1]: 1, [sym2]: 2, a: 3 },
2034 `Expected a value with exactly 2 entries`
2035 )
2036 })
2037
2038 it("isPropertyNames", async () => {
2039 const schema = Schema.Record(Schema.String, Schema.Finite).check(
2040 Schema.isPropertyNames(Schema.String.check(Schema.isPattern(/^[A-Z]/)))
2041 )
2042 const asserts = new TestSchema.Asserts(schema)
2043
2044 const decoding = asserts.decoding()
2045 await decoding.succeed({ Ab: 1 })
2046 await decoding.fail(
2047 { ab: 1 },
2048 `Expected a string matching the RegExp ^[A-Z]
2049 at ["ab"]`
2050 )
2051 })
2052
2053 it("isPropertyNames with Never", async () => {
2054 const schema = Schema.Record(Schema.String, Schema.Finite).check(Schema.isPropertyNames(Schema.Never))
2055 const asserts = new TestSchema.Asserts(schema)
2056
2057 const decoding = asserts.decoding()
2058 await decoding.succeed({})
2059 await decoding.fail(
2060 { a: 1 },
2061 `Expected never
2062 at ["a"]`
2063 )
2064 })
2065 })
2066
2067 describe("Array checks", () => {
2068 it("UniqueArray", async () => {
2069 const schema = Schema.UniqueArray(Schema.Struct({
2070 a: Schema.String,
2071 b: Schema.String
2072 }))
2073 const asserts = new TestSchema.Asserts(schema)
2074
2075 const decoding = asserts.decoding()
2076 await decoding.succeed([{ a: "a", b: "b" }, { a: "c", b: "d" }])
2077 await decoding.fail(
2078 [{ a: "a", b: "b" }, { a: "a", b: "b" }],
2079 `Expected an array with unique items`
2080 )
2081 })
2082 })
2083 })
2084
2085 it("Finite", async () => {
2086 const schema = Schema.Finite
2087 const asserts = new TestSchema.Asserts(schema)
2088 if (verifyGeneration) {
2089 asserts.arbitrary().verifyGeneration()
2090 }
2091 })
2092
2093 describe("Transformations", () => {
2094 it("NumberFromString", async () => {
2095 const schema = Schema.NumberFromString
2096 const asserts = new TestSchema.Asserts(schema)
2097
2098 if (verifyGeneration) {
2099 asserts.arbitrary().verifyGeneration()
2100 }
2101
2102 const decoding = asserts.decoding()
2103 await decoding.succeed("1", 1)
2104 await decoding.succeed("NaN", NaN)
2105 await decoding.succeed("Infinity", Infinity)
2106 await decoding.succeed("+Infinity", Infinity)
2107 await decoding.succeed("-Infinity", -Infinity)
2108
2109 const encoding = asserts.encoding()
2110 await encoding.succeed(1, "1")
2111 await encoding.succeed(NaN, "NaN")
2112 await encoding.succeed(Infinity, "Infinity")
2113 await encoding.succeed(-Infinity, "-Infinity")
2114 await encoding.fail(
2115 "a",
2116 `Expected number`
2117 )
2118 })
2119
2120 it("DateFromString", async () => {
2121 const schema = Schema.DateFromString
2122 const asserts = new TestSchema.Asserts(schema)
2123 if (verifyGeneration) {
2124 asserts.arbitrary().verifyGeneration()
2125 }
2126
2127 const decoding = asserts.decoding()
2128 await decoding.succeed("2021-01-01T00:00:00.000Z", new Date("2021-01-01T00:00:00.000Z"))
2129 await decoding.fail("invalid", `Expected a valid Date`)
2130
2131 const encoding = asserts.encoding()
2132 await encoding.succeed(new Date("2021-01-01T00:00:00.000Z"), "2021-01-01T00:00:00.000Z")
2133 await encoding.fail(new Date(NaN), `Expected a valid Date`)
2134 })
2135
2136 it("DateFromMillis", async () => {
2137 const schema = Schema.DateFromMillis
2138 const asserts = new TestSchema.Asserts(schema)
2139 if (verifyGeneration) {
2140 asserts.arbitrary().verifyGeneration()
2141 }
2142
2143 const decoding = asserts.decoding()
2144 await decoding.succeed(0, new Date(0))
2145 await decoding.fail(NaN, `Expected an integer`)
2146 await decoding.fail(Infinity, `Expected an integer`)
2147 await decoding.fail(-Infinity, `Expected an integer`)
2148 await decoding.fail(null, `Expected number`)
2149 await decoding.fail(8640000000000001, `Expected a valid Date`)
2150
2151 const encoding = asserts.encoding()
2152 await encoding.succeed(new Date(0), 0)
2153 await encoding.fail(new Date("invalid"), `Expected a valid Date`)
2154 await encoding.fail(new Date(NaN), `Expected a valid Date`)
2155 await encoding.fail(new Date(Infinity), `Expected a valid Date`)
2156 await encoding.fail(new Date(-Infinity), `Expected a valid Date`)
2157 })
2158
2159 it("FiniteFromString", async () => {
2160 const schema = Schema.FiniteFromString
2161 const asserts = new TestSchema.Asserts(schema)
2162
2163 if (verifyGeneration) {
2164 asserts.arbitrary().verifyGeneration()
2165 }
2166
2167 const decoding = asserts.decoding()
2168 await decoding.succeed("1", 1)
2169 await decoding.fail(
2170 "a",
2171 `Expected a finite number`
2172 )
2173 await decoding.fail(
2174 "NaN",
2175 `Expected a finite number`
2176 )
2177 await decoding.fail(
2178 "Infintiy",
2179 `Expected a finite number`
2180 )
2181 await decoding.fail(
2182 "+Infintiy",
2183 `Expected a finite number`
2184 )
2185 await decoding.fail(
2186 "-Infintiy",
2187 `Expected a finite number`
2188 )
2189
2190 const encoding = asserts.encoding()
2191 await encoding.succeed(1, "1")
2192 await encoding.fail(
2193 "a",
2194 `Expected number`
2195 )
2196 })
2197
2198 it("BigIntFromString", async () => {
2199 const schema = Schema.BigIntFromString
2200 const asserts = new TestSchema.Asserts(schema)
2201 if (verifyGeneration) {
2202 asserts.arbitrary().verifyGeneration()
2203 }
2204
2205 const decoding = asserts.decoding()
2206 await decoding.succeed("0", 0n)
2207 await decoding.fail(
2208 "a",
2209 `Expected a string representing a bigint`
2210 )
2211
2212 const encoding = asserts.encoding()
2213 await encoding.succeed(0n, "0")
2214 })
2215
2216 it("BigDecimalFromString", async () => {
2217 const schema = Schema.BigDecimalFromString
2218 const asserts = new TestSchema.Asserts(schema)
2219
2220 if (verifyGeneration) {
2221 asserts.arbitrary().verifyGeneration()
2222 }
2223
2224 const decoding = asserts.decoding()
2225 await decoding.succeed("0", Option.getOrThrow(BigDecimal.fromString("0")))
2226 await decoding.succeed("123.456", Option.getOrThrow(BigDecimal.fromString("123.456")))
2227
2228 const encoding = asserts.encoding()
2229 await encoding.succeed(BigDecimal.make(0n, 0), "0")
2230 await encoding.succeed(BigDecimal.make(123456n, 3), "123.456")
2231 await encoding.fail(
2232 "a",
2233 `Expected BigDecimal`
2234 )
2235 })
2236
2237 it("TimeZoneNamedFromString", async () => {
2238 const schema = Schema.TimeZoneNamedFromString
2239 const asserts = new TestSchema.Asserts(schema)
2240
2241 if (verifyGeneration) {
2242 asserts.arbitrary().verifyGeneration()
2243 }
2244
2245 const decoding = asserts.decoding()
2246 await decoding.succeed("Europe/London", DateTime.zoneMakeNamedUnsafe("Europe/London"))
2247
2248 const encoding = asserts.encoding()
2249 await encoding.succeed(DateTime.zoneMakeNamedUnsafe("Europe/London"), "Europe/London")
2250 await encoding.fail(
2251 "a",
2252 `Expected DateTime.TimeZone.Named`
2253 )
2254 })
2255
2256 it("TimeZoneFromString", async () => {
2257 const schema = Schema.TimeZoneFromString
2258 const asserts = new TestSchema.Asserts(schema)
2259
2260 if (verifyGeneration) {
2261 asserts.arbitrary().verifyGeneration()
2262 }
2263
2264 const decoding = asserts.decoding()
2265 await decoding.succeed("Europe/London", DateTime.zoneMakeNamedUnsafe("Europe/London"))
2266 await decoding.succeed("+03:00", DateTime.zoneMakeOffset(3 * 60 * 60 * 1000))
2267
2268 const encoding = asserts.encoding()
2269 await encoding.succeed(DateTime.zoneMakeNamedUnsafe("Europe/London"), "Europe/London")
2270 await encoding.succeed(DateTime.zoneMakeOffset(3 * 60 * 60 * 1000), "+03:00")
2271 await encoding.fail(
2272 "a",
2273 `Expected DateTime.TimeZone`
2274 )
2275 })
2276
2277 it("DateTimeZonedFromString", async () => {
2278 const schema = Schema.DateTimeZonedFromString
2279 const asserts = new TestSchema.Asserts(schema)
2280
2281 if (verifyGeneration) {
2282 asserts.arbitrary().verifyGeneration()
2283 }
2284
2285 const zoned = DateTime.makeZonedUnsafe("2021-01-01T00:00:00.000Z", { timeZone: "Europe/London" })
2286
2287 const decoding = asserts.decoding()
2288 await decoding.fail("invalid", "Expected a valid Zoned DateTime string")
2289
2290 const encoding = asserts.encoding()
2291 await encoding.succeed(zoned, DateTime.formatIsoZoned(zoned))
2292 await encoding.fail(
2293 "a",
2294 `Expected DateTime.Zoned`
2295 )
2296 })
2297
2298 it("FiniteFromString & isGreaterThan", async () => {
2299 const schema = Schema.FiniteFromString.check(Schema.isGreaterThan(2))
2300 const asserts = new TestSchema.Asserts(schema)
2301
2302 const decoding = asserts.decoding()
2303 await decoding.succeed("3", 3)
2304 await decoding.fail(
2305 "1",
2306 `Expected a value greater than 2`
2307 )
2308
2309 const encoding = asserts.encoding()
2310 await encoding.succeed(3, "3")
2311 await encoding.fail(
2312 1,
2313 `Expected a value greater than 2`
2314 )
2315 })
2316 })
2317
2318 describe("decodeTo", () => {
2319 it("should expose the source and the target schemas", () => {
2320 const schema = Schema.FiniteFromString
2321
2322 strictEqual(schema.from.ast._tag, "String")
2323 strictEqual(schema.to, Schema.Finite)
2324 })
2325
2326 it("required to required", async () => {
2327 const schema = Schema.Struct({
2328 a: Schema.String.pipe(
2329 Schema.decodeTo(
2330 Schema.String,
2331 SchemaTransformation.passthrough()
2332 )
2333 )
2334 })
2335 const asserts = new TestSchema.Asserts(schema)
2336
2337 const decoding = asserts.decoding()
2338 await decoding.succeed({ a: "a" })
2339 await decoding.fail(
2340 {},
2341 `Missing key
2342 at ["a"]`
2343 )
2344
2345 const encoding = asserts.encoding()
2346 await encoding.succeed({ a: "a" })
2347 await encoding.fail(
2348 {},
2349 `Missing key
2350 at ["a"]`
2351 )
2352 })
2353
2354 it("required to optional", async () => {
2355 const schema = Schema.Struct({
2356 a: Schema.String.pipe(
2357 Schema.decodeTo(
2358 Schema.optionalKey(Schema.String),
2359 {
2360 decode: SchemaGetter.required(),
2361 encode: SchemaGetter.withDefault(Effect.succeed("default"))
2362 }
2363 )
2364 )
2365 })
2366 const asserts = new TestSchema.Asserts(schema)
2367
2368 const decoding = asserts.decoding()
2369 await decoding.succeed({ a: "a" })
2370 await decoding.fail(
2371 {},
2372 `Missing key
2373 at ["a"]`
2374 )
2375
2376 const encoding = asserts.encoding()
2377 await encoding.succeed({ a: "a" })
2378 await encoding.succeed({}, { a: "default" })
2379 })
2380
2381 it("optionalKey to required", async () => {
2382 const schema = Schema.Struct({
2383 a: Schema.optionalKey(Schema.String).pipe(
2384 Schema.decodeTo(
2385 Schema.String,
2386 {
2387 decode: SchemaGetter.withDefault(Effect.succeed("default")),
2388 encode: SchemaGetter.passthrough()
2389 }
2390 )
2391 )
2392 })
2393 const asserts = new TestSchema.Asserts(schema)
2394
2395 const decoding = asserts.decoding()
2396 await decoding.succeed({ a: "a" })
2397 await decoding.succeed({}, { a: "default" })
2398
2399 const encoding = asserts.encoding()
2400 await encoding.succeed({ a: "a" })
2401 })
2402
2403 it("double transformation", async () => {
2404 const schema = Schema.Trim.pipe(Schema.decodeTo(
2405 Schema.FiniteFromString,
2406 SchemaTransformation.passthrough()
2407 ))
2408 const asserts = new TestSchema.Asserts(schema)
2409
2410 const decoding = asserts.decoding()
2411 await decoding.succeed(" 2 ", 2)
2412 await decoding.fail(
2413 " a2 ",
2414 `Expected a finite number`
2415 )
2416
2417 const encoding = asserts.encoding()
2418 await encoding.succeed(2, "2")
2419 })
2420
2421 it("double transformation with checks", async () => {
2422 const schema = Schema.Struct({
2423 a: Schema.String.check(Schema.isMinLength(2)).pipe(
2424 Schema.decodeTo(
2425 Schema.String.check(Schema.isMinLength(3)),
2426 SchemaTransformation.passthrough()
2427 ),
2428 Schema.decodeTo(
2429 Schema.String,
2430 SchemaTransformation.passthrough()
2431 )
2432 )
2433 })
2434 const asserts = new TestSchema.Asserts(schema)
2435
2436 const decoding = asserts.decoding()
2437 await decoding.succeed({ a: "aaa" })
2438 await decoding.fail(
2439 { a: "aa" },
2440 `Expected a value with a length of at least 3
2441 at ["a"]`
2442 )
2443
2444 const encoding = asserts.encoding()
2445 await encoding.succeed({ a: "aaa" })
2446 await encoding.fail(
2447 { a: "aa" },
2448 `Expected a value with a length of at least 3
2449 at ["a"]`
2450 )
2451 })
2452
2453 it("nested defaults", async () => {
2454 const schema = Schema.Struct({
2455 a: Schema.optionalKey(Schema.Struct({
2456 b: Schema.optionalKey(Schema.String)
2457 })).pipe(Schema.decodeTo(
2458 Schema.Struct({
2459 b: Schema.optionalKey(Schema.String).pipe(
2460 Schema.decodeTo(
2461 Schema.String,
2462 {
2463 decode: SchemaGetter.withDefault(Effect.succeed("default-b")),
2464 encode: SchemaGetter.passthrough()
2465 }
2466 )
2467 )
2468 }),
2469 {
2470 decode: SchemaGetter.withDefault(Effect.succeed({})),
2471 encode: SchemaGetter.passthrough()
2472 }
2473 ))
2474 })
2475 const asserts = new TestSchema.Asserts(schema)
2476
2477 const decoding = asserts.decoding()
2478 await decoding.succeed({ a: { b: "b" } })
2479 await decoding.succeed({ a: {} }, { a: { b: "default-b" } })
2480 await decoding.succeed({}, { a: { b: "default-b" } })
2481 })
2482 })
2483
2484 describe("decode", () => {
2485 it("double transformation", async () => {
2486 const schema = Schema.String.pipe(
2487 Schema.decode(
2488 SchemaTransformation.composeTransformation(
2489 SchemaTransformation.trim(),
2490 SchemaTransformation.toLowerCase()
2491 )
2492 )
2493 )
2494 const asserts = new TestSchema.Asserts(schema)
2495
2496 const decoding = asserts.decoding()
2497 await decoding.succeed(" A ", "a")
2498
2499 const encoding = asserts.encoding()
2500 await encoding.succeed(" A ", " A ")
2501 })
2502 })
2503
2504 describe("encodeTo", () => {
2505 it("required to required", async () => {
2506 const schema = Schema.Struct({
2507 a: Schema.String.pipe(
2508 Schema.encodeTo(
2509 Schema.String,
2510 SchemaTransformation.passthrough()
2511 )
2512 )
2513 })
2514 const asserts = new TestSchema.Asserts(schema)
2515
2516 const decoding = asserts.decoding()
2517 await decoding.succeed({ a: "a" })
2518 await decoding.fail(
2519 {},
2520 `Missing key
2521 at ["a"]`
2522 )
2523
2524 const encoding = asserts.encoding()
2525 await encoding.succeed({ a: "a" })
2526 await encoding.fail(
2527 {},
2528 `Missing key
2529 at ["a"]`
2530 )
2531 })
2532
2533 it("required to optionalKey", async () => {
2534 const schema = Schema.Struct({
2535 a: Schema.String.pipe(
2536 Schema.encodeTo(
2537 Schema.optionalKey(Schema.String),
2538 {
2539 decode: SchemaGetter.withDefault(Effect.succeed("default")),
2540 encode: SchemaGetter.passthrough()
2541 }
2542 )
2543 )
2544 })
2545 const asserts = new TestSchema.Asserts(schema)
2546
2547 const decoding = asserts.decoding()
2548 await decoding.succeed({ a: "a" })
2549 await decoding.succeed({}, { a: "default" })
2550
2551 const encoding = asserts.encoding()
2552 await encoding.succeed({ a: "a" })
2553 })
2554
2555 it("optionalKey to required", async () => {
2556 const schema = Schema.Struct({
2557 a: Schema.optionalKey(Schema.String).pipe(
2558 Schema.encodeTo(
2559 Schema.String,
2560 {
2561 decode: SchemaGetter.required(),
2562 encode: SchemaGetter.withDefault(Effect.succeed("default"))
2563 }
2564 )
2565 )
2566 })
2567 const asserts = new TestSchema.Asserts(schema)
2568
2569 const decoding = asserts.decoding()
2570 await decoding.succeed({ a: "a" })
2571 await decoding.fail(
2572 {},
2573 `Missing key
2574 at ["a"]`
2575 )
2576
2577 const encoding = asserts.encoding()
2578 await encoding.succeed({ a: "a" })
2579 await encoding.succeed({}, { a: "default" })
2580 })
2581
2582 it("double transformation", async () => {
2583 const schema = Schema.FiniteFromString.pipe(Schema.encodeTo(
2584 Schema.Trim,
2585 SchemaTransformation.passthrough()
2586 ))
2587 const asserts = new TestSchema.Asserts(schema)
2588
2589 const decoding = asserts.decoding()
2590 await decoding.succeed(" 2 ", 2)
2591 await decoding.fail(
2592 " a2 ",
2593 `Expected a finite number`
2594 )
2595
2596 const encoding = asserts.encoding()
2597 await encoding.succeed(2, "2")
2598 })
2599
2600 it("double transformation with checks", async () => {
2601 const schema = Schema.Struct({
2602 a: Schema.String.pipe(
2603 Schema.encodeTo(
2604 Schema.String.check(Schema.isMinLength(3)),
2605 SchemaTransformation.passthrough()
2606 ),
2607 Schema.encodeTo(
2608 Schema.String.check(Schema.isMinLength(2)),
2609 SchemaTransformation.passthrough()
2610 )
2611 )
2612 })
2613 const asserts = new TestSchema.Asserts(schema)
2614
2615 const decoding = asserts.decoding()
2616 await decoding.succeed({ a: "aaa" })
2617 await decoding.fail(
2618 { a: "aa" },
2619 `Expected a value with a length of at least 3
2620 at ["a"]`
2621 )
2622
2623 const encoding = asserts.encoding()
2624 await encoding.succeed({ a: "aaa" })
2625 await encoding.fail(
2626 { a: "aa" },
2627 `Expected a value with a length of at least 3
2628 at ["a"]`
2629 )
2630 })
2631 })
2632
2633 describe("encode", () => {
2634 it("double transformation", async () => {
2635 const schema = Schema.String.pipe(
2636 Schema.encode(
2637 SchemaTransformation.composeTransformation(
2638 SchemaTransformation.trim(),
2639 SchemaTransformation.toLowerCase()
2640 ).flip()
2641 )
2642 )
2643 const asserts = new TestSchema.Asserts(schema)
2644
2645 const decoding = asserts.decoding()
2646 await decoding.succeed(" A ", " A ")
2647
2648 const encoding = asserts.encoding()
2649 await encoding.succeed(" A ", "a")
2650 })
2651 })
2652
2653 describe("flip", () => {
2654 it("should expose the schema", () => {
2655 const schema = Schema.Struct({
2656 a: Schema.String
2657 })
2658 const flipped = schema.pipe(Schema.flip)
2659 strictEqual(flipped.schema, schema)
2660 })
2661
2662 it("string & isMinLength(3) <-> number & isGreaterThan(2)", async () => {
2663 const schema = Schema.FiniteFromString.pipe(
2664 Schema.check(Schema.isGreaterThan(2)),
2665 Schema.flip,
2666 Schema.check(Schema.isMinLength(3))
2667 )
2668 const asserts = new TestSchema.Asserts(schema)
2669
2670 const decoding = asserts.decoding()
2671 await decoding.fail(
2672 2,
2673 `Expected a value greater than 2`
2674 )
2675 await decoding.fail(
2676 3,
2677 `Expected a value with a length of at least 3`
2678 )
2679
2680 const encoding = asserts.encoding()
2681 await encoding.succeed("123", 123)
2682 })
2683
2684 it("Struct & flip & check & flip should apply the check to the encoded side", async () => {
2685 const schema = Schema.Struct({ a: Schema.String }).pipe(
2686 Schema.flip,
2687 Schema.check(Schema.makeFilter((o) => o.a.length > 1, { expected: "a length > 1" })),
2688 Schema.flip
2689 )
2690 assertTrue(SchemaAST.isObjects(schema.ast))
2691 strictEqual(schema.ast.checks, undefined)
2692 strictEqual(schema.ast.encodingChecks?.length, 1)
2693 const asserts = new TestSchema.Asserts(schema)
2694
2695 const decoding = asserts.decoding()
2696 await decoding.fail(
2697 { a: "a" },
2698 `Expected a length > 1`
2699 )
2700 await decoding.succeed({ a: "aa" })
2701
2702 const encoding = asserts.encoding()
2703 await encoding.fail(
2704 { a: "a" },
2705 `Expected a length > 1`
2706 )
2707 await encoding.succeed({ a: "aa" })
2708 })
2709
2710 it("Tuple & flip & check & flip should apply the check to the encoded side", async () => {
2711 const schema = Schema.Tuple([Schema.String]).pipe(
2712 Schema.flip,
2713 Schema.check(Schema.makeFilter((tuple) => tuple[0].length > 1, { expected: "head length > 1" })),
2714 Schema.flip
2715 )
2716 assertTrue(SchemaAST.isArrays(schema.ast))
2717 strictEqual(schema.ast.checks, undefined)
2718 strictEqual(schema.ast.encodingChecks?.length, 1)
2719 const asserts = new TestSchema.Asserts(schema)
2720
2721 const decoding = asserts.decoding()
2722 await decoding.fail(
2723 ["a"],
2724 `Expected head length > 1`
2725 )
2726 await decoding.succeed(["aa"])
2727
2728 const encoding = asserts.encoding()
2729 await encoding.fail(
2730 ["a"],
2731 `Expected head length > 1`
2732 )
2733 await encoding.succeed(["aa"])
2734 })
2735
2736 it("Union & flip & check & flip should apply the check to the encoded side", async () => {
2737 const schema = Schema.Union([Schema.Literal("a"), Schema.Literal("aa")]).pipe(
2738 Schema.flip,
2739 Schema.check(Schema.makeFilter((s) => s === "aa", { expected: `"aa"` })),
2740 Schema.flip
2741 )
2742 assertTrue(SchemaAST.isUnion(schema.ast))
2743 strictEqual(schema.ast.checks, undefined)
2744 strictEqual(schema.ast.encodingChecks?.length, 1)
2745 const asserts = new TestSchema.Asserts(schema)
2746
2747 const decoding = asserts.decoding()
2748 await decoding.fail(
2749 "a",
2750 `Expected "aa"`
2751 )
2752 await decoding.succeed("aa")
2753
2754 const encoding = asserts.encoding()
2755 await encoding.fail(
2756 "a",
2757 `Expected "aa"`
2758 )
2759 await encoding.succeed("aa")
2760 })
2761
2762 it("Declaration & flip & check & flip should apply the check to the encoded side", async () => {
2763 const schema = Schema.declare(
2764 (u): u is string => typeof u === "string",
2765 { expected: "string declaration" }
2766 ).pipe(
2767 Schema.flip,
2768 Schema.check(Schema.makeFilter((s) => s.length > 1, { expected: "a length > 1" })),
2769 Schema.flip
2770 )
2771 assertTrue(SchemaAST.isDeclaration(schema.ast))
2772 strictEqual(schema.ast.checks, undefined)
2773 strictEqual(schema.ast.encodingChecks?.length, 1)
2774 const asserts = new TestSchema.Asserts(schema)
2775
2776 const decoding = asserts.decoding()
2777 await decoding.fail(
2778 "a",
2779 `Expected a length > 1`
2780 )
2781 await decoding.succeed("aa")
2782
2783 const encoding = asserts.encoding()
2784 await encoding.fail(
2785 "a",
2786 `Expected a length > 1`
2787 )
2788 await encoding.succeed("aa")
2789 })
2790
2791 it("Struct & flip & check & flip with encoding chain should check the local value", async () => {
2792 const local = Schema.Struct({ a: Schema.String }).pipe(
2793 Schema.flip,
2794 Schema.check(Schema.makeFilter((o) => typeof o.a === "string" && o.a.length > 1, {
2795 expected: "a length > 1"
2796 })),
2797 Schema.flip
2798 )
2799 const schema = Schema.Struct({ b: Schema.String }).pipe(
2800 Schema.decodeTo(local, {
2801 decode: SchemaGetter.transform<{ readonly a: string }, { readonly b: string }>((o) => ({ a: o.b })),
2802 encode: SchemaGetter.transform<{ readonly b: string }, { readonly a: string }>((o) => ({ b: o.a }))
2803 })
2804 )
2805 assertTrue(SchemaAST.isObjects(schema.ast))
2806 strictEqual(schema.ast.encoding?.length, 1)
2807 strictEqual(schema.ast.encodingChecks?.length, 1)
2808 const asserts = new TestSchema.Asserts(schema)
2809
2810 const decoding = asserts.decoding()
2811 await decoding.fail(
2812 { b: "a" },
2813 `Expected a length > 1`
2814 )
2815 await decoding.succeed({ b: "aa" }, { a: "aa" })
2816
2817 const encoding = asserts.encoding()
2818 await encoding.fail(
2819 { a: "a" },
2820 `Expected a length > 1`
2821 )
2822 await encoding.succeed({ a: "aa" }, { b: "aa" })
2823 })
2824
2825 it("should work with withConstructorDefault", async () => {
2826 const schema = Schema.Struct({
2827 a: Schema.FiniteFromString.pipe(Schema.withConstructorDefault(Effect.succeed(-1)))
2828 })
2829 const asserts = new TestSchema.Asserts(schema)
2830
2831 const make = asserts.make()
2832 await make.succeed({ a: 1 })
2833 await make.succeed({}, { a: -1 })
2834
2835 const flipped = schema.pipe(Schema.flip)
2836 const assertsFlipped = new TestSchema.Asserts(flipped)
2837 throws(() => flipped.make({} as any))
2838 const makeFlipped = assertsFlipped.make()
2839 await makeFlipped.succeed({ a: "1" })
2840
2841 const flipped2 = flipped.pipe(Schema.flip)
2842 const assertsFlipped2 = new TestSchema.Asserts(flipped2)
2843 deepStrictEqual(flipped2.fields, schema.fields)
2844 const makeFlipped2 = assertsFlipped2.make()
2845 await makeFlipped2.succeed({ a: 1 })
2846 await makeFlipped2.succeed({}, { a: -1 })
2847 })
2848 })
2849
2850 it("declare", async () => {
2851 const schema = Schema.declare(
2852 (u) => u instanceof File,
2853 { expected: "File" }
2854 )
2855 const asserts = new TestSchema.Asserts(schema)
2856
2857 const decoding = asserts.decoding()
2858 await decoding.succeed(new File([], "a.txt"))
2859 await decoding.fail("a", `Expected File`)
2860 })
2861
2862 describe("Redacted", () => {
2863 it("should expose the value", () => {
2864 const schema = Schema.Redacted(Schema.String)
2865 strictEqual(schema.value, Schema.String)
2866 strictEqual(schema.annotate({}).value, Schema.String)
2867 })
2868
2869 it("does not expose unwrapped inputs", async () => {
2870 const schema = Schema.Redacted(Schema.String)
2871 const asserts = new TestSchema.Asserts(schema)
2872
2873 const decoding = asserts.decoding()
2874 await decoding.fail("secret", `Expected Redacted`)
2875
2876 const encoding = asserts.encoding()
2877 await encoding.fail("secret", `Expected Redacted`)
2878 })
2879
2880 it("Redacted(Finite)", async () => {
2881 const schema = Schema.Redacted(Schema.Int)
2882 const asserts = new TestSchema.Asserts(schema)
2883
2884 if (verifyGeneration) {
2885 asserts.arbitrary().verifyGeneration()
2886 }
2887
2888 const decoding = asserts.decoding()
2889 await decoding.succeed(Redacted.make(123))
2890 await decoding.fail(null, `Expected Redacted`)
2891 await decoding.fail(
2892 Redacted.make("a"),
2893 `Expected a valid value
2894 at ["value"]`
2895 )
2896 await decoding.fail(
2897 Redacted.make(1.2),
2898 `Expected a valid value
2899 at ["value"]`
2900 )
2901
2902 const encoding = asserts.encoding()
2903 await encoding.succeed(Redacted.make(123))
2904 await encoding.fail(null, `Expected Redacted`)
2905 await encoding.fail(
2906 Redacted.make("a"),
2907 `Expected a valid value
2908 at ["value"]`
2909 )
2910 await encoding.fail(
2911 Redacted.make(1.2),
2912 `Expected a valid value
2913 at ["value"]`
2914 )
2915 })
2916
2917 it("Redacted(FiniteFromString)", async () => {
2918 const schema = Schema.Redacted(Schema.FiniteFromString.check(Schema.isInt()))
2919 const asserts = new TestSchema.Asserts(schema)
2920
2921 if (verifyGeneration) {
2922 asserts.arbitrary().verifyGeneration()
2923 }
2924
2925 const decoding = asserts.decoding()
2926 await decoding.succeed(Redacted.make("123"), Redacted.make(123))
2927 const decoded = Schema.decodeUnknownSync(schema)(Redacted.make("123", { label: "secret" }))
2928 strictEqual(Redacted.value(decoded), 123)
2929 strictEqual(decoded.label, "secret")
2930 await decoding.fail(null, `Expected Redacted`)
2931 await decoding.fail(
2932 Redacted.make(null),
2933 `Expected a valid value
2934 at ["value"]`
2935 )
2936 await decoding.fail(
2937 Redacted.make("a"),
2938 `Expected a valid value
2939 at ["value"]`
2940 )
2941 await decoding.fail(
2942 Redacted.make("1.2"),
2943 `Expected a valid value
2944 at ["value"]`
2945 )
2946
2947 const encoding = asserts.encoding()
2948 await encoding.succeed(Redacted.make(123), Redacted.make("123"))
2949 const encoded = Schema.encodeSync(schema)(Redacted.make(123, { label: "secret" }))
2950 strictEqual(Redacted.value(encoded), "123")
2951 strictEqual(encoded.label, "secret")
2952 await encoding.fail(null, `Expected Redacted`)
2953 await encoding.fail(
2954 Redacted.make(null),
2955 `Expected a valid value
2956 at ["value"]`
2957 )
2958 await encoding.fail(
2959 Redacted.make("a"),
2960 `Expected a valid value
2961 at ["value"]`
2962 )
2963 await encoding.fail(
2964 Redacted.make(1.2),
2965 `Expected a valid value
2966 at ["value"]`
2967 )
2968 })
2969
2970 it("with label", async () => {
2971 const schema = Schema.Redacted(Schema.NonEmptyString, { label: "password" })
2972 const asserts = new TestSchema.Asserts(schema)
2973
2974 const decoding = asserts.decoding()
2975 await decoding.succeed(Redacted.make("a", { label: "password" }))
2976 await decoding.fail(
2977 Redacted.make("a", { label: "API key" }),
2978 `Expected "password"
2979 at ["label"]`
2980 )
2981 await decoding.fail(
2982 Redacted.make(1, { label: "API key" }),
2983 `Expected "password"
2984 at ["label"]`
2985 )
2986 await decoding.fail(
2987 Redacted.make(1, { label: "password" }),
2988 `Expected a valid value
2989 at ["value"]`
2990 )
2991
2992 const encoding = asserts.encoding()
2993 await encoding.succeed(Redacted.make("a", { label: "password" }))
2994 await encoding.fail(
2995 Redacted.make("a", { label: "API key" }),
2996 `Expected "password"
2997 at ["label"]`
2998 )
2999 await encoding.fail(
3000 Redacted.make("", { label: "password" }),
3001 `Expected a valid value
3002 at ["value"]`
3003 )
3004 })
3005 })
3006
3007 describe("RedactedFromValue", () => {
3008 it("should decode a value", async () => {
3009 const schema = Schema.RedactedFromValue(Schema.FiniteFromString.check(Schema.isInt()))
3010 const asserts = new TestSchema.Asserts(schema)
3011
3012 const decoding = asserts.decoding()
3013 await decoding.succeed("123", Redacted.make(123))
3014 await decoding.fail(null, `Expected string`)
3015 await decoding.fail("1.2", `Expected an integer`)
3016
3017 const encoding = asserts.encoding()
3018 await encoding.succeed(Redacted.make(123), "123")
3019 await encoding.fail("schema-secret", `Expected Redacted`)
3020 await encoding.fail(
3021 Redacted.make("schema-secret"),
3022 `Expected a valid value
3023 at ["value"]`
3024 )
3025 await encoding.fail(
3026 Redacted.make(1.2),
3027 `Expected a valid value
3028 at ["value"]`
3029 )
3030 })
3031 })
3032
3033 describe("Option", () => {
3034 it("should expose the value", () => {
3035 const schema = Schema.Option(Schema.String)
3036 strictEqual(schema.value, Schema.String)
3037 strictEqual(schema.annotate({}).value, Schema.String)
3038 })
3039
3040 it("Option(FiniteFromString)", async () => {
3041 const schema = Schema.Option(Schema.FiniteFromString)
3042 const asserts = new TestSchema.Asserts(schema)
3043
3044 if (verifyGeneration) {
3045 asserts.arbitrary().verifyGeneration()
3046 }
3047
3048 const decoding = asserts.decoding()
3049 await decoding.succeed(Option.none())
3050 await decoding.succeed(Option.some("123"), Option.some(123))
3051 await decoding.fail(null, `Expected Option`)
3052 await decoding.fail(
3053 Option.some(null),
3054 `Expected string
3055 at ["value"]`
3056 )
3057
3058 const encoding = asserts.encoding()
3059 await encoding.succeed(Option.none())
3060 await encoding.succeed(Option.some(123), Option.some("123"))
3061 await encoding.fail(null, `Expected Option`)
3062 await encoding.fail(
3063 Option.some(null),
3064 `Expected number
3065 at ["value"]`
3066 )
3067 })
3068 })
3069
3070 it("OptionFromNullOr", async () => {
3071 const schema = Schema.OptionFromNullOr(Schema.FiniteFromString)
3072 const asserts = new TestSchema.Asserts(schema)
3073
3074 if (verifyGeneration) {
3075 asserts.arbitrary().verifyGeneration()
3076 }
3077
3078 const decoding = asserts.decoding()
3079 await decoding.succeed(null, Option.none())
3080 await decoding.succeed("1", Option.some(1))
3081 await decoding.fail("a", `Expected a finite number`)
3082
3083 const encoding = asserts.encoding()
3084 await encoding.succeed(Option.none(), null)
3085 await encoding.succeed(Option.some(1), "1")
3086 })
3087
3088 it("OptionFromUndefinedOr", async () => {
3089 const schema = Schema.OptionFromUndefinedOr(Schema.FiniteFromString)
3090 const asserts = new TestSchema.Asserts(schema)
3091
3092 if (verifyGeneration) {
3093 asserts.arbitrary().verifyGeneration()
3094 }
3095
3096 const decoding = asserts.decoding()
3097 await decoding.succeed(undefined, Option.none())
3098 await decoding.succeed("1", Option.some(1))
3099 await decoding.fail("a", `Expected a finite number`)
3100
3101 const encoding = asserts.encoding()
3102 await encoding.succeed(Option.none(), undefined)
3103 await encoding.succeed(Option.some(1), "1")
3104 })
3105
3106 describe("OptionFromNullishOr", () => {
3107 it("onNoneEncoding: null", async () => {
3108 const schema = Schema.OptionFromNullishOr(Schema.FiniteFromString, { onNoneEncoding: null })
3109 const asserts = new TestSchema.Asserts(schema)
3110
3111 if (verifyGeneration) {
3112 asserts.arbitrary().verifyGeneration()
3113 }
3114
3115 const decoding = asserts.decoding()
3116 await decoding.succeed(null, Option.none())
3117 await decoding.succeed(undefined, Option.none())
3118 await decoding.succeed("1", Option.some(1))
3119 await decoding.fail("a", `Expected a finite number`)
3120
3121 const encoding = asserts.encoding()
3122 await encoding.succeed(Option.none(), null)
3123 await encoding.succeed(Option.some(1), "1")
3124 })
3125
3126 it("onNoneEncoding: undefined", async () => {
3127 const schema = Schema.OptionFromNullishOr(Schema.FiniteFromString, { onNoneEncoding: undefined })
3128 const asserts = new TestSchema.Asserts(schema)
3129
3130 if (verifyGeneration) {
3131 asserts.arbitrary().verifyGeneration()
3132 }
3133
3134 const decoding = asserts.decoding()
3135 await decoding.succeed(null, Option.none())
3136 await decoding.succeed(undefined, Option.none())
3137 await decoding.succeed("1", Option.some(1))
3138 await decoding.fail("a", `Expected a finite number`)
3139
3140 const encoding = asserts.encoding()
3141 await encoding.succeed(Option.none(), undefined)
3142 await encoding.succeed(Option.some(1), "1")
3143 })
3144 })
3145
3146 it("OptionFromOptionalKey", async () => {
3147 const schema = Schema.Struct({
3148 a: Schema.OptionFromOptionalKey(Schema.FiniteFromString)
3149 })
3150 const asserts = new TestSchema.Asserts(schema)
3151
3152 if (verifyGeneration) {
3153 asserts.arbitrary().verifyGeneration()
3154 }
3155
3156 const decoding = asserts.decoding()
3157 await decoding.succeed({}, { a: Option.none() })
3158 await decoding.succeed({ a: "1" }, { a: Option.some(1) })
3159 await decoding.fail(
3160 { a: undefined },
3161 `Expected string
3162 at ["a"]`
3163 )
3164 await decoding.fail(
3165 { a: "a" },
3166 `Expected a finite number
3167 at ["a"]`
3168 )
3169
3170 const encoding = asserts.encoding()
3171 await encoding.succeed({ a: Option.none() }, {})
3172 await encoding.succeed({ a: Option.some(1) }, { a: "1" })
3173 })
3174
3175 it("OptionFromOptional", async () => {
3176 const schema = Schema.Struct({
3177 a: Schema.OptionFromOptional(Schema.FiniteFromString)
3178 })
3179 const asserts = new TestSchema.Asserts(schema)
3180
3181 if (verifyGeneration) {
3182 asserts.arbitrary().verifyGeneration()
3183 }
3184
3185 const decoding = asserts.decoding()
3186 await decoding.succeed({}, { a: Option.none() })
3187 await decoding.succeed({ a: undefined }, { a: Option.none() })
3188 await decoding.succeed({ a: "1" }, { a: Option.some(1) })
3189 await decoding.fail(
3190 { a: "a" },
3191 `Expected a finite number
3192 at ["a"]`
3193 )
3194
3195 const encoding = asserts.encoding()
3196 await encoding.succeed({ a: Option.none() }, {})
3197 await encoding.succeed({ a: Option.some(1) }, { a: "1" })
3198 })
3199
3200 describe("OptionFromOptionalNullOr", () => {
3201 it("default (omit)", async () => {
3202 const schema = Schema.Struct({
3203 a: Schema.OptionFromOptionalNullOr(Schema.FiniteFromString)
3204 })
3205 const asserts = new TestSchema.Asserts(schema)
3206
3207 if (verifyGeneration) {
3208 asserts.arbitrary().verifyGeneration()
3209 }
3210
3211 const decoding = asserts.decoding()
3212 await decoding.succeed({}, { a: Option.none() })
3213 await decoding.succeed({ a: null }, { a: Option.none() })
3214 await decoding.succeed({ a: undefined }, { a: Option.none() })
3215 await decoding.succeed({ a: "1" }, { a: Option.some(1) })
3216 await decoding.fail(
3217 { a: "a" },
3218 `Expected a finite number
3219 at ["a"]`
3220 )
3221
3222 const encoding = asserts.encoding()
3223 await encoding.succeed({ a: Option.none() }, {})
3224 await encoding.succeed({ a: Option.some(1) }, { a: "1" })
3225 await encoding.fail(
3226 { a: null },
3227 `Expected Option
3228 at ["a"]`
3229 )
3230 })
3231
3232 it("onNoneEncoding: null", async () => {
3233 const schema = Schema.Struct({
3234 a: Schema.OptionFromOptionalNullOr(Schema.FiniteFromString, { onNoneEncoding: null })
3235 })
3236 const asserts = new TestSchema.Asserts(schema)
3237
3238 const decoding = asserts.decoding()
3239 await decoding.succeed({}, { a: Option.none() })
3240 await decoding.succeed({ a: null }, { a: Option.none() })
3241 await decoding.succeed({ a: undefined }, { a: Option.none() })
3242 await decoding.succeed({ a: "1" }, { a: Option.some(1) })
3243
3244 const encoding = asserts.encoding()
3245 await encoding.succeed({ a: Option.none() }, { a: null })
3246 await encoding.succeed({ a: Option.some(1) }, { a: "1" })
3247 })
3248
3249 it("onNoneEncoding: undefined", async () => {
3250 const schema = Schema.Struct({
3251 a: Schema.OptionFromOptionalNullOr(Schema.FiniteFromString, { onNoneEncoding: undefined })
3252 })
3253 const asserts = new TestSchema.Asserts(schema)
3254
3255 const decoding = asserts.decoding()
3256 await decoding.succeed({}, { a: Option.none() })
3257 await decoding.succeed({ a: null }, { a: Option.none() })
3258 await decoding.succeed({ a: undefined }, { a: Option.none() })
3259 await decoding.succeed({ a: "1" }, { a: Option.some(1) })
3260
3261 const encoding = asserts.encoding()
3262 await encoding.succeed({ a: Option.none() }, { a: undefined })
3263 await encoding.succeed({ a: Option.some(1) }, { a: "1" })
3264 })
3265 })
3266
3267 describe("Result", () => {
3268 it("should expose the values", () => {
3269 const schema = Schema.Result(Schema.String, Schema.Number)
3270 strictEqual(schema.success, Schema.String)
3271 strictEqual(schema.annotate({}).success, Schema.String)
3272 strictEqual(schema.failure, Schema.Number)
3273 strictEqual(schema.annotate({}).failure, Schema.Number)
3274 })
3275
3276 it("Result(FiniteFromString, FiniteFromString)", async () => {
3277 const schema = Schema.Result(Schema.FiniteFromString, Schema.FiniteFromString)
3278 const asserts = new TestSchema.Asserts(schema)
3279
3280 if (verifyGeneration) {
3281 asserts.arbitrary().verifyGeneration()
3282 }
3283
3284 const decoding = asserts.decoding()
3285 await decoding.succeed(Result.succeed("1"), Result.succeed(1))
3286 await decoding.succeed(Result.fail("2"), Result.fail(2))
3287 await decoding.fail(null, `Expected Result`)
3288 await decoding.fail(
3289 Result.succeed("a"),
3290 `Expected a finite number
3291 at ["success"]`
3292 )
3293 await decoding.fail(
3294 Result.fail("b"),
3295 `Expected a finite number
3296 at ["failure"]`
3297 )
3298
3299 const encoding = asserts.encoding()
3300 await encoding.succeed(Result.succeed(1), Result.succeed("1"))
3301 await encoding.succeed(Result.fail(2), Result.fail("2"))
3302 })
3303 })
3304
3305 it("Defect", async () => {
3306 const schema = Schema.Defect()
3307 const asserts = new TestSchema.Asserts(schema)
3308
3309 const noPrototypeObject = Object.create(null)
3310 noPrototypeObject.message = "a"
3311
3312 if (verifyGeneration) {
3313 asserts.arbitrary().verifyGeneration()
3314 }
3315
3316 const decoding = asserts.decoding()
3317 // Error: message only
3318 await decoding.succeed({ message: "a" }, new Error("a"))
3319 await decoding.succeed(noPrototypeObject, new Error("a"))
3320 // Error: message and name
3321 await decoding.succeed(
3322 { message: "a", name: "b" },
3323 (() => {
3324 const err = new Error("a")
3325 err.name = "b"
3326 return err
3327 })()
3328 )
3329 // Error: message, name, and stack
3330 await decoding.succeed(
3331 { message: "a", name: "b", stack: "c" },
3332 (() => {
3333 const err = new Error("a")
3334 err.name = "b"
3335 err.stack = "c"
3336 return err
3337 })()
3338 )
3339 // anything else
3340 await decoding.succeed("a")
3341 await decoding.succeed({ a: 1 })
3342
3343 const encoding = asserts.encoding()
3344 // Error
3345 await encoding.succeed(new Error("a"), { name: "Error", message: "a" })
3346 // anything else
3347 await encoding.succeed("a")
3348 await encoding.succeed({ a: 1 })
3349 await encoding.succeed(noPrototypeObject, { message: "a" })
3350 })
3351
3352 it("ErrorInstance and Defect memoize equivalent options", () => {
3353 const assertMemoized = <S>(schema: (options?: Schema.ErrorOptions) => S) => {
3354 strictEqual(schema(), schema({}))
3355 strictEqual(schema(), schema({ includeStack: false }))
3356 strictEqual(schema(), schema({ excludeCause: false }))
3357 strictEqual(schema(), schema({ includeStack: false, excludeCause: false }))
3358 strictEqual(schema({ includeStack: true }), schema({ includeStack: true }))
3359 strictEqual(schema({ includeStack: true }), schema({ includeStack: true, excludeCause: false }))
3360 strictEqual(schema({ excludeCause: true }), schema({ excludeCause: true }))
3361 strictEqual(schema({ excludeCause: true }), schema({ includeStack: false, excludeCause: true }))
3362 strictEqual(
3363 schema({ includeStack: true, excludeCause: true }),
3364 schema({ includeStack: true, excludeCause: true })
3365 )
3366 assertFalse(schema() === schema({ includeStack: true }))
3367 assertFalse(schema() === schema({ excludeCause: true }))
3368 assertFalse(schema({ includeStack: true }) === schema({ includeStack: true, excludeCause: true }))
3369 assertFalse(schema({ excludeCause: true }) === schema({ includeStack: true, excludeCause: true }))
3370 }
3371
3372 assertMemoized(Schema.ErrorInstance)
3373 assertMemoized(Schema.Defect)
3374 })
3375
3376 describe("CauseReason", () => {
3377 it("should expose the values", () => {
3378 const schema = Schema.CauseReason(Schema.String, Schema.Number)
3379 strictEqual(schema.error, Schema.String)
3380 strictEqual(schema.annotate({}).error, Schema.String)
3381 strictEqual(schema.defect, Schema.Number)
3382 strictEqual(schema.annotate({}).defect, Schema.Number)
3383 })
3384
3385 it("CauseReason(FiniteFromString, FiniteFromString)", async () => {
3386 const schema = Schema.CauseReason(Schema.FiniteFromString, Schema.FiniteFromString)
3387 const asserts = new TestSchema.Asserts(schema)
3388
3389 if (verifyGeneration) {
3390 asserts.arbitrary().verifyGeneration()
3391 }
3392 })
3393 })
3394
3395 describe("Cause", () => {
3396 it("strict encoding preserves the default wire format for every reason variant", () => {
3397 const schema = Schema.fromJsonString(Schema.toCodecJson(Schema.Cause(Schema.String, Schema.Number)))
3398 const cause = Cause.fromReasons([
3399 Cause.makeFailReason("boom"),
3400 Cause.makeDieReason(42),
3401 Cause.makeInterruptReason(7),
3402 Cause.makeInterruptReason()
3403 ])
3404 const wire =
3405 "[{\"_tag\":\"Fail\",\"error\":\"boom\"},{\"_tag\":\"Die\",\"defect\":42},{\"_tag\":\"Interrupt\",\"fiberId\":7},{\"_tag\":\"Interrupt\",\"fiberId\":null}]"
3406 strictEqual(Schema.encodeSync(schema)(cause), wire)
3407 strictEqual(Schema.encodeSync(schema, { onExcessProperty: "error" })(cause), wire)
3408 assert.deepStrictEqual(Schema.decodeSync(schema)(wire), cause)
3409 assert.deepStrictEqual(Schema.decodeSync(schema, { onExcessProperty: "error" })(wire), cause)
3410 })
3411
3412 it("should expose the values", () => {
3413 const schema = Schema.Cause(Schema.String, Schema.Number)
3414 strictEqual(schema.error, Schema.String)
3415 strictEqual(schema.annotate({}).error, Schema.String)
3416 strictEqual(schema.defect, Schema.Number)
3417 strictEqual(schema.annotate({}).defect, Schema.Number)
3418 })
3419
3420 it("Cause(FiniteFromString, FiniteFromString)", async () => {
3421 const schema = Schema.Cause(Schema.FiniteFromString, Schema.FiniteFromString)
3422 const asserts = new TestSchema.Asserts(schema)
3423
3424 if (verifyGeneration) {
3425 asserts.arbitrary().verifyGeneration()
3426 }
3427
3428 const decoding = asserts.decoding()
3429 await decoding.succeed(Cause.fail("1"), Cause.fail(1))
3430 await decoding.succeed(Cause.die("2"), Cause.die(2))
3431 await decoding.succeed(Cause.interrupt(3))
3432
3433 await decoding.fail(
3434 Cause.fail("a"),
3435 `Expected a finite number
3436 at ["failures"][0]["error"]`
3437 )
3438 await decoding.fail(
3439 Cause.die("a"),
3440 `Expected a finite number
3441 at ["failures"][0]["defect"]`
3442 )
3443
3444 const encoding = asserts.encoding()
3445 await encoding.succeed(Cause.fail(1), Cause.fail("1"))
3446 await encoding.succeed(Cause.die(2), Cause.die("2"))
3447 await encoding.succeed(Cause.interrupt(3))
3448
3449 await encoding.fail(
3450 Cause.fail("a"),
3451 `Expected number
3452 at ["failures"][0]["error"]`
3453 )
3454 await encoding.fail(
3455 Cause.die("a"),
3456 `Expected number
3457 at ["failures"][0]["defect"]`
3458 )
3459 })
3460 })
3461
3462 it("Error", async () => {
3463 const schema = Schema.ErrorInstance()
3464 const asserts = new TestSchema.Asserts(schema)
3465
3466 if (verifyGeneration) {
3467 asserts.arbitrary().verifyGeneration()
3468 }
3469
3470 const error = new Error("a")
3471 const customError = new Error("b")
3472 customError.name = "CustomError"
3473 customError.stack = "stack"
3474
3475 const decoding = asserts.decoding()
3476 await decoding.succeed(error)
3477 await decoding.succeed(customError)
3478 await decoding.fail(
3479 { message: "a" },
3480 `Expected Error`
3481 )
3482 await decoding.fail(
3483 "a",
3484 `Expected Error`
3485 )
3486
3487 const encoding = asserts.encoding()
3488 await encoding.succeed(error)
3489 await encoding.succeed(customError)
3490 await encoding.fail(
3491 { message: "a" },
3492 `Expected Error`
3493 )
3494 await encoding.fail(
3495 "a",
3496 `Expected Error`
3497 )
3498 })
3499
3500 describe("Exit", () => {
3501 it("should expose the values", () => {
3502 const schema = Schema.Exit(Schema.String, Schema.Number, Schema.Boolean)
3503 strictEqual(schema.value, Schema.String)
3504 strictEqual(schema.annotate({}).value, Schema.String)
3505 strictEqual(schema.error, Schema.Number)
3506 strictEqual(schema.annotate({}).error, Schema.Number)
3507 strictEqual(schema.defect, Schema.Boolean)
3508 strictEqual(schema.annotate({}).defect, Schema.Boolean)
3509 })
3510
3511 it("Exit(FiniteFromString, String, Unknown)", async () => {
3512 const schema = Schema.Exit(Schema.FiniteFromString, Schema.String, Schema.Unknown)
3513 const asserts = new TestSchema.Asserts(schema)
3514
3515 if (verifyGeneration) {
3516 asserts.arbitrary().verifyGeneration()
3517 }
3518
3519 const decoding = asserts.decoding()
3520 await decoding.succeed(Exit.succeed("123"), Exit.succeed(123))
3521 await decoding.succeed(Exit.fail("boom"))
3522 await decoding.fail(
3523 null,
3524 `Expected Exit`
3525 )
3526 await decoding.fail(
3527 Exit.succeed(123),
3528 `Expected string
3529 at ["value"]`
3530 )
3531 await decoding.fail(
3532 Exit.fail(null),
3533 `Expected string
3534 at ["cause"]["failures"][0]["error"]`
3535 )
3536 })
3537
3538 it("Exit(FiniteFromString, String, Defect)", async () => {
3539 const schema = Schema.Exit(Schema.FiniteFromString, Schema.String, Schema.Defect())
3540 const asserts = new TestSchema.Asserts(schema)
3541
3542 const decoding = asserts.decoding()
3543 const boomError = new Error("boom message")
3544 boomError.name = "boom"
3545
3546 await decoding.succeed(
3547 Exit.die({
3548 name: "boom",
3549 message: "boom message"
3550 }),
3551 Exit.die(boomError)
3552 )
3553 })
3554 })
3555
3556 describe("suspend", () => {
3557 it("should work", async () => {
3558 interface Category<A, T> {
3559 readonly a: A
3560 readonly categories: ReadonlyArray<T>
3561 }
3562 interface CategoryType extends Category<number, CategoryType> {}
3563 interface CategoryEncoded extends Category<string, CategoryEncoded> {}
3564
3565 const schema = Schema.Struct({
3566 a: Schema.FiniteFromString.check(Schema.isGreaterThan(0)),
3567 categories: Schema.Array(Schema.suspend((): Schema.Codec<CategoryType, CategoryEncoded> => schema))
3568 })
3569 const asserts = new TestSchema.Asserts(schema)
3570
3571 const decoding = asserts.decoding()
3572 await decoding.succeed({ a: "1", categories: [] }, { a: 1, categories: [] })
3573 await decoding.succeed({ a: "1", categories: [{ a: "2", categories: [] }] }, {
3574 a: 1,
3575 categories: [{ a: 2, categories: [] }]
3576 })
3577 await decoding.fail(
3578 {
3579 a: "1",
3580 categories: [{ a: "a", categories: [] }]
3581 },
3582 `Expected a finite number
3583 at ["categories"][0]["a"]`
3584 )
3585
3586 const encoding = asserts.encoding()
3587 await encoding.succeed({ a: 1, categories: [] }, { a: "1", categories: [] })
3588 await encoding.succeed({ a: 1, categories: [{ a: 2, categories: [] }] }, {
3589 a: "1",
3590 categories: [{ a: "2", categories: [] }]
3591 })
3592 await encoding.fail(
3593 { a: 1, categories: [{ a: -1, categories: [] }] },
3594 `Expected a value greater than 0
3595 at ["categories"][0]["a"]`
3596 )
3597 })
3598 })
3599
3600 describe("make", () => {
3601 it("preserves __proto__ as an own option", () => {
3602 const value = { polluted: true }
3603 const schema = Schema.make(Schema.String.ast, {
3604 ["__proto__"]: value
3605 })
3606
3607 assertTrue(Schema.isSchema(schema))
3608 assertTrue(Object.hasOwn(schema, "__proto__"))
3609 strictEqual((schema as any)["__proto__"], value)
3610 })
3611
3612 it("preserves name and length as options", () => {
3613 const schema = Schema.make<Schema.String>(Schema.String.ast, {
3614 name: "CustomSchema",
3615 length: 2
3616 })
3617
3618 strictEqual((schema as any).name, "CustomSchema")
3619 strictEqual((schema as any).length, 2)
3620
3621 const rebuilt = schema.annotate({})
3622
3623 strictEqual((rebuilt as any).name, "CustomSchema")
3624 strictEqual((rebuilt as any).length, 2)
3625 })
3626
3627 it("assigns options", () => {
3628 const schema = Schema.make<Schema.String>(Schema.String.ast, {
3629 custom: "value"
3630 })
3631
3632 assertTrue(Schema.isSchema(schema))
3633 strictEqual((schema as any).custom, "value")
3634
3635 const rebuilt = schema.annotate({})
3636
3637 strictEqual((rebuilt as any).custom, "value")
3638 })
3639
3640 it("should throw an error when the cause contains both a schema issue and a defect", () => {
3641 const cause = Cause.combine(
3642 Cause.fail(new SchemaIssue.InvalidValue({ message: "schema issue" })),
3643 Cause.die(new Error("defect"))
3644 )
3645 const schema = Schema.Struct({
3646 a: Schema.String.pipe(Schema.withConstructorDefault(Effect.failCause(cause)))
3647 })
3648
3649 throws(() => schema.make({}), (e) => {
3650 assertTrue(e instanceof Error)
3651 strictEqual(e.message, "Constructor adapter can only throw schema issues")
3652 assertTrue(Cause.hasDies(e.cause as Cause.Cause<never>))
3653 })
3654 })
3655 })
3656
3657 describe("makeOption", () => {
3658 it("Struct", () => {
3659 const schema = Schema.Struct({ a: Schema.Number.check(Schema.isGreaterThan(0)) })
3660 deepStrictEqual(schema.makeOption({ a: 1 }), Option.some({ a: 1 }))
3661 deepStrictEqual(schema.makeOption({ a: -1 }), Option.none())
3662 })
3663
3664 it("Class", () => {
3665 class A extends Schema.Class<A>("A")(Schema.Struct({ a: Schema.Number.check(Schema.isGreaterThan(0)) })) {}
3666 deepStrictEqual(A.makeOption({ a: 1 }), Option.some(new A({ a: 1 })))
3667 deepStrictEqual(A.makeOption({ a: -1 }), Option.none())
3668 })
3669
3670 it("should throw an error when the cause is not a schema issue", () => {
3671 const schema = Schema.Struct({
3672 a: Schema.String.pipe(Schema.withConstructorDefault(Effect.die(new Error("make defect"))))
3673 })
3674 class A extends Schema.Class<A>("A")(schema) {}
3675
3676 throws(() => schema.makeOption({}), (e) => {
3677 assertTrue(e instanceof Error)
3678 strictEqual(e.message, "Option adapter can only return none for schema issues")
3679 assertTrue(Cause.hasDies(e.cause as Cause.Cause<never>))
3680 })
3681 throws(() => A.makeOption(), (e) => {
3682 assertTrue(e instanceof Error)
3683 strictEqual(e.message, "Option adapter can only return none for schema issues")
3684 assertTrue(Cause.hasDies(e.cause as Cause.Cause<never>))
3685 })
3686 })
3687 })
3688
3689 describe("makeEffect", () => {
3690 it.effect("Struct", () =>
3691 Effect.gen(function*() {
3692 const schema = Schema.Struct({ a: Schema.Number.check(Schema.isGreaterThan(0)) })
3693
3694 const success = yield* schema.makeEffect({ a: 1 }).pipe(Effect.result)
3695 deepStrictEqual(success, Result.succeed({ a: 1 }))
3696
3697 const failure = yield* schema.makeEffect({ a: -1 }).pipe(Effect.flip)
3698 assertTrue(SchemaIssue.isIssue(failure))
3699 }))
3700
3701 it.effect("Class", () =>
3702 Effect.gen(function*() {
3703 class A extends Schema.Class<A>("A")(Schema.Struct({ a: Schema.Number.check(Schema.isGreaterThan(0)) })) {}
3704
3705 const success = yield* A.makeEffect({ a: 1 })
3706 deepStrictEqual(success, new A({ a: 1 }))
3707
3708 const failure = yield* A.makeEffect({ a: -1 }).pipe(Effect.flip)
3709 assertTrue(SchemaIssue.isIssue(failure))
3710 }))
3711
3712 it.effect("should preserve mixed schema issue and defect causes", () =>
3713 Effect.gen(function*() {
3714 const cause = Cause.combine(
3715 Cause.fail(new SchemaIssue.InvalidValue({ message: "schema issue" })),
3716 Cause.die(new Error("defect"))
3717 )
3718 const schema = Schema.Struct({
3719 a: Schema.String.pipe(Schema.withConstructorDefault(Effect.failCause(cause)))
3720 })
3721
3722 const exit = yield* schema.makeEffect({}).pipe(Effect.exit)
3723 assertTrue(Exit.isFailure(exit))
3724 assertTrue(Exit.hasDies(exit))
3725 const error = Cause.findError(exit.cause)
3726 assertTrue(Result.isSuccess(error))
3727 assertTrue(SchemaIssue.isIssue(error.success))
3728 }))
3729 })
3730
3731 describe("withConstructorDefault", () => {
3732 describe("Struct", () => {
3733 it("should not apply defaults when decoding / encoding", async () => {
3734 const schema = Schema.Struct({
3735 a: Schema.String.pipe(Schema.optionalKey, Schema.withConstructorDefault(Effect.succeed("a")))
3736 })
3737 const asserts = new TestSchema.Asserts(schema)
3738
3739 const decoding = asserts.decoding()
3740 await decoding.succeed({}, {})
3741
3742 const encoding = asserts.encoding()
3743 await encoding.succeed({}, {})
3744 })
3745
3746 it("should apply constructor default when the field is not present", async () => {
3747 const schema = Schema.Struct({
3748 a: Schema.FiniteFromString.pipe(Schema.withConstructorDefault(Effect.succeed(-1)))
3749 })
3750 const asserts = new TestSchema.Asserts(schema)
3751
3752 const make = asserts.make()
3753 await make.succeed({ a: 1 })
3754 await make.succeed({}, { a: -1 })
3755 })
3756
3757 it("should construct a Class field from a plain object", () => {
3758 class A extends Schema.Class<A>("A")({ a: Schema.String }) {}
3759 const schema = Schema.Struct({
3760 a: A.pipe(Schema.withConstructorDefault(Effect.succeed(A.make({ a: "default" }))))
3761 })
3762
3763 const result = schema.make({ a: { a: "a" } })
3764 assertTrue(result.a instanceof A)
3765 })
3766
3767 describe("nested defaults", () => {
3768 it("Struct", async () => {
3769 const schema = Schema.Struct({
3770 a: Schema.Struct({
3771 b: Schema.FiniteFromString.pipe(Schema.withConstructorDefault(Effect.succeed(-1)))
3772 }).pipe(Schema.withConstructorDefault(Effect.succeed({})))
3773 })
3774 const asserts = new TestSchema.Asserts(schema)
3775
3776 const make = asserts.make()
3777 await make.succeed({ a: { b: 1 } })
3778 await make.succeed({ a: {} }, { a: { b: -1 } })
3779 await make.succeed({}, { a: { b: -1 } })
3780 })
3781
3782 it("Class", async () => {
3783 class A extends Schema.Class<A>("A")(Schema.Struct({
3784 a: Schema.Struct({
3785 b: Schema.FiniteFromString.pipe(Schema.withConstructorDefault(Effect.succeed(-1)))
3786 }).pipe(Schema.withConstructorDefault(Effect.succeed({})))
3787 })) {}
3788
3789 const asserts = new TestSchema.Asserts(A)
3790
3791 const make = asserts.make()
3792 await make.succeed({ a: { b: 1 } }, new A({ a: { b: 1 } }))
3793 await make.succeed({ a: {} }, new A({ a: { b: -1 } }))
3794 await make.succeed({}, new A({ a: { b: -1 } }))
3795
3796 deepStrictEqual(A.make({ a: { b: 1 } }), new A({ a: { b: 1 } }))
3797 deepStrictEqual(A.make({ a: {} }), new A({ a: { b: -1 } }))
3798 deepStrictEqual(A.make({}), new A({ a: { b: -1 } }))
3799 })
3800 })
3801
3802 it("applies constructor default when disableChecks is true", () => {
3803 class A extends Schema.Class<A>("A")({
3804 a: Schema.String.pipe(Schema.withConstructorDefault(Effect.succeed("default")))
3805 }) {}
3806
3807 const instance = new A({}, { disableChecks: true })
3808 deepStrictEqual(instance.a, "default")
3809 })
3810
3811 it("Struct & Effect sync", async () => {
3812 const schema = Schema.Struct({
3813 a: Schema.FiniteFromString.pipe(Schema.withConstructorDefault(Effect.succeed(-1)))
3814 })
3815 const asserts = new TestSchema.Asserts(schema)
3816
3817 const make = asserts.make()
3818 await make.succeed({ a: 1 })
3819 await make.succeed({}, { a: -1 })
3820 })
3821
3822 it("Struct & Effect async", async () => {
3823 const schema = Schema.Struct({
3824 a: Schema.FiniteFromString.pipe(Schema.withConstructorDefault(
3825 Effect.gen(function*() {
3826 yield* Effect.sleep(100)
3827 return -1
3828 })
3829 ))
3830 })
3831 const asserts = new TestSchema.Asserts(schema)
3832
3833 const make = asserts.make()
3834 await make.succeed({ a: 1 })
3835 await make.succeed({}, { a: -1 })
3836 })
3837
3838 it("Effect failing with SchemaIssue propagates as parse failure", async () => {
3839 const schema = Schema.Struct({
3840 a: Schema.FiniteFromString.pipe(Schema.withConstructorDefault(
3841 Effect.fail(new SchemaIssue.InvalidValue({ message: "ctor default failed" }))
3842 ))
3843 })
3844 const asserts = new TestSchema.Asserts(schema)
3845
3846 const make = asserts.make()
3847 await make.succeed({ a: 1 })
3848 await make.fail(
3849 {},
3850 `ctor default failed
3851 at ["a"]`
3852 )
3853 })
3854
3855 it("Effect failing via another schema's makeEffect propagates as parse failure", async () => {
3856 const Inner = Schema.Struct({
3857 n: Schema.FiniteFromString.pipe(Schema.check(Schema.isGreaterThan(0)))
3858 })
3859 const schema = Schema.Struct({
3860 a: Schema.FiniteFromString.pipe(Schema.withConstructorDefault(
3861 Inner.makeEffect({ n: -1 }).pipe(Effect.map((s) => s.n))
3862 ))
3863 })
3864 const asserts = new TestSchema.Asserts(schema)
3865
3866 const make = asserts.make()
3867 await make.succeed({ a: 1 })
3868 await make.fail(
3869 {},
3870 `Expected a value greater than 0
3871 at ["a"]["n"]`
3872 )
3873 })
3874
3875 it("Struct & Effect async & service", async () => {
3876 class Service extends Context.Service<Service, { value: Effect.Effect<number> }>()("Service") {}
3877
3878 const schema = Schema.Struct({
3879 a: Schema.FiniteFromString.pipe(Schema.withConstructorDefault(
3880 Effect.gen(function*() {
3881 yield* Effect.sleep(100)
3882 const oservice = yield* Effect.serviceOption(Service)
3883 if (Option.isNone(oservice)) {
3884 return -1
3885 }
3886 return yield* oservice.value.value
3887 })
3888 ))
3889 })
3890 const asserts = new TestSchema.Asserts(schema)
3891
3892 const make = asserts.make()
3893
3894 await make.succeed({ a: 1 })
3895 await make.succeed({}, { a: -1 })
3896 const effect = await schema.makeEffect({}).pipe(
3897 Effect.provideService(Service, { value: Effect.succeed(0) }),
3898 Effect.result,
3899 Effect.runPromise
3900 )
3901 deepStrictEqual(effect, Result.succeed({ a: 0 }))
3902 })
3903 })
3904
3905 describe("Tuple", () => {
3906 it("Tuple & Some", async () => {
3907 const schema = Schema.Tuple(
3908 [Schema.FiniteFromString.pipe(Schema.withConstructorDefault(Effect.succeed(-1)))]
3909 )
3910 const asserts = new TestSchema.Asserts(schema)
3911
3912 const make = asserts.make()
3913 await make.succeed([1])
3914 await make.succeed([], [-1])
3915 })
3916
3917 it("nested defaults (Struct)", async () => {
3918 const schema = Schema.Tuple(
3919 [
3920 Schema.Struct({
3921 b: Schema.FiniteFromString.pipe(Schema.withConstructorDefault(Effect.succeed(-1)))
3922 }).pipe(Schema.withConstructorDefault(Effect.succeed({})))
3923 ]
3924 )
3925 const asserts = new TestSchema.Asserts(schema)
3926
3927 const make = asserts.make()
3928 await make.succeed([{ b: 1 }])
3929 await make.succeed([{}], [{ b: -1 }])
3930 await make.succeed([], [{ b: -1 }])
3931 })
3932
3933 it("inner defaults (Tuple)", async () => {
3934 const schema = Schema.Tuple(
3935 [
3936 Schema.Tuple([
3937 Schema.FiniteFromString.pipe(Schema.withConstructorDefault(Effect.succeed(-1)))
3938 ])
3939 ]
3940 )
3941 const asserts = new TestSchema.Asserts(schema)
3942
3943 const make = asserts.make()
3944 await make.succeed([[1]])
3945 await make.succeed([[]], [[-1]])
3946 })
3947
3948 it("nested defaults (Tuple)", async () => {
3949 const schema = Schema.Tuple(
3950 [
3951 Schema.Tuple([
3952 Schema.FiniteFromString.pipe(Schema.withConstructorDefault(Effect.succeed(-1)))
3953 ]).pipe(Schema.withConstructorDefault(Effect.succeed([] as const)))
3954 ]
3955 )
3956 const asserts = new TestSchema.Asserts(schema)
3957
3958 const make = asserts.make()
3959 await make.succeed([[1]])
3960 await make.succeed([[]], [[-1]])
3961 await make.succeed([], [[-1]])
3962 })
3963 })
3964 })
3965
3966 describe("Record", () => {
3967 it("Record(String, Number)", async () => {
3968 const schema = Schema.Record(Schema.String, Schema.Number)
3969 const asserts = new TestSchema.Asserts(schema)
3970
3971 const make = asserts.make()
3972 await make.succeed({ a: 1 })
3973 await make.fail(null, `Expected object`)
3974
3975 const decoding = asserts.decoding()
3976 await decoding.succeed({ a: 1 })
3977 await decoding.fail(null, "Expected object")
3978 await decoding.fail(
3979 { a: "b" },
3980 `Expected number
3981 at ["a"]`
3982 )
3983
3984 const encoding = asserts.encoding()
3985 await encoding.succeed({ a: 1 })
3986 await encoding.fail(
3987 { a: "b" },
3988 `Expected number
3989 at ["a"]`
3990 )
3991 await encoding.fail(null, "Expected object")
3992 })
3993
3994 it("recursive values stay lazy", async () => {
3995 interface Recursive {
3996 readonly [key: string]: Recursive
3997 }
3998 const schema: Schema.Codec<Recursive> = Schema.Record(
3999 Schema.String,
4000 Schema.suspend((): Schema.Codec<Recursive> => schema)
4001 )
4002 const asserts = new TestSchema.Asserts(schema)
4003
4004 const input = { a: { b: {} } }
4005 await asserts.decoding().succeed(input)
4006 await asserts.encoding().succeed(input)
4007 })
4008
4009 it.effect("sequential parsing resumes without replaying entries", () =>
4010 Effect.gen(function*() {
4011 const calls: Array<string> = []
4012 const value = Schema.String.pipe(
4013 Schema.decode({
4014 decode: SchemaGetter.transformEffect((value) => {
4015 calls.push(value)
4016 return value === "b" ? Effect.yieldNow.pipe(Effect.as(value)) : Effect.succeed(value)
4017 }),
4018 encode: SchemaGetter.passthrough()
4019 })
4020 )
4021 const schema = Schema.Record(Schema.String, value)
4022 const input = { a: "a", b: "b", c: "c" }
4023
4024 deepStrictEqual(yield* Schema.decodeUnknownEffect(schema)(input), input)
4025 deepStrictEqual(calls, ["a", "b", "c"])
4026 }))
4027
4028 it("Record(String, optionalKey(Number)) should throw", async () => {
4029 throws(
4030 () => Schema.Record(Schema.String, Schema.optionalKey(Schema.Number)),
4031 new Error("Cannot use `Schema.optionalKey` with index signatures, use `Schema.optional` instead.")
4032 )
4033 })
4034
4035 it("Record(String, optional(Number))", async () => {
4036 const schema = Schema.Record(Schema.String, Schema.optional(Schema.Number))
4037 const asserts = new TestSchema.Asserts(schema)
4038
4039 const make = asserts.make()
4040 await make.succeed({ a: 1 })
4041 await make.succeed({ a: undefined })
4042 await make.fail(null, `Expected object`)
4043
4044 const decoding = asserts.decoding()
4045 await decoding.succeed({ a: 1 })
4046 await decoding.succeed({ a: undefined })
4047
4048 const encoding = asserts.encoding()
4049 await encoding.succeed({ a: 1 })
4050 await encoding.succeed({ a: undefined })
4051 })
4052
4053 it("Record(String.check, Number) should use the key checks to select keys", async () => {
4054 const schema = Schema.Record(Schema.String.check(Schema.isPattern(/^a/)), Schema.Number)
4055 const asserts = new TestSchema.Asserts(schema)
4056
4057 const decoding = asserts.decoding()
4058 await decoding.succeed({ a: 1, ab: 2, b: "ignored" }, { a: 1, ab: 2 })
4059 await decoding.fail(
4060 { a: "bad", b: 1 },
4061 `Expected number
4062 at ["a"]`
4063 )
4064 })
4065
4066 it("applies excess-property options to unmatched own keys", () => {
4067 const schema = Schema.Record(Schema.String.check(Schema.isPattern(/^a/)), Schema.Number)
4068 const input = { a: 1, b: 2 }
4069
4070 deepStrictEqual(Schema.decodeUnknownSync(schema)(input, { onExcessProperty: "ignore" }), { a: 1 })
4071 deepStrictEqual(Schema.encodeUnknownSync(schema)(input, { onExcessProperty: "ignore" }), { a: 1 })
4072 throws(
4073 () => Schema.decodeUnknownSync(schema)(input, { onExcessProperty: "error" }),
4074 `Expected no excess property\n at ["b"]`
4075 )
4076 throws(
4077 () => Schema.encodeUnknownSync(schema)(input, { onExcessProperty: "error" }),
4078 `Expected no excess property\n at ["b"]`
4079 )
4080 })
4081
4082 it("recognizes numeric declared keys with an index signature", () => {
4083 const symbol = Symbol("symbol")
4084 const schema = Schema.Record(Schema.Union([Schema.Literal(1), Schema.Symbol]), Schema.String)
4085 const input = { 1: "one", [symbol]: "symbol" }
4086
4087 deepStrictEqual(Schema.decodeUnknownSync(schema)(input, { onExcessProperty: "error" }), input)
4088 deepStrictEqual(Schema.encodeUnknownSync(schema)(input, { onExcessProperty: "error" }), input)
4089 })
4090
4091 it("Record(Symbol, Number)", async () => {
4092 const schema = Schema.Record(Schema.Symbol, Schema.Number)
4093 const asserts = new TestSchema.Asserts(schema)
4094
4095 const make = asserts.make()
4096 await make.succeed({ [Symbol.for("a")]: 1 })
4097 await make.fail(null, `Expected object`)
4098
4099 const decoding = asserts.decoding()
4100 await decoding.succeed({ [Symbol.for("a")]: 1 })
4101 await decoding.fail(null, "Expected object")
4102 await decoding.fail(
4103 { [Symbol.for("a")]: "b" },
4104 `Expected number
4105 at [Symbol(a)]`
4106 )
4107
4108 const encoding = asserts.encoding()
4109 await encoding.succeed({ [Symbol.for("a")]: 1 })
4110 await encoding.fail(
4111 { [Symbol.for("a")]: "b" },
4112 `Expected number
4113 at [Symbol(a)]`
4114 )
4115 await encoding.fail(null, "Expected object")
4116 })
4117
4118 for (const key of ["visible", Symbol("visible")]) {
4119 it(`index signatures select only enumerable ${typeof key} properties`, async () => {
4120 const input = { [key]: 1 }
4121 const hidden = typeof key === "string" ? "hidden" : Symbol("hidden")
4122 Object.defineProperty(input, hidden, {
4123 get() {
4124 throw new Error("Non-enumerable record entries must not be read")
4125 },
4126 enumerable: false
4127 })
4128 const schema = Schema.Record(typeof key === "string" ? Schema.String : Schema.Symbol, Schema.Number)
4129 const asserts = new TestSchema.Asserts(schema)
4130 const message = `Expected number\n at [${typeof key === "string" ? JSON.stringify(key) : String(key)}]`
4131
4132 for (const parseOptions of [undefined, { onExcessProperty: "error" } as const]) {
4133 await asserts.decoding({ parseOptions }).succeed(input, { [key]: 1 })
4134 await asserts.encoding({ parseOptions }).succeed(input, { [key]: 1 })
4135 await asserts.decoding({ parseOptions }).fail({ [key]: "invalid" }, message)
4136 await asserts.encoding({ parseOptions }).fail({ [key]: "invalid" }, message)
4137 }
4138 })
4139 }
4140
4141 it("Record(Symbol.check, Number) should use the key checks to select keys", async () => {
4142 const a = Symbol.for("a")
4143 const b = Symbol.for("b")
4144 const schema = Schema.Record(
4145 Schema.Symbol.check(Schema.makeFilter((symbol) => Symbol.keyFor(symbol)?.startsWith("a") ?? false)),
4146 Schema.Number
4147 )
4148 const asserts = new TestSchema.Asserts(schema)
4149
4150 const decoding = asserts.decoding()
4151 await decoding.succeed({ [a]: 1, [b]: "ignored" }, { [a]: 1 })
4152 await decoding.fail(
4153 { [a]: "bad", [b]: 1 },
4154 `Expected number
4155 at [Symbol(a)]`
4156 )
4157 })
4158
4159 it("Record(SnakeToCamel, NumberFromString)", async () => {
4160 const schema = Schema.Record(SnakeToCamel, Schema.NumberFromString)
4161 const asserts = new TestSchema.Asserts(schema)
4162
4163 const decoding = asserts.decoding()
4164 await decoding.succeed({ a: "1" }, { a: 1 })
4165 await decoding.succeed({ a_b: "1" }, { aB: 1 })
4166 await decoding.succeed({ a_b: "1", aB: "2" }, { aB: 2 })
4167
4168 const encoding = asserts.encoding()
4169 await encoding.succeed({ a: 1 }, { a: "1" })
4170 await encoding.succeed({ aB: 1 }, { a_b: "1" })
4171 await encoding.succeed({ a_b: 1, aB: 2 }, { a_b: "2" })
4172 })
4173
4174 it("UniqueSymbol", async () => {
4175 const a = Symbol.for("a")
4176 const schema = Schema.Record(Schema.UniqueSymbol(a), Schema.Number)
4177 const asserts = new TestSchema.Asserts(schema)
4178
4179 const decoding = asserts.decoding()
4180 await decoding.succeed({ [a]: 1 })
4181 await decoding.fail(
4182 { [a]: "b" },
4183 `Expected number
4184 at [Symbol(a)]`
4185 )
4186 })
4187
4188 describe("Literals keys", () => {
4189 it("deduplicates repeated literal keys", () => {
4190 const schema = Schema.Record(Schema.Literals(["a", "a", "b"]), Schema.Number)
4191 deepStrictEqual(schema.ast.propertySignatures.map((propertySignature) => propertySignature.name), ["a", "b"])
4192 })
4193
4194 it("Record(Literals, Number)", async () => {
4195 const schema = Schema.Record(Schema.Literals(["a", "b"]), Schema.Number)
4196 const asserts = new TestSchema.Asserts(schema)
4197
4198 const decoding = asserts.decoding()
4199 await decoding.succeed({ a: 1, b: 2 })
4200 await decoding.fail(
4201 { a: 1 },
4202 `Missing key
4203 at ["b"]`
4204 )
4205 await decoding.fail(
4206 { b: 2 },
4207 `Missing key
4208 at ["a"]`
4209 )
4210 })
4211
4212 it("Record(Literals, optionalKey(Number))", async () => {
4213 const schema = Schema.Record(Schema.Literals(["a", "b"]), Schema.optionalKey(Schema.Number))
4214 const asserts = new TestSchema.Asserts(schema)
4215
4216 const decoding = asserts.decoding()
4217 await decoding.succeed({})
4218 await decoding.succeed({ a: 1 })
4219 await decoding.succeed({ b: 2 })
4220 await decoding.succeed({ a: 1, b: 2 })
4221 })
4222
4223 it("Record(Literals, mutableKey(Number))", async () => {
4224 const schema = Schema.Record(Schema.Literals(["a", "b"]), Schema.mutableKey(Schema.Number))
4225 const asserts = new TestSchema.Asserts(schema)
4226
4227 const decoding = asserts.decoding()
4228 await decoding.succeed({ a: 1, b: 2 })
4229 })
4230
4231 it("Record(Literals, mutableKey(optionalKey(Number)))", async () => {
4232 const schema = Schema.Record(
4233 Schema.Literals(["a", "b"]),
4234 Schema.mutableKey(Schema.optionalKey(Schema.Number))
4235 )
4236 const asserts = new TestSchema.Asserts(schema)
4237
4238 const decoding = asserts.decoding()
4239 await decoding.succeed({})
4240 await decoding.succeed({ a: 1 })
4241 await decoding.succeed({ b: 2 })
4242 await decoding.succeed({ a: 1, b: 2 })
4243 })
4244 })
4245
4246 it("Record(Number, String)", async () => {
4247 const schema = Schema.Record(Schema.Number, Schema.FiniteFromString)
4248 const asserts = new TestSchema.Asserts(schema)
4249
4250 const decoding = asserts.decoding()
4251 await decoding.succeed({ 1: "1", 2.2: "2", Infinity: "3", NaN: "4", "-Infinity": "5" }, {
4252 "1": 1,
4253 "2.2": 2,
4254 Infinity: 3,
4255 NaN: 4,
4256 "-Infinity": 5
4257 })
4258 await decoding.fail(
4259 { 1: null },
4260 `Expected string
4261 at ["1"]`
4262 )
4263 await decoding.fail(
4264 { 1: "a" },
4265 `Expected a finite number
4266 at ["1"]`
4267 )
4268 })
4269
4270 it("Record(Int, String)", async () => {
4271 const schema = Schema.Record(Schema.Int, Schema.FiniteFromString)
4272 const asserts = new TestSchema.Asserts(schema)
4273
4274 const decoding = asserts.decoding()
4275 await decoding.succeed({ 1: "1" }, { "1": 1 })
4276 await decoding.succeed({ 1: "1", "1.1": "ignored", Infinity: "ignored", NaN: "ignored" }, { "1": 1 })
4277 await decoding.fail(
4278 { 1: null },
4279 `Expected string
4280 at ["1"]`
4281 )
4282 await decoding.fail(
4283 { 1: "a" },
4284 `Expected a finite number
4285 at ["1"]`
4286 )
4287 })
4288
4289 it("Record(TemplateLiteral with checked part, Number) should use the part checks to select keys", async () => {
4290 const schema = Schema.Record(Schema.TemplateLiteral(["a", Schema.NonEmptyString]), Schema.Number)
4291 const asserts = new TestSchema.Asserts(schema)
4292
4293 const decoding = asserts.decoding()
4294 await decoding.succeed({ a: "ignored", ab: 1 }, { ab: 1 })
4295 await decoding.fail(
4296 { a: 1, ab: "bad" },
4297 `Expected number
4298 at ["ab"]`
4299 )
4300 })
4301
4302 it(`Record(Union(Number, "a"), FiniteFromString)`, async () => {
4303 const schema = Schema.Record(Schema.Union([Schema.Number, Schema.Literal("a")]), Schema.FiniteFromString)
4304 const asserts = new TestSchema.Asserts(schema)
4305
4306 const decoding = asserts.decoding()
4307 await decoding.succeed(
4308 { a: "-1", 1: "1", 2.2: "2", Infinity: "3", NaN: "4", "-Infinity": "5" },
4309 {
4310 a: -1,
4311 "1": 1,
4312 "2.2": 2,
4313 Infinity: 3,
4314 NaN: 4,
4315 "-Infinity": 5
4316 }
4317 )
4318 })
4319 })
4320
4321 describe("Union", () => {
4322 it("empty", async () => {
4323 const schema = Schema.Union([])
4324 const asserts = new TestSchema.Asserts(schema)
4325
4326 const decoding = asserts.decoding()
4327 await decoding.fail(null, `Expected never`)
4328 })
4329
4330 it(`String`, async () => {
4331 const schema = Schema.Union([Schema.String])
4332 const asserts = new TestSchema.Asserts(schema)
4333
4334 const decoding = asserts.decoding()
4335 await decoding.succeed("a")
4336 await decoding.fail(null, `Expected string`)
4337 })
4338
4339 it(`Void`, async () => {
4340 const schema = Schema.Union([Schema.Void])
4341 const asserts = new TestSchema.Asserts(schema)
4342
4343 const decoding = asserts.decoding()
4344 await decoding.succeed(undefined)
4345 await decoding.succeed(null, undefined)
4346 await decoding.succeed("a", undefined)
4347 await decoding.succeed(1, undefined)
4348 await decoding.succeed({}, undefined)
4349 await decoding.succeed([], undefined)
4350 })
4351
4352 it(`Void | String`, async () => {
4353 const schema = Schema.Union([Schema.Void, Schema.String])
4354 const asserts = new TestSchema.Asserts(schema)
4355
4356 const decoding = asserts.decoding()
4357 await decoding.succeed("a", undefined)
4358 })
4359
4360 it(`String | Number`, async () => {
4361 const schema = Schema.Union([Schema.String, Schema.Number])
4362 const asserts = new TestSchema.Asserts(schema)
4363
4364 deepStrictEqual(schema.members, [Schema.String, Schema.Number])
4365
4366 const decoding = asserts.decoding()
4367 await decoding.succeed("a")
4368 await decoding.succeed(1)
4369 await decoding.fail(
4370 null,
4371 `Expected string | number`
4372 )
4373 })
4374
4375 it(`String | Never`, async () => {
4376 const schema = Schema.Union([Schema.String, Schema.Never])
4377 const asserts = new TestSchema.Asserts(schema)
4378
4379 const decoding = asserts.decoding()
4380 await decoding.succeed("a")
4381 await decoding.fail(null, `Expected string | never`)
4382 })
4383
4384 it(`String & isMinLength(1) | number & isGreaterThan(0)`, async () => {
4385 const schema = Schema.Union([
4386 Schema.NonEmptyString,
4387 Schema.Number.check(Schema.isGreaterThan(0))
4388 ])
4389 const asserts = new TestSchema.Asserts(schema)
4390
4391 const decoding = asserts.decoding()
4392 await decoding.succeed("a")
4393 await decoding.succeed(1)
4394 await decoding.fail(
4395 "",
4396 `Expected a value with a length of at least 1`
4397 )
4398 await decoding.fail(
4399 -1,
4400 `Expected a value greater than 0`
4401 )
4402 })
4403
4404 it(`mode: "oneOf"`, async () => {
4405 const schema = Schema.Union([
4406 Schema.Struct({ a: Schema.String }),
4407 Schema.Struct({ b: Schema.Number })
4408 ], { mode: "oneOf" })
4409 const asserts = new TestSchema.Asserts(schema)
4410
4411 const decoding = asserts.decoding()
4412 await decoding.succeed({ a: "a" })
4413 await decoding.succeed({ b: 1 })
4414 await decoding.fail(
4415 { a: "a", b: 1 },
4416 "Expected exactly one member to match"
4417 )
4418 })
4419
4420 it(`mode: "oneOf" with different sentinel keys`, async () => {
4421 const schema = Schema.Union([
4422 Schema.Struct({ kind: Schema.Literal("a"), value: Schema.String }),
4423 Schema.Struct({ status: Schema.Literal("ready"), value: Schema.String })
4424 ], { mode: "oneOf" })
4425 const asserts = new TestSchema.Asserts(schema)
4426
4427 const decoding = asserts.decoding()
4428 await decoding.fail(
4429 { kind: "a", status: "ready", value: "value" },
4430 "Expected exactly one member to match"
4431 )
4432 })
4433
4434 it(`mode: "oneOf" with nested and contradicted sentinels`, async () => {
4435 const nested = Schema.Union([
4436 Schema.Struct({ kind: Schema.Literal("a"), variant: Schema.Literal("x") }),
4437 Schema.Struct({ kind: Schema.Literal("a"), variant: Schema.Literal("y") })
4438 ])
4439 const schema = Schema.Struct({
4440 block: Schema.Union([
4441 nested,
4442 Schema.Struct({ kind: Schema.Literal("b") })
4443 ], { mode: "oneOf" })
4444 })
4445 const decoding = new TestSchema.Asserts(schema).decoding()
4446
4447 await decoding.succeed({ block: { kind: "a", variant: "x" } })
4448 await decoding.fail(
4449 { block: { kind: "a", variant: "z" } },
4450 `Expected { readonly "kind": "a", readonly "variant": "x", ... } | { readonly "kind": "a", readonly "variant": "y", ... }
4451 at ["block"]`
4452 )
4453 await decoding.fail(
4454 { block: { kind: "a", variant: undefined } },
4455 `Expected { readonly "kind": "a", readonly "variant": "x", ... } | { readonly "kind": "a", readonly "variant": "y", ... }
4456 at ["block"]`
4457 )
4458 })
4459
4460 it(`mode: "oneOf" counts repeated member occurrences`, async () => {
4461 const member = Schema.Struct({ kind: Schema.Literal("a") })
4462 const schema = Schema.Union([member, member], { mode: "oneOf" })
4463 const asserts = new TestSchema.Asserts(schema)
4464
4465 const decoding = asserts.decoding()
4466 await decoding.fail(
4467 { kind: "a" },
4468 "Expected exactly one member to match"
4469 )
4470 })
4471
4472 it(`mode: "oneOf" counts repeated literal occurrences`, async () => {
4473 const member = Schema.Literal("a")
4474 const schema = Schema.Union([member, member], { mode: "oneOf" })
4475 const asserts = new TestSchema.Asserts(schema)
4476
4477 const decoding = asserts.decoding()
4478 await decoding.fail(
4479 "a",
4480 "Expected exactly one member to match"
4481 )
4482 })
4483
4484 it("preserves member order after sentinel dispatch", async () => {
4485 const fallback = Schema.Struct({ value: Schema.String }).pipe(
4486 Schema.decodeTo(Schema.String, {
4487 decode: SchemaGetter.transform(() => "fallback"),
4488 encode: SchemaGetter.transform((value) => ({ value }))
4489 })
4490 )
4491 const discriminated = Schema.Struct({ kind: Schema.Literal("a"), value: Schema.String }).pipe(
4492 Schema.decodeTo(Schema.String, {
4493 decode: SchemaGetter.transform(() => "discriminated"),
4494 encode: SchemaGetter.transform((value) => ({ kind: "a" as const, value }))
4495 })
4496 )
4497 const schema = Schema.Union([fallback, discriminated])
4498 const asserts = new TestSchema.Asserts(schema)
4499
4500 const decoding = asserts.decoding()
4501 await decoding.succeed({ kind: "a", value: "value" }, "fallback")
4502 })
4503
4504 it("preserves recovering members during runtime type dispatch", async () => {
4505 const decodingFallback = Schema.String.pipe(
4506 Schema.catchDecoding(() => Effect.succeed(Option.some("fallback")))
4507 )
4508 const encodingFallback = Schema.String.pipe(
4509 Schema.catchEncoding(() => Effect.succeed(Option.some("fallback")))
4510 )
4511 const transformedFallback = Schema.NumberFromString.pipe(
4512 Schema.catchDecoding(() => Effect.succeed(Option.some(0)))
4513 )
4514 const nestedFallback = Schema.Union([decodingFallback, Schema.Number])
4515 const literalFallback = Schema.Literal("a").pipe(
4516 Schema.catchDecoding(() => Effect.succeed(Option.some("a" as const)))
4517 )
4518 const record = Schema.Record(decodingFallback, Schema.String)
4519
4520 const decoding = new TestSchema.Asserts(Schema.Union([decodingFallback, Schema.Null])).decoding()
4521 await decoding.succeed(null, "fallback")
4522
4523 const transformedDecoding = new TestSchema.Asserts(Schema.Union([transformedFallback, Schema.Null])).decoding()
4524 await transformedDecoding.succeed(null, 0)
4525
4526 const nestedDecoding = new TestSchema.Asserts(Schema.Union([nestedFallback, Schema.Null])).decoding()
4527 await nestedDecoding.succeed(null, "fallback")
4528
4529 const recordDecoding = new TestSchema.Asserts(Schema.Union([record, Schema.Null])).decoding()
4530 await recordDecoding.succeed(null, null)
4531
4532 const literalDecoding = new TestSchema.Asserts(
4533 Schema.Union([literalFallback, Schema.Literal("b")])
4534 ).decoding()
4535 await literalDecoding.succeed("b", "a")
4536
4537 const decodingOneOf = new TestSchema.Asserts(
4538 Schema.Union([decodingFallback, Schema.Null], { mode: "oneOf" })
4539 ).decoding()
4540 await decodingOneOf.fail(null, "Expected exactly one member to match")
4541
4542 const encoding = new TestSchema.Asserts(Schema.Union([encodingFallback, Schema.Null])).encoding()
4543 await encoding.succeed(null, "fallback")
4544
4545 const encodingOneOf = new TestSchema.Asserts(
4546 Schema.Union([encodingFallback, Schema.Null], { mode: "oneOf" })
4547 ).encoding()
4548 await encodingOneOf.fail(null, "Expected exactly one member to match")
4549 })
4550
4551 it("preserves recovering members during sentinel dispatch", async () => {
4552 const decodingTag = Schema.Literal("a").pipe(
4553 Schema.catchDecoding(() => Effect.succeed(Option.some("a" as const)))
4554 )
4555 const encodingTag = Schema.Literal("a").pipe(
4556 Schema.catchEncoding(() => Effect.succeed(Option.some("a" as const)))
4557 )
4558 const decodingFirst = Schema.Struct({ kind: decodingTag })
4559 const encodingFirst = Schema.Struct({ kind: encodingTag })
4560 const decodingRoot = Schema.Struct({ kind: Schema.Literal("a") }).pipe(
4561 Schema.catchDecoding(() => Effect.succeed(Option.some({ kind: "a" as const })))
4562 )
4563 const second = Schema.Struct({ kind: Schema.Literal("b") })
4564 const input = { kind: "b" as const }
4565
4566 const decoding = new TestSchema.Asserts(Schema.Union([decodingFirst, second])).decoding()
4567 await decoding.succeed(input, { kind: "a" })
4568
4569 const rootDecoding = new TestSchema.Asserts(Schema.Union([decodingRoot, second])).decoding()
4570 await rootDecoding.succeed(input, { kind: "a" })
4571
4572 const decodingOneOf = new TestSchema.Asserts(
4573 Schema.Union([decodingFirst, second], { mode: "oneOf" })
4574 ).decoding()
4575 await decodingOneOf.fail(input, "Expected exactly one member to match")
4576
4577 const encoding = new TestSchema.Asserts(Schema.Union([encodingFirst, second])).encoding()
4578 await encoding.succeed(input, { kind: "a" })
4579
4580 const encodingOneOf = new TestSchema.Asserts(
4581 Schema.Union([encodingFirst, second], { mode: "oneOf" })
4582 ).encoding()
4583 await encodingOneOf.fail(input, "Expected exactly one member to match")
4584 })
4585
4586 it("keeps suspended members lazy during candidate selection", async () => {
4587 let decodingEvaluations = 0
4588 const decodingSuspended = Schema.suspend(() => {
4589 decodingEvaluations++
4590 return Schema.String
4591 })
4592 const decoding = new TestSchema.Asserts(
4593 Schema.Union([Schema.Literal("a"), decodingSuspended])
4594 ).decoding()
4595
4596 await decoding.succeed("a", "a")
4597 strictEqual(decodingEvaluations, 0)
4598 await decoding.succeed("b", "b")
4599 await decoding.succeed("c", "c")
4600 strictEqual(decodingEvaluations, 1)
4601
4602 let encodingEvaluations = 0
4603 const encodingSuspended = Schema.suspend(() => {
4604 encodingEvaluations++
4605 return Schema.String
4606 })
4607 const encoding = new TestSchema.Asserts(
4608 Schema.Union([Schema.Literal("a"), encodingSuspended])
4609 ).encoding()
4610
4611 await encoding.succeed("a", "a")
4612 strictEqual(encodingEvaluations, 0)
4613
4614 let oneOfEvaluations = 0
4615 const oneOfSuspended = Schema.suspend(() => {
4616 oneOfEvaluations++
4617 return Schema.String
4618 })
4619 const oneOf = new TestSchema.Asserts(
4620 Schema.Union([Schema.Literal("a"), oneOfSuspended], { mode: "oneOf" })
4621 ).decoding()
4622
4623 await oneOf.fail("a", "Expected exactly one member to match")
4624 strictEqual(oneOfEvaluations, 1)
4625 })
4626
4627 it("does not force a recursive suspended member after an earlier success", async () => {
4628 let evaluations = 0
4629 let recursive: Schema.Codec<"end">
4630 const suspended: Schema.Codec<"end"> = Schema.suspend(() => {
4631 evaluations++
4632 return recursive
4633 })
4634 recursive = Schema.Union([Schema.Literal("end"), suspended])
4635
4636 const decoding = new TestSchema.Asserts(recursive).decoding()
4637 await decoding.succeed("end", "end")
4638 strictEqual(evaluations, 0)
4639 })
4640
4641 it.effect("does not start later members after an asynchronous success", () =>
4642 Effect.gen(function*() {
4643 const firstStarted = yield* Deferred.make<void>()
4644 const firstLatch = yield* Deferred.make<void>()
4645 let secondCalls = 0
4646 const first = Schema.String.pipe(
4647 Schema.decode({
4648 decode: SchemaGetter.transformEffect(() =>
4649 Deferred.succeed(firstStarted, undefined).pipe(
4650 Effect.andThen(Deferred.await(firstLatch)),
4651 Effect.as("first")
4652 )
4653 ),
4654 encode: SchemaGetter.passthrough()
4655 })
4656 )
4657 const second = Schema.String.pipe(
4658 Schema.decode({
4659 decode: SchemaGetter.transform(() => {
4660 secondCalls++
4661 return "second"
4662 }),
4663 encode: SchemaGetter.passthrough()
4664 })
4665 )
4666 const fiber = yield* Schema.decodeUnknownEffect(Schema.Union([first, second]))("value").pipe(Effect.forkChild)
4667
4668 yield* Deferred.await(firstStarted)
4669 yield* Effect.yieldNow
4670 strictEqual(secondCalls, 0)
4671 yield* Deferred.succeed(firstLatch, undefined)
4672 strictEqual(yield* Fiber.join(fiber), "first")
4673 strictEqual(secondCalls, 0)
4674 }))
4675
4676 it.effect("starts the next member only after an asynchronous failure", () =>
4677 Effect.gen(function*() {
4678 const firstStarted = yield* Deferred.make<void>()
4679 const firstLatch = yield* Deferred.make<void>()
4680 let secondCalls = 0
4681 const first = Schema.String.pipe(
4682 Schema.decode({
4683 decode: SchemaGetter.transformEffect(() =>
4684 Deferred.succeed(firstStarted, undefined).pipe(
4685 Effect.andThen(Deferred.await(firstLatch)),
4686 Effect.andThen(Effect.fail(new SchemaIssue.Forbidden({ message: "first failed" })))
4687 )
4688 ),
4689 encode: SchemaGetter.passthrough()
4690 })
4691 )
4692 const second = Schema.String.pipe(
4693 Schema.decode({
4694 decode: SchemaGetter.transform(() => {
4695 secondCalls++
4696 return "second"
4697 }),
4698 encode: SchemaGetter.passthrough()
4699 })
4700 )
4701 const fiber = yield* Schema.decodeUnknownEffect(Schema.Union([first, second]))("value").pipe(Effect.forkChild)
4702
4703 yield* Deferred.await(firstStarted)
4704 yield* Effect.yieldNow
4705 strictEqual(secondCalls, 0)
4706 yield* Deferred.succeed(firstLatch, undefined)
4707 strictEqual(yield* Fiber.join(fiber), "second")
4708 strictEqual(secondCalls, 1)
4709 }))
4710
4711 it.effect(`mode: "oneOf" detects asynchronous successes in member order`, () =>
4712 Effect.gen(function*() {
4713 const firstStarted = yield* Deferred.make<void>()
4714 const firstLatch = yield* Deferred.make<void>()
4715 let secondCalls = 0
4716 const first = Schema.String.pipe(
4717 Schema.decode({
4718 decode: SchemaGetter.transformEffect(() =>
4719 Deferred.succeed(firstStarted, undefined).pipe(
4720 Effect.andThen(Deferred.await(firstLatch)),
4721 Effect.as("first")
4722 )
4723 ),
4724 encode: SchemaGetter.passthrough()
4725 })
4726 )
4727 const second = Schema.String.pipe(
4728 Schema.decode({
4729 decode: SchemaGetter.transform(() => {
4730 secondCalls++
4731 return "second"
4732 }),
4733 encode: SchemaGetter.passthrough()
4734 })
4735 )
4736 const fiber = yield* Schema.decodeUnknownEffect(Schema.Union([first, second], { mode: "oneOf" }))(
4737 "value"
4738 ).pipe(Effect.exit, Effect.forkChild)
4739
4740 yield* Deferred.await(firstStarted)
4741 yield* Effect.yieldNow
4742 strictEqual(secondCalls, 0)
4743 yield* Deferred.succeed(firstLatch, undefined)
4744 const exit = yield* Fiber.join(fiber)
4745 strictEqual(secondCalls, 1)
4746 strictEqual(exit._tag, "Failure")
4747 if (exit._tag === "Failure") {
4748 const reason = exit.cause.reasons[0]
4749 strictEqual(reason._tag, "Fail")
4750 if (reason._tag === "Fail") {
4751 assertTrue(Schema.isSchemaError(reason.error))
4752 if (Schema.isSchemaError(reason.error)) {
4753 strictEqual(reason.error.issue._tag, "OneOf")
4754 if (reason.error.issue._tag === "OneOf") {
4755 deepStrictEqual(reason.error.issue.successes, [first.ast, second.ast])
4756 }
4757 }
4758 }
4759 }
4760 }))
4761
4762 it(`mode: "oneOf" with Void`, async () => {
4763 const schema = Schema.Union([Schema.Void, Schema.String], { mode: "oneOf" })
4764 const asserts = new TestSchema.Asserts(schema)
4765
4766 const decoding = asserts.decoding()
4767 await decoding.fail("a", "Expected exactly one member to match")
4768 })
4769
4770 it("Struct({}) preserves its semantics in a union", async () => {
4771 const schema = Schema.Union([
4772 Schema.Struct({}),
4773 Schema.Null
4774 ])
4775 const asserts = new TestSchema.Asserts(schema)
4776
4777 const decoding = asserts.decoding()
4778 const symbol = Symbol()
4779 const fn = () => {}
4780 await decoding.succeed("a")
4781 await decoding.succeed(1)
4782 await decoding.succeed(true)
4783 await decoding.succeed(symbol)
4784 await decoding.succeed(1n)
4785 await decoding.succeed(fn)
4786 await decoding.succeed({})
4787 await decoding.succeed([])
4788 await decoding.succeed(null)
4789 await decoding.fail(undefined, `Expected object | array | null`)
4790 })
4791
4792 describe("should exclude members based on failed sentinels", () => {
4793 it("string | struct", async () => {
4794 const schema = Schema.Union([
4795 Schema.String,
4796 Schema.Struct({ _tag: Schema.Literal("a"), a: Schema.String })
4797 ])
4798 const asserts = new TestSchema.Asserts(schema)
4799
4800 const decoding = asserts.decoding()
4801 await decoding.fail(
4802 {},
4803 `Expected string | { readonly "_tag": "a", ... }`
4804 )
4805 })
4806
4807 it("string | tuple", async () => {
4808 const schema = Schema.Union([
4809 Schema.String,
4810 Schema.Tuple([Schema.Literal("a"), Schema.String])
4811 ])
4812 const asserts = new TestSchema.Asserts(schema)
4813
4814 const decoding = asserts.decoding()
4815 await decoding.fail(
4816 [],
4817 `Expected string | readonly [ "a", ... ]`
4818 )
4819 })
4820
4821 it("tagged struct union", async () => {
4822 const schema = Schema.Union([
4823 Schema.Struct({ _tag: Schema.Literal("a"), a: Schema.String }),
4824 Schema.Struct({ _tag: Schema.Literal("b"), b: Schema.Number })
4825 ])
4826 const asserts = new TestSchema.Asserts(schema)
4827
4828 const decoding = asserts.decoding()
4829 await decoding.fail(
4830 { _tag: "a" },
4831 `Missing key
4832 at ["a"]`
4833 )
4834 await decoding.fail(
4835 { _tag: "b" },
4836 `Missing key
4837 at ["b"]`
4838 )
4839 await decoding.fail(
4840 { _tag: "c" },
4841 `Expected { readonly "_tag": "a", ... } | { readonly "_tag": "b", ... }`
4842 )
4843 })
4844
4845 it("tagged tuple union", async () => {
4846 const schema = Schema.Union([
4847 Schema.Tuple([Schema.Literal("a"), Schema.String]),
4848 Schema.Tuple([Schema.Literal("b"), Schema.Number])
4849 ])
4850 const asserts = new TestSchema.Asserts(schema)
4851
4852 const decoding = asserts.decoding()
4853 await decoding.fail(
4854 ["a"],
4855 `Missing key
4856 at [1]`
4857 )
4858 await decoding.fail(
4859 ["b"],
4860 `Missing key
4861 at [1]`
4862 )
4863 await decoding.fail(
4864 ["c"],
4865 `Expected readonly [ "a", ... ] | readonly [ "b", ... ]`
4866 )
4867 })
4868 })
4869 })
4870
4871 describe("TupleWithRest", () => {
4872 it("A required element cannot follow an optional element", () => {
4873 throws(
4874 () =>
4875 Schema.TupleWithRest(
4876 Schema.Tuple([Schema.optionalKey(Schema.String)]),
4877 [Schema.Boolean, Schema.String]
4878 ),
4879 new Error("A required element cannot follow an optional element. ts(1257)")
4880 )
4881 throws(
4882 () =>
4883 Schema.TupleWithRest(
4884 Schema.Tuple([Schema.optional(Schema.String)]),
4885 [Schema.Boolean, Schema.String]
4886 ),
4887 new Error("A required element cannot follow an optional element. ts(1257)")
4888 )
4889 })
4890
4891 it("An optional element cannot follow a rest element", () => {
4892 throws(
4893 () =>
4894 Schema.TupleWithRest(
4895 Schema.Tuple([]),
4896 [Schema.Boolean, Schema.optionalKey(Schema.String)]
4897 ),
4898 new Error("An optional element cannot follow a rest element. ts(1266)")
4899 )
4900 throws(
4901 () =>
4902 Schema.TupleWithRest(
4903 Schema.Tuple([]),
4904 [Schema.Boolean, Schema.optional(Schema.String)]
4905 ),
4906 new Error("An optional element cannot follow a rest element. ts(1266)")
4907 )
4908 })
4909
4910 it("[FiniteFromString, String] + [Boolean, String]", async () => {
4911 const schema = Schema.TupleWithRest(
4912 Schema.Tuple([Schema.FiniteFromString, Schema.String]),
4913 [Schema.Boolean, Schema.String]
4914 )
4915 const asserts = new TestSchema.Asserts(schema)
4916
4917 const decoding = asserts.decoding()
4918 await decoding.succeed(["1", "a", "b"], [1, "a", "b"])
4919 await decoding.succeed(["1", "a", true, "b"], [1, "a", true, "b"])
4920 await decoding.succeed(["1", "a", true, true, "b"], [1, "a", true, true, "b"])
4921 await decoding.fail(
4922 ["1", "a"],
4923 `Missing key
4924 at [2]`
4925 )
4926 await decoding.fail(
4927 ["1", "a", true],
4928 `Expected string
4929 at [2]`
4930 )
4931 await decoding.fail(
4932 ["1", "a", "b", "c"],
4933 `Expected boolean
4934 at [2]`
4935 )
4936 await decoding.fail(
4937 ["1", "a", true, "b", "c"],
4938 `Expected boolean
4939 at [3]`
4940 )
4941
4942 const encoding = asserts.encoding()
4943 await encoding.succeed([1, "a", "b"], ["1", "a", "b"])
4944 await encoding.succeed([1, "a", true, "b"], ["1", "a", true, "b"])
4945 })
4946
4947 it("[FiniteFromString, String] + [Boolean, String, Number]", async () => {
4948 const schema = Schema.TupleWithRest(
4949 Schema.Tuple([Schema.FiniteFromString, Schema.String]),
4950 [Schema.Boolean, Schema.String, Schema.FiniteFromString]
4951 )
4952 const asserts = new TestSchema.Asserts(schema)
4953
4954 const decoding = asserts.decoding()
4955 await decoding.succeed(["1", "a", "b", "2"], [1, "a", "b", 2])
4956 await decoding.succeed(["1", "a", true, "b", "2"], [1, "a", true, "b", 2])
4957 await decoding.succeed(["1", "a", true, true, "b", "2"], [1, "a", true, true, "b", 2])
4958 await decoding.fail(
4959 ["1", "a"],
4960 `Missing key
4961 at [2]`
4962 )
4963 await decoding.fail(
4964 ["1", "a", "b"],
4965 `Missing key
4966 at [3]`
4967 )
4968 await decoding.fail(
4969 ["1", "a", "b", "c"],
4970 `Expected a finite number
4971 at [3]`
4972 )
4973
4974 const encoding = asserts.encoding()
4975 await encoding.succeed([1, "a", "b", 2], ["1", "a", "b", "2"])
4976 await encoding.succeed([1, "a", true, "b", 2], ["1", "a", true, "b", "2"])
4977 })
4978
4979 it("[String] + [Boolean, String, Number, Number] validates every post-rest index", async () => {
4980 const schema = Schema.TupleWithRest(
4981 Schema.Tuple([Schema.String]),
4982 [Schema.Boolean, Schema.String, Schema.FiniteFromString, Schema.FiniteFromString]
4983 )
4984 const asserts = new TestSchema.Asserts(schema)
4985
4986 const decoding = asserts.decoding()
4987 await decoding.fail(
4988 ["a", true, "b", "1", "x"],
4989 `Expected a finite number
4990 at [4]`
4991 )
4992 await decoding.succeed(["a", true, "b", "1", "2"], ["a", true, "b", 1, 2])
4993 })
4994 })
4995
4996 describe("StructWithRest", () => {
4997 it("should throw an error if there are duplicate property signatures", () => {
4998 throws(
4999 () =>
5000 Schema.StructWithRest(
5001 Schema.Struct({ a: Schema.String }),
5002 [Schema.Record(Schema.Literals(["a", "b"]), Schema.Number)]
5003 ),
5004 new Error(`Duplicate identifier: "a". ts(2300)`)
5005 )
5006 })
5007
5008 it("should throw an error if there are encodings", () => {
5009 throws(
5010 () =>
5011 Schema.StructWithRest(
5012 Schema.Struct({}).pipe(Schema.encodeTo(Schema.Struct({}))),
5013 [Schema.Record(Schema.String, Schema.Number)]
5014 ),
5015 new Error(`StructWithRest does not support encodings`)
5016 )
5017 })
5018
5019 it("Record(String, Number)", async () => {
5020 const schema = Schema.StructWithRest(
5021 Schema.Struct({ a: Schema.Number }),
5022 [Schema.Record(Schema.String, Schema.Number)]
5023 )
5024 const asserts = new TestSchema.Asserts(schema)
5025
5026 const decoding = asserts.decoding()
5027 await decoding.succeed({ a: 1 })
5028 await decoding.succeed({ a: 1, b: 2 })
5029 await decoding.fail(
5030 { a: 1, b: "" },
5031 `Expected number
5032 at ["b"]`
5033 )
5034 })
5035
5036 it("Record(Symbol, Number)", async () => {
5037 const schema = Schema.StructWithRest(
5038 Schema.Struct({ a: Schema.Number }),
5039 [Schema.Record(Schema.Symbol, Schema.Number)]
5040 )
5041 const asserts = new TestSchema.Asserts(schema)
5042
5043 const decoding = asserts.decoding()
5044 await decoding.succeed({ a: 1 })
5045 await decoding.succeed({ a: 1, [Symbol.for("b")]: 2 })
5046 await decoding.fail(
5047 { a: 1, [Symbol.for("b")]: "c" },
5048 `Expected number
5049 at [Symbol(b)]`
5050 )
5051 })
5052
5053 it("Record(`a${string}`, Number)", async () => {
5054 const schema = Schema.StructWithRest(
5055 Schema.Struct({ a: Schema.Number }),
5056 [Schema.Record(Schema.TemplateLiteral(["a", Schema.String]), Schema.Finite)]
5057 )
5058 const asserts = new TestSchema.Asserts(schema)
5059
5060 const decoding = asserts.decoding()
5061 await decoding.succeed({ a: 1 })
5062 await decoding.succeed({ a: 1, "ab": 2 })
5063 await decoding.fail(
5064 { a: NaN, "ab": 2 },
5065 `Expected a finite number
5066 at ["a"]`
5067 )
5068 await decoding.fail(
5069 { a: 1, "ab": "c" },
5070 `Expected number
5071 at ["ab"]`
5072 )
5073 })
5074
5075 it("applies excess-property options after fixed and index signatures", () => {
5076 const schema = Schema.StructWithRest(
5077 Schema.Struct({ fixed: Schema.Number }),
5078 [Schema.Record(Schema.String.check(Schema.isPattern(/^a/)), Schema.Number)]
5079 )
5080 const input = { fixed: 0, a: 1, b: 2 }
5081 const expected = { fixed: 0, a: 1 }
5082
5083 deepStrictEqual(Schema.decodeUnknownSync(schema)(input, { onExcessProperty: "ignore" }), expected)
5084 deepStrictEqual(Schema.encodeUnknownSync(schema)(input, { onExcessProperty: "ignore" }), expected)
5085 throws(
5086 () => Schema.decodeUnknownSync(schema)(input, { onExcessProperty: "error" }),
5087 `Expected no excess property\n at ["b"]`
5088 )
5089 throws(
5090 () => Schema.encodeUnknownSync(schema)(input, { onExcessProperty: "error" }),
5091 `Expected no excess property\n at ["b"]`
5092 )
5093 })
5094
5095 it("should preserve both checks", async () => {
5096 const schema = Schema.StructWithRest(
5097 Schema.Struct({ a: Schema.Number }).check(
5098 Schema.makeFilter((s) => s.a > 0, { expected: "agt(0)" })
5099 ),
5100 [
5101 Schema.Record(Schema.String, Schema.Number).check(
5102 Schema.makeFilter((s) => s.b === undefined || s.b > 1, { expected: "bgt(1)" })
5103 )
5104 ]
5105 )
5106 const asserts = new TestSchema.Asserts(schema)
5107
5108 const decoding = asserts.decoding()
5109 await decoding.succeed({ a: 1 })
5110 await decoding.succeed({ a: 1, b: 2 })
5111 await decoding.fail(
5112 { a: 0 },
5113 `Expected agt(0)`
5114 )
5115 await decoding.fail(
5116 { a: 1, b: 1 },
5117 `Expected bgt(1)`
5118 )
5119 })
5120
5121 it("index signatures should not re-parse fixed properties", async () => {
5122 const Trimmed = Schema.String.pipe(Schema.decodeTo(Schema.String, SchemaTransformation.trim()))
5123 const schema = Schema.StructWithRest(
5124 Schema.Struct({ a: Trimmed }),
5125 [Schema.Record(Schema.String, Schema.String)]
5126 )
5127 const asserts = new TestSchema.Asserts(schema)
5128
5129 const decoding = asserts.decoding()
5130 await decoding.succeed({ a: " x ", b: "y" }, { a: "x", b: "y" })
5131
5132 const encoding = asserts.encoding()
5133 await encoding.succeed({ a: "x", b: "y" })
5134 })
5135
5136 it("index signatures should not overwrite fixed properties after key decoding", async () => {
5137 const UppercaseKey = Schema.String.pipe(Schema.decodeTo(Schema.String, SchemaTransformation.toUpperCase()))
5138 const schema = Schema.StructWithRest(
5139 Schema.Struct({ A: Schema.String }),
5140 [Schema.Record(UppercaseKey, Schema.String)]
5141 )
5142 const asserts = new TestSchema.Asserts(schema)
5143
5144 const decoding = asserts.decoding()
5145 await decoding.succeed({ A: "fixed", a: "rest" }, { A: "fixed" })
5146 })
5147 })
5148
5149 describe("NullOr", () => {
5150 it("NullOr(String)", async () => {
5151 const schema = Schema.NullOr(Schema.NonEmptyString)
5152 const asserts = new TestSchema.Asserts(schema)
5153
5154 const decoding = asserts.decoding()
5155 await decoding.succeed("a")
5156 await decoding.succeed(null)
5157 await decoding.fail(undefined, `Expected string | null`)
5158 await decoding.fail(
5159 "",
5160 `Expected a value with a length of at least 1`
5161 )
5162 })
5163 })
5164
5165 describe("UndefinedOr", () => {
5166 it("UndefinedOr(String)", async () => {
5167 const schema = Schema.UndefinedOr(Schema.NonEmptyString)
5168 const asserts = new TestSchema.Asserts(schema)
5169
5170 const decoding = asserts.decoding()
5171 await decoding.succeed("a")
5172 await decoding.succeed(undefined)
5173 await decoding.fail(null, `Expected string | undefined`)
5174 await decoding.fail(
5175 "",
5176 `Expected a value with a length of at least 1`
5177 )
5178 })
5179 })
5180
5181 describe("NullishOr", () => {
5182 it("NullishOr(String)", async () => {
5183 const schema = Schema.NullishOr(Schema.NonEmptyString)
5184 const asserts = new TestSchema.Asserts(schema)
5185
5186 const decoding = asserts.decoding()
5187 await decoding.succeed("a")
5188 await decoding.succeed(null)
5189 await decoding.succeed(undefined)
5190 await decoding.fail(
5191 "",
5192 `Expected a value with a length of at least 1`
5193 )
5194 })
5195 })
5196
5197 it("PropertyKey", async () => {
5198 const schema = Schema.PropertyKey
5199 const asserts = new TestSchema.Asserts(schema)
5200 if (verifyGeneration) {
5201 asserts.arbitrary().verifyGeneration()
5202 }
5203 })
5204
5205 it("BooleanFromBit", async () => {
5206 const schema = Schema.BooleanFromBit
5207 const asserts = new TestSchema.Asserts(schema)
5208 if (verifyGeneration) {
5209 asserts.arbitrary().verifyGeneration()
5210 }
5211 })
5212
5213 it("Uint8Array", async () => {
5214 const schema = Schema.Uint8Array
5215 const asserts = new TestSchema.Asserts(schema)
5216 if (verifyGeneration) {
5217 asserts.arbitrary().verifyGeneration()
5218 }
5219 })
5220
5221 it("Uint8ArrayFromBase64", async () => {
5222 const schema = Schema.Uint8ArrayFromBase64
5223 const asserts = new TestSchema.Asserts(schema)
5224 const encoder = new TextEncoder()
5225
5226 if (verifyGeneration) {
5227 asserts.arbitrary().verifyGeneration()
5228 }
5229
5230 const decoding = asserts.decoding()
5231 await decoding.succeed("Zm9vYmFy", encoder.encode("foobar"))
5232 await decoding.fail("Zm9vY", "Expected a valid Base64 string")
5233 await decoding.fail("Zm9vYmF-", "Expected a valid Base64 string")
5234 await decoding.fail("=Zm9vYmF", "Expected a valid Base64 string")
5235
5236 const encoding = asserts.encoding()
5237 await encoding.succeed(encoder.encode("foobar"), "Zm9vYmFy")
5238 })
5239
5240 it("StringFromBase64", async () => {
5241 const schema = Schema.StringFromBase64
5242 const asserts = new TestSchema.Asserts(schema)
5243
5244 const decoding = asserts.decoding()
5245 await decoding.succeed("Zm9vYmFy", "foobar")
5246 await decoding.fail("Zm9vY", "Expected a valid Base64 string")
5247 await decoding.fail("Zm9vYmF-", "Expected a valid Base64 string")
5248 await decoding.fail("=Zm9vYmF", "Expected a valid Base64 string")
5249
5250 const encoding = asserts.encoding()
5251 await encoding.succeed("foobar", "Zm9vYmFy")
5252 })
5253
5254 it("StringFromBase64Url", async () => {
5255 const schema = Schema.StringFromBase64Url
5256 const asserts = new TestSchema.Asserts(schema)
5257
5258 const decoding = asserts.decoding()
5259 await decoding.succeed("Zm9vYmFy", "foobar")
5260 await decoding.fail("Zm9vY", "Expected a valid Base64Url string")
5261 await decoding.succeed("Pj8-ZD_Dnw", ">?>d?\u00DF")
5262 await decoding.fail("Pj8/ZD+Dnw", "Expected a valid Base64Url string")
5263
5264 const encoding = asserts.encoding()
5265 await encoding.succeed("foobar", "Zm9vYmFy")
5266 await encoding.succeed(">?>d?\u00DF", "Pj8-ZD_Dnw")
5267 })
5268
5269 it("StringFromHex", async () => {
5270 const schema = Schema.StringFromHex
5271 const asserts = new TestSchema.Asserts(schema)
5272
5273 const decoding = asserts.decoding()
5274 await decoding.succeed("67", "g")
5275 await decoding.fail("0", "Expected a valid hexadecimal string")
5276 await decoding.fail("zd4aa", "Expected a valid hexadecimal string")
5277 await decoding.fail("0\x01", "Expected a valid hexadecimal string")
5278
5279 const encoding = asserts.encoding()
5280 await encoding.succeed("g", "67")
5281 })
5282
5283 it("StringFromUriComponent", async () => {
5284 const schema = Schema.StringFromUriComponent
5285 const asserts = new TestSchema.Asserts(schema)
5286
5287 const decoding = asserts.decoding()
5288 await decoding.succeed("%7B%22a%22%3A1%7D", "{\"a\":1}")
5289 await decoding.succeed("%D1%88%D0%B5%D0%BB%D0%BB%D1%8B", "шеллы")
5290 await decoding.succeed("hello%20world", "hello world")
5291 await decoding.succeed("hello", "hello")
5292 await decoding.fail("%ZZ", "Expected a valid URI component")
5293
5294 const encoding = asserts.encoding()
5295 await encoding.succeed("{\"a\":1}", "%7B%22a%22%3A1%7D")
5296 await encoding.succeed("hello world", "hello%20world")
5297 })
5298
5299 it("Uint8ArrayFromBase64Url", async () => {
5300 const schema = Schema.Uint8ArrayFromBase64Url
5301 const asserts = new TestSchema.Asserts(schema)
5302 const encoder = new TextEncoder()
5303
5304 if (verifyGeneration) {
5305 asserts.arbitrary().verifyGeneration()
5306 }
5307
5308 const decoding = asserts.decoding()
5309 await decoding.succeed("Zm9vYmFy", encoder.encode("foobar"))
5310 await decoding.fail("Zm9vY", "Expected a valid Base64Url string")
5311 await decoding.succeed("Pj8-ZD_Dnw", encoder.encode(">?>d?ß"))
5312 await decoding.fail("Pj8/ZD+Dnw", "Expected a valid Base64Url string")
5313
5314 const encoding = asserts.encoding()
5315 await encoding.succeed(encoder.encode("foobar"), "Zm9vYmFy")
5316 await encoding.succeed(encoder.encode(">?>d?ß"), "Pj8-ZD_Dnw")
5317 })
5318
5319 it("Uint8ArrayFromHex", async () => {
5320 const schema = Schema.Uint8ArrayFromHex
5321 const asserts = new TestSchema.Asserts(schema)
5322 const encoder = new TextEncoder()
5323
5324 if (verifyGeneration) {
5325 asserts.arbitrary().verifyGeneration()
5326 }
5327
5328 const decoding = asserts.decoding()
5329 await decoding.succeed(
5330 "0001020304050607",
5331 Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7])
5332 )
5333 await decoding.succeed(
5334 "f0f1f2f3f4f5f6f7",
5335 Uint8Array.from([0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7])
5336 )
5337 await decoding.succeed("67", encoder.encode("g"))
5338 await decoding.fail("0", "Expected a valid hexadecimal string")
5339 await decoding.fail("2d4aa", "Expected a valid hexadecimal string")
5340 await decoding.fail("0\x01", "Expected a valid hexadecimal string")
5341
5342 const encoding = asserts.encoding()
5343 await encoding.succeed(Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7]), "0001020304050607")
5344 })
5345
5346 it("Date", async () => {
5347 const schema = Schema.Date
5348 const asserts = new TestSchema.Asserts(schema)
5349
5350 if (verifyGeneration) {
5351 asserts.arbitrary().verifyGeneration()
5352 }
5353
5354 const decoding = asserts.decoding()
5355 await decoding.succeed(new Date("2021-01-01"))
5356 await decoding.fail(new Date(NaN), `Expected a valid Date`)
5357 await decoding.fail(null, `Expected a valid Date`)
5358 await decoding.fail(0, `Expected a valid Date`)
5359
5360 const encoding = asserts.encoding()
5361 await encoding.succeed(new Date("2021-01-01"))
5362 await encoding.fail(new Date(NaN), `Expected a valid Date`)
5363 })
5364
5365 it("DateTimeUtc", async () => {
5366 const schema = Schema.DateTimeUtc
5367 const asserts = new TestSchema.Asserts(schema)
5368
5369 if (verifyGeneration) {
5370 asserts.arbitrary().verifyGeneration()
5371 }
5372
5373 const decoding = asserts.decoding()
5374 await decoding.succeed(DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"))
5375
5376 const encoding = asserts.encoding()
5377 await encoding.succeed(DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"))
5378 })
5379
5380 it("DateTimeUtcFromValidDate", async () => {
5381 const schema = Schema.DateTimeUtcFromDate
5382 const asserts = new TestSchema.Asserts(schema)
5383
5384 if (verifyGeneration) {
5385 asserts.arbitrary().verifyGeneration()
5386 }
5387
5388 const decoding = asserts.decoding()
5389 await decoding.succeed(new Date("2021-01-01T00:00:00.000Z"), DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"))
5390 await decoding.fail(new Date("invalid date"), `Expected a valid Date`)
5391
5392 const encoding = asserts.encoding()
5393 await encoding.succeed(DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"), new Date("2021-01-01T00:00:00.000Z"))
5394 })
5395
5396 it("DateTimeUtcFromString", async () => {
5397 const schema = Schema.DateTimeUtcFromString
5398 const asserts = new TestSchema.Asserts(schema)
5399
5400 if (verifyGeneration) {
5401 asserts.arbitrary().verifyGeneration()
5402 }
5403
5404 const decoding = asserts.decoding()
5405 await decoding.succeed("2021-01-01T00:00:00.000Z", DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"))
5406 await decoding.fail("invalid", "Expected a valid UTC DateTime string")
5407 await decoding.fail(null, `Expected string`)
5408
5409 const encoding = asserts.encoding()
5410 await encoding.succeed(DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"), "2021-01-01T00:00:00.000Z")
5411 })
5412
5413 it("DateTimeUtcFromMillis", async () => {
5414 const schema = Schema.DateTimeUtcFromMillis
5415 const asserts = new TestSchema.Asserts(schema)
5416
5417 if (verifyGeneration) {
5418 asserts.arbitrary().verifyGeneration()
5419 }
5420
5421 const decoding = asserts.decoding()
5422 await decoding.succeed(1609459200000, DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"))
5423 await decoding.fail(null, `Expected number`)
5424
5425 const encoding = asserts.encoding()
5426 await encoding.succeed(DateTime.makeUnsafe("2021-01-01T00:00:00.000Z"), 1609459200000)
5427 })
5428
5429 it("TimeZoneOffset", async () => {
5430 const schema = Schema.TimeZoneOffset
5431 const asserts = new TestSchema.Asserts(schema)
5432
5433 if (verifyGeneration) {
5434 asserts.arbitrary().verifyGeneration()
5435 }
5436
5437 const decoding = asserts.decoding()
5438 await decoding.succeed(DateTime.zoneMakeOffset(3 * 60 * 60 * 1000))
5439
5440 const encoding = asserts.encoding()
5441 await encoding.succeed(DateTime.zoneMakeOffset(3 * 60 * 60 * 1000))
5442 })
5443
5444 it("TimeZoneNamed", async () => {
5445 const schema = Schema.TimeZoneNamed
5446 const asserts = new TestSchema.Asserts(schema)
5447
5448 if (verifyGeneration) {
5449 asserts.arbitrary().verifyGeneration()
5450 }
5451
5452 const decoding = asserts.decoding()
5453 await decoding.succeed(DateTime.zoneMakeNamedUnsafe("Europe/London"))
5454
5455 const encoding = asserts.encoding()
5456 await encoding.succeed(DateTime.zoneMakeNamedUnsafe("Europe/London"))
5457 })
5458
5459 it("TimeZone", async () => {
5460 const schema = Schema.TimeZone
5461 const asserts = new TestSchema.Asserts(schema)
5462
5463 if (verifyGeneration) {
5464 asserts.arbitrary().verifyGeneration()
5465 }
5466
5467 const decoding = asserts.decoding()
5468 await decoding.succeed(DateTime.zoneMakeOffset(3 * 60 * 60 * 1000))
5469 await decoding.succeed(DateTime.zoneMakeNamedUnsafe("Europe/London"))
5470
5471 const encoding = asserts.encoding()
5472 await encoding.succeed(DateTime.zoneMakeOffset(3 * 60 * 60 * 1000))
5473 await encoding.succeed(DateTime.zoneMakeNamedUnsafe("Europe/London"))
5474 })
5475
5476 it("DateTimeZoned", async () => {
5477 const schema = Schema.DateTimeZoned
5478 const asserts = new TestSchema.Asserts(schema)
5479
5480 if (verifyGeneration) {
5481 asserts.arbitrary().verifyGeneration()
5482 }
5483
5484 const decoding = asserts.decoding()
5485 await decoding.succeed(DateTime.makeZonedUnsafe("2021-01-01T00:00:00.000Z", { timeZone: "Europe/London" }))
5486
5487 const encoding = asserts.encoding()
5488 await encoding.succeed(DateTime.makeZonedUnsafe("2021-01-01T00:00:00.000Z", { timeZone: "Europe/London" }))
5489 })
5490
5491 it("ReadonlySet", async () => {
5492 const schema = Schema.ReadonlySet(Schema.FiniteFromString)
5493 const asserts = new TestSchema.Asserts(schema)
5494
5495 strictEqual(schema.value, Schema.FiniteFromString)
5496 strictEqual(schema.annotate({}).value, Schema.FiniteFromString)
5497
5498 if (verifyGeneration) {
5499 asserts.arbitrary().verifyGeneration()
5500 }
5501
5502 const decoding = asserts.decoding()
5503 await decoding.succeed(new Set(["1", "2", "3"]), new Set([1, 2, 3]))
5504 await decoding.fail(null, `Expected ReadonlySet`)
5505 await decoding.fail(
5506 new Set(["1", "2", null]),
5507 `Expected string
5508 at ["values"][2]`
5509 )
5510 })
5511
5512 it("HashSet", async () => {
5513 const schema = Schema.HashSet(Schema.FiniteFromString)
5514 const asserts = new TestSchema.Asserts(schema)
5515
5516 strictEqual(schema.value, Schema.FiniteFromString)
5517 strictEqual(schema.annotate({}).value, Schema.FiniteFromString)
5518
5519 if (verifyGeneration) {
5520 asserts.arbitrary().verifyGeneration()
5521 }
5522
5523 const decoding = asserts.decoding()
5524 await decoding.succeed(HashSet.make("1", "2", "3"), HashSet.make(1, 2, 3))
5525 await decoding.fail(null, `Expected HashSet`)
5526 await decoding.fail(
5527 HashSet.make(null),
5528 `Expected string
5529 at ["values"][0]`
5530 )
5531
5532 const encoding = asserts.encoding()
5533 await encoding.succeed(HashSet.make(1, 2, 3), HashSet.make("1", "2", "3"))
5534 })
5535
5536 it("Chunk", async () => {
5537 const schema = Schema.Chunk(Schema.FiniteFromString)
5538 const asserts = new TestSchema.Asserts(schema)
5539
5540 strictEqual(schema.value, Schema.FiniteFromString)
5541 strictEqual(schema.annotate({}).value, Schema.FiniteFromString)
5542
5543 if (verifyGeneration) {
5544 asserts.arbitrary().verifyGeneration()
5545 }
5546
5547 const decoding = asserts.decoding()
5548 await decoding.succeed(Chunk.make("1", "2", "3"), Chunk.make(1, 2, 3))
5549 await decoding.fail(null, `Expected Chunk`)
5550 await decoding.fail(
5551 Chunk.make(null),
5552 `Expected string
5553 at ["values"][0]`
5554 )
5555
5556 const encoding = asserts.encoding()
5557 await encoding.succeed(Chunk.make(1, 2, 3), Chunk.make("1", "2", "3"))
5558 })
5559
5560 it("ReadonlyMap", async () => {
5561 const schema = Schema.ReadonlyMap(Schema.String, Schema.FiniteFromString)
5562 const asserts = new TestSchema.Asserts(schema)
5563
5564 strictEqual(schema.key, Schema.String)
5565 strictEqual(schema.annotate({}).key, Schema.String)
5566 strictEqual(schema.value, Schema.FiniteFromString)
5567 strictEqual(schema.annotate({}).value, Schema.FiniteFromString)
5568
5569 if (verifyGeneration) {
5570 asserts.arbitrary().verifyGeneration()
5571 }
5572
5573 const decoding = asserts.decoding()
5574 await decoding.succeed(new Map([["a", "1"]]), new Map([["a", 1]]))
5575 await decoding.fail(null, `Expected ReadonlyMap`)
5576 await decoding.fail(
5577 new Map([["a", null]]),
5578 `Expected string
5579 at ["entries"][0][1]`
5580 )
5581
5582 const encoding = asserts.encoding()
5583 await encoding.succeed(new Map([["a", 1]]), new Map([["a", "1"]]))
5584 })
5585
5586 it("HashMap", async () => {
5587 const schema = Schema.HashMap(Schema.String, Schema.FiniteFromString)
5588 const asserts = new TestSchema.Asserts(schema)
5589
5590 strictEqual(schema.key, Schema.String)
5591 strictEqual(schema.annotate({}).key, Schema.String)
5592 strictEqual(schema.value, Schema.FiniteFromString)
5593 strictEqual(schema.annotate({}).value, Schema.FiniteFromString)
5594
5595 if (verifyGeneration) {
5596 asserts.arbitrary().verifyGeneration()
5597 }
5598
5599 const decoding = asserts.decoding()
5600 await decoding.succeed(HashMap.make(["a", "1"]), HashMap.make(["a", 1]))
5601 await decoding.fail(null, `Expected HashMap`)
5602 await decoding.fail(
5603 HashMap.make(["a", null]),
5604 `Expected string
5605 at ["entries"][0][1]`
5606 )
5607
5608 const encoding = asserts.encoding()
5609 await encoding.succeed(HashMap.make(["a", 1]), HashMap.make(["a", "1"]))
5610 })
5611
5612 describe("Transformations", () => {
5613 it("toLowerCase", async () => {
5614 const schema = Schema.String.pipe(
5615 Schema.decodeTo(
5616 Schema.String,
5617 SchemaTransformation.toLowerCase()
5618 )
5619 )
5620 const asserts = new TestSchema.Asserts(schema)
5621
5622 const decoding = asserts.decoding()
5623 await decoding.succeed("A", "a")
5624 await decoding.succeed("B", "b")
5625 })
5626
5627 it("toUpperCase", async () => {
5628 const schema = Schema.String.pipe(
5629 Schema.decodeTo(Schema.String, SchemaTransformation.toUpperCase())
5630 )
5631 const asserts = new TestSchema.Asserts(schema)
5632
5633 const decoding = asserts.decoding()
5634 await decoding.succeed("a", "A")
5635 await decoding.succeed("b", "B")
5636 })
5637 })
5638
5639 describe("Opaque", () => {
5640 it("returns the original schema", () => {
5641 const schema = Schema.Struct({ a: Schema.String })
5642
5643 strictEqual(Schema.Opaque<{ readonly a: string }>()(schema), schema)
5644 })
5645
5646 it("Struct", () => {
5647 class A extends Schema.Opaque<A>()(Schema.Struct({ a: Schema.String })) {}
5648
5649 const schema = A
5650
5651 const instance = schema.make({ a: "a" })
5652 strictEqual(instance.a, "a")
5653 deepStrictEqual(A.fields, { a: Schema.String })
5654 })
5655 })
5656
5657 describe("instanceOf", () => {
5658 it("arg: message: string", async () => {
5659 class MyError extends Error {
5660 constructor(message?: string) {
5661 super(message)
5662 this.name = "MyError"
5663 Object.setPrototypeOf(this, MyError.prototype)
5664 }
5665 }
5666
5667 const schema = Schema.instanceOf(
5668 MyError,
5669 { expected: "MyError" }
5670 )
5671 const asserts = new TestSchema.Asserts(schema)
5672
5673 const decoding = asserts.decoding()
5674 await decoding.succeed(new MyError("a"))
5675 await decoding.fail(null, `Expected MyError`)
5676
5677 const encoding = asserts.encoding()
5678 await encoding.succeed(new MyError("a"))
5679 await encoding.fail(null, `Expected MyError`)
5680 })
5681 })
5682
5683 it("Duration", async () => {
5684 const schema = Schema.Duration
5685 const asserts = new TestSchema.Asserts(schema)
5686
5687 if (verifyGeneration) {
5688 asserts.arbitrary().verifyGeneration()
5689 }
5690 })
5691
5692 it("DurationFromString", async () => {
5693 const schema = Schema.DurationFromString
5694 const asserts = new TestSchema.Asserts(schema)
5695
5696 const decoding = asserts.decoding()
5697 await decoding.succeed("1000 millis", Duration.millis(1000))
5698 await decoding.succeed("1 second", Duration.seconds(1))
5699 await decoding.succeed("Infinity", Duration.infinity)
5700 await decoding.succeed("-Infinity", Duration.negativeInfinity)
5701 await decoding.fail("value", "Expected a valid Duration string")
5702
5703 const encoding = asserts.encoding()
5704 await encoding.succeed(Duration.zero, "0 millis")
5705 await encoding.succeed(Duration.seconds(5), "5000 millis")
5706 await encoding.succeed(Duration.nanos(5000n), "5000 nanos")
5707 await encoding.succeed(Duration.infinity, "Infinity")
5708 await encoding.succeed(Duration.negativeInfinity, "-Infinity")
5709 })
5710
5711 it("DurationFromNanos", async () => {
5712 const schema = Schema.DurationFromNanos
5713 const asserts = new TestSchema.Asserts(schema)
5714
5715 if (verifyGeneration) {
5716 asserts.arbitrary().verifyGeneration()
5717 }
5718
5719 const decoding = asserts.decoding()
5720 await decoding.succeed(0n, Duration.zero)
5721 await decoding.succeed(1000n, Duration.nanos(1000n))
5722 await decoding.succeed(-1000n, Duration.nanos(-1000n))
5723
5724 const encoding = asserts.encoding()
5725 await encoding.succeed(Duration.millis(5), 5_000_000n)
5726 await encoding.succeed(Duration.nanos(5000n), 5000n)
5727 await encoding.succeed(Duration.nanos(-5000n), -5000n)
5728 await encoding.fail(Duration.infinity, "Expected a Duration representable as a bigint")
5729 await encoding.fail(Duration.negativeInfinity, "Expected a Duration representable as a bigint")
5730 })
5731
5732 it("DurationFromMillis", async () => {
5733 const schema = Schema.DurationFromMillis
5734 const asserts = new TestSchema.Asserts(schema)
5735
5736 if (verifyGeneration) {
5737 asserts.arbitrary().verifyGeneration()
5738 }
5739
5740 const decoding = asserts.decoding()
5741 await decoding.succeed(Infinity, Duration.infinity)
5742 await decoding.succeed(-Infinity, Duration.negativeInfinity)
5743 await decoding.succeed(0, Duration.millis(0))
5744 await decoding.succeed(-1, Duration.millis(-1))
5745 await decoding.succeed(1000, Duration.seconds(1))
5746 await decoding.succeed(60 * 1000, Duration.minutes(1))
5747 await decoding.succeed(0.1, Duration.millis(0.1))
5748 await decoding.succeed(NaN, Duration.zero)
5749
5750 const encoding = asserts.encoding()
5751 await encoding.succeed(Duration.infinity, Infinity)
5752 await encoding.succeed(Duration.negativeInfinity, -Infinity)
5753 await encoding.succeed(Duration.millis(NaN), 0)
5754 await encoding.succeed(Duration.millis(-1), -1)
5755 await encoding.succeed(Duration.seconds(5), 5000)
5756 await encoding.succeed(Duration.millis(5000), 5000)
5757 await encoding.succeed(Duration.millis(0.1), 0.1)
5758 await encoding.succeed(Duration.nanos(5000n), 0.005)
5759 })
5760
5761 it("ByteSize", async () => {
5762 const asserts = new TestSchema.Asserts(Schema.ByteSize)
5763 if (verifyGeneration) asserts.arbitrary().verifyGeneration()
5764
5765 const json = new TestSchema.Asserts(Schema.toCodecJson(Schema.ByteSize))
5766 await json.decoding().succeed("9007199254740993", ByteSize.bytes(9_007_199_254_740_993n))
5767 await json.encoding().succeed(ByteSize.bytes(9_007_199_254_740_993n), "9007199254740993")
5768 })
5769
5770 it("ByteSizeFromString", async () => {
5771 const asserts = new TestSchema.Asserts(Schema.ByteSizeFromString)
5772 await asserts.decoding().succeed("1.5 kB", ByteSize.bytes(1500))
5773 await asserts.decoding().fail("1 KB", "Expected a valid ByteSize string")
5774 await asserts.encoding().succeed(ByteSize.bytes(1500), "1500 bytes")
5775 })
5776
5777 it("ByteSizeFromBigInt", async () => {
5778 const asserts = new TestSchema.Asserts(Schema.ByteSizeFromBigInt)
5779 if (verifyGeneration) asserts.arbitrary().verifyGeneration()
5780 await asserts.decoding().succeed(9_007_199_254_740_993n, ByteSize.bytes(9_007_199_254_740_993n))
5781 await asserts.decoding().fail(-1n, "Expected a non-negative bigint byte count")
5782 await asserts.encoding().succeed(ByteSize.bytes(9_007_199_254_740_993n), 9_007_199_254_740_993n)
5783 })
5784
5785 it("ByteSizeFromNumber", async () => {
5786 const asserts = new TestSchema.Asserts(Schema.ByteSizeFromNumber)
5787 await asserts.decoding().succeed(Number.MAX_SAFE_INTEGER, ByteSize.bytes(Number.MAX_SAFE_INTEGER))
5788 await asserts.decoding().fail(-1, "Expected a non-negative safe-integer byte count")
5789 await asserts.decoding().fail(0.5, "Expected a non-negative safe-integer byte count")
5790 await asserts.decoding().fail(Number.MAX_SAFE_INTEGER + 1, "Expected a non-negative safe-integer byte count")
5791 await asserts.encoding().succeed(ByteSize.bytes(1500), 1500)
5792 await asserts.encoding().fail(
5793 ByteSize.bytes(BigInt(Number.MAX_SAFE_INTEGER) + 1n),
5794 "Expected a ByteSize representable as a safe integer"
5795 )
5796 })
5797
5798 it("BigDecimal", async () => {
5799 const schema = Schema.BigDecimal
5800 const asserts = new TestSchema.Asserts(schema)
5801
5802 if (verifyGeneration) {
5803 asserts.arbitrary().verifyGeneration()
5804 }
5805
5806 const decoding = asserts.decoding()
5807 await decoding.succeed(BigDecimal.fromStringUnsafe("123.45"))
5808 await decoding.fail(null, `Expected BigDecimal`)
5809
5810 const encoding = asserts.encoding()
5811 await encoding.succeed(BigDecimal.fromStringUnsafe("123.45"))
5812 })
5813
5814 describe("BigDecimal checks", () => {
5815 it("isGreaterThanBigDecimal", async () => {
5816 const schema = Schema.BigDecimal.check(Schema.isGreaterThanBigDecimal(BigDecimal.fromStringUnsafe("1")))
5817 const asserts = new TestSchema.Asserts(schema)
5818
5819 const decoding = asserts.decoding()
5820 await decoding.succeed(BigDecimal.fromStringUnsafe("2"))
5821 await decoding.fail(
5822 BigDecimal.fromStringUnsafe("1"),
5823 `Expected a value greater than 1`
5824 )
5825 })
5826
5827 it("isGreaterThanOrEqualToBigDecimal", async () => {
5828 const schema = Schema.BigDecimal.check(
5829 Schema.isGreaterThanOrEqualToBigDecimal(BigDecimal.fromStringUnsafe("1"))
5830 )
5831 const asserts = new TestSchema.Asserts(schema)
5832
5833 const decoding = asserts.decoding()
5834 await decoding.succeed(BigDecimal.fromStringUnsafe("1"))
5835 await decoding.fail(
5836 BigDecimal.fromStringUnsafe("0"),
5837 `Expected a value greater than or equal to 1`
5838 )
5839 })
5840
5841 it("isLessThanBigDecimal", async () => {
5842 const schema = Schema.BigDecimal.check(Schema.isLessThanBigDecimal(BigDecimal.fromStringUnsafe("1")))
5843 const asserts = new TestSchema.Asserts(schema)
5844
5845 const decoding = asserts.decoding()
5846 await decoding.succeed(BigDecimal.fromStringUnsafe("0"))
5847 await decoding.fail(
5848 BigDecimal.fromStringUnsafe("1"),
5849 `Expected a value less than 1`
5850 )
5851 })
5852
5853 it("isLessThanOrEqualToBigDecimal", async () => {
5854 const schema = Schema.BigDecimal.check(Schema.isLessThanOrEqualToBigDecimal(BigDecimal.fromStringUnsafe("1")))
5855 const asserts = new TestSchema.Asserts(schema)
5856
5857 const decoding = asserts.decoding()
5858 await decoding.succeed(BigDecimal.fromStringUnsafe("1"))
5859 await decoding.fail(
5860 BigDecimal.fromStringUnsafe("2"),
5861 `Expected a value less than or equal to 1`
5862 )
5863 })
5864
5865 it("isBetweenBigDecimal", async () => {
5866 const schema = Schema.BigDecimal.check(Schema.isBetweenBigDecimal({
5867 minimum: BigDecimal.fromStringUnsafe("1"),
5868 maximum: BigDecimal.fromStringUnsafe("5")
5869 }))
5870 const asserts = new TestSchema.Asserts(schema)
5871
5872 const decoding = asserts.decoding()
5873 await decoding.succeed(BigDecimal.fromStringUnsafe("3"))
5874 await decoding.fail(
5875 BigDecimal.fromStringUnsafe("0"),
5876 `Expected a value between 1 and 5`
5877 )
5878 })
5879 })
5880
5881 describe("tag", () => {
5882 it("decoding: required & encoding: required & constructor: required", async () => {
5883 const schema = Schema.Struct({
5884 _tag: Schema.Literal("a"),
5885 a: Schema.FiniteFromString
5886 })
5887 const asserts = new TestSchema.Asserts(schema)
5888
5889 const make = asserts.make()
5890 await make.succeed({ _tag: "a", a: 1 })
5891
5892 const decoding = asserts.decoding()
5893 await decoding.succeed({ _tag: "a", a: "1" }, { _tag: "a", a: 1 })
5894
5895 const encoding = asserts.encoding()
5896 await encoding.succeed({ _tag: "a", a: 1 }, { _tag: "a", a: "1" })
5897 })
5898
5899 it("decoding: required & encoding: required & constructor: optional", async () => {
5900 const schema = Schema.Struct({
5901 _tag: Schema.tag("a"),
5902 a: Schema.FiniteFromString
5903 })
5904 const asserts = new TestSchema.Asserts(schema)
5905
5906 const make = asserts.make()
5907 await make.succeed({ _tag: "a", a: 1 })
5908 await make.succeed({ a: 1 }, { _tag: "a", a: 1 })
5909
5910 const decoding = asserts.decoding()
5911 await decoding.succeed({ _tag: "a", a: "1" }, { _tag: "a", a: 1 })
5912
5913 const encoding = asserts.encoding()
5914 await encoding.succeed({ _tag: "a", a: 1 }, { _tag: "a", a: "1" })
5915 })
5916
5917 it("decoding: default & encoding: omit & constructor: optional", async () => {
5918 const schema = Schema.Struct({
5919 _tag: Schema.tag("a").pipe(
5920 Schema.encodeTo(
5921 Schema.optionalKey(Schema.Literal("a")),
5922 {
5923 decode: SchemaGetter.withDefault(Effect.succeed("a")),
5924 encode: SchemaGetter.omit()
5925 }
5926 )
5927 ),
5928 a: Schema.FiniteFromString
5929 })
5930 const asserts = new TestSchema.Asserts(schema)
5931
5932 const make = asserts.make()
5933 await make.succeed({ _tag: "a", a: 1 })
5934 await make.succeed({ a: 1 }, { _tag: "a", a: 1 })
5935
5936 const decoding = asserts.decoding()
5937 await decoding.succeed({ _tag: "a", a: "1" }, { _tag: "a", a: 1 })
5938 await decoding.succeed({ a: "1" }, { _tag: "a", a: 1 })
5939
5940 const encoding = asserts.encoding()
5941 await encoding.succeed({ _tag: "a", a: 1 }, { a: "1" })
5942 })
5943 })
5944
5945 it("tagDefaultOmit", async () => {
5946 const schema = Schema.Struct({
5947 _tag: Schema.tagDefaultOmit("a"),
5948 a: Schema.FiniteFromString
5949 })
5950 const asserts = new TestSchema.Asserts(schema)
5951
5952 const make = asserts.make()
5953 await make.succeed({ _tag: "a", a: 1 })
5954 await make.succeed({ a: 1 }, { _tag: "a", a: 1 })
5955 await make.fail(
5956 { _tag: "c", a: 1 },
5957 `Expected "a"
5958 at ["_tag"]`
5959 )
5960
5961 const decoding = asserts.decoding()
5962 await decoding.succeed({ _tag: "a", a: "1" }, { _tag: "a", a: 1 })
5963 await decoding.succeed({ a: "1" }, { _tag: "a", a: 1 })
5964 await decoding.fail(
5965 { _tag: "c", a: 1 },
5966 `Expected "a"
5967 at ["_tag"]`
5968 )
5969
5970 const encoding = asserts.encoding()
5971 await encoding.succeed({ _tag: "a", a: 1 }, { a: "1" })
5972 await encoding.succeed({ a: 1 }, { a: "1" })
5973 })
5974
5975 it("URL", async () => {
5976 const schema = Schema.URL
5977 const asserts = new TestSchema.Asserts(schema)
5978 if (verifyGeneration) {
5979 asserts.arbitrary().verifyGeneration()
5980 }
5981 })
5982
5983 it("RegExp", async () => {
5984 const schema = Schema.RegExp
5985 const asserts = new TestSchema.Asserts(schema)
5986 if (verifyGeneration) {
5987 asserts.arbitrary().verifyGeneration()
5988 }
5989 })
5990
5991 it("URLFromString", async () => {
5992 const schema = Schema.URLFromString
5993 const asserts = new TestSchema.Asserts(schema)
5994
5995 if (verifyGeneration) {
5996 asserts.arbitrary().verifyGeneration()
5997 }
5998
5999 const decoding = asserts.decoding()
6000 await decoding.succeed("https://effect.website", new URL("https://effect.website"))
6001 await decoding.fail(
6002 "123",
6003 "Expected a valid URL string"
6004 )
6005
6006 const encoding = asserts.encoding()
6007 await encoding.succeed(new URL("https://effect.website"), "https://effect.website/")
6008 })
6009
6010 describe("UnknownFromJsonString / fromJsonString", () => {
6011 it("use case: Unknown <-> JSON string", async () => {
6012 const schema = Schema.UnknownFromJsonString
6013 const asserts = new TestSchema.Asserts(schema)
6014
6015 if (verifyGeneration) {
6016 asserts.arbitrary().verifyGeneration()
6017 }
6018
6019 const decoding = asserts.decoding()
6020 await decoding.succeed(`{"a":1}`, { a: 1 })
6021 await decoding.fail(
6022 `{"a"`,
6023 "Expected a valid JSON string"
6024 )
6025
6026 const encoding = asserts.encoding()
6027 await encoding.succeed({ a: 1 }, `{"a":1}`)
6028 })
6029
6030 it("use case: create a JSON string serializer for an existing schema", async () => {
6031 const schema = Schema.fromJsonString(Schema.Struct({ b: Schema.Number }))
6032 const asserts = new TestSchema.Asserts(schema)
6033
6034 if (verifyGeneration) {
6035 asserts.arbitrary().verifyGeneration()
6036 }
6037
6038 const decoding = asserts.decoding()
6039 await decoding.succeed(`{"b":1}`, { b: 1 })
6040 await decoding.fail(
6041 `{"a":null}`,
6042 `Missing key
6043 at ["b"]`
6044 )
6045 })
6046
6047 it("reviver option", async () => {
6048 const schema = Schema.fromJsonString(Schema.Struct({ a: Schema.Number }), {
6049 reviver: (key, value) => key === "a" ? Number(value) : value
6050 })
6051 const decoding = new TestSchema.Asserts(schema).decoding()
6052
6053 await decoding.succeed(`{"a":"1"}`, { a: 1 })
6054 })
6055
6056 it("replacer and space options", async () => {
6057 const schema = Schema.fromJsonString(Schema.Struct({ a: Schema.Number, b: Schema.Number }), {
6058 replacer: (key, value) => key === "b" ? undefined : value,
6059 space: 2
6060 })
6061 const encoding = new TestSchema.Asserts(schema).encoding()
6062
6063 await encoding.succeed(
6064 { a: 1, b: 2 },
6065 `{
6066 "a": 1
6067}`
6068 )
6069 })
6070
6071 it("use case: parse / stringify a nested schema", async () => {
6072 const schema = Schema.Struct({
6073 a: Schema.fromJsonString(Schema.Struct({ b: Schema.Number }))
6074 })
6075 const asserts = new TestSchema.Asserts(schema)
6076
6077 if (verifyGeneration) {
6078 asserts.arbitrary().verifyGeneration()
6079 }
6080
6081 const decoding = asserts.decoding()
6082 await decoding.succeed({ a: `{"b":2}` }, { a: { b: 2 } })
6083 await decoding.fail(
6084 { a: `{"a":null}` },
6085 `Missing key
6086 at ["a"]["b"]`
6087 )
6088 })
6089 })
6090
6091 it("fromFormData", async () => {
6092 const schema = Schema.fromFormData(Schema.Struct({
6093 a: Schema.NonEmptyString,
6094 b: Schema.Struct({
6095 c: Schema.String,
6096 d: Schema.String
6097 }),
6098 e: Schema.Array(Schema.String)
6099 }))
6100 const asserts = new TestSchema.Asserts(schema)
6101
6102 const formData = new FormData()
6103 formData.append("a", "a")
6104 formData.append("b[c]", "bc")
6105 formData.append("b[d]", "bd")
6106 formData.append("e[0]", "e0")
6107 formData.append("e[1]", "e1")
6108
6109 const decoded = { a: "a", b: { c: "bc", d: "bd" }, e: ["e0", "e1"] }
6110
6111 const decoding = asserts.decoding()
6112 await decoding.succeed(formData, decoded)
6113
6114 const encoding = asserts.encoding()
6115
6116 {
6117 const formData = new FormData()
6118 formData.append("a", "a")
6119 formData.append("b[c]", "bc")
6120 formData.append("b[d]", "bd")
6121 formData.append("e", "e0")
6122 formData.append("e", "e1")
6123 await encoding.succeed(decoded, formData)
6124 }
6125
6126 {
6127 const formData = new FormData()
6128 formData.append("a", "")
6129 await decoding.fail(
6130 formData,
6131 `Expected a value with a length of at least 1
6132 at ["a"]`
6133 )
6134 }
6135 })
6136
6137 it("fromURLSearchParams", async () => {
6138 const schema = Schema.fromURLSearchParams(Schema.Struct({
6139 a: Schema.NonEmptyString,
6140 b: Schema.Struct({
6141 c: Schema.String,
6142 d: Schema.String
6143 }),
6144 e: Schema.Array(Schema.String)
6145 }))
6146 const asserts = new TestSchema.Asserts(schema)
6147
6148 const urlSearchParams = new URLSearchParams("a=a&b[c]=bc&b[d]=bd&e=e0&e=e1")
6149
6150 const decoded = { a: "a", b: { c: "bc", d: "bd" }, e: ["e0", "e1"] }
6151
6152 const decoding = asserts.decoding()
6153 await decoding.succeed(urlSearchParams, decoded)
6154
6155 const encoding = asserts.encoding()
6156
6157 {
6158 const urlSearchParams = new URLSearchParams("a=a&b[c]=bc&b[d]=bd&e=e0&e=e1")
6159 await encoding.succeed(decoded, urlSearchParams)
6160 }
6161
6162 {
6163 const urlSearchParams = new URLSearchParams("a=")
6164 await decoding.fail(
6165 urlSearchParams,
6166 `Expected a value with a length of at least 1
6167 at ["a"]`
6168 )
6169 }
6170 })
6171
6172 it("Trim", async () => {
6173 const schema = Schema.Trim
6174 const asserts = new TestSchema.Asserts(schema)
6175
6176 if (verifyGeneration) {
6177 asserts.arbitrary().verifyGeneration()
6178 }
6179
6180 const decoding = asserts.decoding()
6181 await decoding.succeed("a")
6182 await decoding.succeed("a ", "a")
6183 await decoding.succeed(" a", "a")
6184 await decoding.succeed(" a ", "a")
6185 await decoding.succeed("a\n", "a")
6186
6187 const encoding = asserts.encoding()
6188 await encoding.succeed("a")
6189 await encoding.fail(
6190 "a ",
6191 `Expected a string with no leading or trailing whitespace`
6192 )
6193 })
6194
6195 it("transformEffect", async () => {
6196 const schema = Schema.String.pipe(
6197 Schema.decodeTo(
6198 Schema.String,
6199 SchemaTransformation.transformEffect({
6200 decode: (s) =>
6201 s === "a"
6202 ? Effect.fail(new SchemaIssue.Forbidden({ message: `input should not be "a"` }))
6203 : Effect.succeed(s),
6204 encode: (s) =>
6205 s === "b"
6206 ? Effect.fail(new SchemaIssue.Forbidden({ message: `input should not be "b"` }))
6207 : Effect.succeed(s)
6208 })
6209 )
6210 )
6211 const asserts = new TestSchema.Asserts(schema)
6212
6213 const decoding = asserts.decoding()
6214 await decoding.succeed("b")
6215 await decoding.fail(
6216 "a",
6217 `input should not be "a"`
6218 )
6219
6220 const encoding = asserts.encoding()
6221 await encoding.succeed("a")
6222 await encoding.fail(
6223 "b",
6224 `input should not be "b"`
6225 )
6226 })
6227
6228 describe("TemplateLiteral", () => {
6229 it("should expose the parts", () => {
6230 const parts = ["a", Schema.String] as const
6231 const schema = Schema.TemplateLiteral(parts)
6232 deepStrictEqual(schema.parts, parts)
6233 })
6234
6235 it(`NonEmptyString + String`, async () => {
6236 const schema = Schema.TemplateLiteral([Schema.NonEmptyString, Schema.String])
6237 const asserts = new TestSchema.Asserts(schema)
6238
6239 const decoding = asserts.decoding()
6240 await decoding.succeed("a")
6241 })
6242
6243 it("rejects checks on Literal, TemplateLiteral, and Union parts", () => {
6244 const check = Schema.makeFilter(() => true)
6245
6246 throws(
6247 () => Schema.TemplateLiteral([Schema.Literal("a").check(check)]),
6248 "Invalid TemplateLiteral part Literal"
6249 )
6250 throws(
6251 () => Schema.TemplateLiteral([Schema.TemplateLiteral(["a"]).check(check)]),
6252 "Invalid TemplateLiteral part TemplateLiteral"
6253 )
6254 throws(
6255 () => Schema.TemplateLiteral([Schema.Union([Schema.Literal("a"), Schema.Literal("b")]).check(check)]),
6256 "Invalid TemplateLiteral part Union"
6257 )
6258 })
6259
6260 it(`"a"`, async () => {
6261 const schema = Schema.TemplateLiteral(["a"])
6262 const asserts = new TestSchema.Asserts(schema)
6263
6264 const decoding = asserts.decoding()
6265 await decoding.succeed("a")
6266 await decoding.fail(null, "Expected string")
6267 await decoding.fail(
6268 "ab",
6269 "Expected a string matching template literal parts"
6270 )
6271 await decoding.fail(
6272 "",
6273 `Expected a string matching template literal parts`
6274 )
6275 })
6276
6277 it(`"a b"`, async () => {
6278 const schema = Schema.TemplateLiteral(["a", " ", "b"])
6279 const asserts = new TestSchema.Asserts(schema)
6280
6281 const decoding = asserts.decoding()
6282 await decoding.succeed("a b")
6283
6284 await decoding.fail(
6285 "a b",
6286 `Expected a string matching template literal parts`
6287 )
6288 })
6289
6290 it(`"[" + string + "]"`, async () => {
6291 const schema = Schema.TemplateLiteral(["[", Schema.String, "]"])
6292 const asserts = new TestSchema.Asserts(schema)
6293
6294 const decoding = asserts.decoding()
6295 await decoding.succeed("[a]")
6296
6297 await decoding.fail(
6298 "a",
6299 `Expected a string matching template literal parts`
6300 )
6301 })
6302
6303 it(`"a" + string`, async () => {
6304 const schema = Schema.TemplateLiteral(["a", Schema.String])
6305 const asserts = new TestSchema.Asserts(schema)
6306
6307 const decoding = asserts.decoding()
6308 await decoding.succeed("a")
6309 await decoding.succeed("ab")
6310
6311 await decoding.fail(
6312 null,
6313 "Expected string"
6314 )
6315 await decoding.fail(
6316 "",
6317 `Expected a string matching template literal parts`
6318 )
6319 })
6320
6321 it(`"a" + number`, async () => {
6322 const schema = Schema.TemplateLiteral(["a", Schema.Number])
6323 const asserts = new TestSchema.Asserts(schema)
6324
6325 const decoding = asserts.decoding()
6326 await decoding.succeed("a1")
6327 await decoding.succeed("a+1")
6328 await decoding.succeed("a1.2")
6329
6330 await decoding.succeed("a-1.401298464324817e-45")
6331 await decoding.succeed("a1.401298464324817e-45")
6332 await decoding.succeed("a+1.401298464324817e-45")
6333 await decoding.succeed("a-1.401298464324817e+45")
6334 await decoding.succeed("a1.401298464324817e+45")
6335 await decoding.succeed("a+1.401298464324817e+45")
6336
6337 await decoding.succeed("a-1.401298464324817E-45")
6338 await decoding.succeed("a1.401298464324817E-45")
6339 await decoding.succeed("a+1.401298464324817E-45")
6340 await decoding.succeed("a-1.401298464324817E+45")
6341 await decoding.succeed("a1.401298464324817E+45")
6342 await decoding.succeed("a+1.401298464324817E+45")
6343
6344 await decoding.fail(
6345 null,
6346 "Expected string"
6347 )
6348 await decoding.fail(
6349 "",
6350 `Expected a string matching template literal parts`
6351 )
6352 await decoding.fail(
6353 "aa",
6354 `Expected a string matching template literal parts`
6355 )
6356 })
6357
6358 it(`"a" + bigint`, async () => {
6359 const schema = Schema.TemplateLiteral(["a", Schema.BigInt])
6360 const asserts = new TestSchema.Asserts(schema)
6361
6362 const decoding = asserts.decoding()
6363 await decoding.succeed("a0")
6364 await decoding.succeed("a1")
6365 await decoding.succeed("a-1")
6366
6367 await decoding.fail(
6368 null,
6369 "Expected string"
6370 )
6371 await decoding.fail(
6372 "",
6373 `Expected a string matching template literal parts`
6374 )
6375 await decoding.fail(
6376 "aa",
6377 `Expected a string matching template literal parts`
6378 )
6379 await decoding.fail(
6380 "a1.2",
6381 `Expected a string matching template literal parts`
6382 )
6383 await decoding.fail(
6384 "a+1",
6385 `Expected a string matching template literal parts`
6386 )
6387 })
6388
6389 it(`string`, async () => {
6390 const schema = Schema.TemplateLiteral([Schema.String])
6391 const asserts = new TestSchema.Asserts(schema)
6392
6393 const decoding = asserts.decoding()
6394 await decoding.succeed("a")
6395 await decoding.succeed("ab")
6396 await decoding.succeed("")
6397 await decoding.succeed("\n")
6398 await decoding.succeed("\r")
6399 await decoding.succeed("\r\n")
6400 await decoding.succeed("\t")
6401 })
6402
6403 it(`\\n + string`, async () => {
6404 const schema = Schema.TemplateLiteral(["\n", Schema.String])
6405 const asserts = new TestSchema.Asserts(schema)
6406
6407 const decoding = asserts.decoding()
6408 await decoding.succeed("\n")
6409 await decoding.succeed("\na")
6410 await decoding.fail(
6411 "a",
6412 `Expected a string matching template literal parts`
6413 )
6414 })
6415
6416 it(`a\\nb + string`, async () => {
6417 const schema = Schema.TemplateLiteral(["a\nb ", Schema.String])
6418 const asserts = new TestSchema.Asserts(schema)
6419
6420 const decoding = asserts.decoding()
6421 await decoding.succeed("a\nb ")
6422 await decoding.succeed("a\nb c")
6423 })
6424
6425 it(`"a" + string + "b"`, async () => {
6426 const schema = Schema.TemplateLiteral(["a", Schema.String, "b"])
6427 const asserts = new TestSchema.Asserts(schema)
6428
6429 const decoding = asserts.decoding()
6430 await decoding.succeed("ab")
6431 await decoding.succeed("acb")
6432 await decoding.succeed("abb")
6433 await decoding.fail(
6434 "",
6435 `Expected a string matching template literal parts`
6436 )
6437 await decoding.fail(
6438 "a",
6439 `Expected a string matching template literal parts`
6440 )
6441 await decoding.fail(
6442 "b",
6443 `Expected a string matching template literal parts`
6444 )
6445
6446 const encoding = asserts.encoding()
6447 await encoding.succeed("acb")
6448 })
6449
6450 it(`"a" + string + "b" + string`, async () => {
6451 const schema = Schema.TemplateLiteral(["a", Schema.String, "b", Schema.String])
6452 const asserts = new TestSchema.Asserts(schema)
6453
6454 const decoding = asserts.decoding()
6455 await decoding.succeed("ab")
6456 await decoding.succeed("acb")
6457 await decoding.succeed("acbd")
6458 await decoding.fail(
6459 "a",
6460 `Expected a string matching template literal parts`
6461 )
6462 await decoding.fail(
6463 "b",
6464 `Expected a string matching template literal parts`
6465 )
6466 })
6467
6468 it("https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html", async () => {
6469 const EmailLocaleIDs = Schema.Literals(["welcome_email", "email_heading"])
6470 const FooterLocaleIDs = Schema.Literals(["footer_title", "footer_sendoff"])
6471 const schema = Schema.TemplateLiteral([Schema.Union([EmailLocaleIDs, FooterLocaleIDs]), "_id"])
6472 const asserts = new TestSchema.Asserts(schema)
6473
6474 const decoding = asserts.decoding()
6475 await decoding.succeed("welcome_email_id")
6476 await decoding.succeed("email_heading_id")
6477 await decoding.succeed("footer_title_id")
6478 await decoding.succeed("footer_sendoff_id")
6479
6480 await decoding.fail(
6481 "_id",
6482 `Expected a string matching template literal parts`
6483 )
6484 })
6485
6486 it(`string + 0`, async () => {
6487 const schema = Schema.TemplateLiteral([Schema.String, 0])
6488 const asserts = new TestSchema.Asserts(schema)
6489
6490 const decoding = asserts.decoding()
6491 await decoding.succeed("a0")
6492 await decoding.fail(
6493 "a",
6494 `Expected a string matching template literal parts`
6495 )
6496 })
6497
6498 it(`string + 1n`, async () => {
6499 const schema = Schema.TemplateLiteral([Schema.String, 1n])
6500 const asserts = new TestSchema.Asserts(schema)
6501
6502 const decoding = asserts.decoding()
6503 await decoding.succeed("a1")
6504 await decoding.fail(
6505 "a",
6506 `Expected a string matching template literal parts`
6507 )
6508 })
6509
6510 it(`string + ("a" | 0)`, async () => {
6511 const schema = Schema.TemplateLiteral([Schema.String, Schema.Literals(["a", 0])])
6512 const asserts = new TestSchema.Asserts(schema)
6513
6514 const decoding = asserts.decoding()
6515 await decoding.succeed("a0")
6516 await decoding.succeed("aa")
6517 await decoding.fail(
6518 "b",
6519 `Expected a string matching template literal parts`
6520 )
6521 })
6522
6523 it(`(string | 1) + (number | true)`, async () => {
6524 const schema = Schema.TemplateLiteral([
6525 Schema.Union([Schema.String, Schema.Literal(1)]),
6526 Schema.Union([Schema.Number, Schema.Literal("true")])
6527 ])
6528 const asserts = new TestSchema.Asserts(schema)
6529
6530 const decoding = asserts.decoding()
6531 await decoding.succeed("atrue")
6532 await decoding.succeed("-2")
6533 await decoding.succeed("10.1")
6534 await decoding.fail(
6535 "",
6536 `Expected a string matching template literal parts`
6537 )
6538 })
6539
6540 it("`c${`a${string}b` | \"e\"}d`", async () => {
6541 const schema = Schema.TemplateLiteral(
6542 ["c", Schema.Union([Schema.TemplateLiteral(["a", Schema.String, "b"]), Schema.Literal("e")]), "d"]
6543 )
6544 const asserts = new TestSchema.Asserts(schema)
6545
6546 const decoding = asserts.decoding()
6547 await decoding.succeed("ced")
6548 await decoding.succeed("cabd")
6549 await decoding.succeed("casbd")
6550 await decoding.succeed("ca bd")
6551 await decoding.fail(
6552 "",
6553 `Expected a string matching template literal parts`
6554 )
6555 })
6556
6557 it("< + h + (1|2n) + >", async () => {
6558 const schema = Schema.TemplateLiteral(["<", Schema.TemplateLiteral(["h", Schema.Literals([1, 2n])]), ">"])
6559 const asserts = new TestSchema.Asserts(schema)
6560
6561 const decoding = asserts.decoding()
6562 await decoding.succeed("<h1>")
6563 await decoding.succeed("<h2>")
6564 await decoding.fail(
6565 "<h3>",
6566 `Expected a string matching template literal parts`
6567 )
6568 })
6569
6570 it(`"a" + check`, async () => {
6571 const schema = Schema.TemplateLiteral(["a", Schema.NonEmptyString])
6572 const asserts = new TestSchema.Asserts(schema)
6573
6574 const decoding = asserts.decoding()
6575 await decoding.succeed("ab")
6576 await decoding.fail(
6577 null,
6578 "Expected string"
6579 )
6580 await decoding.fail(
6581 "",
6582 `Expected a string matching template literal parts`
6583 )
6584 await decoding.fail(
6585 "a",
6586 `Expected a string matching template literal parts`
6587 )
6588 })
6589
6590 it(`rejects "a" + transformation`, () => {
6591 throws(
6592 () => Schema.TemplateLiteral(["a", Schema.FiniteFromString]),
6593 "TemplateLiteral parts cannot have an encoding at parts[1]"
6594 )
6595 })
6596 })
6597
6598 describe("TemplateLiteralParser", () => {
6599 it("should expose the parts", () => {
6600 const parts = ["a", Schema.String] as const
6601 const schema = Schema.TemplateLiteralParser(parts)
6602 deepStrictEqual(schema.parts, parts)
6603 })
6604
6605 it("preserves greedy segmentation at repeated literal anchors", async () => {
6606 const schema = Schema.TemplateLiteralParser([Schema.String, ":", Schema.String])
6607 const decoding = new TestSchema.Asserts(schema).decoding()
6608
6609 await decoding.succeed("a:b:c", ["a:b", ":", "c"])
6610 })
6611
6612 it("backtracks from an invalid literal anchor", async () => {
6613 const schema = Schema.TemplateLiteralParser([Schema.String, ":", Schema.NonEmptyString, "x"])
6614 const decoding = new TestSchema.Asserts(schema).decoding()
6615
6616 await decoding.succeed("a:b:x", ["a", ":", "b:", "x"])
6617 })
6618
6619 it("backtracks across an empty literal anchor", async () => {
6620 const schema = Schema.TemplateLiteralParser([Schema.String, "", Schema.NonEmptyString])
6621 const decoding = new TestSchema.Asserts(schema).decoding()
6622
6623 await decoding.succeed("a", ["", "", "a"])
6624 })
6625
6626 it(`NonEmptyString + String`, async () => {
6627 const schema = Schema.TemplateLiteralParser([Schema.NonEmptyString, Schema.String])
6628 const asserts = new TestSchema.Asserts(schema)
6629
6630 const decoding = asserts.decoding()
6631 await decoding.succeed("a", ["a", ""])
6632 })
6633
6634 it(`"a"`, async () => {
6635 const schema = Schema.TemplateLiteralParser(["a"])
6636 const asserts = new TestSchema.Asserts(schema)
6637
6638 const decoding = asserts.decoding()
6639 await decoding.succeed("a", ["a"])
6640 await decoding.fail(
6641 "ab",
6642 `Expected a string matching template literal parts`
6643 )
6644 await decoding.fail(
6645 "",
6646 `Expected a string matching template literal parts`
6647 )
6648 await decoding.fail(
6649 null,
6650 "Expected string"
6651 )
6652 })
6653
6654 it(`"a b"`, async () => {
6655 const schema = Schema.TemplateLiteralParser(["a", " ", "b"])
6656 const asserts = new TestSchema.Asserts(schema)
6657
6658 const decoding = asserts.decoding()
6659 await decoding.succeed("a b", ["a", " ", "b"])
6660
6661 await decoding.fail(
6662 "a b",
6663 `Expected a string matching template literal parts`
6664 )
6665 })
6666
6667 it(`Int + "a"`, async () => {
6668 const schema = Schema.TemplateLiteralParser([Schema.Int, "a"])
6669 const asserts = new TestSchema.Asserts(schema)
6670
6671 const decoding = asserts.decoding()
6672 await decoding.succeed("1a", [1, "a"])
6673 await decoding.fail(
6674 "1.1a",
6675 `Expected a string matching template literal parts`
6676 )
6677
6678 const encoding = asserts.encoding()
6679 await encoding.succeed([1, "a"], "1a")
6680 await encoding.fail(
6681 [1.1, "a"],
6682 `Expected an integer
6683 at [0]`
6684 )
6685 })
6686
6687 it(`Int + String`, async () => {
6688 const schema = Schema.TemplateLiteralParser([Schema.Number.check(Schema.isInt()), Schema.String])
6689 const asserts = new TestSchema.Asserts(schema)
6690
6691 const decoding = asserts.decoding()
6692 await decoding.succeed("1.2", [1, ".2"])
6693 })
6694
6695 it(`NumberFromString + "a" + NonEmptyString`, async () => {
6696 const schema = Schema.TemplateLiteralParser([Schema.FiniteFromString, "a", Schema.NonEmptyString])
6697 const asserts = new TestSchema.Asserts(schema)
6698
6699 const decoding = asserts.decoding()
6700 await decoding.succeed("100ab", [100, "a", "b"])
6701 await decoding.succeed("100ab23a", [100, "a", "b23a"])
6702 await decoding.fail(
6703 "-ab",
6704 `Expected a finite number
6705 at [0]`
6706 )
6707
6708 const encoding = asserts.encoding()
6709 await encoding.succeed([100, "a", "b"], "100ab")
6710 await encoding.fail(
6711 [100, "a", ""],
6712 `Expected a value with a length of at least 1
6713 at [2]`
6714 )
6715 })
6716
6717 it(`"h" + (1 | 2 | 3)`, async () => {
6718 const schema = Schema.TemplateLiteralParser(["h", Schema.Literals([1, 2, 3])])
6719 const asserts = new TestSchema.Asserts(schema)
6720
6721 const decoding = asserts.decoding()
6722 await decoding.succeed("h1", ["h", 1])
6723 })
6724
6725 it(`"c" + (\`a\${string}b\`|"e") + "d"`, async () => {
6726 const schema = Schema.TemplateLiteralParser([
6727 "c",
6728 Schema.Union([Schema.TemplateLiteralParser(["a", Schema.NonEmptyString, "b"]), Schema.Literal("e")]),
6729 "d"
6730 ])
6731 const asserts = new TestSchema.Asserts(schema)
6732
6733 const decoding = asserts.decoding()
6734 await decoding.succeed("ca bd", ["c", ["a", " ", "b"], "d"])
6735 await decoding.succeed("ced", ["c", "e", "d"])
6736 await decoding.fail(
6737 "cabd",
6738 `Expected a string matching template literal parts`
6739 )
6740 await decoding.fail(
6741 "ed",
6742 `Expected a string matching template literal parts`
6743 )
6744 })
6745
6746 it(`"c" + (\`a\${number}b\`|"e") + "d"`, async () => {
6747 const schema = Schema.TemplateLiteralParser([
6748 "c",
6749 Schema.Union([
6750 Schema.TemplateLiteralParser(["a", Schema.Finite.check(Schema.isInt()), "b"]),
6751 Schema.Literal("e")
6752 ]),
6753 "d"
6754 ])
6755 const asserts = new TestSchema.Asserts(schema)
6756
6757 const decoding = asserts.decoding()
6758 await decoding.succeed("ced", ["c", "e", "d"])
6759 await decoding.succeed("ca1bd", ["c", ["a", 1, "b"], "d"])
6760 await decoding.fail(
6761 "ca1.1bd",
6762 `Expected a string matching template literal parts`
6763 )
6764 await decoding.fail(
6765 "ca-bd",
6766 `Expected a string matching template literal parts`
6767 )
6768 })
6769
6770 it(`readonly ["<", \`h\${1 | 2}\`, ">"]`, async () => {
6771 const schema = Schema.TemplateLiteralParser(["<", Schema.TemplateLiteral(["h", Schema.Literals([1, 2])]), ">"])
6772 const asserts = new TestSchema.Asserts(schema)
6773
6774 const decoding = asserts.decoding()
6775 await decoding.succeed("<h1>", ["<", "h1", ">"])
6776 await decoding.succeed("<h2>", ["<", "h2", ">"])
6777 await decoding.fail(
6778 "<h3>",
6779 `Expected a string matching template literal parts`
6780 )
6781 })
6782
6783 it(`readonly ["<", readonly ["h", 1 | 2], ">"]`, async () => {
6784 const schema = Schema.TemplateLiteralParser([
6785 "<",
6786 Schema.TemplateLiteralParser(["h", Schema.Literals([1, 2])]),
6787 ">"
6788 ])
6789 const asserts = new TestSchema.Asserts(schema)
6790
6791 const decoding = asserts.decoding()
6792 await decoding.succeed("<h1>", ["<", ["h", 1], ">"])
6793 await decoding.succeed("<h2>", ["<", ["h", 2], ">"])
6794 await decoding.fail(
6795 "<h3>",
6796 `Expected a string matching template literal parts`
6797 )
6798 })
6799 })
6800
6801 describe("Class", () => {
6802 it.effect("make preserves existing instances like the other constructor adapters", () =>
6803 Effect.gen(function*() {
6804 let constructions = 0
6805 class A extends Schema.Class<A>("A")({ a: Schema.String }) {
6806 readonly construction = ++constructions
6807 }
6808 const instance = new A({ a: "a" })
6809
6810 assert.strictEqual(A.make(instance), instance)
6811 assert.strictEqual(SchemaParser.make(A)(instance), instance)
6812 assert.strictEqual(Option.getOrThrow(A.makeOption(instance)), instance)
6813 assert.strictEqual(yield* A.makeEffect(instance), instance)
6814 assert.strictEqual(constructions, 1)
6815
6816 assert.notStrictEqual(new A(instance), instance)
6817 assert.strictEqual(constructions, 2)
6818 }))
6819
6820 it("make applies defaults, source checks, and the constructor once", () => {
6821 let defaults = 0
6822 let checks = 0
6823 let constructions = 0
6824 const struct = Schema.Struct({
6825 a: Schema.String.pipe(Schema.withConstructorDefault(Effect.sync(() => {
6826 defaults++
6827 return "default"
6828 })))
6829 }).check(Schema.makeFilter(() => {
6830 checks++
6831 return true
6832 }))
6833 class A extends Schema.Class<A>("A")(struct) {
6834 readonly construction = ++constructions
6835 }
6836
6837 const instance = A.make({})
6838
6839 assert.instanceOf(instance, A)
6840 assert.strictEqual(instance.a, "default")
6841 assert.strictEqual(defaults, 1)
6842 assert.strictEqual(checks, 1)
6843 assert.strictEqual(constructions, 1)
6844 })
6845
6846 it("make with void input", () => {
6847 class A extends Schema.Class<A>("A")({}) {}
6848 deepStrictEqual(A.make(), new A())
6849 deepStrictEqual(A.makeOption(), Option.some(new A()))
6850 deepStrictEqual(Effect.runSync(A.makeEffect()), new A())
6851 })
6852
6853 it("decoding validates the class struct once", () => {
6854 let checks = 0
6855 const schema = Schema.Struct({
6856 a: Schema.String
6857 }).check(Schema.makeFilter(() => {
6858 checks++
6859 return true
6860 }))
6861 class A extends Schema.Class<A>("A")(schema) {}
6862
6863 const instance = Schema.decodeUnknownSync(A)({ a: "a" })
6864
6865 assertTrue(instance instanceof A)
6866 strictEqual(checks, 1)
6867 })
6868
6869 it("make validates an existing nested Class once", () => {
6870 let checks = 0
6871 class A extends Schema.Class<A>("A")({ a: Schema.String }) {}
6872 const schema = Schema.Struct({
6873 a: A.check(Schema.makeFilter(() => {
6874 checks++
6875 return true
6876 }))
6877 })
6878 const instance = A.make({ a: "a" })
6879
6880 strictEqual(schema.make({ a: instance }).a, instance)
6881 strictEqual(checks, 1)
6882 })
6883
6884 it("make validates a nested Class source and output once", () => {
6885 let sourceChecks = 0
6886 let classChecks = 0
6887 class A extends Schema.Class<A>("A")(
6888 Schema.Struct({ a: Schema.String }).check(Schema.makeFilter(() => {
6889 sourceChecks++
6890 return true
6891 }))
6892 ) {}
6893 const schema = Schema.Struct({
6894 a: A.check(Schema.makeFilter(() => {
6895 classChecks++
6896 return true
6897 }))
6898 })
6899
6900 assertTrue(schema.make({ a: { a: "a" } }).a instanceof A)
6901 strictEqual(sourceChecks, 1)
6902 strictEqual(classChecks, 1)
6903 })
6904
6905 it("make allows an optional nested Class to be omitted", () => {
6906 class A extends Schema.Class<A>("A")({ a: Schema.String }) {}
6907 const schema = Schema.Struct({ a: Schema.optionalKey(A) })
6908
6909 deepStrictEqual(schema.make({}), {})
6910 })
6911
6912 it("make applies constructor defaults only at a field occurrence", () => {
6913 let defaults = 0
6914 const defaulted = Schema.String.pipe(
6915 Schema.withConstructorDefault(Effect.sync(() => {
6916 defaults++
6917 return "default"
6918 }))
6919 )
6920 const field = Schema.Struct({ value: defaulted })
6921 const unionMember = Schema.Struct({ value: Schema.Union([defaulted, Schema.Number]) })
6922
6923 deepStrictEqual(defaulted.makeOption(undefined as any), Option.none())
6924 strictEqual(defaults, 0)
6925 deepStrictEqual(field.make({}), { value: "default" })
6926 strictEqual(defaults, 1)
6927 deepStrictEqual(unionMember.makeOption({} as any), Option.none())
6928 deepStrictEqual(unionMember.makeOption({ value: undefined } as any), Option.none())
6929 strictEqual(defaults, 1)
6930 })
6931
6932 it("suspend before initialization", async () => {
6933 const schema = Schema.suspend(() => string)
6934 class A extends Schema.Class<A>("A")(Schema.Struct({ a: schema })) {}
6935 const string = Schema.String
6936
6937 const asserts = new TestSchema.Asserts(A)
6938 const make = asserts.make()
6939 await make.succeed(new A({ a: "a" }))
6940 await make.succeed({ a: "a" }, new A({ a: "a" }))
6941
6942 const decoding = asserts.decoding()
6943 await decoding.succeed(new A({ a: "a" }))
6944 })
6945
6946 it("should memoize the ast", () => {
6947 class A extends Schema.Class<A>("A")({
6948 a: Schema.String
6949 }) {}
6950 assertTrue(A.ast === A.ast)
6951 })
6952
6953 it("should set the identifier annotation", () => {
6954 class A extends Schema.Class<A>("A")({
6955 a: Schema.String
6956 }) {}
6957 strictEqual(A.ast.annotations?.identifier, "A")
6958 })
6959
6960 describe("should be compatible with `immer`", () => {
6961 it("`[immerable]`", () => {
6962 class A extends Schema.Class<A>("A")({
6963 a: Schema.Struct({ b: Schema.FiniteFromString }).pipe(Schema.optional),
6964 c: Schema.FiniteFromString
6965 }) {}
6966
6967 const a = new A({ a: { b: 1 }, c: 2 })
6968
6969 const modified = produce(a, (draft) => {
6970 if (draft.a) {
6971 draft.a.b = 2
6972 }
6973 })
6974
6975 assertTrue(modified instanceof A)
6976 strictEqual(modified.a?.b, 2)
6977 strictEqual(modified.c, 2)
6978 strictEqual(a.a?.b, 1)
6979 })
6980
6981 it("Equality", () => {
6982 class A extends Schema.Class<A>("A")({
6983 a: Schema.String
6984 }) {}
6985
6986 const a = new A({ a: "a" })
6987 const a1 = produce(a, (draft) => {
6988 draft.a = "a1"
6989 })
6990 const a2 = produce(a, (draft) => {
6991 draft.a = "a1"
6992 })
6993 assertTrue(Equal.equals(a1, new A({ a: "a1" })))
6994 assertTrue(Equal.equals(a1, a2))
6995 })
6996 })
6997
6998 it("mapFields", () => {
6999 class A extends Schema.Class<A>("A")({
7000 a: Schema.String
7001 }) {}
7002 const schema = A.mapFields((fields) => ({ ...fields, b: Schema.Number }))
7003 deepStrictEqual(schema.fields, { a: Schema.String, b: Schema.Number })
7004 })
7005
7006 it("Struct with nested Class", async () => {
7007 class A extends Schema.Class<A, { readonly brand: unique symbol }>("A")(Schema.Struct({
7008 a: Schema.String
7009 })) {}
7010 const schema = Schema.Struct({
7011 a: A.pipe(Schema.withConstructorDefault(Effect.succeed(new A({ a: "default" }))))
7012 })
7013 const asserts = new TestSchema.Asserts(schema)
7014
7015 const make = asserts.make()
7016 await make.succeed({ a: new A({ a: "a" }) })
7017 await make.succeed({}, { a: new A({ a: "default" }) })
7018 })
7019
7020 it("Class with nested Class", async () => {
7021 class A extends Schema.Class<A, { readonly brand: unique symbol }>("A")(Schema.Struct({
7022 a: Schema.String
7023 })) {}
7024 class B extends Schema.Class<B, { readonly brand: unique symbol }>("B")(Schema.Struct({
7025 a: A.pipe(Schema.withConstructorDefault(Effect.succeed(new A({ a: "default" }))))
7026 })) {}
7027 const schema = B
7028 const asserts = new TestSchema.Asserts(schema)
7029
7030 const make = asserts.make()
7031 await make.succeed({ a: new A({ a: "a" }) }, new B({ a: new A({ a: "a" }) }))
7032 await make.succeed({}, new B({ a: new A({ a: "default" }) }))
7033 })
7034
7035 it("make preserves Class instances in Array(Class) and Array(Union(Class))", () => {
7036 class Row extends Schema.Class<Row>("Row")({ value: Schema.String }) {}
7037 class DirectTable extends Schema.Class<DirectTable>("DirectTable")({ rows: Schema.Array(Row) }) {}
7038 class UnionTable extends Schema.Class<UnionTable>("UnionTable")({ rows: Schema.Array(Schema.Union([Row])) }) {}
7039 const row = Row.make({ value: "a" })
7040
7041 strictEqual(DirectTable.make({ rows: [row] }).rows[0], row)
7042 strictEqual(UnionTable.make({ rows: [row] }).rows[0], row)
7043 deepStrictEqual(DirectTable.makeOption({ rows: [{ value: 1 } as any] }), Option.none())
7044 deepStrictEqual(UnionTable.makeOption({ rows: [{ value: 1 } as any] }), Option.none())
7045 })
7046
7047 it("make constructs nested Class instances with and without Union", () => {
7048 class A extends Schema.Class<A>("A")({ a: Schema.String }) {}
7049 const direct = Schema.Struct({ a: A })
7050 const union = Schema.Struct({ a: Schema.Union([A]) })
7051
7052 assertTrue(direct.make({ a: { a: "a" } }).a instanceof A)
7053 assertTrue(union.make({ a: { a: "a" } }).a instanceof A)
7054 })
7055
7056 it("make selects the first TaggedClass Union member when the tag is defaulted", () => {
7057 class A extends Schema.TaggedClass<A>()("A", { a: Schema.String }) {}
7058 class B extends Schema.TaggedClass<B>()("B", { a: Schema.String }) {}
7059 const schema = Schema.Union([A, B])
7060
7061 assertTrue(schema.make({ a: "a" } as any) instanceof A)
7062 })
7063
7064 it("should be possible to define a class with a mutable field", async () => {
7065 class A extends Schema.Class<A>("A")({
7066 a: Schema.mutableKey(Schema.String)
7067 }) {
7068 public update() {
7069 this.a = "b"
7070 }
7071 }
7072 const asserts = new TestSchema.Asserts(A)
7073
7074 const make = asserts.make()
7075 await make.succeed(new A({ a: "a" }))
7076 await make.succeed({ a: "a" }, new A({ a: "a" }))
7077
7078 const a = new A({ a: "a" })
7079 a.update()
7080 strictEqual(a.a, "b")
7081 })
7082
7083 it("Fields argument", async () => {
7084 class A extends Schema.Class<A>("A")({
7085 a: Schema.String
7086 }) {
7087 readonly _a = 1
7088 }
7089 const asserts = new TestSchema.Asserts(A)
7090
7091 // should be a schema
7092 assertTrue(Schema.isSchema(A))
7093 // should expose the fields
7094 deepStrictEqual(A.fields, { a: Schema.String })
7095 // should expose the identifier
7096 strictEqual(A.identifier, "A")
7097
7098 strictEqual(A.name, "A")
7099
7100 assertTrue(new A({ a: "a" }) instanceof A)
7101 assertTrue(A.make({ a: "a" }) instanceof A)
7102
7103 // test additional fields
7104 strictEqual(new A({ a: "a" })._a, 1)
7105 strictEqual(A.make({ a: "a" })._a, 1)
7106
7107 // test Equal.equals
7108 assertTrue(Equal.equals(new A({ a: "a" }), new A({ a: "a" })))
7109 assertFalse(Equal.equals(new A({ a: "a" }), new A({ a: "b" })))
7110
7111 const make = asserts.make()
7112 await make.succeed(new A({ a: "a" }))
7113 await make.succeed({ a: "a" }, new A({ a: "a" }))
7114
7115 const decoding = asserts.decoding()
7116 await decoding.succeed({ a: "a" }, new A({ a: "a" }))
7117 await decoding.fail(
7118 null,
7119 `Expected object`
7120 )
7121 await decoding.fail(
7122 { a: 1 },
7123 `Expected string
7124 at ["a"]`
7125 )
7126
7127 const encoding = asserts.encoding()
7128 await encoding.succeed(new A({ a: "a" }), { a: "a" })
7129 await encoding.fail(
7130 null,
7131 "Expected A"
7132 )
7133 await encoding.fail(
7134 { a: "a" },
7135 `Expected A`
7136 )
7137 })
7138
7139 it("Struct argument", async () => {
7140 class A extends Schema.Class<A>("A")(Schema.Struct({
7141 a: Schema.String
7142 })) {
7143 readonly _a = 1
7144 }
7145 const asserts = new TestSchema.Asserts(A)
7146
7147 // should be a schema
7148 assertTrue(Schema.isSchema(A))
7149 // should expose the fields
7150 deepStrictEqual(A.fields, { a: Schema.String })
7151 // should expose the identifier
7152 strictEqual(A.identifier, "A")
7153
7154 strictEqual(A.name, "A")
7155
7156 assertTrue(new A({ a: "a" }) instanceof A)
7157 assertTrue(A.make({ a: "a" }) instanceof A)
7158
7159 // test additional fields
7160 strictEqual(new A({ a: "a" })._a, 1)
7161 strictEqual(A.make({ a: "a" })._a, 1)
7162
7163 // test Equal.equals
7164 assertTrue(Equal.equals(new A({ a: "a" }), new A({ a: "a" })))
7165 assertFalse(Equal.equals(new A({ a: "a" }), new A({ a: "b" })))
7166
7167 const make = asserts.make()
7168 await make.succeed(new A({ a: "a" }))
7169 await make.succeed({ a: "a" }, new A({ a: "a" }))
7170
7171 if (verifyGeneration) {
7172 asserts.arbitrary().verifyGeneration()
7173 }
7174
7175 const decoding = asserts.decoding()
7176 await decoding.succeed({ a: "a" }, new A({ a: "a" }))
7177 await decoding.fail(
7178 { a: 1 },
7179 `Expected string
7180 at ["a"]`
7181 )
7182
7183 const encoding = asserts.encoding()
7184 await encoding.succeed(new A({ a: "a" }), { a: "a" })
7185 await encoding.fail(
7186 null,
7187 "Expected A"
7188 )
7189 await encoding.fail(
7190 { a: "a" },
7191 `Expected A`
7192 )
7193 })
7194
7195 it("constructor ignores excess properties by default", () => {
7196 class A extends Schema.Class<A>("A")({
7197 a: Schema.String
7198 }) {}
7199
7200 const instance = new A({ a: "a", extra: "extra" } as any)
7201
7202 strictEqual(instance.a, "a")
7203 assertFalse("extra" in instance)
7204 })
7205
7206 it("constructor strips excess properties with explicit ignore", () => {
7207 class A extends Schema.Class<A>("A")({
7208 a: Schema.String
7209 }) {}
7210
7211 const instance = new A({ a: "a", extra: "extra" } as any, {
7212 parseOptions: { onExcessProperty: "ignore" }
7213 })
7214
7215 strictEqual(instance.a, "a")
7216 assertFalse("extra" in instance)
7217 })
7218
7219 it("constructor rejects excess properties when requested", () => {
7220 class A extends Schema.Class<A>("A")({
7221 a: Schema.String
7222 }) {}
7223
7224 throws(() =>
7225 new A({ a: "a", extra: "extra" } as any, {
7226 parseOptions: { onExcessProperty: "error" }
7227 })
7228 )
7229 })
7230
7231 it("annotate", async () => {
7232 class A extends Schema.Class<A>("A")({
7233 a: Schema.String
7234 }) {}
7235
7236 const Annotated = A.annotate({})
7237
7238 // should be a schema
7239 assertTrue(Schema.isSchema(Annotated))
7240 // should expose the fields
7241 deepStrictEqual(Annotated.from.fields, { a: Schema.String })
7242
7243 assertTrue(Annotated.make(new A({ a: "a" })) instanceof A)
7244 })
7245
7246 describe("extend", () => {
7247 it("basic", async () => {
7248 class A extends Schema.Class<A>("A")(Schema.Struct({
7249 a: Schema.String
7250 })) {
7251 readonly _a = 1
7252 }
7253 class B extends A.extend<B>("B")({
7254 b: Schema.Number
7255 }) {
7256 readonly _b = 2
7257 }
7258 const asserts = new TestSchema.Asserts(B)
7259
7260 const instance = new B({ a: "a", b: 2 })
7261
7262 assertTrue(instance instanceof A)
7263 assertTrue(B.make({ a: "a", b: 2 }) instanceof A)
7264 assertTrue(instance instanceof B)
7265 assertTrue(B.make({ a: "a", b: 2 }) instanceof B)
7266
7267 strictEqual(instance.a, "a")
7268 strictEqual(instance._a, 1)
7269 strictEqual(instance.b, 2)
7270 strictEqual(instance._b, 2)
7271
7272 const make = asserts.make()
7273 await make.succeed(new B({ a: "a", b: 2 }))
7274 await make.succeed({ a: "a", b: 2 }, new B({ a: "a", b: 2 }))
7275
7276 const decoding = asserts.decoding()
7277 await decoding.succeed({ a: "a", b: 2 }, new B({ a: "a", b: 2 }))
7278 })
7279
7280 it("decoding validates the extended struct once", () => {
7281 let baseChecks = 0
7282 let extensionChecks = 0
7283 class A extends Schema.Class<A>("A")(
7284 Schema.Struct({
7285 a: Schema.String
7286 }).check(Schema.makeFilter(() => {
7287 baseChecks++
7288 return true
7289 }))
7290 ) {}
7291 class B extends A.extend<B>("B")(
7292 Schema.Struct({
7293 b: Schema.Number
7294 }).check(Schema.makeFilter(() => {
7295 extensionChecks++
7296 return true
7297 }))
7298 ) {}
7299
7300 const instance = Schema.decodeUnknownSync(B)({ a: "a", b: 1 })
7301
7302 assertTrue(instance instanceof B)
7303 strictEqual(baseChecks, 1)
7304 strictEqual(extensionChecks, 1)
7305 })
7306
7307 it("constructor preserves subclass fields while ignoring excess properties by default", () => {
7308 class A extends Schema.Class<A>("A")({
7309 a: Schema.String
7310 }) {}
7311 class B extends A.extend<B>("B")({
7312 b: Schema.Number
7313 }) {}
7314
7315 const instance = new B({ a: "a", b: 2, extra: "extra" } as any)
7316
7317 strictEqual(instance.a, "a")
7318 strictEqual(instance.b, 2)
7319 assertFalse("extra" in instance)
7320 })
7321
7322 it("constructor keeps subclass fields and strips excess properties with explicit ignore", () => {
7323 class A extends Schema.Class<A>("A")({
7324 a: Schema.String
7325 }) {}
7326 class B extends A.extend<B>("B")({
7327 b: Schema.Number
7328 }) {}
7329
7330 const instance = new B({ a: "a", b: 2, extra: "extra" } as any, {
7331 parseOptions: { onExcessProperty: "ignore" }
7332 })
7333
7334 strictEqual(instance.a, "a")
7335 strictEqual(instance.b, 2)
7336 assertFalse("extra" in instance)
7337 })
7338
7339 it("constructor does not treat subclass fields as excess properties", () => {
7340 class A extends Schema.Class<A>("A")({
7341 a: Schema.String
7342 }) {}
7343 class B extends A.extend<B>("B")({
7344 b: Schema.Number
7345 }) {}
7346
7347 const instance = new B({ a: "a", b: 2 }, {
7348 parseOptions: { onExcessProperty: "error" }
7349 })
7350
7351 strictEqual(instance.a, "a")
7352 strictEqual(instance.b, 2)
7353
7354 throws(() =>
7355 new B({ a: "a", b: 2, extra: "extra" } as any, {
7356 parseOptions: { onExcessProperty: "error" }
7357 })
7358 )
7359 })
7360
7361 it("Struct argument", async () => {
7362 class A extends Schema.Class<A>("A")(
7363 Schema.Struct({
7364 a: Schema.Number
7365 }).check(Schema.makeFilter(({ a }) => a > 0, { expected: "positive a" }))
7366 ) {}
7367 class B extends A.extend<B>("B")(
7368 Schema.Struct({
7369 b: Schema.Number
7370 }).check(Schema.makeFilter(({ b }) => b > 0, { expected: "positive b" }))
7371 ) {}
7372 const asserts = new TestSchema.Asserts(B)
7373
7374 const make = asserts.make()
7375 await make.succeed({ a: 1, b: 1 }, new B({ a: 1, b: 1 }))
7376 await make.fail({ a: 0, b: 1 }, `Expected positive a`)
7377 await make.fail({ a: 1, b: 0 }, `Expected positive b`)
7378 })
7379
7380 it("static members", async () => {
7381 class A extends Schema.Class<A>("A")({
7382 a: Schema.String
7383 }) {
7384 static readonly aStatic = "value"
7385 }
7386 class B extends A.extend<B, typeof A>("B")({
7387 b: Schema.Number
7388 }) {}
7389 strictEqual(B.aStatic, "value")
7390 })
7391 })
7392 })
7393
7394 describe("TaggedClass", () => {
7395 it("make with void input", () => {
7396 class A extends Schema.TaggedClass<A>()("A", {}) {}
7397 deepStrictEqual(A.make(), new A())
7398 deepStrictEqual(A.makeOption(), Option.some(new A()))
7399 deepStrictEqual(Effect.runSync(A.makeEffect()), new A())
7400 })
7401
7402 it("explicit identifier", async () => {
7403 class A extends Schema.TaggedClass<A>("B")("A", {
7404 a: Schema.String
7405 }) {}
7406 strictEqual(A.identifier, "B")
7407 })
7408
7409 it("fields argument", async () => {
7410 class A extends Schema.TaggedClass<A>()("A", {
7411 a: Schema.String
7412 }) {}
7413 const asserts = new TestSchema.Asserts(A)
7414
7415 assertTrue(Schema.isSchema(A))
7416 deepStrictEqual(Object.keys(A.fields).sort(), ["_tag", "a"])
7417 strictEqual(A.identifier, "A")
7418
7419 const instance = new A({ a: "a" })
7420 strictEqual(instance._tag, "A")
7421 strictEqual(instance.a, "a")
7422 assertTrue(instance instanceof A)
7423
7424 const make = asserts.make()
7425 await make.succeed(new A({ a: "a" }))
7426 await make.succeed({ a: "a" }, new A({ a: "a" }))
7427
7428 const decoding = asserts.decoding()
7429 await decoding.succeed({ _tag: "A", a: "a" }, new A({ a: "a" }))
7430
7431 const encoding = asserts.encoding()
7432 await encoding.succeed(new A({ a: "a" }), { _tag: "A", a: "a" })
7433 })
7434
7435 it("constructor ignores excess properties by default", () => {
7436 class A extends Schema.TaggedClass<A>()("A", {
7437 a: Schema.String
7438 }) {}
7439
7440 const instance = new A({ a: "a", extra: "extra" } as any)
7441
7442 strictEqual(instance._tag, "A")
7443 strictEqual(instance.a, "a")
7444 assertFalse("extra" in instance)
7445 })
7446
7447 it("Struct argument", async () => {
7448 class A extends Schema.TaggedClass<A>()(
7449 "A",
7450 Schema.Struct({
7451 a: Schema.String
7452 }).check(Schema.makeFilter(({ a }) => a.length > 0, { expected: `"a" being longer than 0` }))
7453 ) {}
7454
7455 const asserts = new TestSchema.Asserts(A)
7456
7457 const make = asserts.make()
7458 await make.succeed({ a: "a" }, new A({ a: "a" }))
7459 await make.fail({ a: "" }, `Expected "a" being longer than 0`)
7460
7461 const decoding = asserts.decoding()
7462 await decoding.succeed({ _tag: "A", a: "a" }, new A({ a: "a" }))
7463 await decoding.fail(
7464 { a: "a" },
7465 `Missing key
7466 at ["_tag"]`
7467 )
7468 await decoding.fail({ _tag: "A", a: "" }, `Expected "a" being longer than 0`)
7469 })
7470
7471 it("extended constructor does not treat subclass fields as excess properties", () => {
7472 class A extends Schema.TaggedClass<A>()("A", {
7473 a: Schema.String
7474 }) {}
7475 class B extends A.extend<B>("B")({
7476 b: Schema.Number
7477 }) {}
7478
7479 const instance = new B({ a: "a", b: 2, extra: "extra" } as any)
7480
7481 strictEqual(instance._tag, "A")
7482 strictEqual(instance.a, "a")
7483 strictEqual(instance.b, 2)
7484 assertFalse("extra" in instance)
7485
7486 const strictInstance = new B({ a: "a", b: 2 }, {
7487 parseOptions: { onExcessProperty: "error" }
7488 })
7489
7490 strictEqual(strictInstance._tag, "A")
7491 strictEqual(strictInstance.a, "a")
7492 strictEqual(strictInstance.b, 2)
7493
7494 throws(() =>
7495 new B({ a: "a", b: 2, extra: "extra" } as any, {
7496 parseOptions: { onExcessProperty: "error" }
7497 })
7498 )
7499 })
7500 })
7501
7502 describe("Error", () => {
7503 it("make with void input", () => {
7504 class E extends Schema.Error<E>("E")({}) {}
7505 deepStrictEqual(E.make(), new E())
7506 deepStrictEqual(E.makeOption(), Option.some(new E()))
7507 deepStrictEqual(Effect.runSync(E.makeEffect()), new E())
7508 })
7509
7510 it("fields argument", async () => {
7511 class E extends Schema.Error<E>("E")({
7512 id: Schema.Number
7513 }) {}
7514 const asserts = new TestSchema.Asserts(E)
7515
7516 const err = new E({ id: 1 })
7517
7518 strictEqual(String(err), `E`)
7519 assertInclude(err.stack, "Schema.test.ts:")
7520 strictEqual(err.id, 1)
7521
7522 const make = asserts.make()
7523 await make.succeed(new E({ id: 1 }))
7524 await make.succeed({ id: 1 }, new E({ id: 1 }))
7525 })
7526
7527 it("constructor ignores excess properties by default", () => {
7528 class E extends Schema.Error<E>("E")({
7529 message: Schema.String,
7530 cause: Schema.optionalKey(Schema.Unknown),
7531 code: Schema.Number
7532 }) {}
7533 const cause = new Error("cause")
7534
7535 const err = new E({ message: "boom", cause, code: 1, extra: "extra" } as any)
7536
7537 strictEqual(err.message, "boom")
7538 strictEqual(err.cause, cause)
7539 strictEqual(err.code, 1)
7540 assertFalse("extra" in err)
7541 })
7542
7543 it("constructor strips excess properties with explicit ignore", () => {
7544 class E extends Schema.Error<E>("E")({
7545 message: Schema.String,
7546 code: Schema.Number
7547 }) {}
7548
7549 const err = new E({ message: "boom", code: 1, extra: "extra" } as any, {
7550 parseOptions: { onExcessProperty: "ignore" }
7551 })
7552
7553 strictEqual(err.message, "boom")
7554 strictEqual(err.code, 1)
7555 assertFalse("extra" in err)
7556 })
7557
7558 it("Struct argument", async () => {
7559 class E extends Schema.Error<E>("E")(Schema.Struct({
7560 id: Schema.Number
7561 })) {}
7562 const asserts = new TestSchema.Asserts(E)
7563
7564 const err = new E({ id: 1 })
7565
7566 strictEqual(String(err), `E`)
7567 assertInclude(err.stack, "Schema.test.ts:")
7568 strictEqual(err.id, 1)
7569
7570 const make = asserts.make()
7571 await make.succeed(new E({ id: 1 }))
7572 await make.succeed({ id: 1 }, new E({ id: 1 }))
7573
7574 if (verifyGeneration) {
7575 asserts.arbitrary().verifyGeneration()
7576 }
7577 })
7578
7579 it("extend", async () => {
7580 class A extends Schema.Error<A>("A")({
7581 a: Schema.String
7582 }) {
7583 readonly _a = 1
7584 }
7585 class B extends A.extend<B>("B")({
7586 b: Schema.Number
7587 }) {
7588 readonly _b = 2
7589 }
7590 const asserts = new TestSchema.Asserts(B)
7591
7592 const instance = new B({ a: "a", b: 2 })
7593
7594 strictEqual(String(instance), `B`)
7595 assertInclude(instance.stack, "Schema.test.ts:")
7596
7597 assertTrue(instance instanceof A)
7598 assertTrue(B.make({ a: "a", b: 2 }) instanceof A)
7599 assertTrue(instance instanceof B)
7600 assertTrue(B.make({ a: "a", b: 2 }) instanceof B)
7601
7602 strictEqual(instance.a, "a")
7603 strictEqual(instance._a, 1)
7604 strictEqual(instance.b, 2)
7605 strictEqual(instance._b, 2)
7606
7607 const make = asserts.make()
7608 await make.succeed(new B({ a: "a", b: 2 }))
7609 await make.succeed({ a: "a", b: 2 }, new B({ a: "a", b: 2 }))
7610
7611 const decoding = asserts.decoding()
7612 await decoding.succeed({ a: "a", b: 2 }, new B({ a: "a", b: 2 }))
7613 })
7614
7615 it("extended constructor ignores excess properties by default", () => {
7616 class A extends Schema.Error<A>("A")({
7617 message: Schema.String
7618 }) {}
7619 class B extends A.extend<B>("B")({
7620 code: Schema.Number
7621 }) {}
7622
7623 const err = new B({ message: "boom", code: 1, extra: "extra" } as any)
7624
7625 strictEqual(err.message, "boom")
7626 strictEqual(err.code, 1)
7627 assertFalse("extra" in err)
7628 })
7629
7630 it("extended constructor does not treat subclass fields as excess properties", () => {
7631 class A extends Schema.Error<A>("A")({
7632 message: Schema.String
7633 }) {}
7634 class B extends A.extend<B>("B")({
7635 code: Schema.Number
7636 }) {}
7637
7638 const err = new B({ message: "boom", code: 1 }, {
7639 parseOptions: { onExcessProperty: "error" }
7640 })
7641
7642 strictEqual(err.message, "boom")
7643 strictEqual(err.code, 1)
7644 })
7645
7646 it("`toString` to match native `Error` output format", async () => {
7647 class E extends Schema.Error<E>("E")({
7648 message: Schema.String
7649 }) {}
7650 const err = new E({ message: "my message" })
7651 strictEqual(String(err), `E: my message`)
7652 })
7653 })
7654
7655 describe("TaggedError", () => {
7656 it("make preserves existing instances", () => {
7657 class E extends Schema.TaggedError<E>()("E", { message: Schema.String }) {}
7658 const instance = new E({ message: "failure" })
7659
7660 assert.strictEqual(E.make(instance), instance)
7661 })
7662
7663 it("make with void input", () => {
7664 class E extends Schema.TaggedError<E>()("E", {}) {}
7665 deepStrictEqual(E.make(), new E())
7666 deepStrictEqual(E.makeOption(), Option.some(new E()))
7667 deepStrictEqual(Effect.runSync(E.makeEffect()), new E())
7668 })
7669
7670 it("fields argument", async () => {
7671 class E extends Schema.TaggedError<E>()("E", {
7672 id: Schema.Number
7673 }) {}
7674 const asserts = new TestSchema.Asserts(E)
7675
7676 const err = new E({ id: 1 })
7677 strictEqual(err._tag, "E")
7678 strictEqual(err.id, 1)
7679 assertInclude(err.stack, "Schema.test.ts:")
7680
7681 const make = asserts.make()
7682 await make.succeed(new E({ id: 1 }))
7683 await make.succeed({ id: 1 }, new E({ id: 1 }))
7684
7685 const decoding = asserts.decoding()
7686 await decoding.succeed({ _tag: "E", id: 1 }, new E({ id: 1 }))
7687
7688 const encoding = asserts.encoding()
7689 await encoding.succeed(new E({ id: 1 }), { _tag: "E", id: 1 })
7690 })
7691
7692 it("constructor ignores excess properties by default", () => {
7693 class E extends Schema.TaggedError<E>()("E", {
7694 id: Schema.Number
7695 }) {}
7696
7697 const err = new E({ id: 1, extra: "extra" } as any)
7698
7699 strictEqual(err._tag, "E")
7700 strictEqual(err.id, 1)
7701 assertFalse("extra" in err)
7702 })
7703
7704 it("Struct argument", async () => {
7705 class E extends Schema.TaggedError<E>()(
7706 "E",
7707 Schema.Struct({
7708 id: Schema.Number
7709 })
7710 ) {}
7711
7712 const err = new E({ id: 1 })
7713 strictEqual(err._tag, "E")
7714 strictEqual(err.id, 1)
7715 deepStrictEqual(Object.keys(E.fields).sort(), ["_tag", "id"])
7716 })
7717
7718 it("name matches tag", () => {
7719 class E extends Schema.TaggedError<E>()("TaggedErrorName", {
7720 id: Schema.Number
7721 }) {}
7722
7723 const err = new E({ id: 1 })
7724 strictEqual(err.name, "TaggedErrorName")
7725 })
7726
7727 it("name matches identifier", () => {
7728 class E extends Schema.TaggedError<E>("A")("B", {
7729 a: Schema.Number
7730 }) {}
7731
7732 const err = new E({ a: 1 })
7733 strictEqual(err.name, "A")
7734 })
7735
7736 it("name matches identifier after extend", () => {
7737 class E extends Schema.TaggedError<E>("A")("B", {
7738 a: Schema.Number
7739 }) {}
7740 class E2 extends E.extend<E2>("C")({
7741 b: Schema.String
7742 }) {}
7743
7744 const err = new E2({ a: 1, b: "b" })
7745 strictEqual(err.name, "C")
7746 })
7747
7748 it("zero-field TaggedError allows omitting props argument", () => {
7749 class NotFoundError extends Schema.TaggedError<NotFoundError>()("NotFoundError", {}) {}
7750
7751 // new NotFoundError() should work without passing {}
7752 const a = new NotFoundError()
7753 strictEqual(a._tag, "NotFoundError")
7754 assertTrue(a instanceof NotFoundError)
7755
7756 // new NotFoundError({}) should also still work
7757 const b = new NotFoundError({})
7758 strictEqual(b._tag, "NotFoundError")
7759 assertTrue(b instanceof NotFoundError)
7760 })
7761
7762 it("extend", async () => {
7763 class A extends Schema.TaggedError<A>()("A", {
7764 a: Schema.String
7765 }) {}
7766 class B extends A.extend<B>("B")({
7767 b: Schema.Number
7768 }) {}
7769 const instance = new B({ a: "a", b: 2 })
7770 strictEqual(instance._tag, "A")
7771 assertTrue(instance instanceof A)
7772 assertTrue(instance instanceof B)
7773 })
7774
7775 it("extended constructor ignores excess properties by default", () => {
7776 class A extends Schema.TaggedError<A>()("A", {
7777 a: Schema.String
7778 }) {}
7779 class B extends A.extend<B>("B")({
7780 b: Schema.Number
7781 }) {}
7782
7783 const instance = new B({ a: "a", b: 2, extra: "extra" } as any)
7784
7785 strictEqual(instance._tag, "A")
7786 strictEqual(instance.a, "a")
7787 strictEqual(instance.b, 2)
7788 assertFalse("extra" in instance)
7789 })
7790
7791 it("extended constructor does not treat subclass fields as excess properties", () => {
7792 class A extends Schema.TaggedError<A>()("A", {
7793 a: Schema.String
7794 }) {}
7795 class B extends A.extend<B>("B")({
7796 b: Schema.Number
7797 }) {}
7798
7799 const instance = new B({ a: "a", b: 2 }, {
7800 parseOptions: { onExcessProperty: "error" }
7801 })
7802
7803 strictEqual(instance._tag, "A")
7804 strictEqual(instance.a, "a")
7805 strictEqual(instance.b, 2)
7806 })
7807 })
7808
7809 describe("Enum", () => {
7810 it("rejects non-finite numeric values", () => {
7811 for (const value of [NaN, Infinity, -Infinity]) {
7812 throws(
7813 () => Schema.Enum({ value }),
7814 new Error(`A numeric enum value must be finite, got ${String(value)}`)
7815 )
7816 }
7817 })
7818
7819 it("enums should be exposed", () => {
7820 enum Fruits {
7821 Apple,
7822 Banana
7823 }
7824 const schema = Schema.Enum(Fruits)
7825 strictEqual(schema.enums.Apple, 0)
7826 strictEqual(schema.enums.Banana, 1)
7827 })
7828
7829 it("Numeric enum", async () => {
7830 enum Fruits {
7831 Apple,
7832 Banana
7833 }
7834 const schema = Schema.Enum(Fruits)
7835 const asserts = new TestSchema.Asserts(schema)
7836
7837 const decoding = asserts.decoding()
7838 await decoding.succeed(Fruits.Apple)
7839 await decoding.succeed(Fruits.Banana)
7840 await decoding.succeed(0)
7841 await decoding.succeed(1)
7842
7843 await decoding.fail(
7844 3,
7845 `Expected 0 | 1`
7846 )
7847
7848 const encoding = asserts.encoding()
7849 await encoding.succeed(Fruits.Apple, 0)
7850 await encoding.succeed(Fruits.Banana, 1)
7851 })
7852
7853 it("String enum", async () => {
7854 enum Fruits {
7855 Apple = "apple",
7856 Banana = "banana",
7857 Cantaloupe = 0
7858 }
7859 const schema = Schema.Enum(Fruits)
7860 const asserts = new TestSchema.Asserts(schema)
7861
7862 const decoding = asserts.decoding()
7863 await decoding.succeed(Fruits.Apple)
7864 await decoding.succeed(Fruits.Cantaloupe)
7865 await decoding.succeed("apple")
7866 await decoding.succeed("banana")
7867 await decoding.succeed(0)
7868 await decoding.succeed(0)
7869
7870 await decoding.fail(
7871 "Cantaloupe",
7872 `Expected "apple" | "banana" | 0`
7873 )
7874
7875 const encoding = asserts.encoding()
7876 await encoding.succeed(Fruits.Apple)
7877 await encoding.succeed(Fruits.Banana)
7878 await encoding.succeed(Fruits.Cantaloupe)
7879 })
7880
7881 it("Const enum", async () => {
7882 const Fruits = {
7883 Apple: "apple",
7884 Banana: "banana",
7885 Cantaloupe: 3
7886 } as const
7887 const schema = Schema.Enum(Fruits)
7888 const asserts = new TestSchema.Asserts(schema)
7889
7890 const decoding = asserts.decoding()
7891 await decoding.succeed("apple")
7892 await decoding.succeed("banana")
7893 await decoding.succeed(3)
7894
7895 await decoding.fail(
7896 "Cantaloupe",
7897 `Expected "apple" | "banana" | 3`
7898 )
7899
7900 const encoding = asserts.encoding()
7901 await encoding.succeed(Fruits.Apple, "apple")
7902 await encoding.succeed(Fruits.Banana, "banana")
7903 await encoding.succeed(Fruits.Cantaloupe, 3)
7904 })
7905 })
7906
7907 describe("catchDecoding", () => {
7908 it("sync fallback", async () => {
7909 const fallback = Effect.succeed(Option.some("b"))
7910 const schema = Schema.String.pipe(Schema.catchDecoding(() => fallback)).check(Schema.isNonEmpty())
7911 const asserts = new TestSchema.Asserts(schema)
7912
7913 const decoding = asserts.decoding()
7914 await decoding.succeed("a")
7915 await decoding.succeed(null, "b")
7916 await decoding.fail(
7917 "",
7918 `Expected a value with a length of at least 1`
7919 )
7920
7921 const encoding = asserts.encoding()
7922 await encoding.succeed("a")
7923 await encoding.fail(
7924 null,
7925 "Expected string"
7926 )
7927 })
7928
7929 it("async fallback", async () => {
7930 const fallback = Effect.succeed(Option.some("b")).pipe(Effect.delay(100))
7931 const schema = Schema.String.pipe(Schema.catchDecoding(() => fallback))
7932 const asserts = new TestSchema.Asserts(schema)
7933
7934 const decoding = asserts.decoding()
7935 await decoding.succeed("a")
7936 await decoding.succeed(null, "b")
7937 })
7938 })
7939
7940 it("catchDecodingWithContext", async () => {
7941 class Service extends Context.Service<Service, { fallback: Effect.Effect<string> }>()("Service") {}
7942
7943 const schema = Schema.String.pipe(Schema.catchDecodingWithContext(() =>
7944 Effect.gen(function*() {
7945 const service = yield* Service
7946 return Option.some(yield* service.fallback)
7947 })
7948 ))
7949 const asserts = new TestSchema.Asserts(schema)
7950
7951 const decoding = asserts.decoding().provide(
7952 Service,
7953 { fallback: Effect.succeed("b") }
7954 )
7955 await decoding.succeed("a")
7956 await decoding.succeed(null, "b")
7957 })
7958
7959 describe("middlewareDecoding", () => {
7960 it("providing a service", async () => {
7961 class Service extends Context.Service<Service, { fallback: Effect.Effect<number> }>()("Service") {}
7962
7963 const schema = Schema.FiniteFromString.pipe(
7964 Schema.catchDecodingWithContext((issue) =>
7965 Effect.gen(function*() {
7966 if (issue._tag === "Encoding" && issue.issue._tag === "InvalidType") {
7967 return yield* Effect.fail(issue)
7968 }
7969 const service = yield* Service
7970 return Option.some(yield* service.fallback)
7971 })
7972 ),
7973 Schema.middlewareDecoding(Effect.provideService(Service, { fallback: Effect.succeed(0) }))
7974 )
7975 const asserts = new TestSchema.Asserts(schema)
7976
7977 const decoding = asserts.decoding()
7978 await decoding.succeed("1", 1)
7979 await decoding.succeed("a", 0)
7980 await decoding.fail(null, "Expected string")
7981 })
7982
7983 it("forced failure", async () => {
7984 const schema = Schema.String.pipe(
7985 Schema.middlewareDecoding(() => Effect.fail(new SchemaIssue.Forbidden({ message: "my message" })))
7986 )
7987 const asserts = new TestSchema.Asserts(schema)
7988
7989 const decoding = asserts.decoding()
7990 await decoding.fail(
7991 "1",
7992 "my message"
7993 )
7994 })
7995 })
7996
7997 describe("catchEncoding", () => {
7998 it("sync fallback", async () => {
7999 const fallback = Effect.succeed(Option.some(0))
8000 const schema = Schema.Number.pipe(Schema.catchEncoding(() => fallback), Schema.encodeTo(Schema.Int))
8001 const asserts = new TestSchema.Asserts(schema)
8002
8003 const decoding = asserts.decoding()
8004 await decoding.succeed(1)
8005 await decoding.fail(
8006 1.2,
8007 `Expected an integer`
8008 )
8009
8010 const encoding = asserts.encoding()
8011 await encoding.succeed(1)
8012 await encoding.succeed(null, 0)
8013 await encoding.fail(
8014 1.2,
8015 `Expected an integer`
8016 )
8017 })
8018
8019 it("async fallback", async () => {
8020 const fallback = Effect.succeed(Option.some(0)).pipe(Effect.delay(100))
8021 const schema = Schema.Number.pipe(Schema.catchEncoding(() => fallback), Schema.encodeTo(Schema.Int))
8022 const asserts = new TestSchema.Asserts(schema)
8023
8024 const encoding = asserts.encoding()
8025 await encoding.succeed(1)
8026 await encoding.succeed(null, 0)
8027 await encoding.fail(
8028 1.2,
8029 `Expected an integer`
8030 )
8031 })
8032 })
8033
8034 it("catchEncodingWithContext", async () => {
8035 class Service extends Context.Service<Service, { fallback: Effect.Effect<number> }>()("Service") {}
8036
8037 const schema = Schema.Number.pipe(
8038 Schema.catchEncodingWithContext(() =>
8039 Effect.gen(function*() {
8040 const service = yield* Service
8041 return Option.some(yield* service.fallback)
8042 })
8043 ),
8044 Schema.encodeTo(Schema.Int)
8045 )
8046 const asserts = new TestSchema.Asserts(schema)
8047
8048 const encoding = asserts.encoding().provide(
8049 Service,
8050 { fallback: Effect.succeed(0) }
8051 )
8052 await encoding.succeed(1)
8053 await encoding.succeed(null, 0)
8054 await encoding.fail(
8055 1.2,
8056 `Expected an integer`
8057 )
8058 })
8059
8060 describe("middlewareEncoding", () => {
8061 it("providing a service", async () => {
8062 class Service extends Context.Service<Service, { fallback: Effect.Effect<string> }>()("Service") {}
8063
8064 const schema = Schema.FiniteFromString.pipe(
8065 Schema.catchEncodingWithContext((issue) =>
8066 Effect.gen(function*() {
8067 if (issue._tag === "InvalidType") {
8068 return yield* Effect.fail(issue)
8069 }
8070 const service = yield* Service
8071 return Option.some(yield* service.fallback)
8072 })
8073 ),
8074 Schema.middlewareEncoding(Effect.provideService(Service, { fallback: Effect.succeed("b") }))
8075 )
8076 const asserts = new TestSchema.Asserts(schema)
8077
8078 const encoding = asserts.encoding()
8079 await encoding.succeed(1, "1")
8080 await encoding.succeed(NaN, "b")
8081 await encoding.fail(null, "Expected number")
8082 })
8083
8084 it("forced failure", async () => {
8085 const schema = Schema.String.pipe(
8086 Schema.middlewareEncoding(() => Effect.fail(new SchemaIssue.Forbidden({ message: "my message" })))
8087 )
8088 const asserts = new TestSchema.Asserts(schema)
8089
8090 const encoding = asserts.encoding()
8091 await encoding.fail(1, "my message")
8092 })
8093 })
8094
8095 describe("Optional Fields", () => {
8096 it("Exact Optional Property", async () => {
8097 const schema = Schema.Struct({
8098 a: Schema.optionalKey(Schema.FiniteFromString)
8099 })
8100 const asserts = new TestSchema.Asserts(schema)
8101
8102 const decoding = asserts.decoding()
8103 await decoding.succeed({ a: "1" }, { a: 1 })
8104 await decoding.succeed({})
8105
8106 const encoding = asserts.encoding()
8107 await encoding.succeed({ a: 1 }, { a: "1" })
8108 await encoding.succeed({})
8109 })
8110
8111 it("Optional Property", async () => {
8112 const schema = Schema.Struct({
8113 a: Schema.optional(Schema.FiniteFromString)
8114 })
8115 const asserts = new TestSchema.Asserts(schema)
8116
8117 const decoding = asserts.decoding()
8118 await decoding.succeed({ a: "1" }, { a: 1 })
8119 await decoding.succeed({})
8120 await decoding.succeed({ a: undefined })
8121
8122 const encoding = asserts.encoding()
8123 await encoding.succeed({ a: 1 }, { a: "1" })
8124 await encoding.succeed({})
8125 await encoding.succeed({ a: undefined })
8126 })
8127
8128 it("Exact Optional Property with Nullability", async () => {
8129 const schema = Schema.Struct({
8130 a: Schema.optionalKey(Schema.NullOr(Schema.FiniteFromString))
8131 })
8132 const asserts = new TestSchema.Asserts(schema)
8133
8134 const decoding = asserts.decoding()
8135 await decoding.succeed({ a: "1" }, { a: 1 })
8136 await decoding.succeed({})
8137 await decoding.succeed({ a: null })
8138
8139 const encoding = asserts.encoding()
8140 await encoding.succeed({ a: 1 }, { a: "1" })
8141 await encoding.succeed({})
8142 await encoding.succeed({ a: null })
8143 })
8144
8145 it("Optional Property with Nullability", async () => {
8146 const schema = Schema.Struct({
8147 a: Schema.optional(Schema.NullOr(Schema.FiniteFromString))
8148 })
8149 const asserts = new TestSchema.Asserts(schema)
8150
8151 const decoding = asserts.decoding()
8152 await decoding.succeed({ a: "1" }, { a: 1 })
8153 await decoding.succeed({})
8154 await decoding.succeed({ a: undefined })
8155 await decoding.succeed({ a: null })
8156
8157 const encoding = asserts.encoding()
8158 await encoding.succeed({ a: 1 }, { a: "1" })
8159 await encoding.succeed({})
8160 await encoding.succeed({ a: null })
8161 await encoding.succeed({ a: undefined })
8162 })
8163
8164 it("Optional Property to Exact Optional Property", async () => {
8165 const schema = Schema.Struct({
8166 a: Schema.optional(Schema.FiniteFromString).pipe(Schema.decodeTo(Schema.optionalKey(Schema.Number), {
8167 decode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotUndefined)),
8168 encode: SchemaGetter.passthrough()
8169 }))
8170 })
8171 const asserts = new TestSchema.Asserts(schema)
8172
8173 const decoding = asserts.decoding()
8174 await decoding.succeed({ a: "1" }, { a: 1 })
8175 await decoding.succeed({})
8176 await decoding.succeed({ a: undefined }, {})
8177
8178 const encoding = asserts.encoding()
8179 await encoding.succeed({ a: 1 }, { a: "1" })
8180 await encoding.succeed({})
8181 })
8182
8183 it("Optional Property with Nullability to Optional Property", async () => {
8184 const schema = Schema.Struct({
8185 a: Schema.optional(Schema.NullOr(Schema.FiniteFromString)).pipe(
8186 Schema.decodeTo(Schema.optional(Schema.Number), {
8187 decode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull)),
8188 encode: SchemaGetter.passthrough()
8189 })
8190 )
8191 })
8192 const asserts = new TestSchema.Asserts(schema)
8193
8194 const decoding = asserts.decoding()
8195 await decoding.succeed({ a: "1" }, { a: 1 })
8196 await decoding.succeed({})
8197 await decoding.succeed({ a: undefined })
8198 await decoding.succeed({ a: null }, {})
8199
8200 const encoding = asserts.encoding()
8201 await encoding.succeed({ a: 1 }, { a: "1" })
8202 await encoding.succeed({})
8203 })
8204 })
8205
8206 describe("asOption", () => {
8207 it("optionalKey -> Option", async () => {
8208 const schema = Schema.Struct({
8209 a: Schema.optionalKey(Schema.FiniteFromString).pipe(
8210 Schema.decodeTo(
8211 Schema.Option(Schema.Number),
8212 SchemaTransformation.transformOptional({
8213 decode: Option.some,
8214 encode: Option.flatten
8215 })
8216 )
8217 )
8218 })
8219 const asserts = new TestSchema.Asserts(schema)
8220
8221 const decoding = asserts.decoding()
8222 await decoding.succeed({ a: "1" }, { a: Option.some(1) })
8223 await decoding.succeed({}, { a: Option.none() })
8224
8225 const encoding = asserts.encoding()
8226 await encoding.succeed({ a: Option.some(1) }, { a: "1" })
8227 await encoding.succeed({ a: Option.none() }, {})
8228 })
8229
8230 it("optional -> Option", async () => {
8231 const schema = Schema.Struct({
8232 a: Schema.optional(Schema.FiniteFromString).pipe(
8233 Schema.decodeTo(
8234 Schema.Option(Schema.Number),
8235 SchemaTransformation.transformOptional({
8236 decode: (on) => on.pipe(Option.filter((nu) => nu !== undefined), Option.some),
8237 encode: Option.flatten
8238 })
8239 )
8240 )
8241 })
8242 const asserts = new TestSchema.Asserts(schema)
8243
8244 const decoding = asserts.decoding()
8245 await decoding.succeed({ a: "1" }, { a: Option.some(1) })
8246 await decoding.succeed({}, { a: Option.none() })
8247 await decoding.succeed({ a: undefined }, { a: Option.none() })
8248
8249 const encoding = asserts.encoding()
8250 await encoding.succeed({ a: Option.some(1) }, { a: "1" })
8251 await encoding.succeed({ a: Option.none() }, {})
8252 })
8253 })
8254
8255 it("decodeTo as composition", async () => {
8256 const From = Schema.Struct({
8257 a: Schema.String,
8258 b: Schema.FiniteFromString
8259 })
8260
8261 const To = Schema.Struct({
8262 a: Schema.FiniteFromString,
8263 b: Schema.UndefinedOr(Schema.Number)
8264 })
8265
8266 const schema = From.pipe(Schema.decodeTo(To))
8267 const asserts = new TestSchema.Asserts(schema)
8268
8269 const decoding = asserts.decoding()
8270 await decoding.succeed({ a: "1", b: "2" }, { a: 1, b: 2 })
8271
8272 const encoding = asserts.encoding()
8273 await encoding.succeed({ a: 1, b: 2 }, { a: "1", b: "2" })
8274 await encoding.fail(
8275 { a: 1, b: NaN },
8276 `Expected a finite number
8277 at ["b"]`
8278 )
8279 await encoding.fail(
8280 { a: 1, b: undefined },
8281 `Expected number
8282 at ["b"]`
8283 )
8284 })
8285
8286 it("encodeTo as composition", async () => {
8287 const From = Schema.Struct({
8288 a: Schema.String,
8289 b: Schema.FiniteFromString
8290 })
8291
8292 const To = Schema.Struct({
8293 a: Schema.FiniteFromString,
8294 b: Schema.UndefinedOr(Schema.Number)
8295 })
8296
8297 const schema = To.pipe(Schema.encodeTo(From))
8298 const asserts = new TestSchema.Asserts(schema)
8299
8300 const decoding = asserts.decoding()
8301 await decoding.succeed({ a: "1", b: "2" }, { a: 1, b: 2 })
8302 await decoding.fail(
8303 { a: "1", b: null },
8304 `Expected string
8305 at ["b"]`
8306 )
8307
8308 const encoding = asserts.encoding()
8309 await encoding.succeed({ a: 1, b: 2 }, { a: "1", b: "2" })
8310 await encoding.fail(
8311 { a: 1, b: NaN },
8312 `Expected a finite number
8313 at ["b"]`
8314 )
8315 await encoding.fail(
8316 { a: 1, b: undefined },
8317 `Expected number
8318 at ["b"]`
8319 )
8320 })
8321
8322 describe("checkEffect", () => {
8323 it("no context", async () => {
8324 const schema = Schema.String.pipe(
8325 Schema.decode({
8326 decode: SchemaGetter.checkEffect((s) =>
8327 Effect.gen(function*() {
8328 if (s.length === 0) {
8329 return new SchemaIssue.InvalidValue({ message: "input should not be empty string" })
8330 }
8331 }).pipe(Effect.delay(100))
8332 ),
8333 encode: SchemaGetter.passthrough()
8334 })
8335 )
8336 const asserts = new TestSchema.Asserts(schema)
8337
8338 const decoding = asserts.decoding()
8339 await decoding.succeed("a")
8340 await decoding.fail(
8341 "",
8342 "input should not be empty string"
8343 )
8344 })
8345
8346 it("with context", async () => {
8347 class Service extends Context.Service<Service, { fallback: Effect.Effect<string> }>()("Service") {}
8348
8349 const schema = Schema.String.pipe(
8350 Schema.decode({
8351 decode: SchemaGetter.checkEffect((s) =>
8352 Effect.gen(function*() {
8353 yield* Service
8354 if (s.length === 0) {
8355 return new SchemaIssue.InvalidValue({ message: "input should not be empty string" })
8356 }
8357 })
8358 ),
8359 encode: SchemaGetter.passthrough()
8360 })
8361 )
8362 const asserts = new TestSchema.Asserts(schema)
8363
8364 const decoding = asserts.decoding().provide(
8365 Service,
8366 { fallback: Effect.succeed("b") }
8367 )
8368 await decoding.succeed("a")
8369 await decoding.fail(
8370 "",
8371 "input should not be empty string"
8372 )
8373 })
8374 })
8375
8376 describe("is", () => {
8377 it("FiniteFromString", () => {
8378 const schema = Schema.FiniteFromString
8379 const is = Schema.is(schema)
8380 assertTrue(is(1))
8381 assertFalse(is("a"))
8382 })
8383 })
8384
8385 describe("asserts", () => {
8386 it("FiniteFromString", () => {
8387 const schema = Schema.FiniteFromString
8388 try {
8389 Schema.asserts(schema, 1)
8390 } catch {
8391 fail("Expected asserts to not throw an error")
8392 }
8393 try {
8394 Schema.asserts(schema, "a")
8395 fail("Expected asserts to throw an error")
8396 } catch (e) {
8397 assertSchemaIssueError(e, "Expected number")
8398 }
8399 })
8400 })
8401
8402 describe("decodeUnknownPromise / encodeUnknownPromise", () => {
8403 it("FiniteFromString", async () => {
8404 const schema = Schema.FiniteFromString
8405 const decodeUnknownPromise = Schema.decodeUnknownPromise(schema)
8406 const encodeUnknownPromise = Schema.encodeUnknownPromise(schema)
8407 const decodeUnknownPromiseIssue = SchemaParser.decodeUnknownPromise(schema)
8408 const encodeUnknownPromiseIssue = SchemaParser.encodeUnknownPromise(schema)
8409
8410 const r1 = await decodeUnknownPromise("1").then(Result.succeed, Result.fail)
8411 deepStrictEqual(r1, Result.succeed(1))
8412
8413 const r2 = await decodeUnknownPromise(null).then(Result.succeed, Result.fail)
8414 assertTrue(Result.isFailure(r2))
8415 assertTrue(Schema.isSchemaError(r2.failure))
8416 strictEqual(r2.failure.message, "Expected string")
8417
8418 const r3 = await encodeUnknownPromise(1).then(Result.succeed, Result.fail)
8419 deepStrictEqual(r3, Result.succeed("1"))
8420
8421 const r4 = await encodeUnknownPromise(null).then(Result.succeed, Result.fail)
8422 assertTrue(Result.isFailure(r4))
8423 assertTrue(Schema.isSchemaError(r4.failure))
8424 strictEqual(r4.failure.message, "Expected number")
8425
8426 const r5 = await decodeUnknownPromiseIssue(null).then(Result.succeed, Result.fail)
8427 assertTrue(Result.isFailure(r5))
8428 assertSchemaIssueError(r5.failure, "Expected string")
8429
8430 const r6 = await encodeUnknownPromiseIssue(null).then(Result.succeed, Result.fail)
8431 assertTrue(Result.isFailure(r6))
8432 assertSchemaIssueError(r6.failure, "Expected number")
8433 })
8434
8435 it("should reject with an error when the cause contains both a schema issue and a defect", async () => {
8436 const cause = Cause.combine(
8437 Cause.fail(new SchemaIssue.InvalidValue({ message: "schema issue" })),
8438 Cause.die(new Error("defect"))
8439 )
8440 const decodeSchema = Schema.String.pipe(Schema.decode({
8441 decode: SchemaGetter.transformOptionalEffect(() => Effect.failCause(cause)),
8442 encode: SchemaGetter.passthrough()
8443 }))
8444 const encodeSchema = Schema.String.pipe(Schema.encode({
8445 decode: SchemaGetter.passthrough(),
8446 encode: SchemaGetter.transformOptionalEffect(() => Effect.failCause(cause))
8447 }))
8448
8449 const r1 = await Schema.decodeUnknownPromise(decodeSchema)("a").then(Result.succeed, Result.fail)
8450 assertTrue(Result.isFailure(r1))
8451 assertTrue(r1.failure instanceof Error)
8452 strictEqual(r1.failure.message, "Promise adapter can only reject schema errors")
8453 assertTrue(Cause.hasDies(r1.failure.cause as Cause.Cause<never>))
8454
8455 const r2 = await Schema.encodeUnknownPromise(encodeSchema)("a").then(Result.succeed, Result.fail)
8456 assertTrue(Result.isFailure(r2))
8457 assertTrue(r2.failure instanceof Error)
8458 strictEqual(r2.failure.message, "Promise adapter can only reject schema errors")
8459 assertTrue(Cause.hasDies(r2.failure.cause as Cause.Cause<never>))
8460 })
8461 })
8462
8463 describe("decodeUnknownOption / encodeUnknownOption", () => {
8464 it("FiniteFromString", () => {
8465 const schema = Schema.FiniteFromString
8466 const decodeUnknownOption = Schema.decodeUnknownOption(schema)
8467 const encodeUnknownOption = Schema.encodeUnknownOption(schema)
8468
8469 const r1 = decodeUnknownOption("1")
8470 assertTrue(Option.isSome(r1))
8471 strictEqual(r1.value, 1)
8472
8473 assertTrue(Option.isNone(decodeUnknownOption(null)))
8474
8475 const r2 = encodeUnknownOption(1)
8476 assertTrue(Option.isSome(r2))
8477 strictEqual(r2.value, "1")
8478
8479 assertTrue(Option.isNone(encodeUnknownOption(null)))
8480 })
8481
8482 it("should throw an error when the cause is not a schema issue", () => {
8483 const decodeSchema = Schema.String.pipe(Schema.decode({
8484 decode: SchemaGetter.transformOptionalEffect(() => Effect.die(new Error("decode defect"))),
8485 encode: SchemaGetter.passthrough()
8486 }))
8487 const encodeSchema = Schema.String.pipe(Schema.encode({
8488 decode: SchemaGetter.passthrough(),
8489 encode: SchemaGetter.transformOptionalEffect(() => Effect.die(new Error("encode defect")))
8490 }))
8491
8492 throws(() => Schema.decodeUnknownOption(decodeSchema)("a"), (e) => {
8493 assertTrue(e instanceof Error)
8494 strictEqual(e.message, "Option adapter can only return none for schema issues")
8495 assertTrue(Cause.hasDies(e.cause as Cause.Cause<never>))
8496 })
8497 throws(() => Schema.encodeUnknownOption(encodeSchema)("a"), (e) => {
8498 assertTrue(e instanceof Error)
8499 strictEqual(e.message, "Option adapter can only return none for schema issues")
8500 assertTrue(Cause.hasDies(e.cause as Cause.Cause<never>))
8501 })
8502 })
8503
8504 it("should throw an error when the cause contains both a schema issue and a defect", () => {
8505 const cause = Cause.combine(
8506 Cause.fail(new SchemaIssue.InvalidValue({ message: "schema issue" })),
8507 Cause.die(new Error("defect"))
8508 )
8509 const decodeSchema = Schema.String.pipe(Schema.decode({
8510 decode: SchemaGetter.transformOptionalEffect(() => Effect.failCause(cause)),
8511 encode: SchemaGetter.passthrough()
8512 }))
8513 const encodeSchema = Schema.String.pipe(Schema.encode({
8514 decode: SchemaGetter.passthrough(),
8515 encode: SchemaGetter.transformOptionalEffect(() => Effect.failCause(cause))
8516 }))
8517
8518 throws(() => Schema.decodeUnknownOption(decodeSchema)("a"), (e) => {
8519 assertTrue(e instanceof Error)
8520 strictEqual(e.message, "Option adapter can only return none for schema issues")
8521 assertTrue(Cause.hasDies(e.cause as Cause.Cause<never>))
8522 })
8523 throws(() => Schema.encodeUnknownOption(encodeSchema)("a"), (e) => {
8524 assertTrue(e instanceof Error)
8525 strictEqual(e.message, "Option adapter can only return none for schema issues")
8526 assertTrue(Cause.hasDies(e.cause as Cause.Cause<never>))
8527 })
8528 })
8529 })
8530
8531 describe("decodeUnknownResult / encodeUnknownResult", () => {
8532 it("FiniteFromString", () => {
8533 const schema = Schema.FiniteFromString
8534 const decodeUnknownResult = Schema.decodeUnknownResult(schema)
8535 const encodeUnknownResult = Schema.encodeUnknownResult(schema)
8536
8537 const r1 = decodeUnknownResult("1")
8538 assertTrue(Result.isSuccess(r1))
8539 strictEqual(r1.success, 1)
8540
8541 const r2 = decodeUnknownResult(null)
8542 assertTrue(Result.isFailure(r2))
8543 assertTrue(Schema.isSchemaError(r2.failure))
8544 strictEqual(r2.failure.message, "Expected string")
8545
8546 const r3 = encodeUnknownResult(1)
8547 assertTrue(Result.isSuccess(r3))
8548 strictEqual(r3.success, "1")
8549
8550 const r4 = encodeUnknownResult(null)
8551 assertTrue(Result.isFailure(r4))
8552 assertTrue(Schema.isSchemaError(r4.failure))
8553 strictEqual(r4.failure.message, "Expected number")
8554
8555 const r5 = SchemaParser.decodeUnknownResult(schema)(null)
8556 assertTrue(Result.isFailure(r5))
8557 assertTrue(SchemaIssue.isIssue(r5.failure))
8558 strictEqual(formatIssue(r5.failure), "Expected string")
8559
8560 const r6 = SchemaParser.encodeUnknownResult(schema)(null)
8561 assertTrue(Result.isFailure(r6))
8562 assertTrue(SchemaIssue.isIssue(r6.failure))
8563 strictEqual(formatIssue(r6.failure), "Expected number")
8564 })
8565
8566 it("should throw an error when the cause contains both a schema issue and a defect", () => {
8567 const cause = Cause.combine(
8568 Cause.fail(new SchemaIssue.InvalidValue({ message: "schema issue" })),
8569 Cause.die(new Error("defect"))
8570 )
8571 const decodeSchema = Schema.String.pipe(Schema.decode({
8572 decode: SchemaGetter.transformOptionalEffect(() => Effect.failCause(cause)),
8573 encode: SchemaGetter.passthrough()
8574 }))
8575 const encodeSchema = Schema.String.pipe(Schema.encode({
8576 decode: SchemaGetter.passthrough(),
8577 encode: SchemaGetter.transformOptionalEffect(() => Effect.failCause(cause))
8578 }))
8579
8580 throws(() => Schema.decodeUnknownResult(decodeSchema)("a"), (e) => {
8581 assertTrue(e instanceof Error)
8582 strictEqual(e.message, "Result adapter can only return schema issues")
8583 assertTrue(Cause.hasDies(e.cause as Cause.Cause<never>))
8584 })
8585 throws(() => Schema.encodeUnknownResult(encodeSchema)("a"), (e) => {
8586 assertTrue(e instanceof Error)
8587 strictEqual(e.message, "Result adapter can only return schema issues")
8588 assertTrue(Cause.hasDies(e.cause as Cause.Cause<never>))
8589 })
8590 })
8591 })
8592
8593 describe("decodeUnknownSync / encodeUnknownSync", () => {
8594 it("FiniteFromString", () => {
8595 const schema = Schema.FiniteFromString
8596
8597 strictEqual(Schema.decodeUnknownSync(schema)("1"), 1)
8598 strictEqual(Schema.encodeUnknownSync(schema)(1), "1")
8599
8600 throws(() => Schema.decodeUnknownSync(schema)(null), (e) => {
8601 assertTrue(Schema.isSchemaError(e))
8602 strictEqual(e.message, "Expected string")
8603 })
8604
8605 throws(() => Schema.encodeUnknownSync(schema)(null), (e) => {
8606 assertTrue(Schema.isSchemaError(e))
8607 strictEqual(e.message, "Expected number")
8608 })
8609
8610 throws(() => SchemaParser.decodeUnknownSync(schema)(null), (e) => {
8611 assertSchemaIssueError(e, "Expected string")
8612 })
8613
8614 throws(() => SchemaParser.encodeUnknownSync(schema)(null), (e) => {
8615 assertSchemaIssueError(e, "Expected number")
8616 })
8617 })
8618
8619 it("should throw an error when the cause contains both a schema issue and a defect", () => {
8620 const cause = Cause.combine(
8621 Cause.fail(new SchemaIssue.InvalidValue({ message: "schema issue" })),
8622 Cause.die(new Error("defect"))
8623 )
8624 const decodeSchema = Schema.String.pipe(Schema.decode({
8625 decode: SchemaGetter.transformOptionalEffect(() => Effect.failCause(cause)),
8626 encode: SchemaGetter.passthrough()
8627 }))
8628 const encodeSchema = Schema.String.pipe(Schema.encode({
8629 decode: SchemaGetter.passthrough(),
8630 encode: SchemaGetter.transformOptionalEffect(() => Effect.failCause(cause))
8631 }))
8632
8633 throws(() => Schema.decodeUnknownSync(decodeSchema)("a"), (e) => {
8634 assertTrue(e instanceof Error)
8635 strictEqual(e.message, "Sync adapter can only throw schema errors")
8636 assertTrue(Cause.hasDies(e.cause as Cause.Cause<never>))
8637 })
8638 throws(() => Schema.encodeUnknownSync(encodeSchema)("a"), (e) => {
8639 assertTrue(e instanceof Error)
8640 strictEqual(e.message, "Sync adapter can only throw schema errors")
8641 assertTrue(Cause.hasDies(e.cause as Cause.Cause<never>))
8642 })
8643 })
8644 })
8645
8646 describe("decodeUnknownResult", () => {
8647 it("should throw on async decoding", () => {
8648 const AsyncString = Schema.String.pipe(Schema.decode({
8649 decode: SchemaGetter.transformOptionalEffect((os: Option.Option<string>) =>
8650 Effect.gen(function*() {
8651 yield* Effect.sleep("10 millis")
8652 return os
8653 })
8654 ),
8655 encode: SchemaGetter.passthrough()
8656 }))
8657 const schema = AsyncString
8658
8659 throws(() => SchemaParser.decodeUnknownResult(schema)("1"))
8660 })
8661
8662 it("should throw on missing dependency", () => {
8663 class MagicNumber extends Context.Service<MagicNumber, number>()("MagicNumber") {}
8664 const DepString = Schema.Number.pipe(Schema.decode({
8665 decode: SchemaGetter.transformEffect((n) =>
8666 Effect.gen(function*() {
8667 const magicNumber = yield* MagicNumber
8668 return n * magicNumber
8669 })
8670 ),
8671 encode: SchemaGetter.passthrough()
8672 }))
8673 const schema = DepString
8674
8675 throws(() => SchemaParser.decodeUnknownResult(schema as any)(1))
8676 })
8677 })
8678
8679 describe("decodeUnknownExit", () => {
8680 it("should die on async decoding", () => {
8681 const AsyncString = Schema.String.pipe(Schema.decode({
8682 decode: SchemaGetter.transformOptionalEffect((os: Option.Option<string>) =>
8683 Effect.gen(function*() {
8684 yield* Effect.sleep("10 millis")
8685 return os
8686 })
8687 ),
8688 encode: SchemaGetter.passthrough()
8689 }))
8690 const schema = AsyncString
8691
8692 const exit = SchemaParser.decodeUnknownExit(schema)("1")
8693 assertTrue(Exit.hasDies(exit))
8694 })
8695
8696 it("should die on missing dependency", () => {
8697 class MagicNumber extends Context.Service<MagicNumber, number>()("MagicNumber") {}
8698 const DepString = Schema.Number.pipe(Schema.decode({
8699 decode: SchemaGetter.transformEffect((n) =>
8700 Effect.gen(function*() {
8701 const magicNumber = yield* MagicNumber
8702 return n * magicNumber
8703 })
8704 ),
8705 encode: SchemaGetter.passthrough()
8706 }))
8707 const schema = DepString
8708 const exit = SchemaParser.decodeUnknownExit(schema as any)(1)
8709 assertTrue(Exit.hasDies(exit))
8710 })
8711 })
8712
8713 describe("decodeUnknownExit / encodeUnknownExit", () => {
8714 it("should preserve mixed schema issue and defect causes", () => {
8715 const cause = Cause.combine(
8716 Cause.fail(new SchemaIssue.InvalidValue({ message: "schema issue" })),
8717 Cause.die(new Error("defect"))
8718 )
8719 const decodeSchema = Schema.String.pipe(Schema.decode({
8720 decode: SchemaGetter.transformOptionalEffect(() => Effect.failCause(cause)),
8721 encode: SchemaGetter.passthrough()
8722 }))
8723 const encodeSchema = Schema.String.pipe(Schema.encode({
8724 decode: SchemaGetter.passthrough(),
8725 encode: SchemaGetter.transformOptionalEffect(() => Effect.failCause(cause))
8726 }))
8727
8728 const decodeExit = Schema.decodeUnknownExit(decodeSchema)("a")
8729 assertTrue(Exit.isFailure(decodeExit))
8730 assertTrue(Exit.hasDies(decodeExit))
8731 const decodeError = Cause.findError(decodeExit.cause)
8732 assertTrue(Result.isSuccess(decodeError))
8733 assertTrue(Schema.isSchemaError(decodeError.success))
8734
8735 const encodeExit = Schema.encodeUnknownExit(encodeSchema)("a")
8736 assertTrue(Exit.isFailure(encodeExit))
8737 assertTrue(Exit.hasDies(encodeExit))
8738 const encodeError = Cause.findError(encodeExit.cause)
8739 assertTrue(Result.isSuccess(encodeError))
8740 assertTrue(Schema.isSchemaError(encodeError.success))
8741 })
8742 })
8743
8744 describe("annotateKey", () => {
8745 describe("the messageMissingKey annotation should be used as a error message", () => {
8746 it("Struct", async () => {
8747 const schema = Schema.Struct({
8748 a: Schema.String.pipe(Schema.annotateKey({ messageMissingKey: "this field is required" }))
8749 })
8750 const asserts = new TestSchema.Asserts(schema)
8751
8752 const decoding = asserts.decoding()
8753 await decoding.fail(
8754 {},
8755 `this field is required
8756 at ["a"]`
8757 )
8758 })
8759
8760 it("Tuple", async () => {
8761 const schema = Schema.Tuple([
8762 Schema.String.pipe(Schema.annotateKey({ messageMissingKey: "this element is required" }))
8763 ])
8764 const asserts = new TestSchema.Asserts(schema)
8765
8766 const decoding = asserts.decoding()
8767 await decoding.fail(
8768 [],
8769 `this element is required
8770 at [0]`
8771 )
8772 })
8773 })
8774 })
8775
8776 describe("annotateEncoded", () => {
8777 it("non-transforming schema", () => {
8778 const schema = Schema.String.pipe(
8779 Schema.annotateEncoded({ title: "encoded title" })
8780 )
8781 strictEqual(SchemaAST.toEncoded(schema.ast).annotations?.title, "encoded title")
8782 })
8783
8784 it("transforming schema", () => {
8785 const schema = Schema.NumberFromString.pipe(
8786 Schema.annotateEncoded({ title: "encoded title" })
8787 )
8788 strictEqual(SchemaAST.toEncoded(schema.ast).annotations?.title, "encoded title")
8789 })
8790 })
8791
8792 describe("Struct.mapFields", () => {
8793 it("evolve", () => {
8794 const schema = Schema.Struct({
8795 a: Schema.String,
8796 b: Schema.Number
8797 }).mapFields(Struct.evolve({ a: (v) => Schema.optionalKey(v) }))
8798
8799 equals(schema.fields, {
8800 a: Schema.optionalKey(Schema.String),
8801 b: Schema.Number
8802 })
8803 })
8804
8805 it("evolveKeys", () => {
8806 const schema = Schema.Struct({
8807 a: Schema.String,
8808 b: Schema.Number
8809 }).mapFields(Struct.evolveKeys({ a: (k) => Str.toUpperCase(k) }))
8810
8811 equals(schema.fields, {
8812 A: Schema.String,
8813 b: Schema.Number
8814 })
8815 })
8816
8817 it("renameKeys", () => {
8818 const schema = Schema.Struct({
8819 a: Schema.String,
8820 b: Schema.Number,
8821 c: Schema.Boolean
8822 }).mapFields(Struct.renameKeys({ a: "A", b: "B" }))
8823
8824 equals(schema.fields, {
8825 A: Schema.String,
8826 B: Schema.Number,
8827 c: Schema.Boolean
8828 })
8829 })
8830
8831 it("evolveEntries", () => {
8832 const schema = Schema.Struct({
8833 a: Schema.String,
8834 b: Schema.Number
8835 }).mapFields(Struct.evolveEntries({ a: (k, v) => [Str.toUpperCase(k), Schema.optionalKey(v)] }))
8836
8837 equals(schema.fields, {
8838 A: Schema.optionalKey(Schema.String),
8839 b: Schema.Number
8840 })
8841 })
8842
8843 it("optionalKey", () => {
8844 const schema = Schema.Struct({
8845 a: Schema.String,
8846 b: Schema.Number
8847 }).mapFields(Struct.map(Schema.optionalKey))
8848
8849 equals(schema.fields, {
8850 a: Schema.optionalKey(Schema.String),
8851 b: Schema.optionalKey(Schema.Number)
8852 })
8853 })
8854
8855 it("mapPick", () => {
8856 const schema = Schema.Struct({
8857 a: Schema.String,
8858 b: Schema.Number
8859 }).mapFields(Struct.mapPick(["a"], Schema.optionalKey))
8860
8861 equals(schema.fields, {
8862 a: Schema.optionalKey(Schema.String),
8863 b: Schema.Number
8864 })
8865 })
8866
8867 it("mapOmit", () => {
8868 const schema = Schema.Struct({
8869 a: Schema.String,
8870 b: Schema.Number
8871 }).mapFields(Struct.mapOmit(["b"], Schema.optionalKey))
8872
8873 equals(schema.fields, {
8874 a: Schema.optionalKey(Schema.String),
8875 b: Schema.Number
8876 })
8877 })
8878
8879 it("optional", () => {
8880 const schema = Schema.Struct({
8881 a: Schema.String,
8882 b: Schema.Number
8883 }).mapFields(Struct.map(Schema.optional))
8884
8885 equals(schema.fields, {
8886 a: Schema.optional(Schema.String),
8887 b: Schema.optional(Schema.Number)
8888 })
8889 })
8890
8891 it("mutableKey", () => {
8892 const schema = Schema.Struct({
8893 a: Schema.String,
8894 b: Schema.Number
8895 }).mapFields(Struct.map(Schema.mutableKey))
8896
8897 equals(schema.fields, {
8898 a: Schema.mutableKey(Schema.String),
8899 b: Schema.mutableKey(Schema.Number)
8900 })
8901 })
8902
8903 it("readonlyKey", () => {
8904 const schema = Schema.Struct({
8905 a: Schema.mutableKey(Schema.String),
8906 b: Schema.mutableKey(Schema.Number)
8907 }).mapFields(Struct.map(Schema.readonlyKey))
8908
8909 equals(schema.fields, {
8910 a: Schema.String,
8911 b: Schema.Number
8912 })
8913 })
8914
8915 it("mutable", () => {
8916 const schema = Schema.Struct({
8917 a: Schema.Array(Schema.String),
8918 b: Schema.Tuple([Schema.Number])
8919 }).mapFields(Struct.map(Schema.mutable))
8920
8921 equals(schema.fields, {
8922 a: Schema.mutable(Schema.Array(Schema.String)),
8923 b: Schema.mutable(Schema.Tuple([Schema.Number]))
8924 })
8925 })
8926
8927 it("NullOr", () => {
8928 const schema = Schema.Struct({
8929 a: Schema.String,
8930 b: Schema.Number
8931 }).mapFields(Struct.map(Schema.NullOr))
8932
8933 equals(schema.fields, {
8934 a: Schema.NullOr(Schema.String),
8935 b: Schema.NullOr(Schema.Number)
8936 })
8937 })
8938
8939 it("UndefinedOr", () => {
8940 const schema = Schema.Struct({
8941 a: Schema.String,
8942 b: Schema.Number
8943 }).mapFields(Struct.map(Schema.UndefinedOr))
8944
8945 equals(schema.fields, {
8946 a: Schema.UndefinedOr(Schema.String),
8947 b: Schema.UndefinedOr(Schema.Number)
8948 })
8949 })
8950
8951 it("NullishOr", () => {
8952 const schema = Schema.Struct({
8953 a: Schema.String,
8954 b: Schema.Number
8955 }).mapFields(Struct.map(Schema.NullishOr))
8956
8957 equals(schema.fields, {
8958 a: Schema.NullishOr(Schema.String),
8959 b: Schema.NullishOr(Schema.Number)
8960 })
8961 })
8962
8963 it("should work with flow", () => {
8964 const schema = Schema.Struct({
8965 a: Schema.String,
8966 b: Schema.FiniteFromString,
8967 c: Schema.Boolean
8968 }).mapFields(flow(
8969 Struct.map(Schema.NullOr),
8970 Struct.mapPick(["a", "c"], Schema.mutableKey)
8971 ))
8972
8973 equals(schema.fields, {
8974 a: Schema.mutableKey(Schema.NullOr(Schema.String)),
8975 b: Schema.NullOr(Schema.FiniteFromString),
8976 c: Schema.mutableKey(Schema.NullOr(Schema.Boolean))
8977 })
8978 })
8979 })
8980
8981 describe("Tuple.mapElements", () => {
8982 it("appendElement", () => {
8983 const schema = Schema.Tuple([Schema.String]).mapElements(Tuple.appendElement(Schema.Number))
8984
8985 TestSchema.Asserts.ast.elements.equals(schema.elements, [Schema.String, Schema.Number])
8986 })
8987
8988 it("appendElements", () => {
8989 const schema = Schema.Tuple([Schema.String]).mapElements(Tuple.appendElements([Schema.Number, Schema.Boolean]))
8990
8991 TestSchema.Asserts.ast.elements.equals(schema.elements, [Schema.String, Schema.Number, Schema.Boolean])
8992 })
8993
8994 it("pick", () => {
8995 const schema = Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]).mapElements(Tuple.pick([0, 2]))
8996
8997 TestSchema.Asserts.ast.elements.equals(schema.elements, [Schema.String, Schema.Boolean])
8998 })
8999
9000 it("omit", () => {
9001 const schema = Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]).mapElements(Tuple.omit([1]))
9002
9003 TestSchema.Asserts.ast.elements.equals(schema.elements, [Schema.String, Schema.Boolean])
9004 })
9005
9006 describe("evolve", () => {
9007 it("readonly [string] -> readonly [string?]", () => {
9008 const schema = Schema.Tuple([Schema.String]).mapElements(Tuple.evolve([(v) => Schema.optionalKey(v)]))
9009
9010 TestSchema.Asserts.ast.elements.equals(schema.elements, [Schema.optionalKey(Schema.String)])
9011 })
9012
9013 it("readonly [string, number] -> readonly [string, number?]", () => {
9014 const schema = Schema.Tuple([Schema.String, Schema.Number]).mapElements(
9015 Tuple.evolve([undefined, (v) => Schema.optionalKey(v)])
9016 )
9017
9018 TestSchema.Asserts.ast.elements.equals(schema.elements, [Schema.String, Schema.optionalKey(Schema.Number)])
9019 })
9020 })
9021
9022 describe("renameIndices", () => {
9023 it("partial index mapping", () => {
9024 const schema = Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]).mapElements(
9025 Tuple.renameIndices(["1", "0"])
9026 )
9027 TestSchema.Asserts.ast.elements.equals(schema.elements, [Schema.Number, Schema.String, Schema.Boolean])
9028 })
9029
9030 it("full index mapping", () => {
9031 const schema = Schema.Tuple([Schema.String, Schema.Number, Schema.Boolean]).mapElements(
9032 Tuple.renameIndices(["2", "1", "0"])
9033 )
9034 TestSchema.Asserts.ast.elements.equals(schema.elements, [Schema.Boolean, Schema.Number, Schema.String])
9035 })
9036 })
9037
9038 it("NullOr", () => {
9039 const schema = Schema.Tuple([Schema.String, Schema.Number]).mapElements(Tuple.map(Schema.NullOr))
9040
9041 TestSchema.Asserts.ast.elements.equals(schema.elements, [
9042 Schema.NullOr(Schema.String),
9043 Schema.NullOr(Schema.Number)
9044 ])
9045 })
9046 })
9047
9048 describe("Union options", () => {
9049 it("preserves Union options through public derivations", () => {
9050 const options: SchemaAST.UnionOptions = { mode: "oneOf" }
9051 const schema = Schema.Union([Schema.String, Schema.Number], options)
9052
9053 strictEqual(schema.mapMembers(Tuple.appendElement(Schema.Boolean)).ast.options, options)
9054 const flipped = Schema.flip(schema).ast
9055 assertTrue(SchemaAST.isUnion(flipped))
9056 strictEqual(flipped.options, options)
9057 })
9058 })
9059
9060 describe("Union.mapMembers", () => {
9061 it("appendElement", () => {
9062 const schema = Schema.Union([Schema.String, Schema.Number]).mapMembers(Tuple.appendElement(Schema.Boolean))
9063
9064 TestSchema.Asserts.ast.elements.equals(schema.members, [Schema.String, Schema.Number, Schema.Boolean])
9065 })
9066
9067 it("evolve", () => {
9068 const schema = Schema.Union([Schema.String, Schema.Number, Schema.Boolean]).mapMembers(
9069 Tuple.evolve([
9070 (v) => Schema.Array(v),
9071 undefined,
9072 (v) => Schema.Array(v)
9073 ])
9074 )
9075
9076 TestSchema.Asserts.ast.elements.equals(schema.members, [
9077 Schema.Array(Schema.String),
9078 Schema.Number,
9079 Schema.Array(Schema.Boolean)
9080 ])
9081 })
9082
9083 it("Array", () => {
9084 const schema = Schema.Union([Schema.String, Schema.Number]).mapMembers(Tuple.map(Schema.Array))
9085
9086 TestSchema.Asserts.ast.elements.equals(schema.members, [
9087 Schema.Array(Schema.String),
9088 Schema.Array(Schema.Number)
9089 ])
9090 })
9091 })
9092
9093 describe("Literals.mapMembers", () => {
9094 it("evolve", () => {
9095 const schema = Schema.Literals(["a", "b", "c"]).mapMembers(Tuple.evolve([
9096 (a) => Schema.Struct({ _tag: a, a: Schema.String }),
9097 (b) => Schema.Struct({ _tag: b, b: Schema.Number }),
9098 (c) => Schema.Struct({ _tag: c, c: Schema.Boolean })
9099 ]))
9100
9101 TestSchema.Asserts.ast.elements.equals(schema.members, [
9102 Schema.Struct({ _tag: Schema.Literal("a"), a: Schema.String }),
9103 Schema.Struct({ _tag: Schema.Literal("b"), b: Schema.Number }),
9104 Schema.Struct({ _tag: Schema.Literal("c"), c: Schema.Boolean })
9105 ])
9106 })
9107 })
9108
9109 describe("encodeKeys", () => {
9110 it("Struct", async () => {
9111 const schema = Schema.Struct({
9112 a: Schema.FiniteFromString,
9113 b: Schema.FiniteFromString
9114 }).pipe(Schema.encodeKeys({ a: "c" }))
9115 const asserts = new TestSchema.Asserts(schema)
9116
9117 const decoding = asserts.decoding()
9118 await decoding.succeed({ c: "1", b: "2" }, { a: 1, b: 2 })
9119
9120 const encoding = asserts.encoding()
9121 await encoding.succeed({ a: 1, b: 2 }, { c: "1", b: "2" })
9122 })
9123
9124 it("Class", async () => {
9125 class A extends Schema.Class<A>("A")({
9126 a: Schema.FiniteFromString,
9127 b: Schema.String
9128 }) {}
9129 const schema = A.pipe(Schema.encodeKeys({ a: "c" }))
9130 const asserts = new TestSchema.Asserts(schema)
9131
9132 const decoding = asserts.decoding()
9133 await decoding.succeed({ c: "1", b: "b" }, new A({ a: 1, b: "b" }))
9134
9135 const encoding = asserts.encoding()
9136 await encoding.succeed(new A({ a: 1, b: "b" }), { c: "1", b: "b" })
9137 })
9138
9139 it("supports symbol source keys", () => {
9140 const field = Symbol("field")
9141 const schema = Schema.Struct({
9142 [field]: Schema.String
9143 }).pipe(Schema.encodeKeys({ [field]: "field" }))
9144
9145 deepStrictEqual(Schema.decodeUnknownSync(schema)({ field: "a" }), { [field]: "a" })
9146 deepStrictEqual(Schema.encodeSync(schema)({ [field]: "a" }), { field: "a" })
9147 })
9148
9149 it("supports symbol destination keys", () => {
9150 const field = Symbol("field")
9151 const schema = Schema.Struct({
9152 field: Schema.String
9153 }).pipe(Schema.encodeKeys({ field }))
9154
9155 deepStrictEqual(Schema.decodeUnknownSync(schema)({ [field]: "a" }), { field: "a" })
9156 deepStrictEqual(Schema.encodeSync(schema)({ field: "a" }), { [field]: "a" })
9157 })
9158
9159 it("rejects duplicate destination keys", () => {
9160 throws(
9161 () =>
9162 Schema.Struct({
9163 a: Schema.String,
9164 b: Schema.String
9165 }).pipe(Schema.encodeKeys({ a: "c", b: "c" })),
9166 (e) => {
9167 assertInclude(String(e), "Duplicate encoded keys")
9168 }
9169 )
9170 })
9171
9172 it("rejects destination keys that collide with unmapped fields", () => {
9173 throws(
9174 () =>
9175 Schema.Struct({
9176 a: Schema.String,
9177 b: Schema.String
9178 }).pipe(Schema.encodeKeys({ a: "b" })),
9179 (e) => {
9180 assertInclude(String(e), "Duplicate encoded keys")
9181 }
9182 )
9183 })
9184
9185 it("rejects canonical number and string destination key collisions", () => {
9186 throws(
9187 () =>
9188 Schema.Struct({
9189 a: Schema.String,
9190 b: Schema.String
9191 }).pipe(Schema.encodeKeys({ a: 1, b: "1" })),
9192 (e) => {
9193 assertInclude(String(e), "Duplicate encoded keys")
9194 }
9195 )
9196 })
9197 })
9198
9199 describe("Schema.makeFilter", () => {
9200 it("returns undefined", async () => {
9201 const schema = Schema.String.check(Schema.makeFilter(() => undefined))
9202 const asserts = new TestSchema.Asserts(schema)
9203 const decoding = asserts.decoding()
9204 await decoding.succeed("a")
9205 })
9206
9207 it("returns true", async () => {
9208 const schema = Schema.String.check(Schema.makeFilter(() => true))
9209 const asserts = new TestSchema.Asserts(schema)
9210 const decoding = asserts.decoding()
9211 await decoding.succeed("a")
9212 })
9213
9214 it("returns false", async () => {
9215 const schema = Schema.String.check(Schema.makeFilter(() => false))
9216 const asserts = new TestSchema.Asserts(schema)
9217 const decoding = asserts.decoding()
9218 await decoding.fail(
9219 "a",
9220 `Expected <filter>`
9221 )
9222 })
9223
9224 it("returns string", async () => {
9225 const schema = Schema.String.check(Schema.makeFilter(() => "error message"))
9226 const asserts = new TestSchema.Asserts(schema)
9227 const decoding = asserts.decoding()
9228 await decoding.fail(
9229 "a",
9230 `error message`
9231 )
9232 })
9233
9234 describe("returns issue", () => {
9235 it("abort: false", async () => {
9236 const schema = Schema.String.check(
9237 Schema.makeFilter(() => new SchemaIssue.InvalidValue({ message: "error message 1" }), {
9238 title: "filter title 1"
9239 }),
9240 Schema.makeFilter(() => false, { title: "filter title 2", message: "error message 2" })
9241 )
9242 const asserts = new TestSchema.Asserts(schema)
9243
9244 const decoding = asserts.decoding({ parseOptions: { errors: "all" } })
9245 await decoding.fail(
9246 "a",
9247 `error message 1
9248error message 2`
9249 )
9250 })
9251
9252 it("abort: true", async () => {
9253 const schema = Schema.String.check(
9254 Schema.makeFilter(() => new SchemaIssue.InvalidValue({ message: "error message 1" }), {
9255 title: "filter title 1"
9256 }, true),
9257 Schema.makeFilter(() => false, { title: "filter title 2", message: "error message 2" })
9258 )
9259 const asserts = new TestSchema.Asserts(schema)
9260
9261 const decoding = asserts.decoding({ parseOptions: { errors: "all" } })
9262 await decoding.fail(
9263 "a",
9264 `error message 1`
9265 )
9266 })
9267 })
9268
9269 describe("returns object", () => {
9270 it("abort: false", async () => {
9271 const schema = Schema.String.check(
9272 Schema.makeFilter(() => ({
9273 path: ["a"],
9274 issue: "error message 1"
9275 }), { title: "filter title 1" }),
9276 Schema.makeFilter(() => false, { title: "filter title 2", message: "error message 2" })
9277 )
9278 const asserts = new TestSchema.Asserts(schema)
9279
9280 const decoding = asserts.decoding({ parseOptions: { errors: "all" } })
9281 await decoding.fail(
9282 "a",
9283 `error message 1
9284 at ["a"]
9285error message 2`
9286 )
9287 })
9288
9289 it("abort: true", async () => {
9290 const schema = Schema.String.check(
9291 Schema.makeFilter(() => ({ path: ["a"], issue: "error message 1" }), { title: "error title 1" }, true),
9292 Schema.makeFilter(() => false, { title: "error title 2", message: "error message 2" })
9293 )
9294 const asserts = new TestSchema.Asserts(schema)
9295
9296 const decoding = asserts.decoding({ parseOptions: { errors: "all" } })
9297 await decoding.fail(
9298 "a",
9299 `error message 1
9300 at ["a"]`
9301 )
9302 })
9303
9304 it("issue: Issue", async () => {
9305 const schema = Schema.String.check(
9306 Schema.makeFilter(
9307 () => ({ path: ["a"], issue: new SchemaIssue.InvalidValue({ message: "custom issue" }) }),
9308 { title: "filter title" }
9309 )
9310 )
9311 const asserts = new TestSchema.Asserts(schema)
9312
9313 const decoding = asserts.decoding()
9314 await decoding.fail(
9315 "a",
9316 `custom issue
9317 at ["a"]`
9318 )
9319 })
9320 })
9321
9322 describe("returns array", () => {
9323 it("empty array is treated as success", async () => {
9324 const schema = Schema.String.check(Schema.makeFilter(() => []))
9325 const asserts = new TestSchema.Asserts(schema)
9326 const decoding = asserts.decoding()
9327 await decoding.succeed("a")
9328 })
9329
9330 it("single-element array collapses to the element", async () => {
9331 const schema = Schema.String.check(
9332 Schema.makeFilter(() => [{ path: ["a"], issue: "error message 1" }], { title: "filter title" })
9333 )
9334 const asserts = new TestSchema.Asserts(schema)
9335 const decoding = asserts.decoding()
9336 await decoding.fail(
9337 "a",
9338 `error message 1
9339 at ["a"]`
9340 )
9341 })
9342
9343 it("multi-element array groups into a Composite", async () => {
9344 const schema = Schema.String.check(
9345 Schema.makeFilter(() => [
9346 { path: ["a"], issue: "error message 1" },
9347 { path: ["b"], issue: "error message 2" }
9348 ], { title: "filter title" })
9349 )
9350 const asserts = new TestSchema.Asserts(schema)
9351 const decoding = asserts.decoding({ parseOptions: { errors: "all" } })
9352 await decoding.fail(
9353 "a",
9354 `error message 1
9355 at ["a"]
9356error message 2
9357 at ["b"]`
9358 )
9359 })
9360
9361 it("array mixing string, Issue, and { path, issue }", async () => {
9362 const schema = Schema.String.check(
9363 Schema.makeFilter(() => [
9364 "top-level message",
9365 new SchemaIssue.InvalidValue({ message: "direct issue" }),
9366 { path: ["a"], issue: "pointed message" }
9367 ], { title: "filter title" })
9368 )
9369 const asserts = new TestSchema.Asserts(schema)
9370 const decoding = asserts.decoding({ parseOptions: { errors: "all" } })
9371 await decoding.fail(
9372 "a",
9373 `top-level message
9374direct issue
9375pointed message
9376 at ["a"]`
9377 )
9378 })
9379 })
9380 })
9381
9382 describe("extendTo", () => {
9383 it("Struct", async () => {
9384 const schema = Schema.Struct({
9385 a: Schema.String,
9386 b: Schema.Number
9387 }).pipe(Schema.extendTo({
9388 c: Schema.String
9389 }, {
9390 c: (value) => Option.some(value.a + "c" + value.b)
9391 }))
9392 const asserts = new TestSchema.Asserts(schema)
9393
9394 const decoding = asserts.decoding()
9395 await decoding.succeed({ a: "1", b: 2 }, { a: "1", b: 2, c: "1c2" })
9396
9397 const encoding = asserts.encoding()
9398 await encoding.succeed({ a: "1", b: 2, c: "1c2" }, { a: "1", b: 2 })
9399 })
9400
9401 it("Union", async () => {
9402 const Circle = Schema.Struct({
9403 radius: Schema.Number
9404 })
9405
9406 const Square = Schema.Struct({
9407 sideLength: Schema.Number
9408 })
9409
9410 const DiscriminatedShape = Schema.Union([
9411 Circle.pipe(Schema.extendTo({ kind: Schema.tag("circle") }, { kind: () => Option.some("circle" as const) })),
9412 Square.pipe(Schema.extendTo({ kind: Schema.tag("square") }, { kind: () => Option.some("square" as const) }))
9413 ])
9414 const asserts = new TestSchema.Asserts(DiscriminatedShape)
9415
9416 const decoding = asserts.decoding()
9417 await decoding.succeed({ radius: 1 }, { radius: 1, kind: "circle" })
9418 await decoding.succeed({ sideLength: 1 }, {
9419 sideLength: 1,
9420 kind: "square"
9421 })
9422
9423 const encoding = asserts.encoding()
9424 await encoding.succeed({ radius: 1, kind: "circle" }, { radius: 1 })
9425 await encoding.succeed({ sideLength: 1, kind: "square" }, {
9426 sideLength: 1
9427 })
9428 })
9429 })
9430
9431 describe("Tagged unions", () => {
9432 describe("toTaggedUnion", () => {
9433 it("should augment a union of structs", () => {
9434 const b = Symbol.for("B")
9435 const schema = Schema.Union([
9436 Schema.Struct({ _tag: Schema.Literal("A"), a: Schema.String }),
9437 Schema.Struct({ _tag: Schema.UniqueSymbol(b), b: Schema.FiniteFromString }),
9438 Schema.Union([
9439 Schema.Struct({ _tag: Schema.Literal(1), c: Schema.Boolean }),
9440 Schema.Struct({ _tag: Schema.Literal("D"), d: Schema.Date })
9441 ])
9442 ]).pipe(Schema.toTaggedUnion("_tag"))
9443
9444 deepStrictEqual(schema.discriminants, ["A", b, 1, "D"])
9445
9446 // cases
9447 deepStrictEqual(schema.cases.A, schema.members[0])
9448 deepStrictEqual(schema.cases[b], schema.members[1])
9449 deepStrictEqual(schema.cases[1], schema.members[2].members[0])
9450 deepStrictEqual(schema.cases["1"], schema.members[2].members[0])
9451 deepStrictEqual(schema.cases.D, schema.members[2].members[1])
9452
9453 // isAnyOf
9454 const isAOr1 = schema.isAnyOf(["A", 1])
9455 assertTrue(isAOr1({ _tag: "A", a: "a" }))
9456 assertTrue(isAOr1({ _tag: 1, c: true }))
9457 assertFalse(isAOr1({ _tag: "D", d: new Date() }))
9458 assertFalse(isAOr1({ _tag: b, b: 1 }))
9459
9460 // guards
9461 assertTrue(schema.guards.A({ _tag: "A", a: "a" }))
9462 assertFalse(schema.guards.A({ _tag: "A", a: 1 }))
9463
9464 assertTrue(schema.guards[b]({ _tag: b, b: 1 }))
9465 assertFalse(schema.guards[b]({ _tag: b, b: "b" }))
9466
9467 assertTrue(schema.guards[1]({ _tag: 1, c: true }))
9468 assertFalse(schema.guards[1]({ _tag: 1, c: 1 }))
9469
9470 assertTrue(schema.guards.D({ _tag: "D", d: new Date() }))
9471 assertFalse(schema.guards.D({ _tag: "D", d: "d" }))
9472
9473 // match
9474 deepStrictEqual(
9475 schema.match({ _tag: "A", a: "a" }, { A: () => "A", [b]: () => "B", 1: () => "C", D: () => "D" }),
9476 "A"
9477 )
9478 deepStrictEqual(
9479 pipe({ _tag: "A", a: "a" }, schema.match({ A: () => "A", [b]: () => "B", 1: () => "C", D: () => "D" })),
9480 "A"
9481 )
9482 deepStrictEqual(
9483 schema.match({ _tag: b, b: 1 }, { A: () => "A", [b]: () => "B", 1: () => "C", D: () => "D" }),
9484 "B"
9485 )
9486 deepStrictEqual(
9487 pipe({ _tag: b, b: 1 }, schema.match({ A: () => "A", [b]: () => "B", 1: () => "C", D: () => "D" })),
9488 "B"
9489 )
9490 deepStrictEqual(
9491 schema.match({ _tag: 1, c: true }, { A: () => "A", [b]: () => "B", 1: () => "C", D: () => "D" }),
9492 "C"
9493 )
9494 deepStrictEqual(
9495 pipe({ _tag: 1, c: true }, schema.match({ A: () => "A", [b]: () => "B", 1: () => "C", D: () => "D" })),
9496 "C"
9497 )
9498 deepStrictEqual(
9499 schema.match({ _tag: "D", d: new Date() }, { A: () => "A", [b]: () => "B", 1: () => "C", D: () => "D" }),
9500 "D"
9501 )
9502 deepStrictEqual(
9503 pipe(
9504 { _tag: "D", d: new Date() },
9505 schema.match({ A: () => "A", [b]: () => "B", 1: () => "C", D: () => "D" })
9506 ),
9507 "D"
9508 )
9509
9510 // matchOrElse
9511 deepStrictEqual(
9512 schema.matchOrElse({ _tag: "A", a: "a" }, { A: () => "A" }, () => "fallback"),
9513 "A"
9514 )
9515 deepStrictEqual(
9516 schema.matchOrElse({ _tag: b, b: 1 }, { A: () => "A" }, (value) => value._tag),
9517 b
9518 )
9519 deepStrictEqual(
9520 pipe({ _tag: b, b: 1 }, schema.matchOrElse({ A: () => "A" }, (value) => value._tag)),
9521 b
9522 )
9523 const undefinedCases = { A: undefined } as unknown as { A?: () => string }
9524 deepStrictEqual(
9525 schema.matchOrElse({ _tag: "A", a: "a" }, undefinedCases, () => "fallback"),
9526 "fallback"
9527 )
9528 })
9529
9530 it("should support multiple tags", () => {
9531 const schema = Schema.Union([
9532 Schema.Struct({ _tag: Schema.tag("A"), type: Schema.tag("TypeA"), a: Schema.String }),
9533 Schema.Struct({ _tag: Schema.tag("B"), type: Schema.tag("TypeB"), b: Schema.FiniteFromString })
9534 ]).pipe(Schema.toTaggedUnion("type"))
9535
9536 // cases
9537 deepStrictEqual(schema.cases.TypeA, schema.members[0])
9538 deepStrictEqual(schema.cases.TypeB, schema.members[1])
9539 })
9540
9541 it("should throw on duplicate discriminants", () => {
9542 throws(
9543 () =>
9544 Schema.Union([
9545 Schema.Struct({ event: Schema.Literal("A"), a: Schema.String }),
9546 Schema.Union([
9547 Schema.Struct({ event: Schema.Literal("B"), b: Schema.String }),
9548 Schema.Struct({ event: Schema.Literal("A"), c: Schema.String })
9549 ])
9550 ]).pipe(Schema.toTaggedUnion("event")),
9551 "Duplicate discriminant: A"
9552 )
9553 throws(
9554 () =>
9555 Schema.Union([
9556 Schema.Struct({ event: Schema.Literal(1) }),
9557 Schema.Struct({ event: Schema.Literal("1") })
9558 ]).pipe(Schema.toTaggedUnion("event")),
9559 "Duplicate discriminant: 1"
9560 )
9561 })
9562
9563 it("should collect no discriminants from an empty union", () => {
9564 const schema = Schema.Union([]).pipe(Schema.toTaggedUnion("event"))
9565
9566 deepStrictEqual(schema.discriminants, [])
9567 })
9568
9569 it("should support __proto__ as a discriminant", () => {
9570 const member = Schema.Struct({ event: Schema.Literal("__proto__"), value: Schema.String })
9571 const schema = Schema.Union([member]).pipe(Schema.toTaggedUnion("event"))
9572
9573 assertTrue(Object.hasOwn(schema.cases, "__proto__"))
9574 strictEqual(schema.cases["__proto__"], member)
9575 assertTrue(Object.hasOwn(schema.guards, "__proto__"))
9576 assertTrue(schema.guards["__proto__"]({ event: "__proto__", value: "a" }))
9577 })
9578
9579 it("should augment a union of classes", () => {
9580 class A extends Schema.Class<A>("A")({
9581 _tag: Schema.tag("A"),
9582 a: Schema.String
9583 }) {}
9584 class B extends Schema.Class<B>("B")({
9585 _tag: Schema.tag("B"),
9586 b: Schema.FiniteFromString
9587 }) {}
9588
9589 const schema = Schema.Union([A, B]).pipe(Schema.toTaggedUnion("_tag"))
9590
9591 // cases
9592 deepStrictEqual(schema.cases.A, A)
9593 deepStrictEqual(schema.cases.B, B)
9594 })
9595 })
9596
9597 describe("TaggedUnion", () => {
9598 it("should create a tagged union", () => {
9599 const schema = Schema.TaggedUnion({
9600 A: { a: Schema.String },
9601 C: { c: Schema.Boolean },
9602 B: { b: Schema.FiniteFromString }
9603 }).annotate({})
9604
9605 const { A, B, C } = schema.cases
9606
9607 // cases
9608 strictEqual(A.fields._tag.ast.literal, "A")
9609 strictEqual(A.fields.a, Schema.String)
9610 strictEqual(B.fields._tag.ast.literal, "B")
9611 strictEqual(B.fields.b, Schema.FiniteFromString)
9612 strictEqual(C.fields._tag.ast.literal, "C")
9613 strictEqual(C.fields.c, Schema.Boolean)
9614
9615 // isAnyOf
9616 const isAOrB = schema.isAnyOf(["A", "B"])
9617 assertTrue(isAOrB({ _tag: "A", a: "a" }))
9618 assertTrue(isAOrB({ _tag: "B", b: 1 }))
9619 assertFalse(isAOrB({ _tag: "C", c: true }))
9620
9621 // guards
9622 assertTrue(schema.guards.A({ _tag: "A", a: "a" }))
9623 assertTrue(schema.guards.B({ _tag: "B", b: 1 }))
9624 assertTrue(schema.guards.C({ _tag: "C", c: true }))
9625 assertFalse(schema.guards.A({ _tag: "A", b: 1 }))
9626 assertFalse(schema.guards.B({ _tag: "B", a: "a" }))
9627 assertFalse(schema.guards.C({ _tag: "C", c: 1 }))
9628
9629 // match
9630 deepStrictEqual(
9631 schema.match({ _tag: "A", a: "a" }, { A: () => "A", B: () => "B", C: () => "C" }),
9632 "A"
9633 )
9634 deepStrictEqual(
9635 pipe({ _tag: "A", a: "a" }, schema.match({ A: () => "A", B: () => "B", C: () => "C" })),
9636 "A"
9637 )
9638 deepStrictEqual(
9639 schema.match({ _tag: "B", b: 1 }, { A: () => "A", B: () => "B", C: () => "C" }),
9640 "B"
9641 )
9642 deepStrictEqual(
9643 pipe({ _tag: "B", b: 1 }, schema.match({ A: () => "A", B: () => "B", C: () => "C" })),
9644 "B"
9645 )
9646 deepStrictEqual(
9647 schema.match({ _tag: "C", c: true }, { A: () => "A", B: () => "B", C: () => "C" }),
9648 "C"
9649 )
9650 deepStrictEqual(
9651 pipe({ _tag: "C", c: true }, schema.match({ A: () => "A", B: () => "B", C: () => "C" })),
9652 "C"
9653 )
9654
9655 // matchOrElse
9656 deepStrictEqual(
9657 schema.matchOrElse({ _tag: "A", a: "a" }, { A: (value) => value.a }, () => "fallback"),
9658 "a"
9659 )
9660 deepStrictEqual(
9661 schema.matchOrElse({ _tag: "B", b: 1 }, { A: () => "A" }, (value) => value._tag),
9662 "B"
9663 )
9664 deepStrictEqual(
9665 pipe({ _tag: "B", b: 1 }, schema.matchOrElse({ A: () => "A" }, (value) => value._tag)),
9666 "B"
9667 )
9668 })
9669 })
9670 })
9671
9672 describe("withDecodingDefaultKey", () => {
9673 it("should return a decoding default value if the key is missing", async () => {
9674 const schema = Schema.Struct({
9675 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultKey(Effect.succeed("1")))
9676 })
9677 const asserts = new TestSchema.Asserts(schema)
9678
9679 const decoding = asserts.decoding()
9680 await decoding.succeed({}, { a: 1 })
9681 await decoding.succeed({ a: "2" }, { a: 2 })
9682 await decoding.fail(
9683 { a: undefined },
9684 `Expected string
9685 at ["a"]`
9686 )
9687 })
9688
9689 it("by default should pass through the value", async () => {
9690 const schema = Schema.Struct({
9691 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultKey(Effect.succeed("1")))
9692 })
9693 const asserts = new TestSchema.Asserts(schema)
9694
9695 const encoding = asserts.encoding()
9696 await encoding.succeed({ a: 1 }, { a: "1" })
9697 })
9698
9699 it("should omit the value if the encoding strategy is set to omit", async () => {
9700 const schema = Schema.Struct({
9701 a: Schema.FiniteFromString.pipe(
9702 Schema.withDecodingDefaultKey(Effect.succeed("1"), { encodingStrategy: "omit" })
9703 )
9704 })
9705 const asserts = new TestSchema.Asserts(schema)
9706
9707 const encoding = asserts.encoding()
9708 await encoding.succeed({ a: 1 }, {})
9709 })
9710
9711 it("nested default values", async () => {
9712 const schema = Schema.Struct({
9713 a: Schema.Struct({
9714 b: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultKey(Effect.succeed("1")))
9715 }).pipe(Schema.withDecodingDefaultKey(Effect.succeed({})))
9716 })
9717 const asserts = new TestSchema.Asserts(schema)
9718
9719 const decoding = asserts.decoding()
9720 await decoding.succeed({}, { a: { b: 1 } })
9721 await decoding.succeed({ a: {} }, { a: { b: 1 } })
9722 await decoding.succeed({ a: { b: "2" } }, { a: { b: 2 } })
9723 await decoding.fail(
9724 { a: { b: undefined } },
9725 `Expected string
9726 at ["a"]["b"]`
9727 )
9728 })
9729
9730 it("Effect failing with SchemaError propagates as decode failure", async () => {
9731 const schema = Schema.Struct({
9732 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultKey(
9733 Effect.fail(
9734 new Schema.SchemaError(new SchemaIssue.InvalidValue({ message: "decoding default failed" }))
9735 )
9736 ))
9737 })
9738 const asserts = new TestSchema.Asserts(schema)
9739
9740 const decoding = asserts.decoding()
9741 await decoding.succeed({ a: "2" }, { a: 2 })
9742 await decoding.fail(
9743 {},
9744 `decoding default failed
9745 at ["a"]`
9746 )
9747 })
9748
9749 it("Effect failing with SchemaError and a defect preserves the mixed cause", () => {
9750 const cause = Cause.combine(
9751 Cause.fail(
9752 new Schema.SchemaError(new SchemaIssue.InvalidValue({ message: "decoding default failed" }))
9753 ),
9754 Cause.die(new Error("defect"))
9755 )
9756 const schema = Schema.Struct({
9757 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultKey(Effect.failCause(cause)))
9758 })
9759
9760 const exit = Schema.decodeUnknownExit(schema)({})
9761 assertTrue(Exit.isFailure(exit))
9762 assertTrue(Exit.hasDies(exit))
9763 const error = Cause.findError(exit.cause)
9764 assertTrue(Result.isSuccess(error))
9765 assertTrue(Schema.isSchemaError(error.success))
9766 strictEqual(
9767 error.success.message,
9768 `decoding default failed
9769 at ["a"]`
9770 )
9771 })
9772
9773 it("default Effect can require a service", async () => {
9774 class Service extends Context.Service<Service, { fallback: Effect.Effect<string> }>()("Service") {}
9775
9776 const schema = Schema.Struct({
9777 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultKey(
9778 Effect.gen(function*() {
9779 const service = yield* Service
9780 return yield* service.fallback
9781 })
9782 ))
9783 })
9784 const asserts = new TestSchema.Asserts(schema)
9785
9786 const decoding = asserts.decoding().provide(Service, { fallback: Effect.succeed("3") })
9787 await decoding.succeed({ a: "2" }, { a: 2 })
9788 await decoding.succeed({}, { a: 3 })
9789 })
9790 })
9791
9792 describe("withDecodingDefault", () => {
9793 it("should return a decoding default value if the key is missing", async () => {
9794 const schema = Schema.Struct({
9795 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefault(Effect.succeed("1")))
9796 })
9797 const asserts = new TestSchema.Asserts(schema)
9798
9799 const decoding = asserts.decoding()
9800 await decoding.succeed({}, { a: 1 })
9801 await decoding.succeed({ a: undefined }, { a: 1 })
9802 await decoding.succeed({ a: "2" }, { a: 2 })
9803 })
9804
9805 it("should return a decoding default value if the schema is used as standalone and the input is undefined", async () => {
9806 const schema = Schema.String.pipe(Schema.withDecodingDefault(Effect.succeed("a")))
9807 const asserts = new TestSchema.Asserts(schema)
9808
9809 const decoding = asserts.decoding()
9810 await decoding.succeed(undefined, "a")
9811 await decoding.succeed("b", "b")
9812 })
9813
9814 it("by default should pass through the value", async () => {
9815 const schema = Schema.Struct({
9816 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefault(Effect.succeed("1")))
9817 })
9818 const asserts = new TestSchema.Asserts(schema)
9819
9820 const encoding = asserts.encoding()
9821 await encoding.succeed({ a: 1 }, { a: "1" })
9822 })
9823
9824 it("should omit the value if the encoding strategy is set to omit", async () => {
9825 const schema = Schema.Struct({
9826 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefault(Effect.succeed("1"), { encodingStrategy: "omit" }))
9827 })
9828 const asserts = new TestSchema.Asserts(schema)
9829
9830 const encoding = asserts.encoding()
9831 await encoding.succeed({ a: 1 }, {})
9832 })
9833
9834 it("nested default values", async () => {
9835 const schema = Schema.Struct({
9836 a: Schema.Struct({
9837 b: Schema.FiniteFromString.pipe(Schema.withDecodingDefault(Effect.succeed("1")))
9838 }).pipe(Schema.withDecodingDefault(Effect.succeed({})))
9839 })
9840 const asserts = new TestSchema.Asserts(schema)
9841
9842 const decoding = asserts.decoding()
9843 await decoding.succeed({}, { a: { b: 1 } })
9844 await decoding.succeed({ a: {} }, { a: { b: 1 } })
9845 await decoding.succeed({ a: undefined }, { a: { b: 1 } })
9846 await decoding.succeed({ a: { b: undefined } }, { a: { b: 1 } })
9847 await decoding.succeed({ a: { b: "2" } }, { a: { b: 2 } })
9848 })
9849
9850 it("Effect failing with SchemaError and a defect preserves the mixed cause", () => {
9851 const cause = Cause.combine(
9852 Cause.fail(
9853 new Schema.SchemaError(new SchemaIssue.InvalidValue({ message: "decoding default failed" }))
9854 ),
9855 Cause.die(new Error("defect"))
9856 )
9857 const schema = Schema.Struct({
9858 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefault(Effect.failCause(cause)))
9859 })
9860
9861 const exit = Schema.decodeUnknownExit(schema)({})
9862 assertTrue(Exit.isFailure(exit))
9863 assertTrue(Exit.hasDies(exit))
9864 const error = Cause.findError(exit.cause)
9865 assertTrue(Result.isSuccess(error))
9866 assertTrue(Schema.isSchemaError(error.success))
9867 strictEqual(
9868 error.success.message,
9869 `decoding default failed
9870 at ["a"]`
9871 )
9872 })
9873
9874 it("default Effect can require a service", async () => {
9875 class Service extends Context.Service<Service, { fallback: Effect.Effect<string> }>()("Service") {}
9876
9877 const schema = Schema.Struct({
9878 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefault(
9879 Effect.gen(function*() {
9880 const service = yield* Service
9881 return yield* service.fallback
9882 })
9883 ))
9884 })
9885 const asserts = new TestSchema.Asserts(schema)
9886
9887 const decoding = asserts.decoding().provide(Service, { fallback: Effect.succeed("3") })
9888 await decoding.succeed({ a: "2" }, { a: 2 })
9889 await decoding.succeed({}, { a: 3 })
9890 await decoding.succeed({ a: undefined }, { a: 3 })
9891 })
9892 })
9893
9894 describe("withDecodingDefaultTypeKey", () => {
9895 it("should return a decoding default value if the key is missing", async () => {
9896 const schema = Schema.Struct({
9897 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultTypeKey(Effect.succeed(1)))
9898 })
9899 const asserts = new TestSchema.Asserts(schema)
9900
9901 const decoding = asserts.decoding()
9902 await decoding.succeed({}, { a: 1 })
9903 await decoding.succeed({ a: "2" }, { a: 2 })
9904 await decoding.fail(
9905 { a: undefined },
9906 `Expected string
9907 at ["a"]`
9908 )
9909 })
9910
9911 it("by default should pass through the value", async () => {
9912 const schema = Schema.Struct({
9913 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultTypeKey(Effect.succeed(1)))
9914 })
9915 const asserts = new TestSchema.Asserts(schema)
9916
9917 const encoding = asserts.encoding()
9918 await encoding.succeed({ a: 1 }, { a: "1" })
9919 })
9920
9921 it("should omit the value if the encoding strategy is set to omit", async () => {
9922 const schema = Schema.Struct({
9923 a: Schema.FiniteFromString.pipe(
9924 Schema.withDecodingDefaultTypeKey(Effect.succeed(1), { encodingStrategy: "omit" })
9925 )
9926 })
9927 const asserts = new TestSchema.Asserts(schema)
9928
9929 const encoding = asserts.encoding()
9930 await encoding.succeed({ a: 1 }, {})
9931 })
9932
9933 it("nested default values", async () => {
9934 const schema = Schema.Struct({
9935 a: Schema.Struct({
9936 b: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultTypeKey(Effect.succeed(1)))
9937 }).pipe(Schema.withDecodingDefaultTypeKey(Effect.succeed({ b: 1 })))
9938 })
9939 const asserts = new TestSchema.Asserts(schema)
9940
9941 const decoding = asserts.decoding()
9942 await decoding.succeed({}, { a: { b: 1 } })
9943 await decoding.succeed({ a: {} }, { a: { b: 1 } })
9944 await decoding.succeed({ a: { b: "2" } }, { a: { b: 2 } })
9945 await decoding.fail(
9946 { a: { b: undefined } },
9947 `Expected string
9948 at ["a"]["b"]`
9949 )
9950 })
9951
9952 it("Effect failing with SchemaError propagates as decode failure", async () => {
9953 const schema = Schema.Struct({
9954 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultTypeKey(
9955 Effect.fail(
9956 new Schema.SchemaError(new SchemaIssue.InvalidValue({ message: "decoding default failed" }))
9957 )
9958 ))
9959 })
9960 const asserts = new TestSchema.Asserts(schema)
9961
9962 const decoding = asserts.decoding()
9963 await decoding.succeed({ a: "2" }, { a: 2 })
9964 await decoding.fail(
9965 {},
9966 `decoding default failed
9967 at ["a"]`
9968 )
9969 })
9970
9971 it("default Effect can require a service", async () => {
9972 class Service extends Context.Service<Service, { fallback: Effect.Effect<number> }>()("Service") {}
9973
9974 const schema = Schema.Struct({
9975 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultTypeKey(
9976 Effect.gen(function*() {
9977 const service = yield* Service
9978 return yield* service.fallback
9979 })
9980 ))
9981 })
9982 const asserts = new TestSchema.Asserts(schema)
9983
9984 const decoding = asserts.decoding().provide(Service, { fallback: Effect.succeed(3) })
9985 await decoding.succeed({ a: "2" }, { a: 2 })
9986 await decoding.succeed({}, { a: 3 })
9987 })
9988 })
9989
9990 describe("withDecodingDefaultType", () => {
9991 it("should return a decoding default value if the key is missing", async () => {
9992 const schema = Schema.Struct({
9993 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultType(Effect.succeed(1)))
9994 })
9995 const asserts = new TestSchema.Asserts(schema)
9996
9997 const decoding = asserts.decoding()
9998 await decoding.succeed({}, { a: 1 })
9999 await decoding.succeed({ a: undefined }, { a: 1 })
10000 await decoding.succeed({ a: "2" }, { a: 2 })
10001 })
10002
10003 it("should return a decoding default value if the schema is used as standalone and the input is undefined", async () => {
10004 const schema = Schema.String.pipe(Schema.withDecodingDefaultType(Effect.succeed("a")))
10005 const asserts = new TestSchema.Asserts(schema)
10006
10007 const decoding = asserts.decoding()
10008 await decoding.succeed(undefined, "a")
10009 await decoding.succeed("b", "b")
10010 })
10011
10012 it("by default should pass through the value", async () => {
10013 const schema = Schema.Struct({
10014 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultType(Effect.succeed(1)))
10015 })
10016 const asserts = new TestSchema.Asserts(schema)
10017
10018 const encoding = asserts.encoding()
10019 await encoding.succeed({ a: 1 }, { a: "1" })
10020 })
10021
10022 it("should omit the value if the encoding strategy is set to omit", async () => {
10023 const schema = Schema.Struct({
10024 a: Schema.FiniteFromString.pipe(
10025 Schema.withDecodingDefaultType(Effect.succeed(1), { encodingStrategy: "omit" })
10026 )
10027 })
10028 const asserts = new TestSchema.Asserts(schema)
10029
10030 const encoding = asserts.encoding()
10031 await encoding.succeed({ a: 1 }, {})
10032 })
10033
10034 it("nested default values", async () => {
10035 const schema = Schema.Struct({
10036 a: Schema.Struct({
10037 b: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultType(Effect.succeed(1)))
10038 }).pipe(Schema.withDecodingDefaultType(Effect.succeed({ b: 1 })))
10039 })
10040 const asserts = new TestSchema.Asserts(schema)
10041
10042 const decoding = asserts.decoding()
10043 await decoding.succeed({}, { a: { b: 1 } })
10044 await decoding.succeed({ a: {} }, { a: { b: 1 } })
10045 await decoding.succeed({ a: undefined }, { a: { b: 1 } })
10046 await decoding.succeed({ a: { b: undefined } }, { a: { b: 1 } })
10047 await decoding.succeed({ a: { b: "2" } }, { a: { b: 2 } })
10048 })
10049
10050 it("Effect failing with SchemaError propagates as decode failure", async () => {
10051 const schema = Schema.Struct({
10052 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultType(
10053 Effect.fail(
10054 new Schema.SchemaError(new SchemaIssue.InvalidValue({ message: "decoding default failed" }))
10055 )
10056 ))
10057 })
10058 const asserts = new TestSchema.Asserts(schema)
10059
10060 const decoding = asserts.decoding()
10061 await decoding.succeed({ a: "2" }, { a: 2 })
10062 await decoding.fail(
10063 {},
10064 `decoding default failed
10065 at ["a"]`
10066 )
10067 })
10068
10069 it("default Effect can require a service", async () => {
10070 class Service extends Context.Service<Service, { fallback: Effect.Effect<number> }>()("Service") {}
10071
10072 const schema = Schema.Struct({
10073 a: Schema.FiniteFromString.pipe(Schema.withDecodingDefaultType(
10074 Effect.gen(function*() {
10075 const service = yield* Service
10076 return yield* service.fallback
10077 })
10078 ))
10079 })
10080 const asserts = new TestSchema.Asserts(schema)
10081
10082 const decoding = asserts.decoding().provide(Service, { fallback: Effect.succeed(3) })
10083 await decoding.succeed({ a: "2" }, { a: 2 })
10084 await decoding.succeed({}, { a: 3 })
10085 await decoding.succeed({ a: undefined }, { a: 3 })
10086 })
10087 })
10088
10089 it("NonEmptyString", async () => {
10090 const schema = Schema.NonEmptyString
10091 const asserts = new TestSchema.Asserts(schema)
10092
10093 const decoding = asserts.decoding()
10094 await decoding.succeed("a")
10095 await decoding.fail(
10096 "",
10097 `Expected a value with a length of at least 1`
10098 )
10099
10100 const encoding = asserts.encoding()
10101 await encoding.succeed("a")
10102 await encoding.fail(
10103 "",
10104 `Expected a value with a length of at least 1`
10105 )
10106 })
10107
10108 it("Char", async () => {
10109 const schema = Schema.Char
10110 const asserts = new TestSchema.Asserts(schema)
10111
10112 if (verifyGeneration) {
10113 asserts.arbitrary().verifyGeneration()
10114 }
10115
10116 const decoding = asserts.decoding()
10117 await decoding.succeed("a")
10118 await decoding.fail(
10119 "ab",
10120 `Expected a value with a length of 1`
10121 )
10122
10123 const encoding = asserts.encoding()
10124 await encoding.succeed("a")
10125 await encoding.fail(
10126 "ab",
10127 `Expected a value with a length of 1`
10128 )
10129 })
10130
10131 it("Int", async () => {
10132 const schema = Schema.Int
10133 const asserts = new TestSchema.Asserts(schema)
10134
10135 if (verifyGeneration) {
10136 asserts.arbitrary().verifyGeneration()
10137 }
10138
10139 const decoding = asserts.decoding()
10140 await decoding.succeed(1)
10141 await decoding.fail(
10142 1.1,
10143 `Expected an integer`
10144 )
10145 await decoding.fail(
10146 NaN,
10147 `Expected an integer`
10148 )
10149 await decoding.fail(
10150 Infinity,
10151 `Expected an integer`
10152 )
10153 await decoding.fail(
10154 -Infinity,
10155 `Expected an integer`
10156 )
10157
10158 const encoding = asserts.encoding()
10159 await encoding.succeed(1)
10160 await encoding.fail(
10161 1.1,
10162 `Expected an integer`
10163 )
10164 })
10165
10166 it("Natural", async () => {
10167 const schema = Schema.Natural
10168 const asserts = new TestSchema.Asserts(schema)
10169
10170 if (verifyGeneration) {
10171 asserts.arbitrary().verifyGeneration()
10172 }
10173
10174 const decoding = asserts.decoding()
10175 await decoding.succeed(0)
10176 await decoding.succeed(1)
10177 await decoding.fail(
10178 -1,
10179 `Expected a value greater than or equal to 0`
10180 )
10181 await decoding.fail(
10182 1.1,
10183 `Expected an integer`
10184 )
10185
10186 const encoding = asserts.encoding()
10187 await encoding.succeed(0)
10188 await encoding.fail(
10189 -1,
10190 `Expected a value greater than or equal to 0`
10191 )
10192 })
10193
10194 it("Capitalize", async () => {
10195 const schema = Schema.String.pipe(
10196 Schema.decodeTo(
10197 Schema.String.check(Schema.isCapitalized()),
10198 SchemaTransformation.capitalize()
10199 )
10200 )
10201 const asserts = new TestSchema.Asserts(schema)
10202
10203 if (verifyGeneration) {
10204 asserts.arbitrary().verifyGeneration()
10205 }
10206
10207 const decoding = asserts.decoding()
10208 await decoding.succeed("abc", "Abc")
10209
10210 const encoding = asserts.encoding()
10211 await encoding.succeed("Abc")
10212 await encoding.fail(
10213 "abc",
10214 `Expected a string with the first character in uppercase`
10215 )
10216 })
10217
10218 it("Uncapitalize", async () => {
10219 const schema = Schema.String.pipe(
10220 Schema.decodeTo(
10221 Schema.String.check(Schema.isUncapitalized()),
10222 SchemaTransformation.uncapitalize()
10223 )
10224 )
10225 const asserts = new TestSchema.Asserts(schema)
10226
10227 if (verifyGeneration) {
10228 asserts.arbitrary().verifyGeneration()
10229 }
10230
10231 const decoding = asserts.decoding()
10232 await decoding.succeed("Abc", "abc")
10233
10234 const encoding = asserts.encoding()
10235 await encoding.succeed("abc")
10236 await encoding.fail(
10237 "Abc",
10238 `Expected a string with the first character in lowercase`
10239 )
10240 })
10241
10242 it("Lowercase", async () => {
10243 const schema = Schema.String.pipe(
10244 Schema.decodeTo(
10245 Schema.String.check(Schema.isLowercased()),
10246 SchemaTransformation.toLowerCase()
10247 )
10248 )
10249 const asserts = new TestSchema.Asserts(schema)
10250
10251 if (verifyGeneration) {
10252 asserts.arbitrary().verifyGeneration()
10253 }
10254
10255 const decoding = asserts.decoding()
10256 await decoding.succeed("ABC", "abc")
10257
10258 const encoding = asserts.encoding()
10259 await encoding.succeed("abc")
10260 await encoding.fail(
10261 "ABC",
10262 `Expected a string with all characters in lowercase`
10263 )
10264 })
10265
10266 it("Uppercase", async () => {
10267 const schema = Schema.String.pipe(
10268 Schema.decodeTo(
10269 Schema.String.check(Schema.isUppercased()),
10270 SchemaTransformation.toUpperCase()
10271 )
10272 )
10273 const asserts = new TestSchema.Asserts(schema)
10274
10275 if (verifyGeneration) {
10276 asserts.arbitrary().verifyGeneration()
10277 }
10278
10279 const decoding = asserts.decoding()
10280 await decoding.succeed("abc", "ABC")
10281
10282 const encoding = asserts.encoding()
10283 await encoding.succeed("ABC")
10284 await encoding.fail(
10285 "abc",
10286 `Expected a string with all characters in uppercase`
10287 )
10288 })
10289})
10290
10291describe("Getter", () => {
10292 it("succeed", async () => {
10293 const schema = Schema.Literal(0).pipe(Schema.decodeTo(Schema.Literal("a"), {
10294 decode: SchemaGetter.succeed("a"),
10295 encode: SchemaGetter.succeed(0)
10296 }))
10297 const asserts = new TestSchema.Asserts(schema)
10298
10299 const decoding = asserts.decoding()
10300 await decoding.succeed(0, "a")
10301 await decoding.fail(1, `Expected 0`)
10302
10303 const encoding = asserts.encoding()
10304 await encoding.succeed("a", 0)
10305 await encoding.fail("b", `Expected "a"`)
10306 })
10307})
10308
10309describe("Check", () => {
10310 it("isStringFinite", async () => {
10311 const schema = Schema.String.check(Schema.isStringFinite())
10312
10313 deepStrictEqual(Schema.resolveAnnotations(schema)?.representation, {
10314 id: "effect/schema/isStringFinite",
10315 payload: null
10316 })
10317 })
10318
10319 it("isStringBigInt", async () => {
10320 const schema = Schema.String.check(Schema.isStringBigInt())
10321
10322 deepStrictEqual(Schema.resolveAnnotations(schema)?.representation, {
10323 id: "effect/schema/isStringBigInt",
10324 payload: null
10325 })
10326 })
10327
10328 it("isStringSymbol", async () => {
10329 const schema = Schema.String.check(Schema.isStringSymbol())
10330
10331 deepStrictEqual(Schema.resolveAnnotations(schema)?.representation, {
10332 id: "effect/schema/isStringSymbol",
10333 payload: null
10334 })
10335 })
10336
10337 it("isUUID", async () => {
10338 const schema = Schema.String.check(Schema.isUUID())
10339 const asserts = new TestSchema.Asserts(schema)
10340
10341 if (verifyGeneration) {
10342 asserts.arbitrary().verifyGeneration()
10343 }
10344
10345 deepStrictEqual(Schema.resolveAnnotations(schema)?.representation, {
10346 id: "effect/schema/isUUID",
10347 payload: { version: null }
10348 })
10349
10350 const decoding = asserts.decoding()
10351 await decoding.succeed("00000000-0000-0000-0000-000000000000")
10352 await decoding.succeed("ffffffff-ffff-ffff-ffff-ffffffffffff")
10353 await decoding.succeed("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")
10354 await decoding.succeed("00000000-0000-4000-8000-000000000001")
10355 await decoding.fail(
10356 "00000000-0000-0000-0000-000000000001",
10357 `Expected a UUID`
10358 )
10359 })
10360
10361 it("isUUID version-specific checks reject nil and max UUIDs", async () => {
10362 const schema = Schema.String.check(Schema.isUUID(4))
10363 const asserts = new TestSchema.Asserts(schema)
10364 const decoding = asserts.decoding()
10365
10366 await decoding.succeed("00000000-0000-4000-8000-000000000001")
10367 await decoding.fail(
10368 "00000000-0000-0000-0000-000000000000",
10369 `Expected a UUID v4`
10370 )
10371 await decoding.fail(
10372 "ffffffff-ffff-ffff-ffff-ffffffffffff",
10373 `Expected a UUID v4`
10374 )
10375 })
10376
10377 it("isGUID", async () => {
10378 const schema = Schema.String.check(Schema.isGUID())
10379 const asserts = new TestSchema.Asserts(schema)
10380
10381 if (verifyGeneration) {
10382 asserts.arbitrary().verifyGeneration()
10383 }
10384
10385 deepStrictEqual(Schema.resolveAnnotations(schema)?.representation, {
10386 id: "effect/schema/isGUID",
10387 payload: null
10388 })
10389
10390 const decoding = asserts.decoding()
10391 await decoding.succeed("00000000-0000-0000-0000-000000000001")
10392 await decoding.succeed("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")
10393 await decoding.fail(
10394 "not-a-guid",
10395 `Expected a GUID`
10396 )
10397 })
10398
10399 it("isULID", async () => {
10400 const schema = Schema.String.check(Schema.isULID())
10401 const asserts = new TestSchema.Asserts(schema)
10402
10403 if (verifyGeneration) {
10404 asserts.arbitrary().verifyGeneration()
10405 }
10406
10407 deepStrictEqual(Schema.resolveAnnotations(schema)?.representation, {
10408 id: "effect/schema/isULID",
10409 payload: null
10410 })
10411
10412 const decoding = asserts.decoding()
10413 await decoding.succeed("01H4PGGGJVN2DKP2K1H7EH996V")
10414 await decoding.fail(
10415 "",
10416 `Expected a string matching the RegExp ^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$`
10417 )
10418 })
10419
10420 it("isBase64", async () => {
10421 const schema = Schema.String.check(Schema.isBase64())
10422
10423 deepStrictEqual(Schema.resolveAnnotations(schema)?.representation, {
10424 id: "effect/schema/isBase64",
10425 payload: null
10426 })
10427 })
10428
10429 it("isBase64Url", async () => {
10430 const schema = Schema.String.check(Schema.isBase64Url())
10431
10432 deepStrictEqual(Schema.resolveAnnotations(schema)?.representation, {
10433 id: "effect/schema/isBase64Url",
10434 payload: null
10435 })
10436 })
10437
10438 describe("brand", () => {
10439 it("single brand", async () => {
10440 const schema = Schema.String.pipe(Schema.brand("Positive"))
10441 strictEqual(schema.ast, Schema.String.ast)
10442 strictEqual(schema.schema, Schema.String)
10443 strictEqual(schema.identifier, "Positive")
10444 })
10445
10446 it("double brand", async () => {
10447 const schema = Schema.String.pipe(Schema.brand("Positive"), Schema.brand("Int"))
10448 strictEqual(schema.ast, Schema.String.ast)
10449 strictEqual(schema.schema.identifier, "Positive")
10450 strictEqual(schema.identifier, "Int")
10451 })
10452
10453 it("override the default identifier", async () => {
10454 const schema = Schema.String.pipe(Schema.brand("Positive"), Schema.brand("Int")).annotate({ identifier: "MyInt" })
10455 deepStrictEqual(schema.ast.annotations, { identifier: "MyInt" })
10456 })
10457 })
10458
10459 describe("fromBrand", () => {
10460 it("nominal", async () => {
10461 const schema = Schema.String.pipe(Schema.fromBrand("a", Brand.nominal<string & Brand.Brand<"a">>()))
10462 const asserts = new TestSchema.Asserts(schema)
10463
10464 const decoding = asserts.decoding()
10465 await decoding.succeed("a")
10466 await decoding.fail(1, `Expected string`)
10467
10468 strictEqual(schema.ast, Schema.String.ast)
10469 })
10470
10471 it("single brand", async () => {
10472 type Int = number & Brand.Brand<"Int">
10473 const Int = Brand.check<Int>(Schema.isInt())
10474 const schema = Schema.Number.pipe(Schema.fromBrand("Int", Int))
10475
10476 const asserts = new TestSchema.Asserts(schema)
10477
10478 const decoding = asserts.decoding()
10479 await decoding.succeed(1)
10480 await decoding.fail("a", `Expected number`)
10481 await decoding.fail(1.2, `Expected an integer`)
10482 })
10483
10484 it("multiple brands", async () => {
10485 type Int = number & Brand.Brand<"Int">
10486 const Int = Brand.check<Int>(Schema.isInt())
10487
10488 type Positive = number & Brand.Brand<"Positive">
10489 const Positive = Brand.check<Positive>(Schema.isGreaterThan(0))
10490
10491 const schema = Schema.Number.pipe(
10492 Schema.fromBrand("Int", Int),
10493 Schema.fromBrand("Positive", Positive)
10494 )
10495
10496 const asserts = new TestSchema.Asserts(schema)
10497
10498 const decoding = asserts.decoding()
10499 await decoding.succeed(1)
10500 await decoding.fail("a", `Expected number`)
10501 await decoding.fail(1.2, `Expected an integer`)
10502 await decoding.fail(-1, `Expected a value greater than 0`)
10503 })
10504 })
10505
10506 describe("requiredKey", () => {
10507 it("should make all optionalKey keys required", async () => {
10508 const schema = Schema.Struct({
10509 a: Schema.optionalKey(Schema.String),
10510 b: Schema.optionalKey(Schema.Number)
10511 }).mapFields(Struct.map(Schema.requiredKey))
10512 const asserts = new TestSchema.Asserts(schema)
10513
10514 const decoding = asserts.decoding()
10515 await decoding.succeed({ a: "a", b: 1 })
10516 await decoding.fail(
10517 { a: "a" },
10518 `Missing key
10519 at ["b"]`
10520 )
10521 await decoding.fail(
10522 { a: "a", b: undefined },
10523 `Expected number
10524 at ["b"]`
10525 )
10526 await decoding.fail(
10527 { b: 1 },
10528 `Missing key
10529 at ["a"]`
10530 )
10531 })
10532
10533 it("should make all optional keys required", async () => {
10534 const schema = Schema.Struct({
10535 a: Schema.optional(Schema.String),
10536 b: Schema.optional(Schema.Number)
10537 }).mapFields(Struct.map(Schema.requiredKey))
10538 const asserts = new TestSchema.Asserts(schema)
10539
10540 const decoding = asserts.decoding()
10541 await decoding.succeed({ a: "a", b: 1 })
10542 await decoding.succeed({ a: "a", b: undefined })
10543 await decoding.succeed({ a: undefined, b: 1 })
10544 await decoding.succeed({ a: undefined, b: undefined })
10545 await decoding.fail(
10546 { a: "a" },
10547 `Missing key
10548 at ["b"]`
10549 )
10550 await decoding.fail(
10551 { b: 1 },
10552 `Missing key
10553 at ["a"]`
10554 )
10555 })
10556 })
10557
10558 describe("required", () => {
10559 it("should make all optional keys required", async () => {
10560 const schema = Schema.Struct({
10561 a: Schema.optional(Schema.String),
10562 b: Schema.optional(Schema.Number)
10563 }).mapFields(Struct.map(Schema.required))
10564 const asserts = new TestSchema.Asserts(schema)
10565
10566 const decoding = asserts.decoding()
10567 await decoding.succeed({ a: "a", b: 1 })
10568 await decoding.fail(
10569 { a: "a" },
10570 `Missing key
10571 at ["b"]`
10572 )
10573 await decoding.fail(
10574 { a: "a", b: undefined },
10575 `Expected number
10576 at ["b"]`
10577 )
10578 await decoding.fail(
10579 { b: 1 },
10580 `Missing key
10581 at ["a"]`
10582 )
10583 await decoding.fail(
10584 { a: undefined, b: 1 },
10585 `Expected string
10586 at ["a"]`
10587 )
10588 })
10589 })
10590
10591 it("fieldsAssign", async () => {
10592 const schema = Schema.Union([
10593 Schema.Struct({
10594 a: Schema.String
10595 }),
10596 Schema.Struct({
10597 b: Schema.Number
10598 })
10599 ]).mapMembers(Tuple.map(Schema.fieldsAssign({ c: Schema.Number })))
10600 const asserts = new TestSchema.Asserts(schema)
10601
10602 const decoding = asserts.decoding()
10603 await decoding.succeed({ a: "a", c: 1 })
10604 await decoding.succeed({ b: 1, c: 1 })
10605 await decoding.fail(
10606 { a: "a" },
10607 `Missing key
10608 at ["c"]
10609Missing key
10610 at ["b"]`
10611 )
10612 await decoding.fail(
10613 { b: 1 },
10614 `Missing key
10615 at ["a"]
10616Missing key
10617 at ["c"]`
10618 )
10619 })
10620
10621 describe("class extension", () => {
10622 it("wrapping a primitive schema", () => {
10623 class A extends Schema.String {}
10624
10625 strictEqual(Schema.decodeUnknownSync(A)("a"), "a")
10626 })
10627
10628 it("inherits the schema protocol", () => {
10629 class A extends Schema.String {}
10630
10631 assertTrue(Schema.isSchema(A))
10632 strictEqual(A.make("a"), "a")
10633
10634 const annotated = A.annotate({ title: "A" })
10635 assertTrue(Schema.isSchema(annotated))
10636 strictEqual(Schema.resolveAnnotations(annotated)?.title, "A")
10637 })
10638
10639 it("extending a rebuilt schema", () => {
10640 class A extends Schema.Struct({ name: Schema.String }).annotate({ title: "A" }) {}
10641
10642 deepStrictEqual(Schema.decodeUnknownSync(A)({ name: "a" }), { name: "a" })
10643 strictEqual(Schema.resolveAnnotations(A)?.title, "A")
10644 })
10645
10646 it("static getter using this", () => {
10647 class A extends Schema.String {
10648 static get decodeUnknownSync() {
10649 return Schema.decodeUnknownSync(this)
10650 }
10651 }
10652
10653 strictEqual(A.decodeUnknownSync("a"), "a")
10654 })
10655
10656 it("static property", () => {
10657 class A extends Schema.String {
10658 static readonly decodeUnknownSync = Schema.decodeUnknownSync(this)
10659 }
10660
10661 strictEqual(A.decodeUnknownSync("a"), "a")
10662 })
10663
10664 it("static property using Schema.suspend", () => {
10665 class A extends Schema.String {
10666 static readonly decodeUnknownSync = Schema.decodeUnknownSync(Schema.suspend(() => this))
10667 }
10668
10669 strictEqual(A.decodeUnknownSync("a"), "a")
10670 })
10671
10672 it("wrapping a Struct schema", () => {
10673 const struct = Schema.Struct({
10674 name: Schema.String
10675 })
10676 class A extends struct {
10677 static get decodeUnknownSync() {
10678 return Schema.decodeUnknownSync(this)
10679 }
10680 }
10681
10682 deepStrictEqual(A.decodeUnknownSync({ name: "a" }), { name: "a" })
10683 strictEqual(A.fields, struct.fields)
10684 })
10685
10686 it("does not create class instances", () => {
10687 class A extends Schema.Struct({ name: Schema.String }) {}
10688
10689 const value = A.make({ name: "a" })
10690
10691 assertFalse(value instanceof A)
10692 deepStrictEqual(value, { name: "a" })
10693 })
10694
10695 it("subclassing", () => {
10696 class A extends Schema.FiniteFromString {
10697 static get decodeUnknownSync() {
10698 return Schema.decodeUnknownSync(this)
10699 }
10700 }
10701
10702 class B extends A {
10703 static encodeSync = Schema.encodeSync(this)
10704 }
10705
10706 strictEqual(B.decodeUnknownSync("1"), 1)
10707 strictEqual(B.encodeSync(1), "1")
10708 })
10709 })
10710})
10711
10712describe("resolveAnnotations", () => {
10713 it("returns undefined for a schema without annotations", () => {
10714 strictEqual(Schema.resolveAnnotations(Schema.String), undefined)
10715 })
10716
10717 it("returns annotations from the base schema", () => {
10718 const schema = Schema.String.annotate({ title: "my string" })
10719 deepStrictEqual(Schema.resolveAnnotations(schema), { title: "my string" })
10720 })
10721
10722 it("returns annotations from the last check", () => {
10723 const schema = Schema.String
10724 .annotate({ title: "base" })
10725 .check(Schema.isNonEmpty().annotate({ title: "check" }))
10726 strictEqual(Schema.resolveAnnotations(schema)?.title, "check")
10727 })
10728})
10729
10730describe("resolveAnnotationsKey", () => {
10731 it("returns undefined for a schema without key annotations", () => {
10732 strictEqual(Schema.resolveAnnotationsKey(Schema.String), undefined)
10733 })
10734
10735 it("returns key annotations", () => {
10736 const schema = Schema.String.annotateKey({ messageMissingKey: "required" })
10737 deepStrictEqual(Schema.resolveAnnotationsKey(schema), { messageMissingKey: "required" })
10738 })
10739})