1import { assert, describe, it } from "@effect/vitest"
2import { Deferred, Effect, Fiber, Schema, SchemaGetter, SchemaParser } from "effect"
3import { SchemaCompiler, SchemaJITCompiler } from "effect/schema"
4
5describe("compiled homogeneous Array traversal", () => {
6 it.effect("keeps detailed errors and sparse input behavior", () =>
7 Effect.gen(function*() {
8 const schema = Schema.Array(Schema.NumberFromString)
9 const inputs = [["1", "2"], ["bad", "2"], ["1", "bad", "bad"], new Array(2)]
10 const snapshot = Effect.fnUntraced(function*() {
11 const parse = SchemaParser.decodeUnknownEffect(schema)
12 const results = []
13 for (const errors of ["first", "all"] as const) {
14 for (const input of inputs) {
15 results.push(yield* Effect.result(parse(input, { errors })))
16 }
17 }
18 return results
19 })
20 const interpreted = yield* snapshot()
21 SchemaJITCompiler.enable(schema.ast)
22 assert.deepStrictEqual(yield* snapshot(), interpreted)
23 }))
24
25 it.effect("resumes without replaying the pending element or rereading its accessor", () =>
26 Effect.gen(function*() {
27 const events: Array<string> = []
28 const element = Schema.String.pipe(Schema.decode({
29 decode: SchemaGetter.transformEffect((value) =>
30 Effect.gen(function*() {
31 events.push(value)
32 yield* Effect.yieldNow
33 return value.toUpperCase()
34 })
35 ),
36 encode: SchemaGetter.passthrough()
37 }))
38 const schema = Schema.Array(element)
39 let reads = 0
40 const input = ["a", "b"]
41 Object.defineProperty(input, "0", {
42 get() {
43 reads++
44 return "a"
45 }
46 })
47 SchemaJITCompiler.enable(schema.ast)
48 assert.deepStrictEqual(yield* SchemaParser.decodeUnknownEffect(schema)(input), ["A", "B"])
49 assert.deepStrictEqual(events, ["a", "b"])
50 assert.strictEqual(reads, 1)
51 }))
52
53 it("passes missing constructor results to the shared element step", () => {
54 for (const optional of [false, true]) {
55 const required = Schema.String.annotate({ title: "Array constructor element" })
56 const element = optional ? Schema.optionalKey(required) : required
57 SchemaCompiler.set(element.ast, {
58 decodeEffect: Effect.succeed,
59 makeEffect: () => Effect.succeed(SchemaCompiler.missing)
60 })
61 const schema = Schema.Array(element)
62 SchemaJITCompiler.enable(schema.ast)
63 const make = SchemaParser.make(schema)
64 if (optional) {
65 const output = make(["a"])
66 assert.strictEqual(output.length, 1)
67 assert.strictEqual(0 in output, false)
68 } else {
69 assert.throws(() => make(["a"]), /Schema validation failed/)
70 }
71 }
72 })
73
74 it.effect("keeps bounded concurrency on the interpreter's concurrent traversal", () =>
75 Effect.gen(function*() {
76 const started = yield* Effect.forEach([0, 1, 2], () => Deferred.make<void>())
77 const releases = yield* Effect.forEach([0, 1, 2], () => Deferred.make<void>())
78 const calls = [0, 0, 0]
79 const element = Schema.Number.pipe(Schema.decode({
80 decode: SchemaGetter.transformEffect((index) =>
81 Effect.gen(function*() {
82 calls[index]++
83 yield* Deferred.succeed(started[index], undefined)
84 yield* Deferred.await(releases[index])
85 return index
86 })
87 ),
88 encode: SchemaGetter.passthrough()
89 }))
90 const schema = Schema.Array(element)
91 SchemaJITCompiler.enable(schema.ast)
92 const fiber = yield* SchemaParser.decodeUnknownEffect(schema)([0, 1, 2], { concurrency: 2 }).pipe(
93 Effect.forkChild
94 )
95 yield* Deferred.await(started[0])
96 yield* Deferred.await(started[1])
97 assert.strictEqual(yield* Deferred.isDone(started[2]), false)
98 yield* Deferred.succeed(releases[0], undefined)
99 yield* Deferred.await(started[2])
100 yield* Deferred.succeed(releases[1], undefined)
101 yield* Deferred.succeed(releases[2], undefined)
102 assert.deepStrictEqual(yield* Fiber.join(fiber), [0, 1, 2])
103 assert.deepStrictEqual(calls, [1, 1, 1])
104 }))
105})