1import * as Effect from "effect/Effect"
2import * as Schema from "effect/Schema"
3import * as SchemaGetter from "effect/SchemaGetter"
4import * as SchemaParser from "effect/SchemaParser"
5import * as SchemaTransformation from "effect/SchemaTransformation"
6import assert from "node:assert/strict"
7
8const decodeCase = (schema, input, success, options) => () => {
9 const run = Schema.decodeUnknownExit(schema, options)
10 const isOutput = success ? Schema.is(schema) : undefined
11 return {
12 run: () => run(input),
13 validate: (result) => {
14 assert.equal(result._tag, success ? "Success" : "Failure")
15 if (result._tag === "Success") assert.equal(isOutput?.(result.value), true)
16 }
17 }
18}
19
20const decodeParserCase = (schema, input, success, options) => () => {
21 const run = SchemaParser.decodeUnknownExit(schema, options)
22 const isOutput = success ? Schema.is(schema) : undefined
23 return {
24 run: () => run(input),
25 validate: (result) => {
26 assert.equal(result._tag, success ? "Success" : "Failure")
27 if (result._tag === "Success") assert.equal(isOutput?.(result.value), true)
28 }
29 }
30}
31
32const encodeParserCase = (schema, input, success, options) => () => {
33 const run = SchemaParser.encodeUnknownExit(schema, options)
34 const isOutput = success ? Schema.is(Schema.flip(schema)) : undefined
35 return {
36 run: () => run(input),
37 validate: (result) => {
38 assert.equal(result._tag, success ? "Success" : "Failure")
39 if (result._tag === "Success") assert.equal(isOutput?.(result.value), true)
40 }
41 }
42}
43
44const checkedString = Schema.String
45 .check(Schema.isMinLength(2))
46 .check(Schema.isPattern(/^[a-z]+$/))
47
48export const checksInvalidFirst = decodeParserCase(checkedString, "", false)
49export const checksInvalidLast = decodeParserCase(checkedString, "runtime-perf", false)
50
51const encodingCheckedString = Schema.String.pipe(
52 Schema.flip,
53 Schema.check(Schema.isMinLength(2)),
54 Schema.flip
55)
56
57export const encodingCheckValid = decodeParserCase(encodingCheckedString, "runtimeperf", true)
58
59const templateLiteralLinear = Schema.TemplateLiteralParser([
60 "prefix-",
61 Schema.String,
62 "-middle-",
63 Schema.Number,
64 "-suffix"
65])
66
67export const templateLiteralLinearValid = decodeParserCase(
68 templateLiteralLinear,
69 "prefix-value-middle-123-suffix",
70 true
71)
72export const templateLiteralLinearInvalid = decodeParserCase(
73 templateLiteralLinear,
74 "prefix-value-middle-invalid",
75 false
76)
77
78const templateLiteralBacktracking = Schema.TemplateLiteralParser([
79 Schema.String,
80 ":",
81 Schema.NonEmptyString,
82 "x"
83])
84
85export const templateLiteralBacktrackingValid = decodeParserCase(
86 templateLiteralBacktracking,
87 "a:b:x",
88 true
89)
90export const templateLiteralBacktrackingInvalid = decodeParserCase(
91 templateLiteralBacktracking,
92 "a:x",
93 false
94)
95
96export const templateLiteralTransformedValid = decodeParserCase(
97 Schema.TemplateLiteralParser([Schema.FiniteFromString, "a", Schema.NonEmptyString]),
98 "100ab23a",
99 true
100)
101
102const templateLiteralRecordInput = Object.fromEntries(
103 Array.from({ length: 32 }, (_, index) => [`field-${index}`, `value${index}`])
104)
105
106export const templateLiteralRecord32Valid = decodeParserCase(
107 Schema.Record(Schema.TemplateLiteral(["field-", Schema.Number]), Schema.String),
108 templateLiteralRecordInput,
109 true
110)
111
112export const transformationDecodeValid = decodeParserCase(Schema.FiniteFromString, "123", true)
113export const transformationDecodeInvalid = decodeParserCase(Schema.FiniteFromString, "invalid", false)
114export const transformationEncodeValid = encodeParserCase(Schema.FiniteFromString, 123, true)
115
116const makeEncodingChain = (size) => {
117 let schema = Schema.FiniteFromString
118 for (let i = 1; i < size; i++) {
119 schema = Schema.String.pipe(
120 Schema.decodeTo(schema, SchemaTransformation.passthrough())
121 )
122 }
123 return schema
124}
125
126const encodingChain8 = makeEncodingChain(8)
127
128export const encodingChain8DecodeValid = decodeParserCase(encodingChain8, "123", true)
129export const encodingChain8DecodeInvalid = decodeParserCase(encodingChain8, "invalid", false)
130export const encodingChain8EncodeValid = encodeParserCase(encodingChain8, 123, true)
131
132const transformedKeyRecord = Schema.Record(
133 Schema.String.pipe(Schema.decode(SchemaTransformation.snakeToCamel())),
134 Schema.String
135)
136const transformedKeyRecordInput = Object.fromEntries(
137 Array.from({ length: 32 }, (_, index) => [`field_${index}_value`, `value${index}`])
138)
139
140export const transformedKeyRecordValid = decodeParserCase(
141 transformedKeyRecord,
142 transformedKeyRecordInput,
143 true
144)
145
146const optionalStruct = Schema.Struct({
147 required: Schema.String,
148 optionalKey: Schema.optionalKey(Schema.String),
149 optionalValue: Schema.optional(Schema.String)
150})
151
152export const optionalValid = decodeCase(optionalStruct, { required: "value" }, true)
153export const optionalPresentValid = decodeCase(
154 optionalStruct,
155 { required: "value", optionalKey: "key", optionalValue: "value" },
156 true
157)
158export const optionalPresentInvalid = decodeCase(optionalStruct, { required: "value", optionalKey: 1 }, false)
159
160const suspendedString = Schema.String.pipe(Schema.decode({
161 decode: SchemaGetter.transformOptionalEffect((input) => Effect.suspend(() => Effect.succeed(input))),
162 encode: SchemaGetter.passthrough()
163}))
164const suspendedObjectFields = Object.fromEntries(
165 Array.from({ length: 32 }, (_, index) => [`field${index}`, index === 16 ? suspendedString : Schema.String])
166)
167const suspendedObjectInput = Object.fromEntries(
168 Array.from({ length: 32 }, (_, index) => [`field${index}`, `value${index}`])
169)
170
171export const object32SuspendedMiddleValid = decodeParserCase(
172 Schema.Struct(suspendedObjectFields),
173 suspendedObjectInput,
174 true
175)
176
177const suspendedArrayInput = Array.from({ length: 32 }, (_, index) => `value${index}`)
178const suspendedObjectAllFields = Object.fromEntries(
179 Array.from({ length: 32 }, (_, index) => [`field${index}`, suspendedString])
180)
181
182export const array32SuspendedConcurrent4 = decodeParserCase(
183 Schema.Array(suspendedString),
184 suspendedArrayInput,
185 true,
186 { concurrency: 4 }
187)
188
189export const object32SuspendedConcurrent4 = decodeParserCase(
190 Schema.Struct(suspendedObjectAllFields),
191 suspendedObjectInput,
192 true,
193 { concurrency: 4 }
194)
195
196export const record32SuspendedConcurrent4 = decodeParserCase(
197 Schema.Record(Schema.String, suspendedString),
198 suspendedObjectInput,
199 true,
200 { concurrency: 4 }
201)
202
203const literal2 = Schema.Literals(["value0", "value1"])
204const literal100 = Schema.Literals(Array.from({ length: 100 }, (_, index) => `value${index}`))
205const homogeneousUnion100 = Schema.Union(
206 Array.from(
207 { length: 100 },
208 (_, index) => Schema.String.check(Schema.makeFilter((value) => value === `value${index}`))
209 )
210)
211const tagged2 = Schema.Union([
212 Schema.Struct({ kind: Schema.Literal("a"), value: Schema.String }),
213 Schema.Struct({ kind: Schema.Literal("b"), value: Schema.String })
214])
215const taggedWithFallback = Schema.Union([
216 Schema.Struct({ kind: Schema.Literal("a"), value: Schema.String }),
217 Schema.Struct({ value: Schema.String })
218])
219
220export const literal2ValidLast = decodeParserCase(literal2, "value1", true)
221export const literal100ValidFirst = decodeParserCase(literal100, "value0", true)
222export const homogeneousUnion100Invalid = decodeParserCase(homogeneousUnion100, "missing", false)
223export const tagged2ValidLast = decodeParserCase(tagged2, { kind: "b", value: "value" }, true)
224export const taggedWithFallbackValid = decodeParserCase(
225 taggedWithFallback,
226 { kind: "a", value: "value" },
227 true
228)
229
230const recursiveTree = Schema.Struct({
231 value: Schema.String,
232 children: Schema.Array(Schema.suspend(() => recursiveTree))
233})
234
235const makeTree = (depth) =>
236 depth === 0
237 ? { value: "leaf", children: [] }
238 : { value: `node${depth}`, children: [makeTree(depth - 1)] }
239
240export const recursiveTreeDepth16Valid = decodeCase(recursiveTree, makeTree(16), true)