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