@atproto/lex-schema 0.0.2 → 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.
Files changed (289) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/dist/core/$type.d.ts +6 -3
  3. package/dist/core/$type.d.ts.map +1 -1
  4. package/dist/core/$type.js +1 -0
  5. package/dist/core/$type.js.map +1 -1
  6. package/dist/core/record-key.d.ts +3 -3
  7. package/dist/core/record-key.d.ts.map +1 -1
  8. package/dist/core/record-key.js +12 -6
  9. package/dist/core/record-key.js.map +1 -1
  10. package/dist/core/result.d.ts.map +1 -1
  11. package/dist/core/result.js +6 -0
  12. package/dist/core/result.js.map +1 -1
  13. package/dist/core/string-format.d.ts +30 -27
  14. package/dist/core/string-format.d.ts.map +1 -1
  15. package/dist/core/string-format.js +56 -42
  16. package/dist/core/string-format.js.map +1 -1
  17. package/dist/core/types.d.ts +9 -1
  18. package/dist/core/types.d.ts.map +1 -1
  19. package/dist/core/types.js.map +1 -1
  20. package/dist/external.d.ts +31 -28
  21. package/dist/external.d.ts.map +1 -1
  22. package/dist/external.js +33 -17
  23. package/dist/external.js.map +1 -1
  24. package/dist/schema/_parameters.d.ts +2 -2
  25. package/dist/schema/_parameters.d.ts.map +1 -1
  26. package/dist/schema/array.d.ts +5 -6
  27. package/dist/schema/array.d.ts.map +1 -1
  28. package/dist/schema/array.js +5 -6
  29. package/dist/schema/array.js.map +1 -1
  30. package/dist/schema/blob.d.ts +2 -3
  31. package/dist/schema/blob.d.ts.map +1 -1
  32. package/dist/schema/blob.js +1 -2
  33. package/dist/schema/blob.js.map +1 -1
  34. package/dist/schema/boolean.d.ts +4 -5
  35. package/dist/schema/boolean.d.ts.map +1 -1
  36. package/dist/schema/boolean.js +2 -3
  37. package/dist/schema/boolean.js.map +1 -1
  38. package/dist/schema/bytes.d.ts +3 -4
  39. package/dist/schema/bytes.d.ts.map +1 -1
  40. package/dist/schema/bytes.js +2 -3
  41. package/dist/schema/bytes.js.map +1 -1
  42. package/dist/schema/cid.d.ts +13 -6
  43. package/dist/schema/cid.d.ts.map +1 -1
  44. package/dist/schema/cid.js +2 -4
  45. package/dist/schema/cid.js.map +1 -1
  46. package/dist/schema/custom.d.ts +3 -4
  47. package/dist/schema/custom.d.ts.map +1 -1
  48. package/dist/schema/custom.js +4 -3
  49. package/dist/schema/custom.js.map +1 -1
  50. package/dist/schema/dict.d.ts +3 -3
  51. package/dist/schema/dict.d.ts.map +1 -1
  52. package/dist/schema/dict.js +1 -1
  53. package/dist/schema/dict.js.map +1 -1
  54. package/dist/schema/discriminated-union.d.ts +15 -24
  55. package/dist/schema/discriminated-union.d.ts.map +1 -1
  56. package/dist/schema/discriminated-union.js +40 -64
  57. package/dist/schema/discriminated-union.js.map +1 -1
  58. package/dist/schema/enum.d.ts +8 -4
  59. package/dist/schema/enum.d.ts.map +1 -1
  60. package/dist/schema/enum.js +5 -3
  61. package/dist/schema/enum.js.map +1 -1
  62. package/dist/schema/integer.d.ts +3 -4
  63. package/dist/schema/integer.d.ts.map +1 -1
  64. package/dist/schema/integer.js +3 -4
  65. package/dist/schema/integer.js.map +1 -1
  66. package/dist/schema/intersection.d.ts +22 -14
  67. package/dist/schema/intersection.d.ts.map +1 -1
  68. package/dist/schema/intersection.js +12 -22
  69. package/dist/schema/intersection.js.map +1 -1
  70. package/dist/schema/literal.d.ts +7 -3
  71. package/dist/schema/literal.d.ts.map +1 -1
  72. package/dist/schema/literal.js +5 -3
  73. package/dist/schema/literal.js.map +1 -1
  74. package/dist/schema/never.d.ts +2 -2
  75. package/dist/schema/never.d.ts.map +1 -1
  76. package/dist/schema/never.js +1 -1
  77. package/dist/schema/never.js.map +1 -1
  78. package/dist/schema/null.d.ts +2 -3
  79. package/dist/schema/null.d.ts.map +1 -1
  80. package/dist/schema/null.js +1 -2
  81. package/dist/schema/null.js.map +1 -1
  82. package/dist/schema/nullable.d.ts +7 -0
  83. package/dist/schema/nullable.d.ts.map +1 -0
  84. package/dist/schema/nullable.js +19 -0
  85. package/dist/schema/nullable.js.map +1 -0
  86. package/dist/schema/object.d.ts +10 -44
  87. package/dist/schema/object.d.ts.map +1 -1
  88. package/dist/schema/object.js +10 -46
  89. package/dist/schema/object.js.map +1 -1
  90. package/dist/schema/optional.d.ts +7 -0
  91. package/dist/schema/optional.d.ts.map +1 -0
  92. package/dist/schema/optional.js +25 -0
  93. package/dist/schema/optional.js.map +1 -0
  94. package/dist/schema/params.d.ts +14 -19
  95. package/dist/schema/params.d.ts.map +1 -1
  96. package/dist/schema/params.js +10 -24
  97. package/dist/schema/params.js.map +1 -1
  98. package/dist/schema/payload.d.ts +4 -4
  99. package/dist/schema/payload.d.ts.map +1 -1
  100. package/dist/schema/payload.js.map +1 -1
  101. package/dist/schema/permission-set.d.ts +6 -6
  102. package/dist/schema/permission-set.d.ts.map +1 -1
  103. package/dist/schema/permission-set.js +1 -2
  104. package/dist/schema/permission-set.js.map +1 -1
  105. package/dist/schema/permission.d.ts +0 -1
  106. package/dist/schema/permission.d.ts.map +1 -1
  107. package/dist/schema/permission.js +0 -1
  108. package/dist/schema/permission.js.map +1 -1
  109. package/dist/schema/procedure.d.ts +8 -9
  110. package/dist/schema/procedure.d.ts.map +1 -1
  111. package/dist/schema/procedure.js +0 -1
  112. package/dist/schema/procedure.js.map +1 -1
  113. package/dist/schema/query.d.ts +7 -8
  114. package/dist/schema/query.d.ts.map +1 -1
  115. package/dist/schema/query.js +0 -1
  116. package/dist/schema/query.js.map +1 -1
  117. package/dist/schema/record.d.ts +34 -28
  118. package/dist/schema/record.d.ts.map +1 -1
  119. package/dist/schema/record.js +1 -2
  120. package/dist/schema/record.js.map +1 -1
  121. package/dist/schema/ref.d.ts +2 -3
  122. package/dist/schema/ref.d.ts.map +1 -1
  123. package/dist/schema/ref.js +1 -2
  124. package/dist/schema/ref.js.map +1 -1
  125. package/dist/schema/refine.d.ts +18 -0
  126. package/dist/schema/refine.d.ts.map +1 -0
  127. package/dist/schema/refine.js +33 -0
  128. package/dist/schema/refine.js.map +1 -0
  129. package/dist/schema/regexp.d.ts +7 -0
  130. package/dist/schema/regexp.d.ts.map +1 -0
  131. package/dist/schema/regexp.js +22 -0
  132. package/dist/schema/regexp.js.map +1 -0
  133. package/dist/schema/string.d.ts +4 -8
  134. package/dist/schema/string.d.ts.map +1 -1
  135. package/dist/schema/string.js +6 -3
  136. package/dist/schema/string.js.map +1 -1
  137. package/dist/schema/subscription.d.ts +7 -6
  138. package/dist/schema/subscription.d.ts.map +1 -1
  139. package/dist/schema/subscription.js.map +1 -1
  140. package/dist/schema/token.d.ts +2 -3
  141. package/dist/schema/token.d.ts.map +1 -1
  142. package/dist/schema/token.js +1 -2
  143. package/dist/schema/token.js.map +1 -1
  144. package/dist/schema/typed-object.d.ts +29 -27
  145. package/dist/schema/typed-object.d.ts.map +1 -1
  146. package/dist/schema/typed-object.js +1 -2
  147. package/dist/schema/typed-object.js.map +1 -1
  148. package/dist/schema/typed-ref.d.ts +2 -2
  149. package/dist/schema/typed-ref.d.ts.map +1 -1
  150. package/dist/schema/typed-ref.js +1 -1
  151. package/dist/schema/typed-ref.js.map +1 -1
  152. package/dist/schema/typed-union.d.ts +3 -4
  153. package/dist/schema/typed-union.d.ts.map +1 -1
  154. package/dist/schema/typed-union.js +3 -10
  155. package/dist/schema/typed-union.js.map +1 -1
  156. package/dist/schema/union.d.ts +2 -2
  157. package/dist/schema/union.d.ts.map +1 -1
  158. package/dist/schema/union.js +1 -1
  159. package/dist/schema/union.js.map +1 -1
  160. package/dist/schema/unknown-object.d.ts +2 -3
  161. package/dist/schema/unknown-object.d.ts.map +1 -1
  162. package/dist/schema/unknown-object.js +1 -2
  163. package/dist/schema/unknown-object.js.map +1 -1
  164. package/dist/schema/unknown.d.ts +2 -2
  165. package/dist/schema/unknown.d.ts.map +1 -1
  166. package/dist/schema/unknown.js +1 -1
  167. package/dist/schema/unknown.js.map +1 -1
  168. package/dist/schema.d.ts +4 -0
  169. package/dist/schema.d.ts.map +1 -1
  170. package/dist/schema.js +6 -1
  171. package/dist/schema.js.map +1 -1
  172. package/dist/util/array-agg.d.ts.map +1 -1
  173. package/dist/util/array-agg.js +1 -0
  174. package/dist/util/array-agg.js.map +1 -1
  175. package/dist/util/lazy-property.d.ts +2 -0
  176. package/dist/util/lazy-property.d.ts.map +1 -0
  177. package/dist/util/lazy-property.js +14 -0
  178. package/dist/util/lazy-property.js.map +1 -0
  179. package/dist/validation/schema.d.ts +24 -0
  180. package/dist/validation/schema.d.ts.map +1 -0
  181. package/dist/validation/schema.js +57 -0
  182. package/dist/validation/schema.js.map +1 -0
  183. package/dist/validation/validation-error.d.ts +3 -3
  184. package/dist/validation/validation-error.d.ts.map +1 -1
  185. package/dist/validation/validation-error.js +32 -4
  186. package/dist/validation/validation-error.js.map +1 -1
  187. package/dist/validation/validation-issue.d.ts +32 -24
  188. package/dist/validation/validation-issue.d.ts.map +1 -1
  189. package/dist/validation/validation-issue.js +136 -92
  190. package/dist/validation/validation-issue.js.map +1 -1
  191. package/dist/validation/validator.d.ts +20 -50
  192. package/dist/validation/validator.d.ts.map +1 -1
  193. package/dist/validation/validator.js +40 -134
  194. package/dist/validation/validator.js.map +1 -1
  195. package/dist/validation.d.ts +1 -0
  196. package/dist/validation.d.ts.map +1 -1
  197. package/dist/validation.js +1 -0
  198. package/dist/validation.js.map +1 -1
  199. package/package.json +8 -4
  200. package/src/core/$type.ts +7 -4
  201. package/src/core/record-key.ts +12 -5
  202. package/src/core/result.ts +6 -0
  203. package/src/core/string-format.ts +97 -61
  204. package/src/core/types.ts +12 -6
  205. package/src/external.ts +92 -70
  206. package/src/schema/_parameters.test.ts +416 -0
  207. package/src/schema/array.test.ts +237 -0
  208. package/src/schema/array.ts +17 -11
  209. package/src/schema/blob.test.ts +506 -0
  210. package/src/schema/blob.ts +3 -5
  211. package/src/schema/boolean.test.ts +116 -0
  212. package/src/schema/boolean.ts +5 -7
  213. package/src/schema/bytes.test.ts +226 -0
  214. package/src/schema/bytes.ts +4 -6
  215. package/src/schema/cid.test.ts +155 -0
  216. package/src/schema/cid.ts +14 -8
  217. package/src/schema/custom.test.ts +413 -0
  218. package/src/schema/custom.ts +10 -8
  219. package/src/schema/dict.test.ts +198 -0
  220. package/src/schema/dict.ts +6 -8
  221. package/src/schema/discriminated-union.test.ts +675 -0
  222. package/src/schema/discriminated-union.ts +68 -95
  223. package/src/schema/enum.test.ts +396 -0
  224. package/src/schema/enum.ts +12 -5
  225. package/src/schema/integer.test.ts +312 -0
  226. package/src/schema/integer.ts +5 -7
  227. package/src/schema/intersection.test.ts +32 -0
  228. package/src/schema/intersection.ts +37 -40
  229. package/src/schema/literal.test.ts +531 -0
  230. package/src/schema/literal.ts +12 -5
  231. package/src/schema/never.test.ts +174 -0
  232. package/src/schema/never.ts +3 -10
  233. package/src/schema/null.test.ts +79 -0
  234. package/src/schema/null.ts +3 -5
  235. package/src/schema/nullable.test.ts +480 -0
  236. package/src/schema/nullable.ts +23 -0
  237. package/src/schema/object.test.ts +47 -115
  238. package/src/schema/object.ts +19 -123
  239. package/src/schema/optional.test.ts +485 -0
  240. package/src/schema/optional.ts +31 -0
  241. package/src/schema/params.test.ts +582 -0
  242. package/src/schema/params.ts +37 -55
  243. package/src/schema/payload.test.ts +345 -0
  244. package/src/schema/payload.ts +5 -5
  245. package/src/schema/permission-set.test.ts +679 -0
  246. package/src/schema/permission-set.ts +6 -8
  247. package/src/schema/permission.test.ts +536 -0
  248. package/src/schema/permission.ts +0 -2
  249. package/src/schema/procedure.test.ts +443 -0
  250. package/src/schema/procedure.ts +11 -13
  251. package/src/schema/query.test.ts +408 -0
  252. package/src/schema/query.ts +9 -11
  253. package/src/schema/record.test.ts +694 -0
  254. package/src/schema/record.ts +38 -36
  255. package/src/schema/ref.test.ts +365 -0
  256. package/src/schema/ref.ts +8 -5
  257. package/src/schema/refine.test.ts +578 -0
  258. package/src/schema/refine.ts +85 -0
  259. package/src/schema/regexp.test.ts +580 -0
  260. package/src/schema/regexp.ts +22 -0
  261. package/src/schema/string.test.ts +612 -0
  262. package/src/schema/string.ts +11 -17
  263. package/src/schema/subscription.test.ts +689 -0
  264. package/src/schema/subscription.ts +13 -8
  265. package/src/schema/token.test.ts +428 -0
  266. package/src/schema/token.ts +3 -5
  267. package/src/schema/typed-object.test.ts +612 -0
  268. package/src/schema/typed-object.ts +23 -20
  269. package/src/schema/typed-ref.test.ts +823 -0
  270. package/src/schema/typed-ref.ts +10 -5
  271. package/src/schema/typed-union.test.ts +378 -0
  272. package/src/schema/typed-union.ts +6 -15
  273. package/src/schema/union.test.ts +200 -0
  274. package/src/schema/union.ts +5 -4
  275. package/src/schema/unknown-object.test.ts +592 -0
  276. package/src/schema/unknown-object.ts +3 -5
  277. package/src/schema/unknown.test.ts +312 -0
  278. package/src/schema/unknown.ts +3 -3
  279. package/src/schema.ts +7 -1
  280. package/src/util/array-agg.ts +1 -0
  281. package/src/util/lazy-property.ts +14 -0
  282. package/src/validation/schema.ts +92 -0
  283. package/src/validation/validation-error.ts +60 -9
  284. package/src/validation/validation-issue.ts +141 -144
  285. package/src/validation/validator.ts +67 -206
  286. package/src/validation.ts +1 -0
  287. package/tsconfig.build.json +12 -0
  288. package/tsconfig.json +7 -0
  289. package/tsconfig.tests.json +9 -0
