perfect-payload 1.3.0-beta.2 → 1.5.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.
- package/README.md +767 -376
- package/index.js +148 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
# perfect-payload
|
|
2
2
|
|
|
3
3
|
A lightweight JavaScript payload validation utility for validating API
|
|
4
|
-
|
|
5
4
|
and JSON payloads with simple rule-based configuration.
|
|
6
5
|
|
|
6
|
+
`perfect-payload` supports structured validation errors, nested field
|
|
7
|
+
paths, synchronous custom validators, synchronous payload
|
|
8
|
+
transformation/sanitization, array size constraints, and deeply nested
|
|
9
|
+
array/object validation while keeping the validation schema simple.
|
|
10
|
+
|
|
11
|
+
## Quick Links
|
|
12
|
+
|
|
13
|
+
- [Installation](#installation)
|
|
14
|
+
- [Basic Usage](#basic-usage)
|
|
15
|
+
- [Validation Rules](#validation-rules)
|
|
16
|
+
- [Array Size and Nested Validation](#array-size-and-nested-validation)
|
|
17
|
+
- [Transformations and
|
|
18
|
+
Sanitization](#transformations-and-sanitization)
|
|
19
|
+
- [Custom Validators](#customvalidator)
|
|
20
|
+
- [Error Codes](#error-codes)
|
|
21
|
+
- [Custom Error Messages](#custom-error-messages)
|
|
22
|
+
- [Nested Objects and Array Field
|
|
23
|
+
Paths](#nested-objects-and-array-field-paths)
|
|
24
|
+
- [Examples and Usage](#examples-and-usage)
|
|
25
|
+
- [Legacy API](#legacy-api)
|
|
26
|
+
|
|
7
27
|
## Installation
|
|
8
28
|
|
|
9
29
|
```bash
|
|
10
30
|
|
|
11
31
|
npm install perfect-payload
|
|
12
|
-
|
|
13
32
|
```
|
|
14
33
|
|
|
15
34
|
## Basic Usage
|
|
@@ -60,25 +79,25 @@ console.log(result);
|
|
|
60
79
|
|
|
61
80
|
{
|
|
62
81
|
|
|
63
|
-
|
|
82
|
+
statusCode: 200,
|
|
64
83
|
|
|
65
|
-
|
|
84
|
+
valid: true,
|
|
66
85
|
|
|
67
|
-
|
|
86
|
+
validatedPayload: {
|
|
68
87
|
|
|
69
|
-
|
|
88
|
+
name: "Kiran",
|
|
70
89
|
|
|
71
|
-
|
|
90
|
+
email: "kiran@example.com",
|
|
72
91
|
|
|
73
|
-
|
|
92
|
+
age: 29
|
|
74
93
|
|
|
75
|
-
|
|
94
|
+
}
|
|
76
95
|
|
|
77
96
|
}
|
|
78
|
-
|
|
79
97
|
```
|
|
80
98
|
|
|
81
|
-
|
|
99
|
+
Note: The validatedPayload contains only the fields
|
|
100
|
+
defined in the
|
|
82
101
|
|
|
83
102
|
schema, automatically filtering out any extra attributes. You can use it
|
|
84
103
|
|
|
@@ -92,28 +111,27 @@ to safely overwrite request.body or assign it to a new request property
|
|
|
92
111
|
|
|
93
112
|
{
|
|
94
113
|
|
|
95
|
-
|
|
114
|
+
statusCode: 400,
|
|
96
115
|
|
|
97
|
-
|
|
116
|
+
valid: false,
|
|
98
117
|
|
|
99
|
-
|
|
118
|
+
message: "One or more attribute values are invalid",
|
|
100
119
|
|
|
101
|
-
|
|
120
|
+
errors: [
|
|
102
121
|
|
|
103
|
-
|
|
122
|
+
{
|
|
104
123
|
|
|
105
|
-
|
|
124
|
+
path: "email",
|
|
106
125
|
|
|
107
|
-
|
|
126
|
+
code: "INVALID_EMAIL",
|
|
108
127
|
|
|
109
|
-
|
|
128
|
+
message: "Invalid email format for attribute email"
|
|
110
129
|
|
|
111
|
-
|
|
130
|
+
}
|
|
112
131
|
|
|
113
|
-
|
|
132
|
+
]
|
|
114
133
|
|
|
115
134
|
}
|
|
116
|
-
|
|
117
135
|
```
|
|
118
136
|
|
|
119
137
|
Each error returned by `perfectPayload()` contains:
|
|
@@ -122,14 +140,13 @@ Each error returned by `perfectPayload()` contains:
|
|
|
122
140
|
|
|
123
141
|
{
|
|
124
142
|
|
|
125
|
-
|
|
143
|
+
path: "field.path",
|
|
126
144
|
|
|
127
|
-
|
|
145
|
+
code: "ERROR_CODE",
|
|
128
146
|
|
|
129
|
-
|
|
147
|
+
message: "Human readable validation message"
|
|
130
148
|
|
|
131
149
|
}
|
|
132
|
-
|
|
133
150
|
```
|
|
134
151
|
|
|
135
152
|
\- `path` identifies the exact field that failed validation.
|
|
@@ -152,7 +169,7 @@ import { perfectPayloadV1 } from "perfect-payload";
|
|
|
152
169
|
|
|
153
170
|
`perfectPayloadV1()` is deprecated and will no longer be supported after
|
|
154
171
|
|
|
155
|
-
|
|
172
|
+
March 31, 2027.
|
|
156
173
|
|
|
157
174
|
Existing applications can continue using it during the migration period,
|
|
158
175
|
|
|
@@ -182,7 +199,8 @@ errors: [
|
|
|
182
199
|
];
|
|
183
200
|
```
|
|
184
201
|
|
|
185
|
-
|
|
202
|
+
Note: If an inValidPayloadResponse is provided, the
|
|
203
|
+
system returns
|
|
186
204
|
|
|
187
205
|
it alongside an automatically generated errors property. Do not include
|
|
188
206
|
|
|
@@ -192,13 +210,14 @@ object.
|
|
|
192
210
|
|
|
193
211
|
## Validation Rules
|
|
194
212
|
|
|
195
|
-
`perfectPayload()` supports
|
|
213
|
+
`perfectPayload()` supports validation, nested-schema,
|
|
214
|
+
custom-validation, and transformation rules.
|
|
196
215
|
|
|
197
216
|
### `mandatory`
|
|
198
217
|
|
|
199
|
-
Marks a field as required
|
|
218
|
+
Marks a field as required. An empty string is also treated as missing.
|
|
200
219
|
|
|
201
|
-
|
|
220
|
+
Default: `false`, the field is not required.
|
|
202
221
|
|
|
203
222
|
```js
|
|
204
223
|
const rules = {
|
|
@@ -210,13 +229,13 @@ const rules = {
|
|
|
210
229
|
|
|
211
230
|
Error code: `REQUIRED`
|
|
212
231
|
|
|
213
|
-
|
|
232
|
+
---
|
|
214
233
|
|
|
215
234
|
### `allowNull`
|
|
216
235
|
|
|
217
236
|
Controls whether `null` values are accepted.
|
|
218
237
|
|
|
219
|
-
|
|
238
|
+
Default: `true`, `null` values are allowed.
|
|
220
239
|
|
|
221
240
|
Example:
|
|
222
241
|
|
|
@@ -230,13 +249,13 @@ const rules = {
|
|
|
230
249
|
|
|
231
250
|
Error code: `NULL_NOT_ALLOWED`
|
|
232
251
|
|
|
233
|
-
|
|
252
|
+
---
|
|
234
253
|
|
|
235
254
|
### `allowEmptyObject`
|
|
236
255
|
|
|
237
256
|
Controls whether an empty object `{}` is accepted.
|
|
238
257
|
|
|
239
|
-
|
|
258
|
+
Default: `true`, empty objects are allowed.
|
|
240
259
|
|
|
241
260
|
Example:
|
|
242
261
|
|
|
@@ -252,13 +271,13 @@ const rules = {
|
|
|
252
271
|
|
|
253
272
|
Error code: `EMPTY_OBJECT_NOT_ALLOWED`
|
|
254
273
|
|
|
255
|
-
|
|
274
|
+
---
|
|
256
275
|
|
|
257
276
|
### `allowEmptyArray`
|
|
258
277
|
|
|
259
278
|
Controls whether an empty array `[]` is accepted.
|
|
260
279
|
|
|
261
|
-
|
|
280
|
+
Default: `true`, empty arrays are allowed.
|
|
262
281
|
|
|
263
282
|
Example:
|
|
264
283
|
|
|
@@ -274,7 +293,69 @@ const rules = {
|
|
|
274
293
|
|
|
275
294
|
Error code: `EMPTY_ARRAY_NOT_ALLOWED`
|
|
276
295
|
|
|
277
|
-
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
### `minItems`
|
|
299
|
+
|
|
300
|
+
Defines the minimum number of items required in an array.
|
|
301
|
+
|
|
302
|
+
Default: Not applied when omitted.
|
|
303
|
+
|
|
304
|
+
```js
|
|
305
|
+
const rules = {
|
|
306
|
+
tags: {
|
|
307
|
+
type: "array",
|
|
308
|
+
minItems: 2,
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
An array with fewer than 2 items returns `MIN_ITEMS`.
|
|
314
|
+
|
|
315
|
+
```js
|
|
316
|
+
{
|
|
317
|
+
path: "tags",
|
|
318
|
+
code: "MIN_ITEMS",
|
|
319
|
+
message: "Attribute tags must contain at least 2 item(s)"
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`minItems` is enforced even when `allowEmptyArray: true` is set. For example, `minItems: 2` still rejects `[]`.
|
|
324
|
+
|
|
325
|
+
Error code: `MIN_ITEMS`
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
### `maxItems`
|
|
330
|
+
|
|
331
|
+
Defines the maximum number of items allowed in an array.
|
|
332
|
+
|
|
333
|
+
Default: Not applied when omitted.
|
|
334
|
+
|
|
335
|
+
```js
|
|
336
|
+
const rules = {
|
|
337
|
+
tags: {
|
|
338
|
+
type: "array",
|
|
339
|
+
maxItems: 5,
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
An array with more than 5 items returns `MAX_ITEMS`.
|
|
345
|
+
|
|
346
|
+
```js
|
|
347
|
+
{
|
|
348
|
+
path: "tags",
|
|
349
|
+
code: "MAX_ITEMS",
|
|
350
|
+
message: "Attribute tags must contain at most 5 item(s)"
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
`minItems` and `maxItems` can be used together.
|
|
355
|
+
|
|
356
|
+
Error code: `MAX_ITEMS`
|
|
357
|
+
|
|
358
|
+
---
|
|
278
359
|
|
|
279
360
|
### `type`
|
|
280
361
|
|
|
@@ -311,7 +392,6 @@ objectId
|
|
|
311
392
|
array
|
|
312
393
|
|
|
313
394
|
object
|
|
314
|
-
|
|
315
395
|
```
|
|
316
396
|
|
|
317
397
|
Example:
|
|
@@ -397,16 +477,17 @@ INVALID_UUID_V4
|
|
|
397
477
|
INVALID_UUID_V5
|
|
398
478
|
|
|
399
479
|
INVALID_OBJECT_ID
|
|
400
|
-
|
|
401
480
|
```
|
|
402
481
|
|
|
403
|
-
|
|
482
|
+
For `type: "number"`, `NaN` is rejected as `INVALID_TYPE`.
|
|
483
|
+
|
|
484
|
+
---
|
|
404
485
|
|
|
405
486
|
### `regex`
|
|
406
487
|
|
|
407
488
|
Validates a value using a regular expression.
|
|
408
489
|
|
|
409
|
-
|
|
490
|
+
Default: Not applied when omitted.
|
|
410
491
|
|
|
411
492
|
Example:
|
|
412
493
|
|
|
@@ -422,13 +503,13 @@ const rules = {
|
|
|
422
503
|
|
|
423
504
|
Error code: `REGEX_MISMATCH`
|
|
424
505
|
|
|
425
|
-
|
|
506
|
+
---
|
|
426
507
|
|
|
427
508
|
### `minLength`
|
|
428
509
|
|
|
429
510
|
Defines the minimum allowed string length.
|
|
430
511
|
|
|
431
|
-
|
|
512
|
+
Default: Not applied when omitted.
|
|
432
513
|
|
|
433
514
|
Example:
|
|
434
515
|
|
|
@@ -444,13 +525,13 @@ const rules = {
|
|
|
444
525
|
|
|
445
526
|
Error code: `MIN_LENGTH`
|
|
446
527
|
|
|
447
|
-
|
|
528
|
+
---
|
|
448
529
|
|
|
449
530
|
### `maxLength`
|
|
450
531
|
|
|
451
532
|
Defines the maximum allowed string length.
|
|
452
533
|
|
|
453
|
-
|
|
534
|
+
Default: Not applied when omitted.
|
|
454
535
|
|
|
455
536
|
Example:
|
|
456
537
|
|
|
@@ -466,13 +547,14 @@ const rules = {
|
|
|
466
547
|
|
|
467
548
|
Error code: `MAX_LENGTH`
|
|
468
549
|
|
|
469
|
-
|
|
550
|
+
---
|
|
470
551
|
|
|
471
552
|
### `preventDecimal`
|
|
472
553
|
|
|
473
554
|
Prevents decimal numbers.
|
|
474
555
|
|
|
475
|
-
|
|
556
|
+
Default: `false`; both integer and decimal numbers are
|
|
557
|
+
allowed.
|
|
476
558
|
|
|
477
559
|
Example:
|
|
478
560
|
|
|
@@ -488,13 +570,13 @@ const rules = {
|
|
|
488
570
|
|
|
489
571
|
Error code: `DECIMAL_NOT_ALLOWED`
|
|
490
572
|
|
|
491
|
-
|
|
573
|
+
---
|
|
492
574
|
|
|
493
575
|
### `min`
|
|
494
576
|
|
|
495
577
|
Defines the minimum allowed numeric value.
|
|
496
578
|
|
|
497
|
-
|
|
579
|
+
Default: Not applied when omitted.
|
|
498
580
|
|
|
499
581
|
Example:
|
|
500
582
|
|
|
@@ -510,13 +592,13 @@ const rules = {
|
|
|
510
592
|
|
|
511
593
|
Error code: `MIN_VALUE`
|
|
512
594
|
|
|
513
|
-
|
|
595
|
+
---
|
|
514
596
|
|
|
515
597
|
### `max`
|
|
516
598
|
|
|
517
599
|
Defines the maximum allowed numeric value.
|
|
518
600
|
|
|
519
|
-
|
|
601
|
+
Default: Not applied when omitted.
|
|
520
602
|
|
|
521
603
|
Example:
|
|
522
604
|
|
|
@@ -532,13 +614,13 @@ const rules = {
|
|
|
532
614
|
|
|
533
615
|
Error code: `MAX_VALUE`
|
|
534
616
|
|
|
535
|
-
|
|
617
|
+
---
|
|
536
618
|
|
|
537
619
|
### `range`
|
|
538
620
|
|
|
539
621
|
Defines the allowed numeric range.
|
|
540
622
|
|
|
541
|
-
|
|
623
|
+
Default: Not applied when omitted.
|
|
542
624
|
|
|
543
625
|
Example:
|
|
544
626
|
|
|
@@ -554,7 +636,7 @@ const rules = {
|
|
|
554
636
|
|
|
555
637
|
Error code: `OUT_OF_RANGE`
|
|
556
638
|
|
|
557
|
-
|
|
639
|
+
---
|
|
558
640
|
|
|
559
641
|
### `elementConstraints`
|
|
560
642
|
|
|
@@ -582,16 +664,15 @@ Example error:
|
|
|
582
664
|
|
|
583
665
|
{
|
|
584
666
|
|
|
585
|
-
|
|
667
|
+
path: "marks[2]",
|
|
586
668
|
|
|
587
|
-
|
|
669
|
+
code: "OUT_OF_RANGE",
|
|
588
670
|
|
|
589
|
-
|
|
671
|
+
message:
|
|
590
672
|
|
|
591
|
-
|
|
673
|
+
"Attribute marks[2] should have a value between 0 and 100"
|
|
592
674
|
|
|
593
675
|
}
|
|
594
|
-
|
|
595
676
|
```
|
|
596
677
|
|
|
597
678
|
When `elementConstraintsError` is explicitly provided, the error code
|
|
@@ -614,7 +695,7 @@ const rules = {
|
|
|
614
695
|
};
|
|
615
696
|
```
|
|
616
697
|
|
|
617
|
-
|
|
698
|
+
---
|
|
618
699
|
|
|
619
700
|
### `objectAttr`
|
|
620
701
|
|
|
@@ -658,19 +739,18 @@ Nested errors include the complete field path:
|
|
|
658
739
|
|
|
659
740
|
{
|
|
660
741
|
|
|
661
|
-
|
|
742
|
+
path: "address.location.latitude",
|
|
662
743
|
|
|
663
|
-
|
|
744
|
+
code: "INVALID_TYPE",
|
|
664
745
|
|
|
665
|
-
|
|
746
|
+
message:
|
|
666
747
|
|
|
667
|
-
|
|
748
|
+
"Invalid type for attribute address.location.latitude, required number value"
|
|
668
749
|
|
|
669
750
|
}
|
|
670
|
-
|
|
671
751
|
```
|
|
672
752
|
|
|
673
|
-
|
|
753
|
+
---
|
|
674
754
|
|
|
675
755
|
### `dependency`
|
|
676
756
|
|
|
@@ -704,25 +784,326 @@ Example error:
|
|
|
704
784
|
|
|
705
785
|
{
|
|
706
786
|
|
|
707
|
-
|
|
787
|
+
path: "maxSalary",
|
|
788
|
+
|
|
789
|
+
code: "MIN_VALUE",
|
|
790
|
+
|
|
791
|
+
message:
|
|
792
|
+
|
|
793
|
+
"maxSalary must be more than minSalary"
|
|
794
|
+
|
|
795
|
+
}
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
## Array Size and Nested Validation
|
|
801
|
+
|
|
802
|
+
`perfectPayload()` supports array size constraints and recursive validation of arrays and objects at multiple depths. Array indexes and nested object keys are preserved in structured error paths.
|
|
708
803
|
|
|
709
|
-
|
|
804
|
+
### Array size constraints
|
|
710
805
|
|
|
711
|
-
|
|
806
|
+
Use `minItems` and `maxItems` with `type: "array"`:
|
|
712
807
|
|
|
713
|
-
|
|
808
|
+
```js
|
|
809
|
+
const rules = {
|
|
810
|
+
products: {
|
|
811
|
+
type: "array",
|
|
812
|
+
minItems: 1,
|
|
813
|
+
maxItems: 3,
|
|
814
|
+
elementConstraints: {
|
|
815
|
+
type: "object",
|
|
816
|
+
objectAttr: {
|
|
817
|
+
productId: { mandatory: true, type: "string" },
|
|
818
|
+
quantity: { mandatory: true, type: "number", min: 1 },
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
},
|
|
822
|
+
};
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
If the array is empty, `minItems` reports the array path itself:
|
|
714
826
|
|
|
827
|
+
```js
|
|
828
|
+
{
|
|
829
|
+
path: "products",
|
|
830
|
+
code: "MIN_ITEMS",
|
|
831
|
+
message: "Attribute products must contain at least 1 item(s)"
|
|
715
832
|
}
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
### Arrays of objects
|
|
836
|
+
|
|
837
|
+
`elementConstraints` can contain `objectAttr`, allowing every object in an array to use a nested schema. An invalid quantity in the second product is reported as:
|
|
838
|
+
|
|
839
|
+
```text
|
|
840
|
+
products[1].quantity
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
### Deeply nested arrays and objects
|
|
844
|
+
|
|
845
|
+
`objectAttr` and `elementConstraints` can be combined recursively:
|
|
846
|
+
|
|
847
|
+
```js
|
|
848
|
+
const rules = {
|
|
849
|
+
orders: {
|
|
850
|
+
type: "array",
|
|
851
|
+
minItems: 1,
|
|
852
|
+
maxItems: 2,
|
|
853
|
+
elementConstraints: {
|
|
854
|
+
type: "object",
|
|
855
|
+
objectAttr: {
|
|
856
|
+
orderId: { mandatory: true, type: "string" },
|
|
857
|
+
items: {
|
|
858
|
+
mandatory: true,
|
|
859
|
+
type: "array",
|
|
860
|
+
minItems: 1,
|
|
861
|
+
maxItems: 2,
|
|
862
|
+
elementConstraints: {
|
|
863
|
+
type: "object",
|
|
864
|
+
objectAttr: {
|
|
865
|
+
productId: { mandatory: true, type: "string" },
|
|
866
|
+
quantity: { mandatory: true, type: "number", min: 1 },
|
|
867
|
+
},
|
|
868
|
+
},
|
|
869
|
+
},
|
|
870
|
+
},
|
|
871
|
+
},
|
|
872
|
+
},
|
|
873
|
+
};
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
A deep validation failure preserves the complete indexed path, for example:
|
|
877
|
+
|
|
878
|
+
```text
|
|
879
|
+
orders[1].items[2].quantity
|
|
880
|
+
```
|
|
716
881
|
|
|
882
|
+
Array constraints work at nested levels too. A nested array can report paths such as:
|
|
883
|
+
|
|
884
|
+
```text
|
|
885
|
+
orders[1].items
|
|
717
886
|
```
|
|
718
887
|
|
|
719
|
-
|
|
888
|
+
Nested arrays are supported and every array index is preserved:
|
|
889
|
+
|
|
890
|
+
```text
|
|
891
|
+
matrix[1][1]
|
|
892
|
+
matrix[1][1][1]
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
Transformations applied inside nested objects or array elements are preserved in `validatedPayload`, while the original input remains unchanged.
|
|
896
|
+
|
|
897
|
+
### Transformations and Sanitization
|
|
898
|
+
|
|
899
|
+
`perfectPayload()` can transform a field before its validation rules
|
|
900
|
+
run. The transformed value is returned in `validatedPayload`, while the
|
|
901
|
+
original input object is not mutated.
|
|
902
|
+
|
|
903
|
+
Supported transformation rules:
|
|
904
|
+
|
|
905
|
+
Rule Purpose
|
|
906
|
+
|
|
907
|
+
---
|
|
908
|
+
|
|
909
|
+
`trim` Removes leading and trailing whitespace from strings
|
|
910
|
+
`lowercase` Converts strings to lowercase
|
|
911
|
+
`uppercase` Converts strings to uppercase
|
|
912
|
+
`transform` Runs a custom synchronous transformation function
|
|
913
|
+
|
|
914
|
+
Transformations always run in this fixed order, regardless of the order
|
|
915
|
+
in which the rule properties are written:
|
|
916
|
+
|
|
917
|
+
```text
|
|
918
|
+
trim
|
|
919
|
+
↓
|
|
920
|
+
lowercase
|
|
921
|
+
↓
|
|
922
|
+
uppercase
|
|
923
|
+
↓
|
|
924
|
+
transform(value, payload)
|
|
925
|
+
↓
|
|
926
|
+
validation rules
|
|
927
|
+
↓
|
|
928
|
+
customValidator
|
|
929
|
+
↓
|
|
930
|
+
validatedPayload
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
#### `trim`
|
|
934
|
+
|
|
935
|
+
```js
|
|
936
|
+
const payload = {
|
|
937
|
+
name: " Kiran Poojary ",
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
const rules = {
|
|
941
|
+
name: {
|
|
942
|
+
type: "string",
|
|
943
|
+
trim: true,
|
|
944
|
+
},
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
const result = perfectPayload(payload, rules);
|
|
948
|
+
|
|
949
|
+
console.log(result.validatedPayload.name);
|
|
950
|
+
// "Kiran Poojary"
|
|
951
|
+
|
|
952
|
+
console.log(payload.name);
|
|
953
|
+
// " Kiran Poojary "
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
`trim` applies only to string values. Non-string values are left
|
|
957
|
+
unchanged.
|
|
958
|
+
|
|
959
|
+
#### `lowercase`
|
|
960
|
+
|
|
961
|
+
```js
|
|
962
|
+
const rules = {
|
|
963
|
+
email: {
|
|
964
|
+
trim: true,
|
|
965
|
+
lowercase: true,
|
|
966
|
+
type: "email",
|
|
967
|
+
},
|
|
968
|
+
};
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
For `" KIRAN@EXAMPLE.COM "`, the validated value becomes
|
|
972
|
+
`"kiran@example.com"`.
|
|
973
|
+
|
|
974
|
+
#### `uppercase`
|
|
975
|
+
|
|
976
|
+
```js
|
|
977
|
+
const rules = {
|
|
978
|
+
countryCode: {
|
|
979
|
+
type: "string",
|
|
980
|
+
uppercase: true,
|
|
981
|
+
},
|
|
982
|
+
};
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
For `"in"`, the validated value becomes `"IN"`.
|
|
986
|
+
|
|
987
|
+
`lowercase: true` and `uppercase: true` cannot be enabled together for
|
|
988
|
+
the same field. Doing so throws a schema configuration error.
|
|
989
|
+
|
|
990
|
+
#### `transform`
|
|
991
|
+
|
|
992
|
+
Use `transform` when the built-in string transformations are not enough.
|
|
993
|
+
|
|
994
|
+
```js
|
|
995
|
+
const rules = {
|
|
996
|
+
phone: {
|
|
997
|
+
type: "string",
|
|
998
|
+
transform: (value) => value.replace(/\s+/g, ""),
|
|
999
|
+
},
|
|
1000
|
+
};
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
For `"98765 43210"`, the validated value becomes `"9876543210"`.
|
|
1004
|
+
|
|
1005
|
+
The transformer receives two arguments:
|
|
1006
|
+
|
|
1007
|
+
```js
|
|
1008
|
+
transform: (value, payload) => {
|
|
1009
|
+
return value;
|
|
1010
|
+
};
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
- `value` is the field value after the built-in transformations have
|
|
1014
|
+
run.
|
|
1015
|
+
- `payload` is the current payload/object being validated.
|
|
1016
|
+
|
|
1017
|
+
This makes cross-field transformations possible:
|
|
1018
|
+
|
|
1019
|
+
```js
|
|
1020
|
+
const payload = {
|
|
1021
|
+
amount: 100,
|
|
1022
|
+
multiplier: 2,
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
const rules = {
|
|
1026
|
+
amount: {
|
|
1027
|
+
transform: (value, payload) => value * payload.multiplier,
|
|
1028
|
+
type: "number",
|
|
1029
|
+
},
|
|
1030
|
+
multiplier: {
|
|
1031
|
+
type: "number",
|
|
1032
|
+
},
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
const result = perfectPayload(payload, rules);
|
|
1036
|
+
|
|
1037
|
+
console.log(result.validatedPayload.amount);
|
|
1038
|
+
// 200
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
A custom transformer may also change the data type before validation:
|
|
1042
|
+
|
|
1043
|
+
```js
|
|
1044
|
+
const rules = {
|
|
1045
|
+
quantity: {
|
|
1046
|
+
transform: (value) => Number(value),
|
|
1047
|
+
type: "number",
|
|
1048
|
+
min: 1,
|
|
1049
|
+
max: 100,
|
|
1050
|
+
},
|
|
1051
|
+
};
|
|
1052
|
+
```
|
|
1053
|
+
|
|
1054
|
+
The transformed value is validated by the normal validation rules and is
|
|
1055
|
+
also the value received by `customValidator`.
|
|
1056
|
+
|
|
1057
|
+
Transformations work inside `objectAttr` and `elementConstraints`, and
|
|
1058
|
+
transformed nested/array values are preserved in `validatedPayload`.
|
|
1059
|
+
|
|
1060
|
+
```js
|
|
1061
|
+
const rules = {
|
|
1062
|
+
profile: {
|
|
1063
|
+
type: "object",
|
|
1064
|
+
objectAttr: {
|
|
1065
|
+
name: {
|
|
1066
|
+
trim: true,
|
|
1067
|
+
uppercase: true,
|
|
1068
|
+
type: "string",
|
|
1069
|
+
},
|
|
1070
|
+
},
|
|
1071
|
+
},
|
|
1072
|
+
tags: {
|
|
1073
|
+
type: "array",
|
|
1074
|
+
elementConstraints: {
|
|
1075
|
+
trim: true,
|
|
1076
|
+
lowercase: true,
|
|
1077
|
+
type: "string",
|
|
1078
|
+
},
|
|
1079
|
+
},
|
|
1080
|
+
};
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
Missing optional fields are not transformed. An input value of `null` is
|
|
1084
|
+
not passed to transformation functions; null handling remains controlled
|
|
1085
|
+
by `allowNull`.
|
|
1086
|
+
|
|
1087
|
+
**Important:** `transform` is synchronous. A non-function transformer,
|
|
1088
|
+
an `async` transformer, a transformer that returns a Promise, or a
|
|
1089
|
+
transformer that returns `undefined` is not supported and throws an error.
|
|
1090
|
+
Returning `null`, `""`, `0`, or `false` is allowed; the transformed value is
|
|
1091
|
+
then processed by the normal validation rules. Exceptions thrown inside the
|
|
1092
|
+
transformer propagate to the caller.
|
|
1093
|
+
|
|
1094
|
+
For example, returning `undefined` throws:
|
|
1095
|
+
|
|
1096
|
+
```text
|
|
1097
|
+
perfect-payload:- transform must not return undefined for attribute username
|
|
1098
|
+
```
|
|
720
1099
|
|
|
721
1100
|
### `customValidator`
|
|
722
1101
|
|
|
723
|
-
Allows you to define custom synchronous validation logic for a field
|
|
1102
|
+
Allows you to define custom synchronous validation logic for a field
|
|
1103
|
+
when the built-in validation rules are not enough.
|
|
724
1104
|
|
|
725
|
-
The validator receives the field value and the
|
|
1105
|
+
The validator receives the field value and the current payload/object
|
|
1106
|
+
being validated:
|
|
726
1107
|
|
|
727
1108
|
```js
|
|
728
1109
|
customValidator: (value, payload) => {
|
|
@@ -730,7 +1111,8 @@ customValidator: (value, payload) => {
|
|
|
730
1111
|
};
|
|
731
1112
|
```
|
|
732
1113
|
|
|
733
|
-
The validator must return `true` to pass validation. Any other return
|
|
1114
|
+
The validator must return `true` to pass validation. Any other return
|
|
1115
|
+
value causes validation to fail.
|
|
734
1116
|
|
|
735
1117
|
Example:
|
|
736
1118
|
|
|
@@ -738,11 +1120,15 @@ Example:
|
|
|
738
1120
|
const rules = {
|
|
739
1121
|
username: {
|
|
740
1122
|
mandatory: true,
|
|
1123
|
+
|
|
741
1124
|
type: "string",
|
|
1125
|
+
|
|
742
1126
|
customValidator: (value) => {
|
|
743
1127
|
return !value.toLowerCase().includes("admin");
|
|
744
1128
|
},
|
|
1129
|
+
|
|
745
1130
|
customValidatorCode: "RESERVED_USERNAME",
|
|
1131
|
+
|
|
746
1132
|
customValidatorError: "Username cannot contain admin",
|
|
747
1133
|
},
|
|
748
1134
|
};
|
|
@@ -759,44 +1145,64 @@ const payload = {
|
|
|
759
1145
|
The validation error is:
|
|
760
1146
|
|
|
761
1147
|
```js
|
|
1148
|
+
|
|
762
1149
|
{
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
1150
|
+
|
|
1151
|
+
path: "username",
|
|
1152
|
+
|
|
1153
|
+
code: "RESERVED_USERNAME",
|
|
1154
|
+
|
|
1155
|
+
message: "Username cannot contain admin"
|
|
1156
|
+
|
|
766
1157
|
}
|
|
767
1158
|
```
|
|
768
1159
|
|
|
769
|
-
If `customValidatorCode` and `customValidatorError` are not provided,
|
|
1160
|
+
If `customValidatorCode` and `customValidatorError` are not provided,
|
|
1161
|
+
the default error is:
|
|
770
1162
|
|
|
771
1163
|
```js
|
|
1164
|
+
|
|
772
1165
|
{
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
1166
|
+
|
|
1167
|
+
path: "username",
|
|
1168
|
+
|
|
1169
|
+
code: "CUSTOM_VALIDATION_FAILED",
|
|
1170
|
+
|
|
1171
|
+
message: "Custom validation failed for attribute username"
|
|
1172
|
+
|
|
776
1173
|
}
|
|
777
1174
|
```
|
|
778
1175
|
|
|
779
|
-
The
|
|
1176
|
+
The current payload/object being validated can be used as the second
|
|
1177
|
+
argument when required:
|
|
780
1178
|
|
|
781
1179
|
```js
|
|
782
1180
|
const rules = {
|
|
783
1181
|
limit: {
|
|
784
1182
|
type: "number",
|
|
785
1183
|
},
|
|
1184
|
+
|
|
786
1185
|
amount: {
|
|
787
1186
|
type: "number",
|
|
1187
|
+
|
|
788
1188
|
customValidator: (value, payload) => {
|
|
789
1189
|
return value <= payload.limit;
|
|
790
1190
|
},
|
|
1191
|
+
|
|
791
1192
|
customValidatorCode: "LIMIT_EXCEEDED",
|
|
1193
|
+
|
|
792
1194
|
customValidatorError: "Amount cannot exceed limit",
|
|
793
1195
|
},
|
|
794
1196
|
};
|
|
795
1197
|
```
|
|
796
1198
|
|
|
797
|
-
`customValidator` also works with nested objects and array
|
|
1199
|
+
`customValidator` also works with nested objects and array
|
|
1200
|
+
`elementConstraints`. The generated structured error automatically
|
|
1201
|
+
contains the corresponding nested or array path.
|
|
798
1202
|
|
|
799
|
-
|
|
1203
|
+
Important: `customValidator` is synchronous. An `async`
|
|
1204
|
+
validator or a validator that returns a Promise is not supported and
|
|
1205
|
+
throws an error. Asynchronous validation is not part of this feature.
|
|
800
1206
|
|
|
801
1207
|
Error code when no custom code is provided: `CUSTOM_VALIDATION_FAILED`
|
|
802
1208
|
|
|
@@ -816,6 +1222,10 @@ EMPTY_OBJECT_NOT_ALLOWED
|
|
|
816
1222
|
|
|
817
1223
|
EMPTY_ARRAY_NOT_ALLOWED
|
|
818
1224
|
|
|
1225
|
+
MIN_ITEMS
|
|
1226
|
+
|
|
1227
|
+
MAX_ITEMS
|
|
1228
|
+
|
|
819
1229
|
INVALID_ARRAY_ELEMENT
|
|
820
1230
|
|
|
821
1231
|
REGEX_MISMATCH
|
|
@@ -852,6 +1262,7 @@ MAX_VALUE
|
|
|
852
1262
|
|
|
853
1263
|
OUT_OF_RANGE
|
|
854
1264
|
|
|
1265
|
+
CUSTOM_VALIDATION_FAILED
|
|
855
1266
|
```
|
|
856
1267
|
|
|
857
1268
|
These codes are designed for programmatic handling while `message`
|
|
@@ -866,20 +1277,19 @@ const result = perfectPayload(payload, validationRules);
|
|
|
866
1277
|
|
|
867
1278
|
if (!result.valid) {
|
|
868
1279
|
|
|
869
|
-
|
|
1280
|
+
const emailError = result.errors.find(
|
|
870
1281
|
|
|
871
|
-
|
|
1282
|
+
(error) => error.code === "INVALID_EMAIL",
|
|
872
1283
|
|
|
873
|
-
|
|
1284
|
+
);
|
|
874
1285
|
|
|
875
|
-
|
|
1286
|
+
if (emailError) {
|
|
876
1287
|
|
|
877
|
-
|
|
1288
|
+
**// Handle invalid email**
|
|
878
1289
|
|
|
879
|
-
|
|
1290
|
+
}
|
|
880
1291
|
|
|
881
1292
|
}
|
|
882
|
-
|
|
883
1293
|
```
|
|
884
1294
|
|
|
885
1295
|
## Custom Error Messages
|
|
@@ -894,14 +1304,13 @@ keeping the same structured error format:
|
|
|
894
1304
|
|
|
895
1305
|
{
|
|
896
1306
|
|
|
897
|
-
|
|
1307
|
+
path: "email",
|
|
898
1308
|
|
|
899
|
-
|
|
1309
|
+
code: "INVALID_EMAIL",
|
|
900
1310
|
|
|
901
|
-
|
|
1311
|
+
message: "Email address is invalid"
|
|
902
1312
|
|
|
903
1313
|
}
|
|
904
|
-
|
|
905
1314
|
```
|
|
906
1315
|
|
|
907
1316
|
Example:
|
|
@@ -926,14 +1335,13 @@ If `email` is missing:
|
|
|
926
1335
|
|
|
927
1336
|
{
|
|
928
1337
|
|
|
929
|
-
|
|
1338
|
+
path: "email",
|
|
930
1339
|
|
|
931
|
-
|
|
1340
|
+
code: "REQUIRED",
|
|
932
1341
|
|
|
933
|
-
|
|
1342
|
+
message: "Email is required"
|
|
934
1343
|
|
|
935
1344
|
}
|
|
936
|
-
|
|
937
1345
|
```
|
|
938
1346
|
|
|
939
1347
|
If `email` is present but invalid:
|
|
@@ -942,14 +1350,13 @@ If `email` is present but invalid:
|
|
|
942
1350
|
|
|
943
1351
|
{
|
|
944
1352
|
|
|
945
|
-
|
|
1353
|
+
path: "email",
|
|
946
1354
|
|
|
947
|
-
|
|
1355
|
+
code: "INVALID_EMAIL",
|
|
948
1356
|
|
|
949
|
-
|
|
1357
|
+
message: "Email address is invalid"
|
|
950
1358
|
|
|
951
1359
|
}
|
|
952
|
-
|
|
953
1360
|
```
|
|
954
1361
|
|
|
955
1362
|
### Supported Custom Error Properties
|
|
@@ -1036,56 +1443,55 @@ Example result:
|
|
|
1036
1443
|
|
|
1037
1444
|
{
|
|
1038
1445
|
|
|
1039
|
-
|
|
1446
|
+
statusCode: 400,
|
|
1040
1447
|
|
|
1041
|
-
|
|
1448
|
+
valid: false,
|
|
1042
1449
|
|
|
1043
|
-
|
|
1450
|
+
message:
|
|
1044
1451
|
|
|
1045
|
-
|
|
1452
|
+
"One or more attribute values are invalid",
|
|
1046
1453
|
|
|
1047
|
-
|
|
1454
|
+
errors: [
|
|
1048
1455
|
|
|
1049
|
-
|
|
1456
|
+
{
|
|
1050
1457
|
|
|
1051
|
-
|
|
1458
|
+
path: "username",
|
|
1052
1459
|
|
|
1053
|
-
|
|
1460
|
+
code: "MIN_LENGTH",
|
|
1054
1461
|
|
|
1055
|
-
|
|
1462
|
+
message:
|
|
1056
1463
|
|
|
1057
|
-
|
|
1464
|
+
"Username must contain at least 3 characters"
|
|
1058
1465
|
|
|
1059
|
-
|
|
1466
|
+
},
|
|
1060
1467
|
|
|
1061
|
-
|
|
1468
|
+
{
|
|
1062
1469
|
|
|
1063
|
-
|
|
1470
|
+
path: "age",
|
|
1064
1471
|
|
|
1065
|
-
|
|
1472
|
+
code: "MIN_VALUE",
|
|
1066
1473
|
|
|
1067
|
-
|
|
1474
|
+
message:
|
|
1068
1475
|
|
|
1069
|
-
|
|
1476
|
+
"Age must be at least 18"
|
|
1070
1477
|
|
|
1071
|
-
|
|
1478
|
+
},
|
|
1072
1479
|
|
|
1073
|
-
|
|
1480
|
+
{
|
|
1074
1481
|
|
|
1075
|
-
|
|
1482
|
+
path: "score",
|
|
1076
1483
|
|
|
1077
|
-
|
|
1484
|
+
code: "OUT_OF_RANGE",
|
|
1078
1485
|
|
|
1079
|
-
|
|
1486
|
+
message:
|
|
1080
1487
|
|
|
1081
|
-
|
|
1488
|
+
"Score must be between 0 and 100"
|
|
1082
1489
|
|
|
1083
|
-
|
|
1490
|
+
}
|
|
1084
1491
|
|
|
1085
|
-
|
|
1492
|
+
]
|
|
1086
1493
|
|
|
1087
1494
|
}
|
|
1088
|
-
|
|
1089
1495
|
```
|
|
1090
1496
|
|
|
1091
1497
|
### Custom Messages and Error Codes
|
|
@@ -1114,14 +1520,13 @@ Still returns:
|
|
|
1114
1520
|
|
|
1115
1521
|
{
|
|
1116
1522
|
|
|
1117
|
-
|
|
1523
|
+
path: "age",
|
|
1118
1524
|
|
|
1119
|
-
|
|
1525
|
+
code: "MIN_VALUE",
|
|
1120
1526
|
|
|
1121
|
-
|
|
1527
|
+
message: "You must be 18 or older"
|
|
1122
1528
|
|
|
1123
1529
|
}
|
|
1124
|
-
|
|
1125
1530
|
```
|
|
1126
1531
|
|
|
1127
1532
|
This makes it possible to:
|
|
@@ -1166,24 +1571,23 @@ When validation succeeds, `validatedPayload` is automatically added:
|
|
|
1166
1571
|
|
|
1167
1572
|
{
|
|
1168
1573
|
|
|
1169
|
-
|
|
1574
|
+
statusCode: 201,
|
|
1170
1575
|
|
|
1171
|
-
|
|
1576
|
+
valid: true,
|
|
1172
1577
|
|
|
1173
|
-
|
|
1578
|
+
message: "Payload validated successfully",
|
|
1174
1579
|
|
|
1175
|
-
|
|
1580
|
+
validatedPayload: {
|
|
1176
1581
|
|
|
1177
|
-
|
|
1582
|
+
name: "Kiran",
|
|
1178
1583
|
|
|
1179
|
-
|
|
1584
|
+
email: "kiran@example.com",
|
|
1180
1585
|
|
|
1181
|
-
|
|
1586
|
+
age: 29
|
|
1182
1587
|
|
|
1183
|
-
|
|
1588
|
+
}
|
|
1184
1589
|
|
|
1185
1590
|
}
|
|
1186
|
-
|
|
1187
1591
|
```
|
|
1188
1592
|
|
|
1189
1593
|
### Custom Invalid Response
|
|
@@ -1216,30 +1620,29 @@ When validation fails, `errors` is automatically added:
|
|
|
1216
1620
|
|
|
1217
1621
|
{
|
|
1218
1622
|
|
|
1219
|
-
|
|
1623
|
+
statusCode: 422,
|
|
1220
1624
|
|
|
1221
|
-
|
|
1625
|
+
valid: false,
|
|
1222
1626
|
|
|
1223
|
-
|
|
1627
|
+
message: "Payload validation failed",
|
|
1224
1628
|
|
|
1225
|
-
|
|
1629
|
+
errors: [
|
|
1226
1630
|
|
|
1227
|
-
|
|
1631
|
+
{
|
|
1228
1632
|
|
|
1229
|
-
|
|
1633
|
+
path: "email",
|
|
1230
1634
|
|
|
1231
|
-
|
|
1635
|
+
code: "INVALID_EMAIL",
|
|
1232
1636
|
|
|
1233
|
-
|
|
1637
|
+
message:
|
|
1234
1638
|
|
|
1235
|
-
|
|
1639
|
+
"Invalid email format for attribute email"
|
|
1236
1640
|
|
|
1237
|
-
|
|
1641
|
+
}
|
|
1238
1642
|
|
|
1239
|
-
|
|
1643
|
+
]
|
|
1240
1644
|
|
|
1241
1645
|
}
|
|
1242
|
-
|
|
1243
1646
|
```
|
|
1244
1647
|
|
|
1245
1648
|
### Custom Valid and Invalid Responses Together
|
|
@@ -1279,7 +1682,6 @@ automatically adds either:
|
|
|
1279
1682
|
```text
|
|
1280
1683
|
|
|
1281
1684
|
validatedPayload
|
|
1282
|
-
|
|
1283
1685
|
```
|
|
1284
1686
|
|
|
1285
1687
|
for successful validation, or:
|
|
@@ -1287,7 +1689,6 @@ for successful validation, or:
|
|
|
1287
1689
|
```text
|
|
1288
1690
|
|
|
1289
1691
|
errors
|
|
1290
|
-
|
|
1291
1692
|
```
|
|
1292
1693
|
|
|
1293
1694
|
for failed validation.
|
|
@@ -1302,18 +1703,17 @@ is:
|
|
|
1302
1703
|
|
|
1303
1704
|
{
|
|
1304
1705
|
|
|
1305
|
-
|
|
1706
|
+
statusCode: 200,
|
|
1306
1707
|
|
|
1307
|
-
|
|
1708
|
+
valid: true,
|
|
1308
1709
|
|
|
1309
|
-
|
|
1710
|
+
validatedPayload: {
|
|
1310
1711
|
|
|
1311
|
-
|
|
1712
|
+
**// validated fields**
|
|
1312
1713
|
|
|
1313
|
-
|
|
1714
|
+
}
|
|
1314
1715
|
|
|
1315
1716
|
}
|
|
1316
|
-
|
|
1317
1717
|
```
|
|
1318
1718
|
|
|
1319
1719
|
The default invalid response is:
|
|
@@ -1322,28 +1722,27 @@ The default invalid response is:
|
|
|
1322
1722
|
|
|
1323
1723
|
{
|
|
1324
1724
|
|
|
1325
|
-
|
|
1725
|
+
statusCode: 400,
|
|
1326
1726
|
|
|
1327
|
-
|
|
1727
|
+
valid: false,
|
|
1328
1728
|
|
|
1329
|
-
|
|
1729
|
+
message: "One or more attribute values are invalid",
|
|
1330
1730
|
|
|
1331
|
-
|
|
1731
|
+
errors: [
|
|
1332
1732
|
|
|
1333
|
-
|
|
1733
|
+
{
|
|
1334
1734
|
|
|
1335
|
-
|
|
1735
|
+
path: "field",
|
|
1336
1736
|
|
|
1337
|
-
|
|
1737
|
+
code: "ERROR_CODE",
|
|
1338
1738
|
|
|
1339
|
-
|
|
1739
|
+
message: "Validation error message"
|
|
1340
1740
|
|
|
1341
|
-
|
|
1741
|
+
}
|
|
1342
1742
|
|
|
1343
|
-
|
|
1743
|
+
]
|
|
1344
1744
|
|
|
1345
1745
|
}
|
|
1346
|
-
|
|
1347
1746
|
```
|
|
1348
1747
|
|
|
1349
1748
|
## Nested Objects and Array Field Paths
|
|
@@ -1372,14 +1771,13 @@ An error can be returned as:
|
|
|
1372
1771
|
|
|
1373
1772
|
{
|
|
1374
1773
|
|
|
1375
|
-
|
|
1774
|
+
path: "email",
|
|
1376
1775
|
|
|
1377
|
-
|
|
1776
|
+
code: "INVALID_EMAIL",
|
|
1378
1777
|
|
|
1379
|
-
|
|
1778
|
+
message: "Invalid email format for attribute email"
|
|
1380
1779
|
|
|
1381
1780
|
}
|
|
1382
|
-
|
|
1383
1781
|
```
|
|
1384
1782
|
|
|
1385
1783
|
### Nested Object
|
|
@@ -1436,16 +1834,15 @@ its complete nested path:
|
|
|
1436
1834
|
|
|
1437
1835
|
{
|
|
1438
1836
|
|
|
1439
|
-
|
|
1837
|
+
path: "address.location.latitude",
|
|
1440
1838
|
|
|
1441
|
-
|
|
1839
|
+
code: "INVALID_TYPE",
|
|
1442
1840
|
|
|
1443
|
-
|
|
1841
|
+
message:
|
|
1444
1842
|
|
|
1445
|
-
|
|
1843
|
+
"Invalid type for attribute address.location.latitude, required number value"
|
|
1446
1844
|
|
|
1447
1845
|
}
|
|
1448
|
-
|
|
1449
1846
|
```
|
|
1450
1847
|
|
|
1451
1848
|
Nested paths use dot notation:
|
|
@@ -1457,7 +1854,6 @@ address.city
|
|
|
1457
1854
|
address.location.latitude
|
|
1458
1855
|
|
|
1459
1856
|
address.location.longitude
|
|
1460
|
-
|
|
1461
1857
|
```
|
|
1462
1858
|
|
|
1463
1859
|
### Array Elements
|
|
@@ -1492,16 +1888,15 @@ The invalid third element is reported as:
|
|
|
1492
1888
|
|
|
1493
1889
|
{
|
|
1494
1890
|
|
|
1495
|
-
|
|
1891
|
+
path: "marks[2]",
|
|
1496
1892
|
|
|
1497
|
-
|
|
1893
|
+
code: "OUT_OF_RANGE",
|
|
1498
1894
|
|
|
1499
|
-
|
|
1895
|
+
message:
|
|
1500
1896
|
|
|
1501
|
-
|
|
1897
|
+
"Attribute marks[2] should have a value between 0 and 100"
|
|
1502
1898
|
|
|
1503
1899
|
}
|
|
1504
|
-
|
|
1505
1900
|
```
|
|
1506
1901
|
|
|
1507
1902
|
Array paths use zero-based indexes:
|
|
@@ -1513,9 +1908,10 @@ marks[0]
|
|
|
1513
1908
|
marks[1]
|
|
1514
1909
|
|
|
1515
1910
|
marks[2]
|
|
1516
|
-
|
|
1517
1911
|
```
|
|
1518
1912
|
|
|
1913
|
+
Array-level constraints such as `minItems` and `maxItems` report the path of the array itself. For nested arrays, the complete parent path is retained, for example `orders[1].items`.
|
|
1914
|
+
|
|
1519
1915
|
### Nested Fields Inside Arrays
|
|
1520
1916
|
|
|
1521
1917
|
Paths can also identify fields inside array elements.
|
|
@@ -1529,7 +1925,6 @@ products[0].quantity
|
|
|
1529
1925
|
products[1].quantity
|
|
1530
1926
|
|
|
1531
1927
|
products[2].price
|
|
1532
|
-
|
|
1533
1928
|
```
|
|
1534
1929
|
|
|
1535
1930
|
This provides enough information for consumers to identify the exact
|
|
@@ -1574,391 +1969,387 @@ Result:
|
|
|
1574
1969
|
|
|
1575
1970
|
{
|
|
1576
1971
|
|
|
1577
|
-
|
|
1972
|
+
"email": "Invalid email format for attribute email",
|
|
1578
1973
|
|
|
1579
|
-
|
|
1974
|
+
"address.location.latitude": "Invalid type for attribute address.location.latitude, required number value",
|
|
1580
1975
|
|
|
1581
|
-
|
|
1976
|
+
"marks[2]": "Attribute marks[2] should have a value between 0 and 100"
|
|
1582
1977
|
|
|
1583
1978
|
}
|
|
1584
|
-
|
|
1585
1979
|
```
|
|
1586
1980
|
|
|
1587
|
-
## Examples
|
|
1981
|
+
## Examples and Usage
|
|
1588
1982
|
|
|
1589
1983
|
### Sample Validation Rule
|
|
1590
1984
|
|
|
1591
1985
|
sample-1
|
|
1592
1986
|
|
|
1593
|
-
```
|
|
1987
|
+
```js
|
|
1594
1988
|
|
|
1595
1989
|
{
|
|
1596
1990
|
|
|
1597
|
-
|
|
1991
|
+
firstName: {
|
|
1598
1992
|
|
|
1599
|
-
|
|
1993
|
+
mandatory: true,
|
|
1600
1994
|
|
|
1601
|
-
|
|
1995
|
+
allowNull: false,
|
|
1602
1996
|
|
|
1603
|
-
|
|
1997
|
+
type: "string",
|
|
1604
1998
|
|
|
1605
|
-
|
|
1999
|
+
minLength: 3,
|
|
1606
2000
|
|
|
1607
|
-
|
|
2001
|
+
minLengthError: "First name must have minimum 3 characters."
|
|
1608
2002
|
|
|
1609
|
-
|
|
2003
|
+
},
|
|
1610
2004
|
|
|
1611
|
-
|
|
2005
|
+
lastName: {
|
|
1612
2006
|
|
|
1613
|
-
|
|
2007
|
+
mandatory: false,
|
|
1614
2008
|
|
|
1615
|
-
|
|
2009
|
+
allowNull: true,
|
|
1616
2010
|
|
|
1617
|
-
|
|
2011
|
+
type: "string",
|
|
1618
2012
|
|
|
1619
|
-
|
|
2013
|
+
},
|
|
1620
2014
|
|
|
1621
|
-
|
|
2015
|
+
email: {
|
|
1622
2016
|
|
|
1623
|
-
|
|
2017
|
+
mandatory: true,
|
|
1624
2018
|
|
|
1625
|
-
|
|
2019
|
+
allowNull: false,
|
|
1626
2020
|
|
|
1627
|
-
|
|
2021
|
+
type: "email",
|
|
1628
2022
|
|
|
1629
|
-
|
|
2023
|
+
},
|
|
1630
2024
|
|
|
1631
|
-
|
|
2025
|
+
phone: {
|
|
1632
2026
|
|
|
1633
|
-
|
|
2027
|
+
mandatory: true,
|
|
1634
2028
|
|
|
1635
|
-
|
|
2029
|
+
allowNull: false,
|
|
1636
2030
|
|
|
1637
|
-
|
|
2031
|
+
type: "string",
|
|
1638
2032
|
|
|
1639
|
-
|
|
2033
|
+
},
|
|
1640
2034
|
|
|
1641
|
-
|
|
2035
|
+
age: {
|
|
1642
2036
|
|
|
1643
|
-
|
|
2037
|
+
mandatory: false,
|
|
1644
2038
|
|
|
1645
|
-
|
|
2039
|
+
type: "number",
|
|
1646
2040
|
|
|
1647
|
-
|
|
2041
|
+
min: 1,
|
|
1648
2042
|
|
|
1649
|
-
|
|
2043
|
+
max: 120,
|
|
1650
2044
|
|
|
1651
|
-
|
|
2045
|
+
},
|
|
1652
2046
|
|
|
1653
2047
|
};
|
|
1654
|
-
|
|
1655
2048
|
```
|
|
1656
2049
|
|
|
1657
2050
|
sample-2
|
|
1658
2051
|
|
|
1659
|
-
```
|
|
2052
|
+
```js
|
|
1660
2053
|
|
|
1661
2054
|
{
|
|
1662
2055
|
|
|
1663
|
-
|
|
2056
|
+
id: {
|
|
1664
2057
|
|
|
1665
|
-
|
|
2058
|
+
mandatory: true,
|
|
1666
2059
|
|
|
1667
|
-
|
|
2060
|
+
allowNull: true,
|
|
1668
2061
|
|
|
1669
|
-
|
|
2062
|
+
type: "uuidv4",
|
|
1670
2063
|
|
|
1671
|
-
|
|
2064
|
+
},
|
|
1672
2065
|
|
|
1673
|
-
|
|
2066
|
+
batchId: {
|
|
1674
2067
|
|
|
1675
|
-
|
|
2068
|
+
mandatory: true,
|
|
1676
2069
|
|
|
1677
|
-
|
|
2070
|
+
allowNull: true,
|
|
1678
2071
|
|
|
1679
|
-
|
|
2072
|
+
type: "objectId",
|
|
1680
2073
|
|
|
1681
|
-
|
|
2074
|
+
},
|
|
1682
2075
|
|
|
1683
|
-
|
|
2076
|
+
firstName: {
|
|
1684
2077
|
|
|
1685
|
-
|
|
2078
|
+
mandatory: true,
|
|
1686
2079
|
|
|
1687
|
-
|
|
2080
|
+
type: "string",
|
|
1688
2081
|
|
|
1689
|
-
|
|
2082
|
+
minLength: 3,
|
|
1690
2083
|
|
|
1691
|
-
|
|
2084
|
+
},
|
|
1692
2085
|
|
|
1693
|
-
|
|
2086
|
+
lastName: {
|
|
1694
2087
|
|
|
1695
|
-
|
|
2088
|
+
mandatory: false,
|
|
1696
2089
|
|
|
1697
|
-
|
|
2090
|
+
allowNull: true,
|
|
1698
2091
|
|
|
1699
|
-
|
|
2092
|
+
type: "string",
|
|
1700
2093
|
|
|
1701
|
-
|
|
2094
|
+
},
|
|
1702
2095
|
|
|
1703
|
-
|
|
2096
|
+
age: {
|
|
1704
2097
|
|
|
1705
|
-
|
|
2098
|
+
type: "number",
|
|
1706
2099
|
|
|
1707
|
-
|
|
2100
|
+
min: 0.1,
|
|
1708
2101
|
|
|
1709
|
-
|
|
2102
|
+
max: 120,
|
|
1710
2103
|
|
|
1711
|
-
|
|
2104
|
+
},
|
|
1712
2105
|
|
|
1713
|
-
|
|
2106
|
+
isAdult: {
|
|
1714
2107
|
|
|
1715
|
-
|
|
2108
|
+
type: "boolean",
|
|
1716
2109
|
|
|
1717
|
-
|
|
2110
|
+
},
|
|
1718
2111
|
|
|
1719
|
-
|
|
2112
|
+
totalWins: {
|
|
1720
2113
|
|
|
1721
|
-
|
|
2114
|
+
type: "number",
|
|
1722
2115
|
|
|
1723
|
-
|
|
2116
|
+
min: 0,
|
|
1724
2117
|
|
|
1725
|
-
|
|
2118
|
+
preventDecimal: true,
|
|
1726
2119
|
|
|
1727
|
-
|
|
2120
|
+
},
|
|
1728
2121
|
|
|
1729
|
-
|
|
2122
|
+
email: {
|
|
1730
2123
|
|
|
1731
|
-
|
|
2124
|
+
regex: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$/,
|
|
1732
2125
|
|
|
1733
|
-
|
|
2126
|
+
},
|
|
1734
2127
|
|
|
1735
|
-
|
|
2128
|
+
githubLink: {
|
|
1736
2129
|
|
|
1737
|
-
|
|
2130
|
+
type: "url",
|
|
1738
2131
|
|
|
1739
|
-
|
|
2132
|
+
},
|
|
1740
2133
|
|
|
1741
|
-
|
|
2134
|
+
accountStatus: {
|
|
1742
2135
|
|
|
1743
|
-
|
|
2136
|
+
type: "enum",
|
|
1744
2137
|
|
|
1745
|
-
|
|
2138
|
+
enumValues: ["Active", "Inactive", 200],
|
|
1746
2139
|
|
|
1747
|
-
|
|
2140
|
+
},
|
|
1748
2141
|
|
|
1749
|
-
|
|
2142
|
+
marks: {
|
|
1750
2143
|
|
|
1751
|
-
|
|
2144
|
+
range: "0-100",
|
|
1752
2145
|
|
|
1753
|
-
|
|
2146
|
+
},
|
|
1754
2147
|
|
|
1755
|
-
|
|
2148
|
+
allMarks: {
|
|
1756
2149
|
|
|
1757
|
-
|
|
2150
|
+
type: "array",
|
|
1758
2151
|
|
|
1759
|
-
|
|
2152
|
+
allowEmptyArray: false,
|
|
1760
2153
|
|
|
1761
|
-
|
|
2154
|
+
elementConstraints: {
|
|
1762
2155
|
|
|
1763
|
-
|
|
2156
|
+
type: "number",
|
|
1764
2157
|
|
|
1765
|
-
|
|
2158
|
+
allowNull: false,
|
|
1766
2159
|
|
|
1767
|
-
|
|
2160
|
+
range: "0-100",
|
|
1768
2161
|
|
|
1769
|
-
|
|
2162
|
+
},
|
|
1770
2163
|
|
|
1771
|
-
|
|
2164
|
+
},
|
|
1772
2165
|
|
|
1773
|
-
|
|
2166
|
+
totalScore: {
|
|
1774
2167
|
|
|
1775
|
-
|
|
2168
|
+
type: "number",
|
|
1776
2169
|
|
|
1777
|
-
|
|
2170
|
+
dependency: {
|
|
1778
2171
|
|
|
1779
|
-
|
|
2172
|
+
result: {
|
|
1780
2173
|
|
|
1781
|
-
|
|
2174
|
+
setDependencyRule: (totalScore, result) => {
|
|
1782
2175
|
|
|
1783
|
-
|
|
2176
|
+
return { mandatory: true, allowNull: false, type: "string" };
|
|
1784
2177
|
|
|
1785
|
-
|
|
2178
|
+
},
|
|
1786
2179
|
|
|
1787
|
-
|
|
2180
|
+
},
|
|
1788
2181
|
|
|
1789
|
-
|
|
2182
|
+
},
|
|
1790
2183
|
|
|
1791
|
-
|
|
2184
|
+
},
|
|
1792
2185
|
|
|
1793
|
-
|
|
2186
|
+
result: {
|
|
1794
2187
|
|
|
1795
|
-
|
|
2188
|
+
type: "string",
|
|
1796
2189
|
|
|
1797
|
-
|
|
2190
|
+
dependency: {
|
|
1798
2191
|
|
|
1799
|
-
|
|
2192
|
+
totalScore: {
|
|
1800
2193
|
|
|
1801
|
-
|
|
2194
|
+
setDependencyRule: (result, totalScore) => {
|
|
1802
2195
|
|
|
1803
|
-
|
|
2196
|
+
return { mandatory: true, allowNull: false, type: "number" };
|
|
1804
2197
|
|
|
1805
|
-
|
|
2198
|
+
},
|
|
1806
2199
|
|
|
1807
|
-
|
|
2200
|
+
},
|
|
1808
2201
|
|
|
1809
|
-
|
|
2202
|
+
},
|
|
1810
2203
|
|
|
1811
|
-
|
|
2204
|
+
},
|
|
1812
2205
|
|
|
1813
|
-
|
|
2206
|
+
minSalary: {
|
|
1814
2207
|
|
|
1815
|
-
|
|
2208
|
+
mandatory: true,
|
|
1816
2209
|
|
|
1817
|
-
|
|
2210
|
+
min: 1,
|
|
1818
2211
|
|
|
1819
|
-
|
|
2212
|
+
type: "number",
|
|
1820
2213
|
|
|
1821
|
-
|
|
2214
|
+
dependency: {
|
|
1822
2215
|
|
|
1823
|
-
|
|
2216
|
+
maxSalary: {
|
|
1824
2217
|
|
|
1825
|
-
|
|
2218
|
+
setDependencyRule: (minSalary, maxSalary) => {
|
|
1826
2219
|
|
|
1827
|
-
|
|
2220
|
+
return {
|
|
1828
2221
|
|
|
1829
|
-
|
|
2222
|
+
mandatory: true,
|
|
1830
2223
|
|
|
1831
|
-
|
|
2224
|
+
min: minSalary + 1,
|
|
1832
2225
|
|
|
1833
|
-
|
|
2226
|
+
minError: "maxSalary must be more than minSalary",
|
|
1834
2227
|
|
|
1835
|
-
|
|
2228
|
+
};
|
|
1836
2229
|
|
|
1837
|
-
|
|
2230
|
+
},
|
|
1838
2231
|
|
|
1839
|
-
|
|
2232
|
+
},
|
|
1840
2233
|
|
|
1841
|
-
|
|
2234
|
+
},
|
|
1842
2235
|
|
|
1843
|
-
|
|
2236
|
+
},
|
|
1844
2237
|
|
|
1845
|
-
|
|
2238
|
+
maxSalary: {
|
|
1846
2239
|
|
|
1847
|
-
|
|
2240
|
+
dependency: {
|
|
1848
2241
|
|
|
1849
|
-
|
|
2242
|
+
minSalary: {
|
|
1850
2243
|
|
|
1851
|
-
|
|
2244
|
+
setDependencyRule: (maxSalary, minSalary) => {
|
|
1852
2245
|
|
|
1853
|
-
|
|
2246
|
+
return {
|
|
1854
2247
|
|
|
1855
|
-
|
|
2248
|
+
mandatory: true,
|
|
1856
2249
|
|
|
1857
|
-
|
|
2250
|
+
max: maxSalary - 1,
|
|
1858
2251
|
|
|
1859
|
-
|
|
2252
|
+
maxError: "minSalary must be less than maxSalary",
|
|
1860
2253
|
|
|
1861
|
-
|
|
2254
|
+
};
|
|
1862
2255
|
|
|
1863
|
-
|
|
2256
|
+
},
|
|
1864
2257
|
|
|
1865
|
-
|
|
2258
|
+
},
|
|
1866
2259
|
|
|
1867
|
-
|
|
2260
|
+
},
|
|
1868
2261
|
|
|
1869
|
-
|
|
2262
|
+
},
|
|
1870
2263
|
|
|
1871
|
-
|
|
2264
|
+
address: {
|
|
1872
2265
|
|
|
1873
|
-
|
|
2266
|
+
mandatory: true,
|
|
1874
2267
|
|
|
1875
|
-
|
|
2268
|
+
type: "object",
|
|
1876
2269
|
|
|
1877
|
-
|
|
2270
|
+
allowEmptyObject: false,
|
|
1878
2271
|
|
|
1879
|
-
|
|
2272
|
+
objectAttr: {
|
|
1880
2273
|
|
|
1881
|
-
|
|
2274
|
+
country: { mandatory: true, type: "string" },
|
|
1882
2275
|
|
|
1883
|
-
|
|
2276
|
+
state: {
|
|
1884
2277
|
|
|
1885
|
-
|
|
2278
|
+
mandatory: true,
|
|
1886
2279
|
|
|
1887
|
-
|
|
2280
|
+
type: "string",
|
|
1888
2281
|
|
|
1889
|
-
|
|
2282
|
+
},
|
|
1890
2283
|
|
|
1891
|
-
|
|
2284
|
+
city: {},
|
|
1892
2285
|
|
|
1893
|
-
|
|
2286
|
+
zip: {
|
|
1894
2287
|
|
|
1895
|
-
|
|
2288
|
+
mandatory: true,
|
|
1896
2289
|
|
|
1897
|
-
|
|
2290
|
+
type: "string",
|
|
1898
2291
|
|
|
1899
|
-
|
|
2292
|
+
},
|
|
1900
2293
|
|
|
1901
|
-
|
|
2294
|
+
position: {
|
|
1902
2295
|
|
|
1903
|
-
|
|
2296
|
+
mandatory: true,
|
|
1904
2297
|
|
|
1905
|
-
|
|
2298
|
+
type: "object",
|
|
1906
2299
|
|
|
1907
|
-
|
|
2300
|
+
allowEmptyObject: false,
|
|
1908
2301
|
|
|
1909
|
-
|
|
2302
|
+
objectAttr: {
|
|
1910
2303
|
|
|
1911
|
-
|
|
2304
|
+
lattitude: { mandatory: true, type: "number" },
|
|
1912
2305
|
|
|
1913
|
-
|
|
2306
|
+
longitude: {
|
|
1914
2307
|
|
|
1915
|
-
|
|
2308
|
+
mandatory: true,
|
|
1916
2309
|
|
|
1917
|
-
|
|
2310
|
+
type: "number",
|
|
1918
2311
|
|
|
1919
|
-
|
|
2312
|
+
},
|
|
1920
2313
|
|
|
1921
|
-
|
|
2314
|
+
},
|
|
1922
2315
|
|
|
1923
|
-
|
|
2316
|
+
},
|
|
1924
2317
|
|
|
1925
|
-
|
|
2318
|
+
},
|
|
1926
2319
|
|
|
1927
|
-
|
|
2320
|
+
},
|
|
1928
2321
|
|
|
1929
2322
|
}
|
|
1930
|
-
|
|
1931
2323
|
```
|
|
1932
2324
|
|
|
1933
2325
|
### Usage
|
|
1934
2326
|
|
|
1935
|
-
####
|
|
2327
|
+
#### Creating a route with payload validation middleware
|
|
1936
2328
|
|
|
1937
|
-
```
|
|
2329
|
+
```js
|
|
1938
2330
|
|
|
1939
|
-
|
|
2331
|
+
**// validatePayload is the middleware that invokes perfectPayload()**
|
|
1940
2332
|
|
|
1941
2333
|
router.post(
|
|
1942
2334
|
|
|
1943
|
-
|
|
2335
|
+
"/payload-validation",
|
|
1944
2336
|
|
|
1945
|
-
|
|
2337
|
+
validatePayload({ rule: <your validation rule json object> }),
|
|
1946
2338
|
|
|
1947
|
-
|
|
2339
|
+
(req, res) => res.send("OK")
|
|
1948
2340
|
|
|
1949
2341
|
);
|
|
1950
|
-
|
|
1951
2342
|
```
|
|
1952
2343
|
|
|
1953
|
-
####
|
|
2344
|
+
#### ES Modules middleware example
|
|
1954
2345
|
|
|
1955
|
-
```
|
|
1956
|
-
import {
|
|
2346
|
+
```js
|
|
2347
|
+
import { perfectPayload } from "perfect-payload";
|
|
1957
2348
|
|
|
1958
2349
|
export const validatePayload = ({ rule }) => {
|
|
1959
2350
|
return (req, res, next) => {
|
|
1960
2351
|
try {
|
|
1961
|
-
const { statusCode, ...response } =
|
|
2352
|
+
const { statusCode, ...response } = perfectPayload(req?.body, rule);
|
|
1962
2353
|
|
|
1963
2354
|
if (+statusCode >= 200 && +statusCode <= 299) {
|
|
1964
2355
|
req.validatedBody = response?.validatedPayload;
|
|
@@ -1974,15 +2365,15 @@ export const validatePayload = ({ rule }) => {
|
|
|
1974
2365
|
};
|
|
1975
2366
|
```
|
|
1976
2367
|
|
|
1977
|
-
####
|
|
2368
|
+
#### CommonJS middleware example
|
|
1978
2369
|
|
|
1979
|
-
```
|
|
2370
|
+
```js
|
|
1980
2371
|
function validatePayload({ rule }) {
|
|
1981
2372
|
return async (req, res, next) => {
|
|
1982
2373
|
try {
|
|
1983
|
-
const {
|
|
2374
|
+
const { perfectPayload } = await import("perfect-payload");
|
|
1984
2375
|
|
|
1985
|
-
const { statusCode, ...response } =
|
|
2376
|
+
const { statusCode, ...response } = perfectPayload(req?.body, rule);
|
|
1986
2377
|
|
|
1987
2378
|
if (+statusCode >= 200 && +statusCode <= 299) {
|
|
1988
2379
|
req.validatedBody = response?.validatedPayload;
|
|
@@ -2002,7 +2393,7 @@ function validatePayload({ rule }) {
|
|
|
2002
2393
|
module.exports = { validatePayload };
|
|
2003
2394
|
```
|
|
2004
2395
|
|
|
2005
|
-
|
|
2396
|
+
---
|
|
2006
2397
|
|
|
2007
2398
|
This documentation provides a comprehensive guide to using the data
|
|
2008
2399
|
|