@cleverbrush/schema 0.0.16 → 1.0.0-beta.0

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 (75) hide show
  1. package/README.md +224 -149
  2. package/dist/builders/AliasSchemaBuilder.d.ts +14 -0
  3. package/dist/builders/AliasSchemaBuilder.js +91 -0
  4. package/dist/builders/ArraySchemaBuilder.d.ts +15 -0
  5. package/dist/builders/ArraySchemaBuilder.js +141 -0
  6. package/dist/builders/BooleanSchemaBuilder.d.ts +31 -0
  7. package/dist/builders/BooleanSchemaBuilder.js +90 -0
  8. package/dist/builders/FunctionSchemaBuilder.d.ts +25 -0
  9. package/dist/builders/FunctionSchemaBuilder.js +66 -0
  10. package/dist/builders/NumberSchemaBuilder.d.ts +61 -0
  11. package/dist/builders/NumberSchemaBuilder.js +206 -0
  12. package/dist/builders/ObjectSchemaBuilder.d.ts +83 -0
  13. package/dist/builders/ObjectSchemaBuilder.js +344 -0
  14. package/dist/builders/SchemaBuilder.d.ts +36 -0
  15. package/dist/builders/SchemaBuilder.js +88 -0
  16. package/dist/builders/StringSchemaBuilder.d.ts +14 -0
  17. package/dist/builders/StringSchemaBuilder.js +140 -0
  18. package/dist/builders/UnionSchemaBuilder.d.ts +10 -0
  19. package/dist/builders/UnionSchemaBuilder.js +100 -0
  20. package/dist/defaultSchemas.d.ts +4 -0
  21. package/dist/defaultSchemas.js +45 -0
  22. package/dist/index.d.ts +38 -90
  23. package/dist/index.js +30 -4
  24. package/dist/schema.d.ts +242 -0
  25. package/dist/schema.js +2 -0
  26. package/dist/schemaRegistry.d.ts +54 -0
  27. package/dist/schemaRegistry.js +301 -0
  28. package/dist/validators/validateArray.d.ts +3 -2
  29. package/dist/validators/validateBoolean.d.ts +2 -2
  30. package/dist/validators/validateBoolean.js +1 -4
  31. package/dist/validators/validateFunction.d.ts +2 -0
  32. package/dist/validators/validateFunction.js +21 -0
  33. package/dist/validators/validateNumber.d.ts +2 -2
  34. package/dist/validators/validateNumber.js +1 -1
  35. package/dist/validators/validateObject.d.ts +3 -2
  36. package/dist/validators/validateObject.js +33 -11
  37. package/dist/validators/validateString.d.ts +2 -2
  38. package/dist/validators/validateString.js +1 -1
  39. package/dist/validators/validateUnion.d.ts +3 -0
  40. package/dist/validators/validateUnion.js +30 -0
  41. package/package.json +3 -3
  42. package/src/builders/AliasSchemaBuilder.test.ts +124 -0
  43. package/src/builders/AliasSchemaBuilder.ts +250 -0
  44. package/src/builders/ArraySchemaBuilder.test.ts +270 -0
  45. package/src/builders/ArraySchemaBuilder.ts +381 -0
  46. package/src/builders/BooleanSchemaBuilder.test.ts +196 -0
  47. package/src/builders/BooleanSchemaBuilder.ts +155 -0
  48. package/src/builders/FunctionSchemaBuilder.test.ts +134 -0
  49. package/src/builders/FunctionSchemaBuilder.ts +109 -0
  50. package/src/builders/NumberSchemaBuilder.test.ts +493 -0
  51. package/src/builders/NumberSchemaBuilder.ts +789 -0
  52. package/src/builders/ObjectSchemaBuilder.test.ts +657 -0
  53. package/src/builders/ObjectSchemaBuilder.ts +794 -0
  54. package/src/builders/SchemaBuilder.test.ts +73 -0
  55. package/src/builders/SchemaBuilder.ts +135 -0
  56. package/src/builders/StringSchemaBuilder.test.ts +318 -0
  57. package/src/builders/StringSchemaBuilder.ts +392 -0
  58. package/src/builders/UnionSchemaBuilder.test.ts +162 -0
  59. package/src/builders/UnionSchemaBuilder.ts +159 -0
  60. package/src/defaultSchemas.ts +44 -0
  61. package/src/index.ts +70 -190
  62. package/src/schema.ts +922 -0
  63. package/src/schemaRegistry.builders.test.ts +1438 -0
  64. package/src/{schemaValidator.test.ts → schemaRegistry.test.ts} +561 -185
  65. package/src/schemaRegistry.ts +532 -0
  66. package/src/validators/validateArray.ts +4 -7
  67. package/src/validators/validateBoolean.ts +2 -11
  68. package/src/validators/validateFunction.ts +25 -0
  69. package/src/validators/validateNumber.ts +2 -7
  70. package/src/validators/validateObject.ts +32 -21
  71. package/src/validators/validateString.ts +2 -7
  72. package/src/validators/validateUnion.ts +36 -0
  73. package/dist/schemaValidator.d.ts +0 -16
  74. package/dist/schemaValidator.js +0 -234
  75. package/src/schemaValidator.ts +0 -367