@@ -0,0 +1,675 @@
1
+ import { DiscriminatedUnionSchema } from './discriminated-union.js'
2
+ import { EnumSchema } from './enum.js'
3
+ import { IntegerSchema } from './integer.js'
4
+ import { LiteralSchema } from './literal.js'
5
+ import { ObjectSchema } from './object.js'
6
+ import { StringSchema } from './string.js'
7
+
8
+ describe('DiscriminatedUnionSchema', () => {
9
+ describe('with literal discriminators', () => {
10
+ const schema = new DiscriminatedUnionSchema('type', [
11
+ new ObjectSchema({
12
+ type: new LiteralSchema('cat'),
13
+ meow: new StringSchema({}),
14
+ }),
15
+ new ObjectSchema({
16
+ type: new LiteralSchema('dog'),
17
+ bark: new StringSchema({}),
18
+ }),
19
+ ])
20
+
21
+ it('validates first variant', () => {
22
+ const result = schema.safeParse({
23
+ type: 'cat',
24
+ meow: 'meow',
25
+ })
26
+ expect(result.success).toBe(true)
27
+ })
28
+
29
+ it('validates second variant', () => {
30
+ const result = schema.safeParse({
31
+ type: 'dog',
32
+ bark: 'woof',
33
+ })
34
+ expect(result.success).toBe(true)
35
+ })
36
+
37
+ it('rejects non-objects', () => {
38
+ const result = schema.safeParse('not an object')
39
+ expect(result.success).toBe(false)
40
+ })
41
+
42
+ it('rejects null', () => {
43
+ const result = schema.safeParse(null)
44
+ expect(result.success).toBe(false)
45
+ })
46
+
47
+ it('rejects undefined', () => {
48
+ const result = schema.safeParse(undefined)
49
+ expect(result.success).toBe(false)
50
+ })
51
+
52
+ it('rejects arrays', () => {
53
+ const result = schema.safeParse([{ type: 'cat', meow: 'meow' }])
54
+ expect(result.success).toBe(false)
55
+ })
56
+
57
+ it('rejects objects missing discriminator', () => {
58
+ const result = schema.safeParse({
59
+ meow: 'meow',
60
+ })
61
+ expect(result.success).toBe(false)
62
+ })
63
+
64
+ it('rejects objects with invalid discriminator value', () => {
65
+ const result = schema.safeParse({
66
+ type: 'bird',
67
+ chirp: 'tweet',
68
+ })
69
+ expect(result.success).toBe(false)
70
+ })
71
+
72
+ it('rejects objects with valid discriminator but invalid properties', () => {
73
+ const result = schema.safeParse({
74
+ type: 'cat',
75
+ meow: 123,
76
+ })
77
+ expect(result.success).toBe(false)
78
+ })
79
+
80
+ it('rejects objects with valid discriminator but missing required properties', () => {
81
+ const result = schema.safeParse({
82
+ type: 'cat',
83
+ })
84
+ expect(result.success).toBe(false)
85
+ })
86
+
87
+ it('allows extra properties', () => {
88
+ const result = schema.safeParse({
89
+ type: 'cat',
90
+ meow: 'meow',
91
+ extra: 'property',
92
+ })
93
+ expect(result.success).toBe(true)
94
+ })
95
+ })
96
+
97
+ describe('with enum discriminators', () => {
98
+ const schema = new DiscriminatedUnionSchema('status', [
99
+ new ObjectSchema({
100
+ status: new EnumSchema(['pending', 'processing']),
101
+ progress: new IntegerSchema({}),
102
+ }),
103
+ new ObjectSchema({
104
+ status: new EnumSchema(['complete', 'failed']),
105
+ result: new StringSchema({}),
106
+ }),
107
+ ])
108
+
109
+ it('validates first variant with first enum value', () => {
110
+ const result = schema.safeParse({
111
+ status: 'pending',
112
+ progress: 0,
113
+ })
114
+ expect(result.success).toBe(true)
115
+ })
116
+
117
+ it('validates first variant with second enum value', () => {
118
+ const result = schema.safeParse({
119
+ status: 'processing',
120
+ progress: 50,
121
+ })
122
+ expect(result.success).toBe(true)
123
+ })
124
+
125
+ it('validates second variant with first enum value', () => {
126
+ const result = schema.safeParse({
127
+ status: 'complete',
128
+ result: 'success',
129
+ })
130
+ expect(result.success).toBe(true)
131
+ })
132
+
133
+ it('validates second variant with second enum value', () => {
134
+ const result = schema.safeParse({
135
+ status: 'failed',
136
+ result: 'error',
137
+ })
138
+ expect(result.success).toBe(true)
139
+ })
140
+
141
+ it('rejects invalid discriminator value', () => {
142
+ const result = schema.safeParse({
143
+ status: 'unknown',
144
+ progress: 0,
145
+ })
146
+ expect(result.success).toBe(false)
147
+ })
148
+
149
+ it('rejects object missing discriminator', () => {
150
+ const result = schema.safeParse({
151
+ progress: 0,
152
+ })
153
+ expect(result.success).toBe(false)
154
+ })
155
+
156
+ it('rejects object with valid discriminator but wrong properties', () => {
157
+ const result = schema.safeParse({
158
+ status: 'pending',
159
+ result: 'success',
160
+ })
161
+ expect(result.success).toBe(false)
162
+ })
163
+ })
164
+
165
+ describe('with mixed literal and enum discriminators', () => {
166
+ const schema = new DiscriminatedUnionSchema('kind', [
167
+ new ObjectSchema({
168
+ kind: new LiteralSchema('simple'),
169
+ value: new StringSchema({}),
170
+ }),
171
+ new ObjectSchema({
172
+ kind: new EnumSchema(['complex', 'advanced']),
173
+ value: new IntegerSchema({}),
174
+ }),
175
+ ])
176
+
177
+ it('validates literal discriminator variant', () => {
178
+ const result = schema.safeParse({
179
+ kind: 'simple',
180
+ value: 'text',
181
+ })
182
+ expect(result.success).toBe(true)
183
+ })
184
+
185
+ it('validates enum discriminator variant with first value', () => {
186
+ const result = schema.safeParse({
187
+ kind: 'complex',
188
+ value: 42,
189
+ })
190
+ expect(result.success).toBe(true)
191
+ })
192
+
193
+ it('validates enum discriminator variant with second value', () => {
194
+ const result = schema.safeParse({
195
+ kind: 'advanced',
196
+ value: 100,
197
+ })
198
+ expect(result.success).toBe(true)
199
+ })
200
+
201
+ it('rejects invalid discriminator value', () => {
202
+ const result = schema.safeParse({
203
+ kind: 'unknown',
204
+ value: 'test',
205
+ })
206
+ expect(result.success).toBe(false)
207
+ })
208
+ })
209
+
210
+ describe('with single variant', () => {
211
+ const schema = new DiscriminatedUnionSchema('type', [
212
+ new ObjectSchema({
213
+ type: new LiteralSchema('only'),
214
+ value: new StringSchema({}),
215
+ }),
216
+ ])
217
+
218
+ it('validates the single variant', () => {
219
+ const result = schema.safeParse({
220
+ type: 'only',
221
+ value: 'test',
222
+ })
223
+ expect(result.success).toBe(true)
224
+ })
225
+
226
+ it('rejects other discriminator values', () => {
227
+ const result = schema.safeParse({
228
+ type: 'other',
229
+ value: 'test',
230
+ })
231
+ expect(result.success).toBe(false)
232
+ })
233
+ })
234
+
235
+ describe('with three variants', () => {
236
+ const schema = new DiscriminatedUnionSchema('shape', [
237
+ new ObjectSchema({
238
+ shape: new LiteralSchema('circle'),
239
+ radius: new IntegerSchema({}),
240
+ }),
241
+ new ObjectSchema({
242
+ shape: new LiteralSchema('square'),
243
+ side: new IntegerSchema({}),
244
+ }),
245
+ new ObjectSchema({
246
+ shape: new LiteralSchema('rectangle'),
247
+ width: new IntegerSchema({}),
248
+ height: new IntegerSchema({}),
249
+ }),
250
+ ])
251
+
252
+ it('validates first variant', () => {
253
+ const result = schema.safeParse({
254
+ shape: 'circle',
255
+ radius: 10,
256
+ })
257
+ expect(result.success).toBe(true)
258
+ })
259
+
260
+ it('validates second variant', () => {
261
+ const result = schema.safeParse({
262
+ shape: 'square',
263
+ side: 5,
264
+ })
265
+ expect(result.success).toBe(true)
266
+ })
267
+
268
+ it('validates third variant', () => {
269
+ const result = schema.safeParse({
270
+ shape: 'rectangle',
271
+ width: 10,
272
+ height: 20,
273
+ })
274
+ expect(result.success).toBe(true)
275
+ })
276
+
277
+ it('rejects invalid discriminator', () => {
278
+ const result = schema.safeParse({
279
+ shape: 'triangle',
280
+ sides: 3,
281
+ })
282
+ expect(result.success).toBe(false)
283
+ })
284
+ })
285
+
286
+ describe('with number discriminators', () => {
287
+ const schema = new DiscriminatedUnionSchema('version', [
288
+ new ObjectSchema({
289
+ version: new LiteralSchema(1),
290
+ oldFormat: new StringSchema({}),
291
+ }),
292
+ new ObjectSchema({
293
+ version: new LiteralSchema(2),
294
+ newFormat: new StringSchema({}),
295
+ }),
296
+ ])
297
+
298
+ it('validates first version', () => {
299
+ const result = schema.safeParse({
300
+ version: 1,
301
+ oldFormat: 'data',
302
+ })
303
+ expect(result.success).toBe(true)
304
+ })
305
+
306
+ it('validates second version', () => {
307
+ const result = schema.safeParse({
308
+ version: 2,
309
+ newFormat: 'data',
310
+ })
311
+ expect(result.success).toBe(true)
312
+ })
313
+
314
+ it('rejects invalid version number', () => {
315
+ const result = schema.safeParse({
316
+ version: 3,
317
+ format: 'data',
318
+ })
319
+ expect(result.success).toBe(false)
320
+ })
321
+
322
+ it('rejects string version', () => {
323
+ const result = schema.safeParse({
324
+ version: '1',
325
+ oldFormat: 'data',
326
+ })
327
+ expect(result.success).toBe(false)
328
+ })
329
+ })
330
+
331
+ describe('with boolean discriminators', () => {
332
+ const schema = new DiscriminatedUnionSchema('enabled', [
333
+ new ObjectSchema({
334
+ enabled: new LiteralSchema(true),
335
+ config: new StringSchema({}),
336
+ }),
337
+ new ObjectSchema({
338
+ enabled: new LiteralSchema(false),
339
+ reason: new StringSchema({}),
340
+ }),
341
+ ])
342
+
343
+ it('validates true variant', () => {
344
+ const result = schema.safeParse({
345
+ enabled: true,
346
+ config: 'settings',
347
+ })
348
+ expect(result.success).toBe(true)
349
+ })
350
+
351
+ it('validates false variant', () => {
352
+ const result = schema.safeParse({
353
+ enabled: false,
354
+ reason: 'disabled',
355
+ })
356
+ expect(result.success).toBe(true)
357
+ })
358
+
359
+ it('rejects string boolean', () => {
360
+ const result = schema.safeParse({
361
+ enabled: 'true',
362
+ config: 'settings',
363
+ })
364
+ expect(result.success).toBe(false)
365
+ })
366
+ })
367
+
368
+ describe('with null discriminator', () => {
369
+ const schema = new DiscriminatedUnionSchema('value', [
370
+ new ObjectSchema({
371
+ value: new LiteralSchema(null),
372
+ empty: new StringSchema({}),
373
+ }),
374
+ new ObjectSchema({
375
+ value: new LiteralSchema('present'),
376
+ data: new StringSchema({}),
377
+ }),
378
+ ])
379
+
380
+ it('validates null discriminator variant', () => {
381
+ const result = schema.safeParse({
382
+ value: null,
383
+ empty: 'nothing',
384
+ })
385
+ expect(result.success).toBe(true)
386
+ })
387
+
388
+ it('validates non-null discriminator variant', () => {
389
+ const result = schema.safeParse({
390
+ value: 'present',
391
+ data: 'something',
392
+ })
393
+ expect(result.success).toBe(true)
394
+ })
395
+
396
+ it('rejects undefined discriminator', () => {
397
+ const result = schema.safeParse({
398
+ value: undefined,
399
+ empty: 'nothing',
400
+ })
401
+ expect(result.success).toBe(false)
402
+ })
403
+ })
404
+
405
+ describe('constructor validation', () => {
406
+ it('throws on overlapping literal discriminator values', () => {
407
+ expect(() => {
408
+ new DiscriminatedUnionSchema('type', [
409
+ new ObjectSchema({
410
+ type: new LiteralSchema('duplicate'),
411
+ a: new StringSchema({}),
412
+ }),
413
+ new ObjectSchema({
414
+ type: new LiteralSchema('duplicate'),
415
+ b: new StringSchema({}),
416
+ }),
417
+ ])
418
+ }).toThrow('Overlapping discriminator value: duplicate')
419
+ })
420
+
421
+ it('throws on overlapping enum discriminator values', () => {
422
+ expect(() => {
423
+ new DiscriminatedUnionSchema('status', [
424
+ new ObjectSchema({
425
+ status: new EnumSchema(['active', 'pending']),
426
+ a: new StringSchema({}),
427
+ }),
428
+ new ObjectSchema({
429
+ status: new EnumSchema(['pending', 'complete']),
430
+ b: new StringSchema({}),
431
+ }),
432
+ ])
433
+ }).toThrow('Overlapping discriminator value: pending')
434
+ })
435
+
436
+ it('throws on overlapping literal and enum discriminator values', () => {
437
+ expect(() => {
438
+ new DiscriminatedUnionSchema('kind', [
439
+ new ObjectSchema({
440
+ kind: new LiteralSchema('test'),
441
+ a: new StringSchema({}),
442
+ }),
443
+ new ObjectSchema({
444
+ kind: new EnumSchema(['test', 'other']),
445
+ b: new StringSchema({}),
446
+ }),
447
+ ])
448
+ }).toThrow('Overlapping discriminator value: test')
449
+ })
450
+ })
451
+
452
+ describe('edge cases', () => {
453
+ const schema = new DiscriminatedUnionSchema('type', [
454
+ new ObjectSchema({
455
+ type: new LiteralSchema('a'),
456
+ value: new StringSchema({}),
457
+ }),
458
+ new ObjectSchema({
459
+ type: new LiteralSchema('b'),
460
+ value: new IntegerSchema({}),
461
+ }),
462
+ ])
463
+
464
+ it('rejects empty object', () => {
465
+ const result = schema.safeParse({})
466
+ expect(result.success).toBe(false)
467
+ })
468
+
469
+ it('rejects object with null discriminator', () => {
470
+ const result = schema.safeParse({
471
+ type: null,
472
+ value: 'test',
473
+ })
474
+ expect(result.success).toBe(false)
475
+ })
476
+
477
+ it('rejects object with undefined discriminator', () => {
478
+ const result = schema.safeParse({
479
+ type: undefined,
480
+ value: 'test',
481
+ })
482
+ expect(result.success).toBe(false)
483
+ })
484
+
485
+ it('rejects primitive values', () => {
486
+ expect(schema.safeParse(42).success).toBe(false)
487
+ expect(schema.safeParse('string').success).toBe(false)
488
+ expect(schema.safeParse(true).success).toBe(false)
489
+ })
490
+
491
+ it('handles empty string discriminator', () => {
492
+ const emptySchema = new DiscriminatedUnionSchema('key', [
493
+ new ObjectSchema({
494
+ key: new LiteralSchema(''),
495
+ value: new StringSchema({}),
496
+ }),
497
+ ])
498
+
499
+ const result = emptySchema.safeParse({
500
+ key: '',
501
+ value: 'test',
502
+ })
503
+ expect(result.success).toBe(true)
504
+ })
505
+
506
+ it('handles zero discriminator', () => {
507
+ const zeroSchema = new DiscriminatedUnionSchema('count', [
508
+ new ObjectSchema({
509
+ count: new LiteralSchema(0),
510
+ value: new StringSchema({}),
511
+ }),
512
+ ])
513
+
514
+ const result = zeroSchema.safeParse({
515
+ count: 0,
516
+ value: 'test',
517
+ })
518
+ expect(result.success).toBe(true)
519
+ })
520
+
521
+ it('handles false discriminator', () => {
522
+ const falseSchema = new DiscriminatedUnionSchema('flag', [
523
+ new ObjectSchema({
524
+ flag: new LiteralSchema(false),
525
+ value: new StringSchema({}),
526
+ }),
527
+ ])
528
+
529
+ const result = falseSchema.safeParse({
530
+ flag: false,
531
+ value: 'test',
532
+ })
533
+ expect(result.success).toBe(true)
534
+ })
535
+
536
+ it('rejects class instances', () => {
537
+ class CustomClass {
538
+ type = 'a'
539
+ value = 'test'
540
+ }
541
+ const result = schema.safeParse(new CustomClass())
542
+ expect(result.success).toBe(false)
543
+ })
544
+
545
+ it('handles discriminator with special characters', () => {
546
+ const specialSchema = new DiscriminatedUnionSchema('$type', [
547
+ new ObjectSchema({
548
+ $type: new LiteralSchema('test'),
549
+ value: new StringSchema({}),
550
+ }),
551
+ ])
552
+
553
+ const result = specialSchema.safeParse({
554
+ $type: 'test',
555
+ value: 'data',
556
+ })
557
+ expect(result.success).toBe(true)
558
+ })
559
+
560
+ it('handles objects with prototype properties', () => {
561
+ const obj = Object.create({ type: 'a' })
562
+ obj.value = 'test'
563
+ // Should fail because discriminator is not an own property
564
+ const result = schema.safeParse(obj)
565
+ expect(result.success).toBe(false)
566
+ })
567
+
568
+ it('validates object with discriminator as own property', () => {
569
+ const obj = Object.create(null)
570
+ obj.type = 'a'
571
+ obj.value = 'test'
572
+ const result = schema.safeParse(obj)
573
+ expect(result.success).toBe(true)
574
+ })
575
+ })
576
+
577
+ describe('complex nested structures', () => {
578
+ const schema = new DiscriminatedUnionSchema('type', [
579
+ new ObjectSchema({
580
+ type: new LiteralSchema('user'),
581
+ name: new StringSchema({}),
582
+ age: new IntegerSchema({}),
583
+ }),
584
+ new ObjectSchema({
585
+ type: new LiteralSchema('post'),
586
+ title: new StringSchema({}),
587
+ content: new StringSchema({}),
588
+ }),
589
+ ])
590
+
591
+ it('validates complex user object', () => {
592
+ const result = schema.safeParse({
593
+ type: 'user',
594
+ name: 'Alice',
595
+ age: 30,
596
+ })
597
+ expect(result.success).toBe(true)
598
+ })
599
+
600
+ it('validates complex post object', () => {
601
+ const result = schema.safeParse({
602
+ type: 'post',
603
+ title: 'Hello World',
604
+ content: 'This is a test post',
605
+ })
606
+ expect(result.success).toBe(true)
607
+ })
608
+
609
+ it('rejects user with missing age', () => {
610
+ const result = schema.safeParse({
611
+ type: 'user',
612
+ name: 'Alice',
613
+ })
614
+ expect(result.success).toBe(false)
615
+ })
616
+
617
+ it('rejects post with invalid content type', () => {
618
+ const result = schema.safeParse({
619
+ type: 'post',
620
+ title: 'Hello World',
621
+ content: 123,
622
+ })
623
+ expect(result.success).toBe(false)
624
+ })
625
+
626
+ it('rejects mixed properties from different variants', () => {
627
+ const result = schema.safeParse({
628
+ type: 'user',
629
+ name: 'Alice',
630
+ title: 'Hello World',
631
+ })
632
+ expect(result.success).toBe(false)
633
+ })
634
+ })
635
+
636
+ describe('discriminator key variations', () => {
637
+ it('works with different discriminator key names', () => {
638
+ const kindSchema = new DiscriminatedUnionSchema('kind', [
639
+ new ObjectSchema({
640
+ kind: new LiteralSchema('a'),
641
+ value: new StringSchema({}),
642
+ }),
643
+ ])
644
+
645
+ const tagSchema = new DiscriminatedUnionSchema('tag', [
646
+ new ObjectSchema({
647
+ tag: new LiteralSchema('a'),
648
+ value: new StringSchema({}),
649
+ }),
650
+ ])
651
+
652
+ expect(kindSchema.safeParse({ kind: 'a', value: 'test' }).success).toBe(
653
+ true,
654
+ )
655
+ expect(tagSchema.safeParse({ tag: 'a', value: 'test' }).success).toBe(
656
+ true,
657
+ )
658
+ })
659
+
660
+ it('rejects when discriminator key does not match schema', () => {
661
+ const schema = new DiscriminatedUnionSchema('type', [
662
+ new ObjectSchema({
663
+ type: new LiteralSchema('a'),
664
+ value: new StringSchema({}),
665
+ }),
666
+ ])
667
+
668
+ const result = schema.safeParse({
669
+ kind: 'a',
670
+ value: 'test',
671
+ })
672
+ expect(result.success).toBe(false)
673
+ })
674
+ })
675
+ })