| 1 | import * as BigDecimal from "effect/BigDecimal" |
| 2 | import * as DateTime from "effect/DateTime" |
| 3 | import type * as Option from "effect/Option" |
| 4 | import * as Schema from "effect/Schema" |
| 5 | import assert from "node:assert/strict" |
| 6 | |
| 7 | export interface Tree { |
| 8 | readonly label: string |
| 9 | readonly score: Option.Option<number> |
| 10 | readonly children: ReadonlyArray<Tree> |
| 11 | } |
| 12 | |
| 13 | export const makeTreeSchema = (): Schema.Codec<Tree> => { |
| 14 | const Tree: Schema.Codec<Tree> = Schema.Struct({ |
| 15 | label: Schema.String.check(Schema.isMinLength(2), Schema.isMaxLength(12)), |
| 16 | score: Schema.Option(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 100 }))), |
| 17 | children: Schema.Array(Schema.suspend(() => Tree)).check(Schema.isMaxLength(3)) |
| 18 | }) |
| 19 | return Tree |
| 20 | } |
| 21 | |
| 22 | export const makeConstrainedStringSchema = () => Schema.String.check(Schema.isMinLength(32), Schema.isMaxLength(32)) |
| 23 | |
| 24 | const optionalInt = Schema.optionalKey(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 1_000 }))) |
| 25 | |
| 26 | export const makeOptionalStructSchema = () => |
| 27 | Schema.Struct({ |
| 28 | a: optionalInt, |
| 29 | b: optionalInt, |
| 30 | c: optionalInt, |
| 31 | d: optionalInt, |
| 32 | e: optionalInt, |
| 33 | f: optionalInt, |
| 34 | g: optionalInt, |
| 35 | h: optionalInt |
| 36 | }) |
| 37 | |
| 38 | export const regularExpression = /^(?:a|[B-D]{2}|[0-9]{3}){16}$/ |
| 39 | |
| 40 | export const makeRegExpSchema = () => Schema.String.check(Schema.isPattern(regularExpression)) |
| 41 | |
| 42 | export const makeRareFilterSchema = () => |
| 43 | Schema.Int.check( |
| 44 | Schema.isBetween({ minimum: 0, maximum: 255 }), |
| 45 | Schema.makeFilter((value: number) => value % 16 === 0) |
| 46 | ) |
| 47 | |
| 48 | export const makeUniqueArraySchema = () => |
| 49 | Schema.UniqueArray(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 1_023 }))).check( |
| 50 | Schema.isMinLength(32), |
| 51 | Schema.isMaxLength(32) |
| 52 | ) |
| 53 | |
| 54 | export const makeBigDecimalSchema = () => |
| 55 | Schema.BigDecimal.check(Schema.isBetweenBigDecimal({ |
| 56 | minimum: BigDecimal.make(BigInt(1234), 3), |
| 57 | maximum: BigDecimal.make(BigInt(1236), 3), |
| 58 | exclusiveMinimum: true, |
| 59 | exclusiveMaximum: true |
| 60 | })) |
| 61 | |
| 62 | const isBetweenDateTime = Schema.makeIsBetween({ order: DateTime.Order }) |
| 63 | |
| 64 | export const makeDateTimeUtcSchema = () => |
| 65 | Schema.DateTimeUtc.check(isBetweenDateTime({ |
| 66 | minimum: DateTime.makeUnsafe(-1_000_000_000), |
| 67 | maximum: DateTime.makeUnsafe(1_000_000_000) |
| 68 | })) |
| 69 | |
| 70 | export const makeDateTimeZonedSchema = () => |
| 71 | Schema.DateTimeZoned.check(isBetweenDateTime({ |
| 72 | minimum: DateTime.makeZonedUnsafe(-1_000_000_000, { timeZone: "UTC" }), |
| 73 | maximum: DateTime.makeZonedUnsafe(1_000_000_000, { timeZone: "UTC" }) |
| 74 | })) |
| 75 | |
| 76 | const validateTree = Schema.is(makeTreeSchema()) |
| 77 | |
| 78 | const countTreeNodes = (tree: Tree): number => |
| 79 | 1 + tree.children.reduce((total, child) => total + countTreeNodes(child), 0) |
| 80 | |
| 81 | export const validateTrees = (count: number, minimumNodes: number, maximumNodes: number) => (values: unknown) => { |
| 82 | assert.ok(Array.isArray(values)) |
| 83 | assert.equal(values.length, count) |
| 84 | assert.equal(values.every(validateTree), true) |
| 85 | const nodes = values.reduce((total, tree) => total + countTreeNodes(tree), 0) |
| 86 | assert.ok(nodes >= minimumNodes && nodes <= maximumNodes) |
| 87 | } |
| 88 | |
| 89 | export const validateStrings = (count: number) => (values: unknown) => { |
| 90 | assert.ok(Array.isArray(values)) |
| 91 | assert.equal(values.length, count) |
| 92 | assert.equal(values.every((value) => typeof value === "string" && value.length === 32), true) |
| 93 | } |
| 94 | |
| 95 | export const validateRegExpValues = (count: number) => (values: unknown) => { |
| 96 | assert.ok(Array.isArray(values)) |
| 97 | assert.equal(values.length, count) |
| 98 | assert.equal( |
| 99 | values.every((value) => typeof value === "string" && regularExpression.test(value)), |
| 100 | true |
| 101 | ) |
| 102 | } |
| 103 | |
| 104 | export const validateRegExpCoverage = (values: unknown) => { |
| 105 | validateRegExpValues(64)(values) |
| 106 | assert.ok(Array.isArray(values)) |
| 107 | assert.ok(new Set(values.map((value) => value.length)).size > 1) |
| 108 | assert.equal(values.some((value) => value.includes("a")), true) |
| 109 | assert.equal(values.some((value) => /[B-D]{2}/.test(value)), true) |
| 110 | assert.equal(values.some((value) => /[0-9]{3}/.test(value)), true) |
| 111 | } |
| 112 | |
| 113 | export const validateNumbers = (count: number) => (values: unknown) => { |
| 114 | assert.ok(Array.isArray(values)) |
| 115 | assert.equal(values.length, count) |
| 116 | assert.equal(values.every((value) => typeof value === "number" && value >= 2 && value <= 4), true) |
| 117 | } |
| 118 | |
| 119 | export const validateUint8Arrays = (count: number) => (values: unknown) => { |
| 120 | assert.ok(Array.isArray(values)) |
| 121 | assert.equal(values.length, count) |
| 122 | assert.equal(values.every((value) => value instanceof Uint8Array && value.length <= 10), true) |
| 123 | const bytes = values.reduce((total, value) => total + value.length, 0) |
| 124 | assert.ok(bytes >= 500 && bytes <= 700) |
| 125 | } |
| 126 | |
| 127 | export const validateSchemaValues = (schema: Schema.Top, count: number) => { |
| 128 | const is = Schema.is(schema) |
| 129 | return (values: unknown) => { |
| 130 | assert.ok(Array.isArray(values)) |
| 131 | assert.equal(values.length, count) |
| 132 | assert.equal(values.every(is), true) |
| 133 | } |
| 134 | } |