1import * as BigDecimal from "effect/BigDecimal"
2import * as DateTime from "effect/DateTime"
3import type * as Option from "effect/Option"
4import * as Schema from "effect/Schema"
5import assert from "node:assert/strict"
6
7export interface Tree {
8 readonly label: string
9 readonly score: Option.Option<number>
10 readonly children: ReadonlyArray<Tree>
11}
12
13export 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
22export const makeConstrainedStringSchema = () => Schema.String.check(Schema.isMinLength(32), Schema.isMaxLength(32))
23
24const optionalInt = Schema.optionalKey(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 1_000 })))
25
26export 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
38export const regularExpression = /^(?:a|[B-D]{2}|[0-9]{3}){16}$/
39
40export const makeRegExpSchema = () => Schema.String.check(Schema.isPattern(regularExpression))
41
42export const makeRareFilterSchema = () =>
43 Schema.Int.check(
44 Schema.isBetween({ minimum: 0, maximum: 255 }),
45 Schema.makeFilter((value: number) => value % 16 === 0)
46 )
47
48export 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
54export 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
62const isBetweenDateTime = Schema.makeIsBetween({ order: DateTime.Order })
63
64export 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
70export 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
76const validateTree = Schema.is(makeTreeSchema())
77
78const countTreeNodes = (tree: Tree): number =>
79 1 + tree.children.reduce((total, child) => total + countTreeNodes(child), 0)
80
81export 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
89export 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
95export 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
104export 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
113export 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
119export 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
127export 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}