@@ -0,0 +1,1438 @@
1
+ import { InferType, Schema } from './schema.js';
2
+ import { object } from './builders/ObjectSchemaBuilder.js';
3
+ import { number } from './builders/NumberSchemaBuilder.js';
4
+ import { string } from './builders/StringSchemaBuilder.js';
5
+ import { array } from './builders/ArraySchemaBuilder.js';
6
+ import { union } from './builders/UnionSchemaBuilder.js';
7
+ import { boolean } from './builders/BooleanSchemaBuilder.js';
8
+ import { alias } from './builders/AliasSchemaBuilder.js';
9
+
10
+ import SchemaRegistry from './schemaRegistry.js';
11
+
12
+ const getUserSchema = () =>
13
+ object({
14
+ id: number(),
15
+ name: object().addProps({
16
+ first: string().minLength(1).maxLength(100),
17
+ last: string().minLength(1).maxLength(100)
18
+ }),
19
+ incomePerMonth: number().min(0),
20
+ bornAt: object(),
21
+ aliases: array()
22
+ });
23
+
24
+ test('Can add schema', () => {
25
+ const validator = new SchemaRegistry().addSchema(
26
+ 'something',
27
+ ({ object, string }) =>
28
+ object().addProps({
29
+ a: string()
30
+ })
31
+ );
32
+ expect(validator.schemas.something).toBeDefined();
33
+ });
34
+
35
+ test('Error thrown on adding a schema with duplicate name', () => {
36
+ expect(() =>
37
+ new SchemaRegistry()
38
+ .addSchema('smth', {} as any)
39
+ .addSchema('smth', {} as any)
40
+ ).toThrow();
41
+ });
42
+
43
+ test('Union - Array as schema type', async () => {
44
+ const validator = new SchemaRegistry()
45
+ .addSchema('name', ({ object, string }) =>
46
+ object().addProps({
47
+ name: string()
48
+ })
49
+ )
50
+ .addSchema('number_or_string', ({ number, string, alias, union }) =>
51
+ union(number()).or(string()).or(alias('name'))
52
+ );
53
+
54
+ let result = await validator.schemas.number_or_string.validate(123);
55
+
56
+ expect(result).toHaveProperty('valid', true);
57
+
58
+ result = await validator.schemas.number_or_string.validate('some string');
59
+
60
+ expect(result).toHaveProperty('valid', true);
61
+
62
+ result = await validator.schemas.number_or_string.validate({});
63
+
64
+ expect(result).toHaveProperty('valid', false);
65
+
66
+ result = await validator.schemas.number_or_string.validate({
67
+ name: 'some name'
68
+ });
69
+ expect(result).toHaveProperty('valid', true);
70
+ });
71
+
72
+ test('Union - Schema Object - 1', async () => {
73
+ const validator = new SchemaRegistry().addSchema(
74
+ 'address',
75
+ ({ object, union, number, string }) =>
76
+ object().addProps({
77
+ first: union(number().min(10).max(20)).or(string().minLength(2))
78
+ })
79
+ );
80
+
81
+ let result = await validator.schemas.address.validate({
82
+ first: 15
83
+ });
84
+ expect(result).toHaveProperty('valid', true);
85
+ result = await validator.schemas.address.validate({
86
+ first: 25
87
+ });
88
+ expect(result).toHaveProperty('valid', false);
89
+ result = await validator.schemas.address.validate({
90
+ first: 'some string'
91
+ });
92
+ expect(result).toHaveProperty('valid', true);
93
+ result = await validator.schemas.address.validate({
94
+ first: 's'
95
+ });
96
+ expect(result).toHaveProperty('valid', false);
97
+ });
98
+
99
+ test('Union - Schema Object - 2', async () => {
100
+ const validator = new SchemaRegistry().addSchema(
101
+ 'address',
102
+ ({ object, number, string, union }) =>
103
+ object({
104
+ first: union(number().min(10).max(20))
105
+ .or(string().minLength(2))
106
+ .optional()
107
+ .nullable()
108
+ })
109
+ );
110
+ let result = await validator.schemas.address.validate({});
111
+ expect(result).toHaveProperty('valid', true);
112
+ result = await validator.schemas.address.validate({
113
+ first: null
114
+ });
115
+ expect(result).toHaveProperty('valid', true);
116
+ });
117
+
118
+ test('Validate - number by schema', async () => {
119
+ const validator = new SchemaRegistry();
120
+ const result = await validator.validate(number(), 10);
121
+ expect(result).toHaveProperty('valid', true);
122
+ });
123
+
124
+ test('Validate - number by schema - not number passed', async () => {
125
+ const validator = new SchemaRegistry();
126
+ const result = await validator.validate(number(), 'str');
127
+ expect(result).toHaveProperty('valid', false);
128
+ });
129
+
130
+ test('Validate - number by schema - min', async () => {
131
+ const validator = new SchemaRegistry();
132
+ const result = await validator.validate(number().min(1000), 10);
133
+ expect(result).toHaveProperty('valid', false);
134
+ });
135
+
136
+ test('Validate - number by schema - min 2', async () => {
137
+ const validator = new SchemaRegistry();
138
+ const result = await validator.validate(number().min(1000), 10000);
139
+ expect(result).toEqual({
140
+ valid: true
141
+ });
142
+ });
143
+
144
+ test('Validate - number by schema - min 3', async () => {
145
+ const validator = new SchemaRegistry();
146
+ const result = await validator.validate(number().min(1000), 1000);
147
+ expect(result).toHaveProperty('valid', true);
148
+ });
149
+
150
+ test('Validate - number by schema - min 4', async () => {
151
+ const validator = new SchemaRegistry();
152
+ const mk = jest.fn();
153
+ validator
154
+ .validate(number().min('wdd' as any), 10)
155
+ .catch(mk)
156
+ .then(() => {
157
+ expect(mk).toBeCalled();
158
+ });
159
+ });
160
+
161
+ test('Validate - number by schema - max', async () => {
162
+ const validator = new SchemaRegistry();
163
+ const result = await validator.validate(number().max(1000), 20000);
164
+ expect(result).toHaveProperty('valid', false);
165
+ });
166
+
167
+ test('Validate - number by schema - max 2', async () => {
168
+ const validator = new SchemaRegistry();
169
+ const result = await validator.validate(number().max(40000), 10000);
170
+ expect(result).toHaveProperty('valid', true);
171
+ });
172
+
173
+ test('Validate - number by schema - max 3', async () => {
174
+ const validator = new SchemaRegistry();
175
+ const result = await validator.validate(number().max(1000), 1000);
176
+ expect(result).toHaveProperty('valid', true);
177
+ });
178
+
179
+ test('Validate - number by schema - max 4', async () => {
180
+ const validator = new SchemaRegistry();
181
+ const mk = jest.fn();
182
+ validator
183
+ .validate(number().max('string' as any), 10)
184
+ .catch(mk)
185
+ .then(() => {
186
+ expect(mk).toBeCalled();
187
+ });
188
+ });
189
+
190
+ test('Validate - number by schema - range', async () => {
191
+ const validator = new SchemaRegistry();
192
+ const result = await validator.validate(number().min(1).max(100), 10);
193
+
194
+ expect(result).toHaveProperty('valid', true);
195
+ });
196
+
197
+ test('Validate - number by schema - range - 2', async () => {
198
+ const validator = new SchemaRegistry();
199
+ const result = await validator.validate(number().min(-1).max(100), -210);
200
+
201
+ expect(result).toHaveProperty('valid', false);
202
+ });
203
+
204
+ test('Validate - number by schema - range - 3', async () => {
205
+ const validator = new SchemaRegistry();
206
+ const result = await validator.validate(number().min(-100).max(100), -100);
207
+
208
+ expect(result).toHaveProperty('valid', true);
209
+ });
210
+
211
+ test('Validate - number by schema - range - 4', async () => {
212
+ const validator = new SchemaRegistry();
213
+ const result = await validator.validate(number().min(-100).max(100), 100);
214
+
215
+ expect(result).toHaveProperty('valid', true);
216
+ });
217
+
218
+ test('Validate - number by schema - range - 5', async () => {
219
+ const validator = new SchemaRegistry();
220
+ const result = await validator.validate(number().min(-100).max(100), 200);
221
+
222
+ expect(result).toHaveProperty('valid', false);
223
+ });
224
+
225
+ test('Validate - number by schema - range - 6', async () => {
226
+ const validator = new SchemaRegistry();
227
+ const result = await validator.validate(number().min(-100).max(100), 200);
228
+
229
+ expect(result).toHaveProperty('valid', false);
230
+ });
231
+
232
+ test('Validate - number by schema - NaN', async () => {
233
+ const validator = new SchemaRegistry();
234
+ const result = await validator.validate(number().min(-100).max(100), 0 / 0);
235
+ expect(result).toHaveProperty('valid', false);
236
+ });
237
+
238
+ test('Validate - number by schema - NaN - 2', async () => {
239
+ const validator = new SchemaRegistry();
240
+ const result = await validator.validate(number().canBeNaN(), 0 / 0);
241
+
242
+ expect(result).toHaveProperty('valid', true);
243
+ });
244
+
245
+ test('Validate - number by schema - Infinity', async () => {
246
+ const validator = new SchemaRegistry();
247
+ const result = await validator.validate(number(), 100 / 0);
248
+
249
+ expect(result).toHaveProperty('valid', false);
250
+ });
251
+
252
+ test('Validate - number by schema - Infinity - 2', async () => {
253
+ const validator = new SchemaRegistry();
254
+ const result = await validator.validate(number().canBeInfinite(), 100 / 0);
255
+
256
+ expect(result).toHaveProperty('valid', true);
257
+ });
258
+
259
+ test('Validate - number by schema - custom validators - 1', async () => {
260
+ const validator = new SchemaRegistry();
261
+ const schema = number().addValidator(async (value) => {
262
+ if ((value & 0xb01) === 0)
263
+ return {
264
+ valid: false,
265
+ errors: ['value should be odd!']
266
+ };
267
+ return {
268
+ valid: true
269
+ };
270
+ });
271
+ let result = await validator.validate(schema, 101);
272
+
273
+ expect(result).toHaveProperty('valid', true);
274
+
275
+ result = await validator.validate(schema, 100);
276
+ expect(result).toHaveProperty('valid', false);
277
+ });
278
+
279
+ test('Validate - number by schema - custom validators - 2', async () => {
280
+ const validator = new SchemaRegistry();
281
+ const schema = number()
282
+ .addValidator((value) => {
283
+ if ((value & 0xb01) === 0)
284
+ return {
285
+ valid: false,
286
+ errors: ['value should be odd!']
287
+ };
288
+ return {
289
+ valid: true
290
+ };
291
+ })
292
+ .addValidator((value) => ({ valid: value > 100 }));
293
+ let result = await validator.validate(schema, 101);
294
+
295
+ expect(result).toHaveProperty('valid', true);
296
+
297
+ result = await validator.validate(schema, 99);
298
+ expect(result).toHaveProperty('valid', false);
299
+
300
+ result = await validator.validate(schema, 100);
301
+ expect(result).toHaveProperty('valid', false);
302
+ });
303
+
304
+ test('Validate - boolean - 1', async () => {
305
+ const validator = new SchemaRegistry();
306
+ const result = await validator.validate(boolean(), 300);
307
+
308
+ expect(result).toHaveProperty('valid', false);
309
+ });
310
+
311
+ test('Validate - boolean - 2', async () => {
312
+ const validator = new SchemaRegistry();
313
+ const result = await validator.validate(boolean(), true);
314
+
315
+ expect(result).toHaveProperty('valid', true);
316
+ });
317
+
318
+ test('Validate - boolean - 3', async () => {
319
+ const validator = new SchemaRegistry();
320
+ const result = await validator.validate(boolean(), false);
321
+
322
+ expect(result).toHaveProperty('valid', true);
323
+ });
324
+
325
+ test('Validate - boolean - 4', async () => {
326
+ const validator = new SchemaRegistry();
327
+ const result = await validator.validate(boolean().equals(true), false);
328
+
329
+ expect(result).toHaveProperty('valid', false);
330
+ });
331
+
332
+ test('Validate - boolean - 5', async () => {
333
+ const validator = new SchemaRegistry();
334
+ const result = await validator.validate(boolean().equals(false), false);
335
+
336
+ expect(result).toHaveProperty('valid', true);
337
+ });
338
+
339
+ test('Validate - string - schema object - 1', async () => {
340
+ const validator = new SchemaRegistry();
341
+ const result = await validator.validate(
342
+ string().equals('123456'),
343
+ '123456'
344
+ );
345
+ expect(result).toHaveProperty('valid', true);
346
+ });
347
+
348
+ test('Validate - string - schema object - 2', async () => {
349
+ const validator = new SchemaRegistry();
350
+ const result = await validator.validate(string().equals('123456'), '12345');
351
+ expect(result).toHaveProperty('valid', false);
352
+ });
353
+
354
+ test('Validate - string - schema object - 3', async () => {
355
+ const validator = new SchemaRegistry();
356
+ const result = await validator.validate(string().equals('123456'), 1234);
357
+ expect(result).toHaveProperty('valid', false);
358
+ });
359
+
360
+ test('Validate - string - length control - 1', async () => {
361
+ const validator = new SchemaRegistry();
362
+ let result = await validator.validate(
363
+ string().minLength(2).maxLength(3),
364
+ 'U'
365
+ );
366
+ expect(result).toHaveProperty('valid', false);
367
+
368
+ result = await validator.validate(string().minLength(2).maxLength(3), 'US');
369
+ expect(result).toHaveProperty('valid', true);
370
+
371
+ result = await validator.validate(
372
+ string().minLength(2).maxLength(3),
373
+ 'USA'
374
+ );
375
+ expect(result).toHaveProperty('valid', true);
376
+
377
+ result = await validator.validate(
378
+ string().minLength(2).maxLength(3),
379
+ 'USA'
380
+ );
381
+ expect(result).toHaveProperty('valid', true);
382
+
383
+ result = await validator.validate(
384
+ string().minLength(2).maxLength(3),
385
+ 'United States of America'
386
+ );
387
+ expect(result).toHaveProperty('valid', false);
388
+ });
389
+
390
+ test('Validate - array - size control - 1', async () => {
391
+ const validator = new SchemaRegistry();
392
+ let result = await validator.validate(
393
+ array().minLength(1).maxLength(3),
394
+ []
395
+ );
396
+ expect(result).toHaveProperty('valid', false);
397
+
398
+ result = await validator.validate(array().minLength(1).maxLength(3), [1]);
399
+ expect(result).toHaveProperty('valid', true);
400
+
401
+ result = await validator.validate(
402
+ array().minLength(1).maxLength(3),
403
+ [1, 2]
404
+ );
405
+ expect(result).toHaveProperty('valid', true);
406
+
407
+ result = await validator.validate(
408
+ array().minLength(1).maxLength(3),
409
+ [1, 2, 3]
410
+ );
411
+ expect(result).toHaveProperty('valid', true);
412
+
413
+ result = await validator.validate(
414
+ array().minLength(1).maxLength(3),
415
+ [1, 2, 3, 4]
416
+ );
417
+ expect(result).toHaveProperty('valid', false);
418
+ });
419
+
420
+ test('Validate - array - ofType - 1', async () => {
421
+ const validator = new SchemaRegistry();
422
+ let result = await validator.validate(array().ofType(number()), [
423
+ '1',
424
+ 2,
425
+ 3
426
+ ]);
427
+ expect(result).toHaveProperty('valid', false);
428
+
429
+ result = await validator.validate(array().ofType(number()), [1, 2, 3]);
430
+ expect(result).toHaveProperty('valid', true);
431
+
432
+ result = await validator.validate(
433
+ array().ofType(number().min(10)),
434
+ [-1, 12, 13]
435
+ );
436
+ expect(result).toHaveProperty('valid', false);
437
+ });
438
+
439
+ test('Validate - object - 1', async () => {
440
+ const validator = new SchemaRegistry().addSchema('user', getUserSchema());
441
+ const schema = getUserSchema();
442
+ type User = InferType<typeof schema>;
443
+ const user: User = {
444
+ id: 1,
445
+ name: {
446
+ first: 'Andrew',
447
+ last: 'Zolotukhin'
448
+ },
449
+ bornAt: new Date(1986, 4, 30),
450
+ incomePerMonth: 134234,
451
+ aliases: []
452
+ };
453
+
454
+ let result = await validator.validate('user', user);
455
+
456
+ expect(result).toHaveProperty('valid', true);
457
+
458
+ result = await validator.validate('user', 10);
459
+
460
+ expect(result).toHaveProperty('valid', false);
461
+
462
+ result = await validator.validate(
463
+ object({
464
+ name: string().maxLength(5),
465
+ address: object({
466
+ street: string(),
467
+ house: number().optional()
468
+ })
469
+ }),
470
+ {
471
+ name: 'Andr',
472
+ address: {
473
+ street: 'something'
474
+ }
475
+ }
476
+ );
477
+
478
+ expect(result).toHaveProperty('valid', true);
479
+
480
+ result = await validator.validate(
481
+ object({
482
+ a: number(),
483
+ b: number()
484
+ }).addValidator((value: { a: number; b: number }) => {
485
+ if (value.a + value.b === 5) {
486
+ return {
487
+ valid: true
488
+ };
489
+ }
490
+ return {
491
+ valid: false,
492
+ error: ['some error']
493
+ };
494
+ }),
495
+ {
496
+ a: 1,
497
+ b: 3
498
+ }
499
+ );
500
+ expect(result).toHaveProperty('valid', false);
501
+ });
502
+
503
+ test('Validate - one of - 1', async () => {
504
+ const validator = new SchemaRegistry();
505
+
506
+ let result = await validator.validate(
507
+ union(number()).or(object()),
508
+ 'something'
509
+ );
510
+ expect(result).toHaveProperty('valid', false);
511
+
512
+ result = await validator.validate(
513
+ union(number()).or(string()),
514
+ 'something'
515
+ );
516
+ expect(result).toHaveProperty('valid', true);
517
+
518
+ result = await validator.validate(
519
+ union(number())
520
+ .or(string())
521
+ .or(
522
+ object({
523
+ first: string(),
524
+ last: string()
525
+ })
526
+ ),
527
+ {
528
+ first: 'something',
529
+ last: 'another'
530
+ }
531
+ );
532
+ expect(result).toHaveProperty('valid', true);
533
+ });
534
+
535
+ test('Validate schema - 1', async () => {
536
+ const validator = new SchemaRegistry()
537
+ .addSchema(
538
+ 'EqualsFilterCondition',
539
+ ({ object, number, string, union }) =>
540
+ object({
541
+ operation: string().equals('equals'),
542
+ value: union(object()).or(string()).or(number())
543
+ })
544
+ )
545
+ .addSchema('LikeFilterCondition', ({ object, string }) =>
546
+ object({
547
+ operation: string().equals('like'),
548
+ value: string()
549
+ })
550
+ )
551
+ .addSchema('StringFilterCondition', ({ union, alias }) =>
552
+ union(alias('EqualsFilterCondition')).or(
553
+ alias('LikeFilterCondition')
554
+ )
555
+ )
556
+ .addSchema('AuthorFilter', ({ alias, object }) =>
557
+ object({
558
+ fullName: alias('StringFilterCondition')
559
+ })
560
+ )
561
+ .addSchema('Date', ({ object }) =>
562
+ object().addValidator((value) =>
563
+ value instanceof Date && !Number.isNaN(value)
564
+ ? {
565
+ valid: true
566
+ }
567
+ : {
568
+ valid: false,
569
+ errors: ['should be a valid Date object']
570
+ }
571
+ )
572
+ )
573
+ .addSchema(
574
+ 'AuthorsReportSpecification',
575
+ ({ object, string, alias, union }) =>
576
+ object({
577
+ type: string().equals('author'),
578
+ start: alias('Date'),
579
+ end: alias('Date'),
580
+ selectionStart: alias('Date'),
581
+ selectionEnd: alias('Date'),
582
+ granularity: union(string().equals('day'))
583
+ .or(string().equals('week'))
584
+ .or(string().equals('month')),
585
+ metrics: array().ofType(
586
+ union(string().equals('articlesPublished'))
587
+ .or(string().equals('searchReferrers'))
588
+ .or(string().equals('socialReferrers'))
589
+ .or(string().equals('views'))
590
+ .or(string().equals('visitors'))
591
+ .or(string().equals('newVisitors'))
592
+ ),
593
+ filters: object({
594
+ author: alias('AuthorFilter')
595
+ })
596
+ }).addValidator((value) =>
597
+ value.start <= value.end &&
598
+ value.selectionStart <= value.selectionEnd &&
599
+ value.selectionStart >= value.start &&
600
+ value.selectionStart <= value.end &&
601
+ value.selectionEnd >= value.start &&
602
+ value.selectionEnd <= value.end
603
+ ? {
604
+ valid: true
605
+ }
606
+ : {
607
+ valid: false,
608
+ errors: [
609
+ 'selectionStart <=> selectionEnd should be inside the start <=> end interval'
610
+ ]
611
+ }
612
+ )
613
+ );
614
+
615
+ let reportSpec = {
616
+ type: 'author',
617
+ start: new Date(2021, 0, 1),
618
+ end: new Date(),
619
+ selectionStart: new Date(2022, 0, 1),
620
+ selectionEnd: new Date(2022, 2, 1),
621
+ granularity: 'day',
622
+ metrics: [
623
+ 'articlesPublished',
624
+ 'searchReferrers',
625
+ 'socialReferrers',
626
+ 'views',
627
+ 'visitors',
628
+ 'newVisitors'
629
+ ],
630
+ filters: {
631
+ author: {
632
+ fullName: {
633
+ operation: 'between',
634
+ value: 'some name'
635
+ }
636
+ }
637
+ }
638
+ };
639
+
640
+ let result = await validator.schemas.AuthorsReportSpecification.validate(
641
+ reportSpec
642
+ );
643
+
644
+ expect(result).toHaveProperty('valid', false);
645
+
646
+ reportSpec = {
647
+ type: 'author',
648
+ start: new Date(2021, 0, 1),
649
+ end: new Date(),
650
+ selectionStart: new Date(2022, 0, 1),
651
+ selectionEnd: new Date(2022, 2, 1),
652
+ granularity: 'day',
653
+ metrics: [
654
+ 'articlesPublished',
655
+ 'searchReferrers',
656
+ 'socialReferrers',
657
+ 'views',
658
+ 'visitors',
659
+ 'newVisitors'
660
+ ],
661
+ filters: {
662
+ author: {
663
+ fullName: {
664
+ operation: 'equals',
665
+ value: 'some name'
666
+ }
667
+ }
668
+ }
669
+ };
670
+
671
+ result = await validator.schemas.AuthorsReportSpecification.validate(
672
+ reportSpec
673
+ );
674
+
675
+ expect(result).toHaveProperty('valid', true);
676
+ });
677
+
678
+ test('Validate schema - 2', async () => {
679
+ const validator = new SchemaRegistry()
680
+ .addSchema('Date', ({ object }) =>
681
+ object().addValidator((value) =>
682
+ value instanceof Date && !Number.isNaN(value)
683
+ ? {
684
+ valid: true
685
+ }
686
+ : {
687
+ valid: false,
688
+ errors: ['should be a valid Date object']
689
+ }
690
+ )
691
+ )
692
+ .addSchema('Module.Schema1', ({ object, string }) =>
693
+ object({
694
+ a: string()
695
+ })
696
+ )
697
+ .addSchema('Module.Schema2', ({ object, number }) =>
698
+ object({
699
+ b: number()
700
+ })
701
+ );
702
+
703
+ const result = await validator.validate(
704
+ object({
705
+ date: alias('Date').optional()
706
+ }),
707
+ {}
708
+ );
709
+
710
+ expect(result).toHaveProperty('valid', true);
711
+ });
712
+
713
+ test('Not required alternative schema alias', async () => {
714
+ const validator = new SchemaRegistry()
715
+ .addSchema('Alternate1', ({ object, string }) =>
716
+ object({
717
+ a1: string()
718
+ })
719
+ )
720
+ .addSchema('Alternate2', ({ object, string }) =>
721
+ object({
722
+ a2: string()
723
+ })
724
+ )
725
+ .addSchema('Alternate', ({ union, alias }) =>
726
+ union(alias('Alternate1')).or(alias('Alternate2'))
727
+ );
728
+
729
+ const s = alias('Alternate').optional();
730
+ let result = await validator.validate(s, undefined);
731
+
732
+ expect(result).toHaveProperty('valid', true);
733
+
734
+ result = await validator.validate(
735
+ {
736
+ type: 'alias',
737
+ isRequired: false,
738
+ schemaName: 'Alternate'
739
+ },
740
+ {
741
+ a2: 'something'
742
+ }
743
+ );
744
+
745
+ expect(result).toHaveProperty('valid', true);
746
+
747
+ result = await validator.validate(
748
+ {
749
+ type: 'alias',
750
+ isRequired: false,
751
+ schemaName: 'Alternate'
752
+ },
753
+ 'invalid'
754
+ );
755
+
756
+ expect(result).toHaveProperty('valid', false);
757
+ });
758
+
759
+ test('Preprocessors - 1', async () => {
760
+ const validator = new SchemaRegistry().addSchema(
761
+ 'Date',
762
+ object().addValidator((value) =>
763
+ value instanceof Date && !Number.isNaN(value)
764
+ ? {
765
+ valid: true
766
+ }
767
+ : {
768
+ valid: false,
769
+ errors: ['should be a valid Date object']
770
+ }
771
+ )
772
+ );
773
+
774
+ const schema = object({
775
+ bornAt: alias('Date')
776
+ }).setPropPreprocessor('bornAt', (value: any): Date | undefined => {
777
+ const time = Date.parse((value as Record<string, unknown>).toString());
778
+ if (Number.isNaN(time)) return undefined;
779
+ return new Date(time);
780
+ });
781
+ const result = await validator.validate(schema as Schema, {
782
+ bornAt: new Date().toJSON()
783
+ });
784
+ expect(result).toHaveProperty('valid', true);
785
+ });
786
+
787
+ test('Preprocessors - 2', async () => {
788
+ const validator = new SchemaRegistry().addSchema(
789
+ 'Date',
790
+ object().addValidator((value) =>
791
+ value instanceof Date && !Number.isNaN(value)
792
+ ? {
793
+ valid: true
794
+ }
795
+ : {
796
+ valid: false,
797
+ errors: ['should be a valid Date object']
798
+ }
799
+ )
800
+ );
801
+
802
+ let schema = object({
803
+ bornAt: alias('Date')
804
+ }).setPropPreprocessor('bornAt', 'StringToDate');
805
+
806
+ validator.addPreprocessor(
807
+ 'StringToDate',
808
+ (value: unknown): Date | undefined => {
809
+ const time = Date.parse(
810
+ (value as Record<string, unknown>).toString()
811
+ );
812
+ if (Number.isNaN(time)) return undefined;
813
+ return new Date(time);
814
+ }
815
+ );
816
+
817
+ expect(() =>
818
+ validator.addPreprocessor(
819
+ 'StringToDate',
820
+ (value: unknown): Date | undefined => {
821
+ const time = Date.parse(
822
+ (value as Record<string, unknown>).toString()
823
+ );
824
+ if (Number.isNaN(time)) return undefined;
825
+ return new Date(time);
826
+ }
827
+ )
828
+ ).toThrow();
829
+
830
+ const result = await validator.validate(schema as Schema, {
831
+ bornAt: new Date().toJSON()
832
+ });
833
+ expect(result).toHaveProperty('valid', true);
834
+
835
+ schema = object({
836
+ bornAt: alias('Date'),
837
+ diedAt: alias('Date')
838
+ })
839
+ .setPropPreprocessor('bornAt', 'StringToDate')
840
+ .setPropPreprocessor('diedAt', 'Unregistered') as any;
841
+
842
+ expect(async () => {
843
+ await validator.validate(schema as Schema, {
844
+ bornAt: new Date().toJSON()
845
+ });
846
+ }).rejects.toBeInstanceOf(Error);
847
+ });
848
+
849
+ test('Preprocessors - 3', async () => {
850
+ const validator = new SchemaRegistry().addSchema('Date', ({ object }) =>
851
+ object().addValidator((value: any) =>
852
+ value instanceof Date && !Number.isNaN(value)
853
+ ? {
854
+ valid: true
855
+ }
856
+ : {
857
+ valid: false,
858
+ errors: ['should be a valid Date object']
859
+ }
860
+ )
861
+ );
862
+ validator.addPreprocessor(
863
+ 'StringToDate',
864
+ (value: unknown): Date | undefined => {
865
+ const time = Date.parse(
866
+ (value as Record<string, unknown>).toString()
867
+ );
868
+ if (Number.isNaN(time)) return undefined;
869
+ return new Date(time);
870
+ }
871
+ );
872
+
873
+ let obj = [new Date().toJSON()];
874
+
875
+ let result = await validator.validate(
876
+ array().ofType(alias('Date')).addPreprocessor('StringToDate'),
877
+ obj
878
+ );
879
+ expect(result).toHaveProperty('valid', true);
880
+
881
+ obj = [new Date().toJSON()];
882
+ result = await validator.validate(
883
+ array()
884
+ .ofType(alias('Date'))
885
+ .addPreprocessor((value: unknown): Date | undefined => {
886
+ const time = Date.parse(
887
+ (value as Record<string, unknown>).toString()
888
+ );
889
+ if (Number.isNaN(time)) return undefined;
890
+ return new Date(time);
891
+ }),
892
+ obj
893
+ );
894
+ expect(result).toHaveProperty('valid', true);
895
+
896
+ obj = ['sdfsdf12', new Date().toJSON()];
897
+ result = await validator.validate(
898
+ array()
899
+ .ofType(alias('Date'))
900
+ .addPreprocessor((value: unknown): Date | undefined => {
901
+ const time = Date.parse(
902
+ (value as Record<string, unknown>).toString()
903
+ );
904
+ if (Number.isNaN(time)) return undefined;
905
+ return new Date(time);
906
+ }),
907
+ obj
908
+ );
909
+ expect(result).toHaveProperty('valid', false);
910
+ });
911
+
912
+ test('Preprocessors - 3', async () => {
913
+ const validator = new SchemaRegistry();
914
+
915
+ type SomeType = { age: number; marriedAt: number };
916
+
917
+ const schema = object({
918
+ age: number(),
919
+ marriedAt: number()
920
+ })
921
+ .setPropPreprocessor('age', (): number => 40)
922
+ .setPropPreprocessor('*', (value: SomeType): void => {
923
+ if (value.marriedAt > value.age) {
924
+ value.marriedAt = value.age;
925
+ }
926
+ });
927
+
928
+ const obj = {
929
+ age: 50,
930
+ marriedAt: 80
931
+ };
932
+ await validator.validate(schema as Schema, obj);
933
+ expect(obj).toHaveProperty('marriedAt', 40);
934
+ });
935
+
936
+ test('Preprocessors - 4', async () => {
937
+ const validator = new SchemaRegistry().addSchema('Date', ({ object }) =>
938
+ object().addValidator((value: any) =>
939
+ value instanceof Date && !Number.isNaN(value)
940
+ ? {
941
+ valid: true
942
+ }
943
+ : {
944
+ valid: false,
945
+ errors: ['should be a valid Date object']
946
+ }
947
+ )
948
+ );
949
+
950
+ validator.addPreprocessor(
951
+ 'StringToDate',
952
+ (value: unknown): Date | undefined => {
953
+ const time = Date.parse(
954
+ (value as Record<string, unknown>).toString()
955
+ );
956
+ if (Number.isNaN(time)) return undefined;
957
+ return new Date(time);
958
+ }
959
+ );
960
+
961
+ let obj = [new Date().toJSON()];
962
+
963
+ let result = await validator.validate(
964
+ array().ofType(alias('Date')).addPreprocessor('StringToDate'),
965
+ obj
966
+ );
967
+ expect(result).toHaveProperty('valid', true);
968
+
969
+ obj = [new Date().toJSON()];
970
+ result = await validator.validate(
971
+ array()
972
+ .ofType(alias('Date'))
973
+ .addPreprocessor((value: unknown): Date | undefined => {
974
+ const time = Date.parse(
975
+ (value as Record<string, unknown>).toString()
976
+ );
977
+ if (Number.isNaN(time)) return undefined;
978
+ return new Date(time);
979
+ }),
980
+ obj
981
+ );
982
+ expect(result).toHaveProperty('valid', true);
983
+
984
+ obj = ['sdfsdf12', new Date().toJSON()];
985
+ result = await validator.validate(
986
+ array()
987
+ .ofType(alias('Date'))
988
+ .addPreprocessor((value: unknown): Date | undefined => {
989
+ const time = Date.parse(
990
+ (value as Record<string, unknown>).toString()
991
+ );
992
+ if (Number.isNaN(time)) return undefined;
993
+ return new Date(time);
994
+ }),
995
+ obj
996
+ );
997
+ expect(result).toHaveProperty('valid', false);
998
+ });
999
+ test('Submodules - 2', async () => {
1000
+ const validator = new SchemaRegistry()
1001
+ .addSchema('Module1.Schema1', ({ object }) => object())
1002
+ .addSchema('Module1.Schema2', ({ object, number }) =>
1003
+ object({
1004
+ a: number()
1005
+ })
1006
+ );
1007
+
1008
+ const result = validator.schemas;
1009
+
1010
+ expect(result).toHaveProperty('Module1');
1011
+ expect(result).toHaveProperty('Module1.Schema1');
1012
+ expect(result).toHaveProperty('Module1.Schema2');
1013
+
1014
+ const result2 = await validator.schemas.Module1.Schema2.validate({
1015
+ a: 234
1016
+ });
1017
+ expect(result2).toHaveProperty('valid', true);
1018
+ });
1019
+
1020
+ test('Submodules - 3', async () => {
1021
+ const validator = new SchemaRegistry()
1022
+ .addSchema('Module1.Schema1', ({ object, number }) =>
1023
+ object({
1024
+ b: number()
1025
+ })
1026
+ )
1027
+ .addSchema('Module1.Schema2', ({ object, alias }) =>
1028
+ object({
1029
+ a: alias('Module1.Schema1')
1030
+ })
1031
+ );
1032
+
1033
+ const result2 = await validator.schemas.Module1.Schema2.validate({
1034
+ a: {
1035
+ b: 20
1036
+ }
1037
+ });
1038
+ expect(result2).toHaveProperty('valid', true);
1039
+ });
1040
+
1041
+ test('Submodules - 4', async () => {
1042
+ const validator = new SchemaRegistry()
1043
+ .addSchema('Module1.Schema1', ({ object, number }) =>
1044
+ object({
1045
+ b: number()
1046
+ })
1047
+ )
1048
+ .addSchema('Module1.Schema2', ({ object, alias }) =>
1049
+ object({
1050
+ a: alias('Module1.Schema3' as any)
1051
+ })
1052
+ );
1053
+
1054
+ await validator.schemas.Module1.Schema2.validate({
1055
+ a: {
1056
+ b: 20
1057
+ }
1058
+ }).catch((e) => expect(e).toBeInstanceOf(Error));
1059
+ });
1060
+
1061
+ test('No unknown fields - 1', async () => {
1062
+ const validator = new SchemaRegistry().addSchema(
1063
+ 'Schema1',
1064
+ ({ object, number }) =>
1065
+ object({
1066
+ b: number()
1067
+ }).noUnknownProps()
1068
+ );
1069
+
1070
+ const result = await validator.schemas.Schema1.validate({
1071
+ b: 20,
1072
+ c: 'something'
1073
+ });
1074
+ expect(result).toHaveProperty('valid', false);
1075
+ });
1076
+
1077
+ test('No unknown fields - 2', async () => {
1078
+ const validator = new SchemaRegistry().addSchema(
1079
+ 'Schema1',
1080
+ ({ object, number }) =>
1081
+ object({
1082
+ b: number()
1083
+ }).canHaveUnknownProps()
1084
+ );
1085
+
1086
+ const result = await validator.schemas.Schema1.validate({
1087
+ b: 20,
1088
+ c: 'something'
1089
+ });
1090
+ expect(result).toHaveProperty('valid', true);
1091
+ });
1092
+
1093
+ test('addSchemaFrom - 1', async () => {
1094
+ const registry = new SchemaRegistry()
1095
+ .addSchema('Person', ({ string, object }) =>
1096
+ object({
1097
+ first: string(),
1098
+ last: string()
1099
+ })
1100
+ )
1101
+ .addSchemaFrom('Person', 'PersonWithEmail', ({ schema, string }) =>
1102
+ schema.addProps({
1103
+ email: string()
1104
+ })
1105
+ );
1106
+ expect(
1107
+ registry.schemas.Person === registry.schemas.PersonWithEmail
1108
+ ).toEqual(false);
1109
+ });
1110
+
1111
+ test('Object returned - 1', async () => {
1112
+ const registry = new SchemaRegistry().addSchema(
1113
+ 'Person',
1114
+ ({ object, string }) =>
1115
+ object({
1116
+ first: string(),
1117
+ last: string()
1118
+ })
1119
+ );
1120
+ const p = {
1121
+ first: 'John',
1122
+ last: 'Smith'
1123
+ };
1124
+ const { valid, object } = await registry.schemas.Person.validate(p);
1125
+
1126
+ expect(valid).toEqual(true);
1127
+ expect(object === p).toEqual(true);
1128
+ });
1129
+
1130
+ test('makeAllPropsOptional - 1', async () => {
1131
+ const registry = new SchemaRegistry().addSchema(
1132
+ 'Schema1',
1133
+ ({ object, number }) =>
1134
+ object({
1135
+ id: number(),
1136
+ first: number(),
1137
+ second: number()
1138
+ })
1139
+ .makeAllPropsOptional()
1140
+ .makePropRequired('id')
1141
+ );
1142
+
1143
+ const { valid } = await registry.schemas.Schema1.validate({
1144
+ id: 123
1145
+ });
1146
+
1147
+ const { valid: valid2 } = await registry.schemas.Schema1.validate({});
1148
+
1149
+ expect(valid).toEqual(true);
1150
+ expect(valid2).toEqual(false);
1151
+ });
1152
+
1153
+ test('no unknown props allowed by default', async () => {
1154
+ const registry = new SchemaRegistry().addSchema(
1155
+ 'User',
1156
+ ({ object, string }) =>
1157
+ object({
1158
+ firstName: string(),
1159
+ lastName: string()
1160
+ })
1161
+ );
1162
+
1163
+ const { valid } = await registry.schemas.User.validate({
1164
+ firstName: 'John',
1165
+ lastName: 'Smith',
1166
+ age: 20
1167
+ });
1168
+
1169
+ expect(valid).toEqual(false);
1170
+
1171
+ const registry2 = registry.addSchemaFrom('User', 'UserOpen', ({ schema }) =>
1172
+ schema.canHaveUnknownProps()
1173
+ );
1174
+
1175
+ const { valid: valid2 } = await registry2.schemas.UserOpen.validate({
1176
+ firstName: 'John',
1177
+ lastName: 'Smith',
1178
+ age: 20
1179
+ });
1180
+
1181
+ expect(valid2).toEqual(true);
1182
+ });
1183
+
1184
+ test('Func - 1', async () => {
1185
+ const validator = new SchemaRegistry().addSchema('something', ({ func }) =>
1186
+ func()
1187
+ );
1188
+ expect(validator.schemas.something).toBeDefined();
1189
+ expect(await validator.schemas.something.validate(123)).toHaveProperty(
1190
+ 'valid',
1191
+ false
1192
+ );
1193
+ expect(await validator.schemas.something.validate(() => 10)).toHaveProperty(
1194
+ 'valid',
1195
+ true
1196
+ );
1197
+ });
1198
+
1199
+ test('Func - 2', async () => {
1200
+ const validator = new SchemaRegistry().addSchema(
1201
+ 'something',
1202
+ ({ func, string, object }) =>
1203
+ object({
1204
+ name: string(),
1205
+ f: func()
1206
+ })
1207
+ );
1208
+ expect(
1209
+ await validator.schemas.something.validate({
1210
+ name: '123',
1211
+ f: 324
1212
+ })
1213
+ ).toHaveProperty('valid', false);
1214
+ expect(
1215
+ await validator.schemas.something.validate({
1216
+ name: '1234',
1217
+ f: () => 10
1218
+ })
1219
+ ).toHaveProperty('valid', true);
1220
+ });
1221
+
1222
+ test('Func - 3', async () => {
1223
+ const validator = new SchemaRegistry().addSchema(
1224
+ 'something',
1225
+ ({ func, string, object }) =>
1226
+ object({
1227
+ name: string(),
1228
+ f: func().optional()
1229
+ })
1230
+ );
1231
+
1232
+ expect(
1233
+ await validator.schemas.something.validate({
1234
+ name: '123',
1235
+ f: 324
1236
+ })
1237
+ ).toHaveProperty('valid', false);
1238
+ expect(
1239
+ await validator.schemas.something.validate({
1240
+ name: '1234'
1241
+ })
1242
+ ).toHaveProperty('valid', true);
1243
+ });
1244
+
1245
+ test('Func - 3', async () => {
1246
+ const validator = new SchemaRegistry().addSchema(
1247
+ 'something',
1248
+ ({ func, string, object, union }) =>
1249
+ object({
1250
+ name: string(),
1251
+ f: union(func(), object().canHaveUnknownProps()).optional()
1252
+ })
1253
+ );
1254
+
1255
+ expect(
1256
+ await validator.schemas.something.validate({
1257
+ name: '123',
1258
+ f: 324
1259
+ })
1260
+ ).toHaveProperty('valid', false);
1261
+ expect(
1262
+ await validator.schemas.something.validate({
1263
+ name: '1234'
1264
+ })
1265
+ ).toHaveProperty('valid', true);
1266
+ expect(
1267
+ await validator.schemas.something.validate({
1268
+ name: '1234',
1269
+ f: { a: 34 }
1270
+ })
1271
+ ).toHaveProperty('valid', true);
1272
+ expect(
1273
+ await validator.schemas.something.validate({
1274
+ name: '1234',
1275
+ f: () => 20
1276
+ })
1277
+ ).toHaveProperty('valid', true);
1278
+ expect(
1279
+ await validator.schemas.something.validate({
1280
+ name: '1234',
1281
+ f: '2345'
1282
+ })
1283
+ ).toHaveProperty('valid', false);
1284
+ });
1285
+
1286
+ test('External Schema - 1', async () => {
1287
+ const validator1 = new SchemaRegistry().addSchema(
1288
+ 'person',
1289
+ ({ object, number }) =>
1290
+ object({
1291
+ age: number().min(0),
1292
+ marriedAt: number().min(18)
1293
+ })
1294
+ );
1295
+
1296
+ const validator2 = new SchemaRegistry().addSchema(
1297
+ 'company',
1298
+ ({ object, string }) =>
1299
+ object({
1300
+ name: string(),
1301
+ director: validator1.schemas.person.schema.optional()
1302
+ })
1303
+ );
1304
+
1305
+ const result = await validator2.schemas.company.validate({
1306
+ name: 'Some',
1307
+ director: {
1308
+ age: 40,
1309
+ marriedAt: 22
1310
+ }
1311
+ });
1312
+
1313
+ expect(result).toHaveProperty('valid', true);
1314
+
1315
+ const result2 = await validator2.schemas.company.validate({
1316
+ name: 'Some',
1317
+ director: {
1318
+ age: -10,
1319
+ marriedAt: 22
1320
+ }
1321
+ });
1322
+
1323
+ expect(result2).toHaveProperty('valid', false);
1324
+ });
1325
+
1326
+ test('External Schema - 2', async () => {
1327
+ const validator1 = new SchemaRegistry()
1328
+ .addSchema('address', ({ object, string }) =>
1329
+ object({
1330
+ city: string(),
1331
+ street: string().addValidator((v) => ({ valid: v.length > 0 }))
1332
+ }).addValidator((val) => ({ valid: val.city.length > 0 }))
1333
+ )
1334
+ .addSchema('person', ({ object, number, alias }) =>
1335
+ object({
1336
+ age: number().min(0),
1337
+ marriedAt: number()
1338
+ .min(18)
1339
+ .addValidator((v) => ({ valid: v > 10 })),
1340
+ address: alias('address')
1341
+ }).addValidator((val) => ({ valid: val.marriedAt < val.age }))
1342
+ );
1343
+
1344
+ const validator2 = new SchemaRegistry().addSchema(
1345
+ 'company',
1346
+ ({ object, string, external }) =>
1347
+ object({
1348
+ name: string().addValidator(() => ({ valid: true })),
1349
+ director: external(validator1, 'person')
1350
+ }).addValidator((v) => ({ valid: true }))
1351
+ );
1352
+
1353
+ const result = await validator2.schemas.company.validate({
1354
+ name: 'Some',
1355
+ director: {
1356
+ age: 40,
1357
+ marriedAt: 22,
1358
+ address: {
1359
+ city: 'City upon a Hill',
1360
+ street: '123 Blossom street'
1361
+ }
1362
+ }
1363
+ });
1364
+
1365
+ expect(result).toHaveProperty('valid', true);
1366
+
1367
+ const result2 = await validator2.schemas.company.validate({
1368
+ name: 'Some',
1369
+ director: {
1370
+ age: -10,
1371
+ marriedAt: 22,
1372
+ address: {
1373
+ city: 'City upon a Hill',
1374
+ street: '123 Blossom street'
1375
+ }
1376
+ }
1377
+ });
1378
+
1379
+ expect(result2).toHaveProperty('valid', false);
1380
+ });
1381
+
1382
+ test('Empty Validators Bug', async () => {
1383
+ const isValidEmail = (s) => {
1384
+ if (typeof s !== 'string') return false;
1385
+ const regex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,8}$/;
1386
+ return regex.test(s);
1387
+ };
1388
+ const validator = new SchemaRegistry().addSchema(
1389
+ 'Report',
1390
+ ({ object, array, string }) =>
1391
+ object({
1392
+ recepients: array()
1393
+ .ofType(
1394
+ string().addValidator((val) => {
1395
+ if (isValidEmail(val)) {
1396
+ return { valid: true };
1397
+ }
1398
+ return {
1399
+ valid: false,
1400
+ errors: [`${val} is not a valid email`]
1401
+ };
1402
+ })
1403
+ )
1404
+ .addValidator((val) => {
1405
+ if (!Array.isArray(val)) {
1406
+ return {
1407
+ valid: false,
1408
+ errors: ['not an array']
1409
+ };
1410
+ }
1411
+ const map = {};
1412
+ const lowerCased = val.map((v) => v.toLowerCase());
1413
+ for (let i = 0; i < lowerCased.length; i++) {
1414
+ if (lowerCased[i] in map) {
1415
+ return {
1416
+ valid: false,
1417
+ errors: ['no duplicates allowed']
1418
+ };
1419
+ }
1420
+ map[lowerCased[i]] = true;
1421
+ }
1422
+ return { valid: true };
1423
+ })
1424
+ })
1425
+ );
1426
+
1427
+ const result1 = await validator.schemas.Report.validate({
1428
+ recepients: ['user1@domain1.com', 'user2@domain1.com']
1429
+ });
1430
+
1431
+ expect(result1).toHaveProperty('valid', true);
1432
+
1433
+ const result2 = await validator.schemas.Report.validate({
1434
+ recepients: ['user1@domain1.com', 'user1@domain1.com']
1435
+ });
1436
+
1437
+ expect(result2).toHaveProperty('valid', false);
1438
+ });