@atproto/lex-schema 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -0
- package/dist/core/$type.d.ts +6 -3
- package/dist/core/$type.d.ts.map +1 -1
- package/dist/core/$type.js +1 -0
- package/dist/core/$type.js.map +1 -1
- package/dist/core/record-key.d.ts +3 -3
- package/dist/core/record-key.d.ts.map +1 -1
- package/dist/core/record-key.js +12 -6
- package/dist/core/record-key.js.map +1 -1
- package/dist/core/result.d.ts.map +1 -1
- package/dist/core/result.js +6 -0
- package/dist/core/result.js.map +1 -1
- package/dist/core/string-format.d.ts +30 -27
- package/dist/core/string-format.d.ts.map +1 -1
- package/dist/core/string-format.js +56 -42
- package/dist/core/string-format.js.map +1 -1
- package/dist/core/types.d.ts +9 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/external.d.ts +31 -28
- package/dist/external.d.ts.map +1 -1
- package/dist/external.js +33 -17
- package/dist/external.js.map +1 -1
- package/dist/schema/_parameters.d.ts +2 -2
- package/dist/schema/_parameters.d.ts.map +1 -1
- package/dist/schema/array.d.ts +5 -6
- package/dist/schema/array.d.ts.map +1 -1
- package/dist/schema/array.js +5 -6
- package/dist/schema/array.js.map +1 -1
- package/dist/schema/blob.d.ts +2 -3
- package/dist/schema/blob.d.ts.map +1 -1
- package/dist/schema/blob.js +1 -2
- package/dist/schema/blob.js.map +1 -1
- package/dist/schema/boolean.d.ts +4 -5
- package/dist/schema/boolean.d.ts.map +1 -1
- package/dist/schema/boolean.js +2 -3
- package/dist/schema/boolean.js.map +1 -1
- package/dist/schema/bytes.d.ts +3 -4
- package/dist/schema/bytes.d.ts.map +1 -1
- package/dist/schema/bytes.js +2 -3
- package/dist/schema/bytes.js.map +1 -1
- package/dist/schema/cid.d.ts +13 -6
- package/dist/schema/cid.d.ts.map +1 -1
- package/dist/schema/cid.js +2 -4
- package/dist/schema/cid.js.map +1 -1
- package/dist/schema/custom.d.ts +3 -4
- package/dist/schema/custom.d.ts.map +1 -1
- package/dist/schema/custom.js +4 -3
- package/dist/schema/custom.js.map +1 -1
- package/dist/schema/dict.d.ts +3 -3
- package/dist/schema/dict.d.ts.map +1 -1
- package/dist/schema/dict.js +1 -1
- package/dist/schema/dict.js.map +1 -1
- package/dist/schema/discriminated-union.d.ts +15 -24
- package/dist/schema/discriminated-union.d.ts.map +1 -1
- package/dist/schema/discriminated-union.js +40 -64
- package/dist/schema/discriminated-union.js.map +1 -1
- package/dist/schema/enum.d.ts +8 -4
- package/dist/schema/enum.d.ts.map +1 -1
- package/dist/schema/enum.js +5 -3
- package/dist/schema/enum.js.map +1 -1
- package/dist/schema/integer.d.ts +3 -4
- package/dist/schema/integer.d.ts.map +1 -1
- package/dist/schema/integer.js +3 -4
- package/dist/schema/integer.js.map +1 -1
- package/dist/schema/intersection.d.ts +22 -14
- package/dist/schema/intersection.d.ts.map +1 -1
- package/dist/schema/intersection.js +12 -22
- package/dist/schema/intersection.js.map +1 -1
- package/dist/schema/literal.d.ts +8 -4
- package/dist/schema/literal.d.ts.map +1 -1
- package/dist/schema/literal.js +5 -3
- package/dist/schema/literal.js.map +1 -1
- package/dist/schema/never.d.ts +2 -2
- package/dist/schema/never.d.ts.map +1 -1
- package/dist/schema/never.js +1 -1
- package/dist/schema/never.js.map +1 -1
- package/dist/schema/null.d.ts +2 -3
- package/dist/schema/null.d.ts.map +1 -1
- package/dist/schema/null.js +1 -2
- package/dist/schema/null.js.map +1 -1
- package/dist/schema/nullable.d.ts +7 -0
- package/dist/schema/nullable.d.ts.map +1 -0
- package/dist/schema/nullable.js +19 -0
- package/dist/schema/nullable.js.map +1 -0
- package/dist/schema/object.d.ts +10 -44
- package/dist/schema/object.d.ts.map +1 -1
- package/dist/schema/object.js +13 -56
- package/dist/schema/object.js.map +1 -1
- package/dist/schema/optional.d.ts +7 -0
- package/dist/schema/optional.d.ts.map +1 -0
- package/dist/schema/optional.js +25 -0
- package/dist/schema/optional.js.map +1 -0
- package/dist/schema/params.d.ts +14 -19
- package/dist/schema/params.d.ts.map +1 -1
- package/dist/schema/params.js +10 -24
- package/dist/schema/params.js.map +1 -1
- package/dist/schema/payload.d.ts +4 -4
- package/dist/schema/payload.d.ts.map +1 -1
- package/dist/schema/payload.js.map +1 -1
- package/dist/schema/permission-set.d.ts +6 -6
- package/dist/schema/permission-set.d.ts.map +1 -1
- package/dist/schema/permission-set.js +1 -2
- package/dist/schema/permission-set.js.map +1 -1
- package/dist/schema/permission.d.ts +0 -1
- package/dist/schema/permission.d.ts.map +1 -1
- package/dist/schema/permission.js +0 -1
- package/dist/schema/permission.js.map +1 -1
- package/dist/schema/procedure.d.ts +8 -9
- package/dist/schema/procedure.d.ts.map +1 -1
- package/dist/schema/procedure.js +0 -1
- package/dist/schema/procedure.js.map +1 -1
- package/dist/schema/query.d.ts +7 -8
- package/dist/schema/query.d.ts.map +1 -1
- package/dist/schema/query.js +0 -1
- package/dist/schema/query.js.map +1 -1
- package/dist/schema/record.d.ts +34 -28
- package/dist/schema/record.d.ts.map +1 -1
- package/dist/schema/record.js +1 -2
- package/dist/schema/record.js.map +1 -1
- package/dist/schema/ref.d.ts +2 -3
- package/dist/schema/ref.d.ts.map +1 -1
- package/dist/schema/ref.js +1 -2
- package/dist/schema/ref.js.map +1 -1
- package/dist/schema/refine.d.ts +18 -0
- package/dist/schema/refine.d.ts.map +1 -0
- package/dist/schema/refine.js +33 -0
- package/dist/schema/refine.js.map +1 -0
- package/dist/schema/regexp.d.ts +7 -0
- package/dist/schema/regexp.d.ts.map +1 -0
- package/dist/schema/regexp.js +22 -0
- package/dist/schema/regexp.js.map +1 -0
- package/dist/schema/string.d.ts +4 -8
- package/dist/schema/string.d.ts.map +1 -1
- package/dist/schema/string.js +6 -3
- package/dist/schema/string.js.map +1 -1
- package/dist/schema/subscription.d.ts +7 -6
- package/dist/schema/subscription.d.ts.map +1 -1
- package/dist/schema/subscription.js.map +1 -1
- package/dist/schema/token.d.ts +2 -3
- package/dist/schema/token.d.ts.map +1 -1
- package/dist/schema/token.js +1 -2
- package/dist/schema/token.js.map +1 -1
- package/dist/schema/typed-object.d.ts +29 -27
- package/dist/schema/typed-object.d.ts.map +1 -1
- package/dist/schema/typed-object.js +1 -2
- package/dist/schema/typed-object.js.map +1 -1
- package/dist/schema/typed-ref.d.ts +2 -2
- package/dist/schema/typed-ref.d.ts.map +1 -1
- package/dist/schema/typed-ref.js +1 -1
- package/dist/schema/typed-ref.js.map +1 -1
- package/dist/schema/typed-union.d.ts +3 -4
- package/dist/schema/typed-union.d.ts.map +1 -1
- package/dist/schema/typed-union.js +3 -10
- package/dist/schema/typed-union.js.map +1 -1
- package/dist/schema/union.d.ts +2 -2
- package/dist/schema/union.d.ts.map +1 -1
- package/dist/schema/union.js +1 -1
- package/dist/schema/union.js.map +1 -1
- package/dist/schema/unknown-object.d.ts +2 -3
- package/dist/schema/unknown-object.d.ts.map +1 -1
- package/dist/schema/unknown-object.js +1 -2
- package/dist/schema/unknown-object.js.map +1 -1
- package/dist/schema/unknown.d.ts +2 -2
- package/dist/schema/unknown.d.ts.map +1 -1
- package/dist/schema/unknown.js +1 -1
- package/dist/schema/unknown.js.map +1 -1
- package/dist/schema.d.ts +4 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +6 -1
- package/dist/schema.js.map +1 -1
- package/dist/util/array-agg.d.ts.map +1 -1
- package/dist/util/array-agg.js +1 -0
- package/dist/util/array-agg.js.map +1 -1
- package/dist/util/lazy-property.d.ts +2 -0
- package/dist/util/lazy-property.d.ts.map +1 -0
- package/dist/util/lazy-property.js +14 -0
- package/dist/util/lazy-property.js.map +1 -0
- package/dist/validation/schema.d.ts +24 -0
- package/dist/validation/schema.d.ts.map +1 -0
- package/dist/validation/schema.js +57 -0
- package/dist/validation/schema.js.map +1 -0
- package/dist/validation/validation-error.d.ts +3 -3
- package/dist/validation/validation-error.d.ts.map +1 -1
- package/dist/validation/validation-error.js +32 -4
- package/dist/validation/validation-error.js.map +1 -1
- package/dist/validation/validation-issue.d.ts +32 -24
- package/dist/validation/validation-issue.d.ts.map +1 -1
- package/dist/validation/validation-issue.js +136 -92
- package/dist/validation/validation-issue.js.map +1 -1
- package/dist/validation/validator.d.ts +20 -50
- package/dist/validation/validator.d.ts.map +1 -1
- package/dist/validation/validator.js +40 -134
- package/dist/validation/validator.js.map +1 -1
- package/dist/validation.d.ts +1 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +1 -0
- package/dist/validation.js.map +1 -1
- package/package.json +8 -4
- package/src/core/$type.ts +7 -4
- package/src/core/record-key.ts +12 -5
- package/src/core/result.ts +6 -0
- package/src/core/string-format.ts +97 -61
- package/src/core/types.ts +12 -6
- package/src/external.ts +92 -70
- package/src/schema/_parameters.test.ts +416 -0
- package/src/schema/array.test.ts +237 -0
- package/src/schema/array.ts +17 -11
- package/src/schema/blob.test.ts +506 -0
- package/src/schema/blob.ts +3 -5
- package/src/schema/boolean.test.ts +116 -0
- package/src/schema/boolean.ts +5 -7
- package/src/schema/bytes.test.ts +226 -0
- package/src/schema/bytes.ts +4 -6
- package/src/schema/cid.test.ts +155 -0
- package/src/schema/cid.ts +14 -8
- package/src/schema/custom.test.ts +413 -0
- package/src/schema/custom.ts +10 -8
- package/src/schema/dict.test.ts +198 -0
- package/src/schema/dict.ts +6 -8
- package/src/schema/discriminated-union.test.ts +675 -0
- package/src/schema/discriminated-union.ts +68 -95
- package/src/schema/enum.test.ts +396 -0
- package/src/schema/enum.ts +12 -5
- package/src/schema/integer.test.ts +312 -0
- package/src/schema/integer.ts +5 -7
- package/src/schema/intersection.test.ts +32 -0
- package/src/schema/intersection.ts +37 -40
- package/src/schema/literal.test.ts +531 -0
- package/src/schema/literal.ts +12 -5
- package/src/schema/never.test.ts +174 -0
- package/src/schema/never.ts +3 -10
- package/src/schema/null.test.ts +79 -0
- package/src/schema/null.ts +3 -5
- package/src/schema/nullable.test.ts +480 -0
- package/src/schema/nullable.ts +23 -0
- package/src/schema/object.test.ts +47 -115
- package/src/schema/object.ts +23 -134
- package/src/schema/optional.test.ts +485 -0
- package/src/schema/optional.ts +31 -0
- package/src/schema/params.test.ts +582 -0
- package/src/schema/params.ts +37 -55
- package/src/schema/payload.test.ts +345 -0
- package/src/schema/payload.ts +5 -5
- package/src/schema/permission-set.test.ts +679 -0
- package/src/schema/permission-set.ts +6 -8
- package/src/schema/permission.test.ts +536 -0
- package/src/schema/permission.ts +0 -2
- package/src/schema/procedure.test.ts +443 -0
- package/src/schema/procedure.ts +11 -13
- package/src/schema/query.test.ts +408 -0
- package/src/schema/query.ts +9 -11
- package/src/schema/record.test.ts +694 -0
- package/src/schema/record.ts +38 -36
- package/src/schema/ref.test.ts +365 -0
- package/src/schema/ref.ts +8 -5
- package/src/schema/refine.test.ts +578 -0
- package/src/schema/refine.ts +85 -0
- package/src/schema/regexp.test.ts +580 -0
- package/src/schema/regexp.ts +22 -0
- package/src/schema/string.test.ts +612 -0
- package/src/schema/string.ts +11 -17
- package/src/schema/subscription.test.ts +689 -0
- package/src/schema/subscription.ts +13 -8
- package/src/schema/token.test.ts +428 -0
- package/src/schema/token.ts +3 -5
- package/src/schema/typed-object.test.ts +612 -0
- package/src/schema/typed-object.ts +23 -20
- package/src/schema/typed-ref.test.ts +823 -0
- package/src/schema/typed-ref.ts +10 -5
- package/src/schema/typed-union.test.ts +378 -0
- package/src/schema/typed-union.ts +6 -15
- package/src/schema/union.test.ts +200 -0
- package/src/schema/union.ts +5 -4
- package/src/schema/unknown-object.test.ts +592 -0
- package/src/schema/unknown-object.ts +3 -5
- package/src/schema/unknown.test.ts +312 -0
- package/src/schema/unknown.ts +3 -3
- package/src/schema.ts +7 -1
- package/src/util/array-agg.ts +1 -0
- package/src/util/lazy-property.ts +14 -0
- package/src/validation/schema.ts +92 -0
- package/src/validation/validation-error.ts +60 -9
- package/src/validation/validation-issue.ts +141 -144
- package/src/validation/validator.ts +67 -206
- package/src/validation.ts +1 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +7 -0
- package/tsconfig.tests.json +9 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
import { EnumSchema } from './enum.js'
|
|
2
|
+
import { IntegerSchema } from './integer.js'
|
|
3
|
+
import { NullableSchema } from './nullable.js'
|
|
4
|
+
import { ObjectSchema } from './object.js'
|
|
5
|
+
import { StringSchema } from './string.js'
|
|
6
|
+
|
|
7
|
+
describe('NullableSchema', () => {
|
|
8
|
+
describe('with StringSchema', () => {
|
|
9
|
+
const schema = new NullableSchema(new StringSchema({}))
|
|
10
|
+
|
|
11
|
+
it('validates null', () => {
|
|
12
|
+
const result = schema.safeParse(null)
|
|
13
|
+
expect(result.success).toBe(true)
|
|
14
|
+
if (result.success) {
|
|
15
|
+
expect(result.value).toBe(null)
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('validates valid string values', () => {
|
|
20
|
+
const result = schema.safeParse('hello')
|
|
21
|
+
expect(result.success).toBe(true)
|
|
22
|
+
if (result.success) {
|
|
23
|
+
expect(result.value).toBe('hello')
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('validates empty strings', () => {
|
|
28
|
+
const result = schema.safeParse('')
|
|
29
|
+
expect(result.success).toBe(true)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('rejects undefined', () => {
|
|
33
|
+
const result = schema.safeParse(undefined)
|
|
34
|
+
expect(result.success).toBe(false)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('rejects numbers', () => {
|
|
38
|
+
const result = schema.safeParse(123)
|
|
39
|
+
expect(result.success).toBe(false)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('rejects booleans', () => {
|
|
43
|
+
const result = schema.safeParse(true)
|
|
44
|
+
expect(result.success).toBe(false)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('rejects objects', () => {
|
|
48
|
+
const result = schema.safeParse({ value: 'test' })
|
|
49
|
+
expect(result.success).toBe(false)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('rejects arrays', () => {
|
|
53
|
+
const result = schema.safeParse(['hello'])
|
|
54
|
+
expect(result.success).toBe(false)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('with IntegerSchema', () => {
|
|
59
|
+
const schema = new NullableSchema(new IntegerSchema({}))
|
|
60
|
+
|
|
61
|
+
it('validates null', () => {
|
|
62
|
+
const result = schema.safeParse(null)
|
|
63
|
+
expect(result.success).toBe(true)
|
|
64
|
+
if (result.success) {
|
|
65
|
+
expect(result.value).toBe(null)
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('validates valid integer values', () => {
|
|
70
|
+
const result = schema.safeParse(42)
|
|
71
|
+
expect(result.success).toBe(true)
|
|
72
|
+
if (result.success) {
|
|
73
|
+
expect(result.value).toBe(42)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('validates zero', () => {
|
|
78
|
+
const result = schema.safeParse(0)
|
|
79
|
+
expect(result.success).toBe(true)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('validates negative integers', () => {
|
|
83
|
+
const result = schema.safeParse(-42)
|
|
84
|
+
expect(result.success).toBe(true)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('rejects floats', () => {
|
|
88
|
+
const result = schema.safeParse(3.14)
|
|
89
|
+
expect(result.success).toBe(false)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('rejects strings', () => {
|
|
93
|
+
const result = schema.safeParse('42')
|
|
94
|
+
expect(result.success).toBe(false)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('rejects undefined', () => {
|
|
98
|
+
const result = schema.safeParse(undefined)
|
|
99
|
+
expect(result.success).toBe(false)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('with EnumSchema', () => {
|
|
104
|
+
const schema = new NullableSchema(new EnumSchema(['red', 'green', 'blue']))
|
|
105
|
+
|
|
106
|
+
it('validates null', () => {
|
|
107
|
+
const result = schema.safeParse(null)
|
|
108
|
+
expect(result.success).toBe(true)
|
|
109
|
+
if (result.success) {
|
|
110
|
+
expect(result.value).toBe(null)
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('validates valid enum values', () => {
|
|
115
|
+
expect(schema.safeParse('red').success).toBe(true)
|
|
116
|
+
expect(schema.safeParse('green').success).toBe(true)
|
|
117
|
+
expect(schema.safeParse('blue').success).toBe(true)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('rejects invalid enum values', () => {
|
|
121
|
+
const result = schema.safeParse('yellow')
|
|
122
|
+
expect(result.success).toBe(false)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('rejects undefined', () => {
|
|
126
|
+
const result = schema.safeParse(undefined)
|
|
127
|
+
expect(result.success).toBe(false)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('rejects empty string when not in enum', () => {
|
|
131
|
+
const result = schema.safeParse('')
|
|
132
|
+
expect(result.success).toBe(false)
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
describe('with constrained StringSchema', () => {
|
|
137
|
+
const schema = new NullableSchema(
|
|
138
|
+
new StringSchema({ minLength: 3, maxLength: 10 }),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
it('validates null', () => {
|
|
142
|
+
const result = schema.safeParse(null)
|
|
143
|
+
expect(result.success).toBe(true)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('validates strings within constraints', () => {
|
|
147
|
+
const result = schema.safeParse('hello')
|
|
148
|
+
expect(result.success).toBe(true)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('rejects strings below minimum length', () => {
|
|
152
|
+
const result = schema.safeParse('hi')
|
|
153
|
+
expect(result.success).toBe(false)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('rejects strings above maximum length', () => {
|
|
157
|
+
const result = schema.safeParse('hello world!')
|
|
158
|
+
expect(result.success).toBe(false)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('accepts strings at minimum boundary', () => {
|
|
162
|
+
const result = schema.safeParse('abc')
|
|
163
|
+
expect(result.success).toBe(true)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('accepts strings at maximum boundary', () => {
|
|
167
|
+
const result = schema.safeParse('1234567890')
|
|
168
|
+
expect(result.success).toBe(true)
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
describe('with constrained IntegerSchema', () => {
|
|
173
|
+
const schema = new NullableSchema(
|
|
174
|
+
new IntegerSchema({ minimum: 0, maximum: 100 }),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
it('validates null', () => {
|
|
178
|
+
const result = schema.safeParse(null)
|
|
179
|
+
expect(result.success).toBe(true)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('validates integers within constraints', () => {
|
|
183
|
+
const result = schema.safeParse(50)
|
|
184
|
+
expect(result.success).toBe(true)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('validates minimum value', () => {
|
|
188
|
+
const result = schema.safeParse(0)
|
|
189
|
+
expect(result.success).toBe(true)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('validates maximum value', () => {
|
|
193
|
+
const result = schema.safeParse(100)
|
|
194
|
+
expect(result.success).toBe(true)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('rejects values below minimum', () => {
|
|
198
|
+
const result = schema.safeParse(-1)
|
|
199
|
+
expect(result.success).toBe(false)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('rejects values above maximum', () => {
|
|
203
|
+
const result = schema.safeParse(101)
|
|
204
|
+
expect(result.success).toBe(false)
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
describe('with StringSchema having default value', () => {
|
|
209
|
+
const schema = new NullableSchema(new StringSchema({ default: 'default' }))
|
|
210
|
+
|
|
211
|
+
it('validates null explicitly', () => {
|
|
212
|
+
const result = schema.safeParse(null)
|
|
213
|
+
expect(result.success).toBe(true)
|
|
214
|
+
if (result.success) {
|
|
215
|
+
expect(result.value).toBe(null)
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it('uses default value when undefined is provided', () => {
|
|
220
|
+
const result = schema.safeParse(undefined)
|
|
221
|
+
expect(result.success).toBe(true)
|
|
222
|
+
if (result.success) {
|
|
223
|
+
expect(result.value).toBe('default')
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('validates explicit string values', () => {
|
|
228
|
+
const result = schema.safeParse('custom')
|
|
229
|
+
expect(result.success).toBe(true)
|
|
230
|
+
if (result.success) {
|
|
231
|
+
expect(result.value).toBe('custom')
|
|
232
|
+
}
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
describe('with ObjectSchema', () => {
|
|
237
|
+
const schema = new NullableSchema(
|
|
238
|
+
new ObjectSchema({
|
|
239
|
+
name: new StringSchema({}),
|
|
240
|
+
age: new IntegerSchema({}),
|
|
241
|
+
}),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
it('validates null', () => {
|
|
245
|
+
const result = schema.safeParse(null)
|
|
246
|
+
expect(result.success).toBe(true)
|
|
247
|
+
if (result.success) {
|
|
248
|
+
expect(result.value).toBe(null)
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('validates valid objects', () => {
|
|
253
|
+
const result = schema.safeParse({ name: 'Alice', age: 30 })
|
|
254
|
+
expect(result.success).toBe(true)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('rejects invalid objects with missing properties', () => {
|
|
258
|
+
const result = schema.safeParse({ name: 'Alice' })
|
|
259
|
+
expect(result.success).toBe(false)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it('rejects invalid objects with wrong types', () => {
|
|
263
|
+
const result = schema.safeParse({ name: 'Alice', age: 'thirty' })
|
|
264
|
+
expect(result.success).toBe(false)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('rejects empty objects', () => {
|
|
268
|
+
const result = schema.safeParse({})
|
|
269
|
+
expect(result.success).toBe(false)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('rejects primitive values', () => {
|
|
273
|
+
const result = schema.safeParse('not an object')
|
|
274
|
+
expect(result.success).toBe(false)
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
describe('nested nullable schemas', () => {
|
|
279
|
+
const schema = new NullableSchema(new NullableSchema(new StringSchema({})))
|
|
280
|
+
|
|
281
|
+
it('validates null at outer level', () => {
|
|
282
|
+
const result = schema.safeParse(null)
|
|
283
|
+
expect(result.success).toBe(true)
|
|
284
|
+
if (result.success) {
|
|
285
|
+
expect(result.value).toBe(null)
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('validates valid string values', () => {
|
|
290
|
+
const result = schema.safeParse('hello')
|
|
291
|
+
expect(result.success).toBe(true)
|
|
292
|
+
if (result.success) {
|
|
293
|
+
expect(result.value).toBe('hello')
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('rejects undefined', () => {
|
|
298
|
+
const result = schema.safeParse(undefined)
|
|
299
|
+
expect(result.success).toBe(false)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('rejects invalid types', () => {
|
|
303
|
+
const result = schema.safeParse(123)
|
|
304
|
+
expect(result.success).toBe(false)
|
|
305
|
+
})
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
describe('with StringSchema format constraints', () => {
|
|
309
|
+
const schema = new NullableSchema(new StringSchema({ format: 'uri' }))
|
|
310
|
+
|
|
311
|
+
it('validates null', () => {
|
|
312
|
+
const result = schema.safeParse(null)
|
|
313
|
+
expect(result.success).toBe(true)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
it('validates valid URIs', () => {
|
|
317
|
+
const result = schema.safeParse('https://example.com')
|
|
318
|
+
expect(result.success).toBe(true)
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
it('rejects invalid URIs', () => {
|
|
322
|
+
const result = schema.safeParse('not a uri')
|
|
323
|
+
expect(result.success).toBe(false)
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
it('rejects invalid format even with valid string', () => {
|
|
327
|
+
const result = schema.safeParse('just a string')
|
|
328
|
+
expect(result.success).toBe(false)
|
|
329
|
+
})
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
describe('edge cases', () => {
|
|
333
|
+
const stringSchema = new NullableSchema(new StringSchema({}))
|
|
334
|
+
|
|
335
|
+
it('handles null correctly without coercion', () => {
|
|
336
|
+
const result = stringSchema.safeParse(null)
|
|
337
|
+
expect(result.success).toBe(true)
|
|
338
|
+
if (result.success) {
|
|
339
|
+
expect(result.value).toBe(null)
|
|
340
|
+
expect(result.value).not.toBe(undefined)
|
|
341
|
+
expect(result.value).not.toBe('')
|
|
342
|
+
expect(result.value).not.toBe(0)
|
|
343
|
+
expect(result.value).not.toBe(false)
|
|
344
|
+
}
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('distinguishes null from falsy values', () => {
|
|
348
|
+
expect(stringSchema.safeParse(null).success).toBe(true)
|
|
349
|
+
expect(stringSchema.safeParse(undefined).success).toBe(false)
|
|
350
|
+
expect(stringSchema.safeParse('').success).toBe(true)
|
|
351
|
+
expect(stringSchema.safeParse(0).success).toBe(false)
|
|
352
|
+
expect(stringSchema.safeParse(false).success).toBe(false)
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
it('handles NaN correctly', () => {
|
|
356
|
+
const result = stringSchema.safeParse(NaN)
|
|
357
|
+
expect(result.success).toBe(false)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it('handles Symbol correctly', () => {
|
|
361
|
+
const result = stringSchema.safeParse(Symbol('test'))
|
|
362
|
+
expect(result.success).toBe(false)
|
|
363
|
+
})
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
describe('type preservation', () => {
|
|
367
|
+
it('preserves string type for valid strings', () => {
|
|
368
|
+
const schema = new NullableSchema(new StringSchema({}))
|
|
369
|
+
const result = schema.safeParse('test')
|
|
370
|
+
expect(result.success).toBe(true)
|
|
371
|
+
if (result.success) {
|
|
372
|
+
expect(typeof result.value).toBe('string')
|
|
373
|
+
expect(result.value).toBe('test')
|
|
374
|
+
}
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('preserves number type for valid integers', () => {
|
|
378
|
+
const schema = new NullableSchema(new IntegerSchema({}))
|
|
379
|
+
const result = schema.safeParse(42)
|
|
380
|
+
expect(result.success).toBe(true)
|
|
381
|
+
if (result.success) {
|
|
382
|
+
expect(typeof result.value).toBe('number')
|
|
383
|
+
expect(result.value).toBe(42)
|
|
384
|
+
}
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
it('preserves object type for valid objects', () => {
|
|
388
|
+
const schema = new NullableSchema(
|
|
389
|
+
new ObjectSchema({ key: new StringSchema({}) }),
|
|
390
|
+
)
|
|
391
|
+
const input = { key: 'value' }
|
|
392
|
+
const result = schema.safeParse(input)
|
|
393
|
+
expect(result.success).toBe(true)
|
|
394
|
+
if (result.success) {
|
|
395
|
+
expect(typeof result.value).toBe('object')
|
|
396
|
+
expect(result.value).toEqual({ key: 'value' })
|
|
397
|
+
}
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
it('preserves null type exactly', () => {
|
|
401
|
+
const schema = new NullableSchema(new StringSchema({}))
|
|
402
|
+
const result = schema.safeParse(null)
|
|
403
|
+
expect(result.success).toBe(true)
|
|
404
|
+
if (result.success) {
|
|
405
|
+
expect(result.value).toBe(null)
|
|
406
|
+
expect(result.value === null).toBe(true)
|
|
407
|
+
expect(typeof result.value).toBe('object')
|
|
408
|
+
}
|
|
409
|
+
})
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
describe('with complex wrapped schemas', () => {
|
|
413
|
+
it('validates nullable enum with default', () => {
|
|
414
|
+
const schema = new NullableSchema(
|
|
415
|
+
new EnumSchema(['option1', 'option2'], { default: 'option1' }),
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
expect(schema.safeParse(null).success).toBe(true)
|
|
419
|
+
expect(schema.safeParse('option1').success).toBe(true)
|
|
420
|
+
expect(schema.safeParse('option2').success).toBe(true)
|
|
421
|
+
expect(schema.safeParse(undefined).success).toBe(true)
|
|
422
|
+
expect(schema.safeParse('invalid').success).toBe(false)
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
it('handles nullable schema with grapheme constraints', () => {
|
|
426
|
+
const schema = new NullableSchema(
|
|
427
|
+
new StringSchema({ minGraphemes: 2, maxGraphemes: 5 }),
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
expect(schema.safeParse(null).success).toBe(true)
|
|
431
|
+
expect(schema.safeParse('ab').success).toBe(true)
|
|
432
|
+
expect(schema.safeParse('hello').success).toBe(true)
|
|
433
|
+
expect(schema.safeParse('a').success).toBe(false)
|
|
434
|
+
expect(schema.safeParse('hello!').success).toBe(false)
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
it('handles nullable integer with negative range', () => {
|
|
438
|
+
const schema = new NullableSchema(
|
|
439
|
+
new IntegerSchema({ minimum: -100, maximum: -10 }),
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
expect(schema.safeParse(null).success).toBe(true)
|
|
443
|
+
expect(schema.safeParse(-50).success).toBe(true)
|
|
444
|
+
expect(schema.safeParse(-100).success).toBe(true)
|
|
445
|
+
expect(schema.safeParse(-10).success).toBe(true)
|
|
446
|
+
expect(schema.safeParse(0).success).toBe(false)
|
|
447
|
+
expect(schema.safeParse(-5).success).toBe(false)
|
|
448
|
+
expect(schema.safeParse(-150).success).toBe(false)
|
|
449
|
+
})
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
describe('validation error behavior', () => {
|
|
453
|
+
it('returns failure for wrapped schema validation errors', () => {
|
|
454
|
+
const schema = new NullableSchema(new IntegerSchema({ minimum: 10 }))
|
|
455
|
+
const result = schema.safeParse(5)
|
|
456
|
+
expect(result.success).toBe(false)
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
it('returns failure for type mismatches', () => {
|
|
460
|
+
const schema = new NullableSchema(new StringSchema({}))
|
|
461
|
+
const result = schema.safeParse(123)
|
|
462
|
+
expect(result.success).toBe(false)
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
it('returns success for null regardless of wrapped constraints', () => {
|
|
466
|
+
const schema = new NullableSchema(
|
|
467
|
+
new StringSchema({ minLength: 100, format: 'uri' }),
|
|
468
|
+
)
|
|
469
|
+
const result = schema.safeParse(null)
|
|
470
|
+
expect(result.success).toBe(true)
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
it('wrapped schema validation applies when value is not null', () => {
|
|
474
|
+
const schema = new NullableSchema(new StringSchema({ minLength: 5 }))
|
|
475
|
+
expect(schema.safeParse(null).success).toBe(true)
|
|
476
|
+
expect(schema.safeParse('hello').success).toBe(true)
|
|
477
|
+
expect(schema.safeParse('hi').success).toBe(false)
|
|
478
|
+
})
|
|
479
|
+
})
|
|
480
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Schema,
|
|
3
|
+
ValidationResult,
|
|
4
|
+
Validator,
|
|
5
|
+
ValidatorContext,
|
|
6
|
+
} from '../validation.js'
|
|
7
|
+
|
|
8
|
+
export class NullableSchema<V> extends Schema<V | null> {
|
|
9
|
+
constructor(readonly schema: Validator<V>) {
|
|
10
|
+
super()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
validateInContext(
|
|
14
|
+
input: unknown,
|
|
15
|
+
ctx: ValidatorContext,
|
|
16
|
+
): ValidationResult<V | null> {
|
|
17
|
+
if (input === null) {
|
|
18
|
+
return ctx.success(null)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return ctx.validate(input, this.schema)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,138 +1,70 @@
|
|
|
1
|
-
import { BooleanSchema } from './boolean.js'
|
|
2
|
-
import { DictSchema } from './dict.js'
|
|
3
1
|
import { EnumSchema } from './enum.js'
|
|
4
2
|
import { IntegerSchema } from './integer.js'
|
|
3
|
+
import { NullableSchema } from './nullable.js'
|
|
5
4
|
import { ObjectSchema } from './object.js'
|
|
5
|
+
import { OptionalSchema } from './optional.js'
|
|
6
6
|
import { StringSchema } from './string.js'
|
|
7
7
|
|
|
8
8
|
describe('ObjectSchema', () => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{
|
|
17
|
-
required: ['name'],
|
|
18
|
-
nullable: ['gender'],
|
|
19
|
-
},
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
it('validates plain objects', () => {
|
|
23
|
-
const result = schema.validate({
|
|
24
|
-
name: 'Alice',
|
|
25
|
-
age: 30,
|
|
26
|
-
gender: 'female',
|
|
27
|
-
})
|
|
28
|
-
expect(result.success).toBe(true)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('rejects non-objects', () => {
|
|
32
|
-
const result = schema.validate('not an object')
|
|
33
|
-
expect(result.success).toBe(false)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('rejects missing properties', () => {
|
|
37
|
-
const result = schema.validate({
|
|
38
|
-
age: 30,
|
|
39
|
-
gender: 'female',
|
|
40
|
-
})
|
|
41
|
-
expect(result.success).toBe(false)
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('validates optional properties', () => {
|
|
45
|
-
const result = schema.validate({
|
|
46
|
-
name: 'Alice',
|
|
47
|
-
})
|
|
48
|
-
expect(result.success).toBe(true)
|
|
49
|
-
})
|
|
9
|
+
const schema = new ObjectSchema({
|
|
10
|
+
name: new StringSchema({}),
|
|
11
|
+
age: new OptionalSchema(new IntegerSchema({})),
|
|
12
|
+
gender: new OptionalSchema(
|
|
13
|
+
new NullableSchema(new EnumSchema(['male', 'female'])),
|
|
14
|
+
),
|
|
15
|
+
})
|
|
50
16
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
expect(result.success).toBe(true)
|
|
17
|
+
it('validates plain objects', () => {
|
|
18
|
+
const result = schema.safeParse({
|
|
19
|
+
name: 'Alice',
|
|
20
|
+
age: 30,
|
|
21
|
+
gender: 'female',
|
|
57
22
|
})
|
|
23
|
+
expect(result.success).toBe(true)
|
|
24
|
+
})
|
|
58
25
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
})
|
|
64
|
-
expect(result.success).toBe(false)
|
|
65
|
-
})
|
|
26
|
+
it('rejects non-objects', () => {
|
|
27
|
+
const result = schema.safeParse('not an object')
|
|
28
|
+
expect(result.success).toBe(false)
|
|
29
|
+
})
|
|
66
30
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
extra: 'value',
|
|
72
|
-
})
|
|
73
|
-
expect(result.success).toBe(true)
|
|
31
|
+
it('rejects missing properties', () => {
|
|
32
|
+
const result = schema.safeParse({
|
|
33
|
+
age: 30,
|
|
34
|
+
gender: 'female',
|
|
74
35
|
})
|
|
36
|
+
expect(result.success).toBe(false)
|
|
75
37
|
})
|
|
76
38
|
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
id: new StringSchema({}),
|
|
81
|
-
score: new IntegerSchema({}),
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
required: ['id', 'score'],
|
|
85
|
-
unknownProperties: 'strict',
|
|
86
|
-
},
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
it('rejects extra properties in strict mode', () => {
|
|
90
|
-
const result = schema.validate({
|
|
91
|
-
id: 'item1',
|
|
92
|
-
score: 100,
|
|
93
|
-
extra: 'not allowed',
|
|
94
|
-
})
|
|
95
|
-
expect(result.success).toBe(false)
|
|
39
|
+
it('validates optional properties', () => {
|
|
40
|
+
const result = schema.safeParse({
|
|
41
|
+
name: 'Alice',
|
|
96
42
|
})
|
|
43
|
+
expect(result.success).toBe(true)
|
|
44
|
+
})
|
|
97
45
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
})
|
|
103
|
-
expect(result.success).toBe(true)
|
|
46
|
+
it('validates nullable properties', () => {
|
|
47
|
+
const result = schema.safeParse({
|
|
48
|
+
name: 'Alice',
|
|
49
|
+
gender: null,
|
|
104
50
|
})
|
|
51
|
+
expect(result.success).toBe(true)
|
|
105
52
|
})
|
|
106
53
|
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
required: ['title'],
|
|
114
|
-
unknownProperties: new DictSchema(
|
|
115
|
-
new EnumSchema(['tag1', 'tag2']),
|
|
116
|
-
new BooleanSchema({}),
|
|
117
|
-
),
|
|
118
|
-
},
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
it('validates extra properties with the provided validator', () => {
|
|
122
|
-
const result = schema.validate({
|
|
123
|
-
title: 'My Post',
|
|
124
|
-
tag1: true,
|
|
125
|
-
tag2: false,
|
|
126
|
-
})
|
|
127
|
-
expect(result.success).toBe(true)
|
|
54
|
+
it('rejects invalid property types', () => {
|
|
55
|
+
const result = schema.safeParse({
|
|
56
|
+
name: 'Alice',
|
|
57
|
+
age: 'thirty',
|
|
128
58
|
})
|
|
59
|
+
expect(result.success).toBe(false)
|
|
60
|
+
})
|
|
129
61
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
expect(result.success).toBe(false)
|
|
62
|
+
it('ignores extra properties', () => {
|
|
63
|
+
const result = schema.safeParse({
|
|
64
|
+
name: 'Alice',
|
|
65
|
+
age: 30,
|
|
66
|
+
extra: 'value',
|
|
136
67
|
})
|
|
68
|
+
expect(result.success).toBe(true)
|
|
137
69
|
})
|
|
138
70
|
})
|