perfect-payload 1.2.3 → 1.4.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 +928 -137
- package/index.js +140 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
A lightweight JavaScript payload validation utility for validating API
|
|
4
4
|
and JSON payloads with simple rule-based configuration.
|
|
5
5
|
|
|
6
|
+
`perfect-payload` supports structured validation errors, nested field
|
|
7
|
+
paths, synchronous custom validators, and synchronous payload
|
|
8
|
+
transformation/sanitization while keeping the validation schema simple.
|
|
9
|
+
|
|
10
|
+
## Quick Links
|
|
11
|
+
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [Basic Usage](#basic-usage)
|
|
14
|
+
- [Validation Rules](#validation-rules)
|
|
15
|
+
- [Transformations and
|
|
16
|
+
Sanitization](#transformations-and-sanitization)
|
|
17
|
+
- [Custom Validators](#customvalidator)
|
|
18
|
+
- [Error Codes](#error-codes)
|
|
19
|
+
- [Custom Error Messages](#custom-error-messages)
|
|
20
|
+
- [Nested Objects and Array Field
|
|
21
|
+
Paths](#nested-objects-and-array-field-paths)
|
|
22
|
+
- [Examples and Usage](#examples-and-usage)
|
|
23
|
+
- [Legacy API](#legacy-api)
|
|
24
|
+
|
|
6
25
|
## Installation
|
|
7
26
|
|
|
8
27
|
```bash
|
|
@@ -19,24 +38,30 @@ import { perfectPayload } from "perfect-payload";
|
|
|
19
38
|
|
|
20
39
|
const payload = {
|
|
21
40
|
name: "Kiran",
|
|
41
|
+
|
|
22
42
|
email: "kiran@example.com",
|
|
43
|
+
|
|
23
44
|
age: 29,
|
|
24
45
|
};
|
|
25
46
|
|
|
26
47
|
const validationRules = {
|
|
27
48
|
name: {
|
|
28
49
|
mandatory: true,
|
|
50
|
+
|
|
29
51
|
type: "string",
|
|
30
52
|
},
|
|
31
53
|
|
|
32
54
|
email: {
|
|
33
55
|
mandatory: true,
|
|
56
|
+
|
|
34
57
|
type: "email",
|
|
35
58
|
},
|
|
36
59
|
|
|
37
60
|
age: {
|
|
38
61
|
mandatory: true,
|
|
62
|
+
|
|
39
63
|
type: "number",
|
|
64
|
+
|
|
40
65
|
min: 18,
|
|
41
66
|
},
|
|
42
67
|
};
|
|
@@ -51,19 +76,31 @@ console.log(result);
|
|
|
51
76
|
```js
|
|
52
77
|
|
|
53
78
|
{
|
|
79
|
+
|
|
54
80
|
statusCode: 200,
|
|
81
|
+
|
|
55
82
|
valid: true,
|
|
83
|
+
|
|
56
84
|
validatedPayload: {
|
|
85
|
+
|
|
57
86
|
name: "Kiran",
|
|
87
|
+
|
|
58
88
|
email: "kiran@example.com",
|
|
89
|
+
|
|
59
90
|
age: 29
|
|
91
|
+
|
|
60
92
|
}
|
|
93
|
+
|
|
61
94
|
}
|
|
62
95
|
```
|
|
63
96
|
|
|
64
|
-
|
|
97
|
+
Note: The validatedPayload contains only the fields
|
|
98
|
+
defined in the
|
|
99
|
+
|
|
65
100
|
schema, automatically filtering out any extra attributes. You can use it
|
|
101
|
+
|
|
66
102
|
to safely overwrite request.body or assign it to a new request property
|
|
103
|
+
|
|
67
104
|
(such as validatedBody, sanitisedData or parsedBody).
|
|
68
105
|
|
|
69
106
|
### Invalid Response
|
|
@@ -71,16 +108,27 @@ to safely overwrite request.body or assign it to a new request property
|
|
|
71
108
|
```js
|
|
72
109
|
|
|
73
110
|
{
|
|
111
|
+
|
|
74
112
|
statusCode: 400,
|
|
113
|
+
|
|
75
114
|
valid: false,
|
|
115
|
+
|
|
76
116
|
message: "One or more attribute values are invalid",
|
|
117
|
+
|
|
77
118
|
errors: [
|
|
119
|
+
|
|
78
120
|
{
|
|
121
|
+
|
|
79
122
|
path: "email",
|
|
123
|
+
|
|
80
124
|
code: "INVALID_EMAIL",
|
|
125
|
+
|
|
81
126
|
message: "Invalid email format for attribute email"
|
|
127
|
+
|
|
82
128
|
}
|
|
129
|
+
|
|
83
130
|
]
|
|
131
|
+
|
|
84
132
|
}
|
|
85
133
|
```
|
|
86
134
|
|
|
@@ -89,17 +137,25 @@ Each error returned by `perfectPayload()` contains:
|
|
|
89
137
|
```js
|
|
90
138
|
|
|
91
139
|
{
|
|
140
|
+
|
|
92
141
|
path: "field.path",
|
|
142
|
+
|
|
93
143
|
code: "ERROR_CODE",
|
|
144
|
+
|
|
94
145
|
message: "Human readable validation message"
|
|
146
|
+
|
|
95
147
|
}
|
|
96
148
|
```
|
|
97
149
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
150
|
+
\- `path` identifies the exact field that failed validation.
|
|
151
|
+
|
|
152
|
+
\- `code` provides a stable machine-readable validation error code.
|
|
153
|
+
|
|
154
|
+
\- `message` provides a human-readable description of the validation
|
|
155
|
+
|
|
156
|
+
failure.
|
|
157
|
+
|
|
158
|
+
\- Submitted payload values are not included in default error messages.
|
|
103
159
|
|
|
104
160
|
## Legacy API
|
|
105
161
|
|
|
@@ -110,9 +166,11 @@ import { perfectPayloadV1 } from "perfect-payload";
|
|
|
110
166
|
```
|
|
111
167
|
|
|
112
168
|
`perfectPayloadV1()` is deprecated and will no longer be supported after
|
|
113
|
-
|
|
169
|
+
|
|
170
|
+
March 31, 2027.
|
|
114
171
|
|
|
115
172
|
Existing applications can continue using it during the migration period,
|
|
173
|
+
|
|
116
174
|
but all new implementations should use:
|
|
117
175
|
|
|
118
176
|
```js
|
|
@@ -131,26 +189,33 @@ while the new `perfectPayload()` API returns structured errors:
|
|
|
131
189
|
errors: [
|
|
132
190
|
{
|
|
133
191
|
path: "email",
|
|
192
|
+
|
|
134
193
|
code: "INVALID_EMAIL",
|
|
194
|
+
|
|
135
195
|
message: "Invalid email format for attribute email",
|
|
136
196
|
},
|
|
137
197
|
];
|
|
138
198
|
```
|
|
139
199
|
|
|
140
|
-
|
|
200
|
+
Note: If an inValidPayloadResponse is provided, the
|
|
201
|
+
system returns
|
|
202
|
+
|
|
141
203
|
it alongside an automatically generated errors property. Do not include
|
|
204
|
+
|
|
142
205
|
your own errors attribute inside the custom inValidPayloadResponse
|
|
206
|
+
|
|
143
207
|
object.
|
|
144
208
|
|
|
145
209
|
## Validation Rules
|
|
146
210
|
|
|
147
|
-
`perfectPayload()` supports
|
|
211
|
+
`perfectPayload()` supports validation, nested-schema,
|
|
212
|
+
custom-validation, and transformation rules.
|
|
148
213
|
|
|
149
214
|
### `mandatory`
|
|
150
215
|
|
|
151
|
-
Marks a field as required
|
|
216
|
+
Marks a field as required. An empty string is also treated as missing.
|
|
152
217
|
|
|
153
|
-
|
|
218
|
+
Default: `false`, the field is not required.
|
|
154
219
|
|
|
155
220
|
```js
|
|
156
221
|
const rules = {
|
|
@@ -168,7 +233,7 @@ Error code: `REQUIRED`
|
|
|
168
233
|
|
|
169
234
|
Controls whether `null` values are accepted.
|
|
170
235
|
|
|
171
|
-
|
|
236
|
+
Default: `true`, `null` values are allowed.
|
|
172
237
|
|
|
173
238
|
Example:
|
|
174
239
|
|
|
@@ -188,7 +253,7 @@ Error code: `NULL_NOT_ALLOWED`
|
|
|
188
253
|
|
|
189
254
|
Controls whether an empty object `{}` is accepted.
|
|
190
255
|
|
|
191
|
-
|
|
256
|
+
Default: `true`, empty objects are allowed.
|
|
192
257
|
|
|
193
258
|
Example:
|
|
194
259
|
|
|
@@ -196,6 +261,7 @@ Example:
|
|
|
196
261
|
const rules = {
|
|
197
262
|
address: {
|
|
198
263
|
type: "object",
|
|
264
|
+
|
|
199
265
|
allowEmptyObject: false,
|
|
200
266
|
},
|
|
201
267
|
};
|
|
@@ -209,7 +275,7 @@ Error code: `EMPTY_OBJECT_NOT_ALLOWED`
|
|
|
209
275
|
|
|
210
276
|
Controls whether an empty array `[]` is accepted.
|
|
211
277
|
|
|
212
|
-
|
|
278
|
+
Default: `true`, empty arrays are allowed.
|
|
213
279
|
|
|
214
280
|
Example:
|
|
215
281
|
|
|
@@ -217,6 +283,7 @@ Example:
|
|
|
217
283
|
const rules = {
|
|
218
284
|
products: {
|
|
219
285
|
type: "array",
|
|
286
|
+
|
|
220
287
|
allowEmptyArray: false,
|
|
221
288
|
},
|
|
222
289
|
};
|
|
@@ -235,18 +302,31 @@ Supported values:
|
|
|
235
302
|
```text
|
|
236
303
|
|
|
237
304
|
number
|
|
305
|
+
|
|
238
306
|
string
|
|
307
|
+
|
|
239
308
|
boolean
|
|
309
|
+
|
|
240
310
|
email
|
|
311
|
+
|
|
241
312
|
url
|
|
313
|
+
|
|
242
314
|
enum
|
|
315
|
+
|
|
243
316
|
uuid
|
|
317
|
+
|
|
244
318
|
uuidv1
|
|
319
|
+
|
|
245
320
|
uuidv3
|
|
321
|
+
|
|
246
322
|
uuidv4
|
|
323
|
+
|
|
247
324
|
uuidv5
|
|
325
|
+
|
|
248
326
|
objectId
|
|
327
|
+
|
|
249
328
|
array
|
|
329
|
+
|
|
250
330
|
object
|
|
251
331
|
```
|
|
252
332
|
|
|
@@ -257,9 +337,11 @@ const rules = {
|
|
|
257
337
|
age: {
|
|
258
338
|
type: "number",
|
|
259
339
|
},
|
|
340
|
+
|
|
260
341
|
email: {
|
|
261
342
|
type: "email",
|
|
262
343
|
},
|
|
344
|
+
|
|
263
345
|
active: {
|
|
264
346
|
type: "boolean",
|
|
265
347
|
},
|
|
@@ -280,6 +362,7 @@ Example:
|
|
|
280
362
|
const rules = {
|
|
281
363
|
status: {
|
|
282
364
|
type: "enum",
|
|
365
|
+
|
|
283
366
|
enumValues: ["active", "inactive", "blocked", 1, 0],
|
|
284
367
|
},
|
|
285
368
|
};
|
|
@@ -299,6 +382,7 @@ Example:
|
|
|
299
382
|
const rules = {
|
|
300
383
|
status: {
|
|
301
384
|
type: "enum",
|
|
385
|
+
|
|
302
386
|
enumValues: ["active", "inactive", "blocked"],
|
|
303
387
|
},
|
|
304
388
|
};
|
|
@@ -311,14 +395,23 @@ Possible error codes for types:
|
|
|
311
395
|
```text
|
|
312
396
|
|
|
313
397
|
INVALID_TYPE
|
|
398
|
+
|
|
314
399
|
INVALID_EMAIL
|
|
400
|
+
|
|
315
401
|
INVALID_URL
|
|
402
|
+
|
|
316
403
|
INVALID_ENUM
|
|
404
|
+
|
|
317
405
|
INVALID_UUID
|
|
406
|
+
|
|
318
407
|
INVALID_UUID_V1
|
|
408
|
+
|
|
319
409
|
INVALID_UUID_V3
|
|
410
|
+
|
|
320
411
|
INVALID_UUID_V4
|
|
412
|
+
|
|
321
413
|
INVALID_UUID_V5
|
|
414
|
+
|
|
322
415
|
INVALID_OBJECT_ID
|
|
323
416
|
```
|
|
324
417
|
|
|
@@ -328,7 +421,7 @@ INVALID_OBJECT_ID
|
|
|
328
421
|
|
|
329
422
|
Validates a value using a regular expression.
|
|
330
423
|
|
|
331
|
-
|
|
424
|
+
Default: Not applied when omitted.
|
|
332
425
|
|
|
333
426
|
Example:
|
|
334
427
|
|
|
@@ -336,6 +429,7 @@ Example:
|
|
|
336
429
|
const rules = {
|
|
337
430
|
employeeCode: {
|
|
338
431
|
type: "string",
|
|
432
|
+
|
|
339
433
|
regex: /^[A-Z]{3}[0-9]{3}$/,
|
|
340
434
|
},
|
|
341
435
|
};
|
|
@@ -349,7 +443,7 @@ Error code: `REGEX_MISMATCH`
|
|
|
349
443
|
|
|
350
444
|
Defines the minimum allowed string length.
|
|
351
445
|
|
|
352
|
-
|
|
446
|
+
Default: Not applied when omitted.
|
|
353
447
|
|
|
354
448
|
Example:
|
|
355
449
|
|
|
@@ -357,6 +451,7 @@ Example:
|
|
|
357
451
|
const rules = {
|
|
358
452
|
username: {
|
|
359
453
|
type: "string",
|
|
454
|
+
|
|
360
455
|
minLength: 5,
|
|
361
456
|
},
|
|
362
457
|
};
|
|
@@ -370,7 +465,7 @@ Error code: `MIN_LENGTH`
|
|
|
370
465
|
|
|
371
466
|
Defines the maximum allowed string length.
|
|
372
467
|
|
|
373
|
-
|
|
468
|
+
Default: Not applied when omitted.
|
|
374
469
|
|
|
375
470
|
Example:
|
|
376
471
|
|
|
@@ -378,6 +473,7 @@ Example:
|
|
|
378
473
|
const rules = {
|
|
379
474
|
username: {
|
|
380
475
|
type: "string",
|
|
476
|
+
|
|
381
477
|
maxLength: 20,
|
|
382
478
|
},
|
|
383
479
|
};
|
|
@@ -391,7 +487,8 @@ Error code: `MAX_LENGTH`
|
|
|
391
487
|
|
|
392
488
|
Prevents decimal numbers.
|
|
393
489
|
|
|
394
|
-
|
|
490
|
+
Default: `false`; both integer and decimal numbers are
|
|
491
|
+
allowed.
|
|
395
492
|
|
|
396
493
|
Example:
|
|
397
494
|
|
|
@@ -399,6 +496,7 @@ Example:
|
|
|
399
496
|
const rules = {
|
|
400
497
|
quantity: {
|
|
401
498
|
type: "number",
|
|
499
|
+
|
|
402
500
|
preventDecimal: true,
|
|
403
501
|
},
|
|
404
502
|
};
|
|
@@ -412,7 +510,7 @@ Error code: `DECIMAL_NOT_ALLOWED`
|
|
|
412
510
|
|
|
413
511
|
Defines the minimum allowed numeric value.
|
|
414
512
|
|
|
415
|
-
|
|
513
|
+
Default: Not applied when omitted.
|
|
416
514
|
|
|
417
515
|
Example:
|
|
418
516
|
|
|
@@ -420,6 +518,7 @@ Example:
|
|
|
420
518
|
const rules = {
|
|
421
519
|
age: {
|
|
422
520
|
type: "number",
|
|
521
|
+
|
|
423
522
|
min: 18,
|
|
424
523
|
},
|
|
425
524
|
};
|
|
@@ -433,7 +532,7 @@ Error code: `MIN_VALUE`
|
|
|
433
532
|
|
|
434
533
|
Defines the maximum allowed numeric value.
|
|
435
534
|
|
|
436
|
-
|
|
535
|
+
Default: Not applied when omitted.
|
|
437
536
|
|
|
438
537
|
Example:
|
|
439
538
|
|
|
@@ -441,6 +540,7 @@ Example:
|
|
|
441
540
|
const rules = {
|
|
442
541
|
quantity: {
|
|
443
542
|
type: "number",
|
|
543
|
+
|
|
444
544
|
max: 100,
|
|
445
545
|
},
|
|
446
546
|
};
|
|
@@ -454,7 +554,7 @@ Error code: `MAX_VALUE`
|
|
|
454
554
|
|
|
455
555
|
Defines the allowed numeric range.
|
|
456
556
|
|
|
457
|
-
|
|
557
|
+
Default: Not applied when omitted.
|
|
458
558
|
|
|
459
559
|
Example:
|
|
460
560
|
|
|
@@ -462,6 +562,7 @@ Example:
|
|
|
462
562
|
const rules = {
|
|
463
563
|
marks: {
|
|
464
564
|
type: "number",
|
|
565
|
+
|
|
465
566
|
range: "0-100",
|
|
466
567
|
},
|
|
467
568
|
};
|
|
@@ -481,8 +582,10 @@ Example:
|
|
|
481
582
|
const rules = {
|
|
482
583
|
marks: {
|
|
483
584
|
type: "array",
|
|
585
|
+
|
|
484
586
|
elementConstraints: {
|
|
485
587
|
type: "number",
|
|
588
|
+
|
|
486
589
|
range: "0-100",
|
|
487
590
|
},
|
|
488
591
|
},
|
|
@@ -496,13 +599,18 @@ Example error:
|
|
|
496
599
|
{
|
|
497
600
|
|
|
498
601
|
path: "marks[2]",
|
|
602
|
+
|
|
499
603
|
code: "OUT_OF_RANGE",
|
|
604
|
+
|
|
500
605
|
message:
|
|
606
|
+
|
|
501
607
|
"Attribute marks[2] should have a value between 0 and 100"
|
|
608
|
+
|
|
502
609
|
}
|
|
503
610
|
```
|
|
504
611
|
|
|
505
612
|
When `elementConstraintsError` is explicitly provided, the error code
|
|
613
|
+
|
|
506
614
|
is: `INVALID_ARRAY_ELEMENT`
|
|
507
615
|
|
|
508
616
|
Example:
|
|
@@ -511,9 +619,11 @@ Example:
|
|
|
511
619
|
const rules = {
|
|
512
620
|
marks: {
|
|
513
621
|
type: "array",
|
|
622
|
+
|
|
514
623
|
elementConstraints: {
|
|
515
624
|
type: "number",
|
|
516
625
|
},
|
|
626
|
+
|
|
517
627
|
elementConstraintsError: "Every marks element must be a number",
|
|
518
628
|
},
|
|
519
629
|
};
|
|
@@ -531,17 +641,22 @@ Example:
|
|
|
531
641
|
const rules = {
|
|
532
642
|
address: {
|
|
533
643
|
type: "object",
|
|
644
|
+
|
|
534
645
|
objectAttr: {
|
|
535
646
|
city: {
|
|
536
647
|
mandatory: true,
|
|
648
|
+
|
|
537
649
|
type: "string",
|
|
538
650
|
},
|
|
651
|
+
|
|
539
652
|
location: {
|
|
540
653
|
type: "object",
|
|
654
|
+
|
|
541
655
|
objectAttr: {
|
|
542
656
|
latitude: {
|
|
543
657
|
type: "number",
|
|
544
658
|
},
|
|
659
|
+
|
|
545
660
|
longitude: {
|
|
546
661
|
type: "number",
|
|
547
662
|
},
|
|
@@ -557,10 +672,15 @@ Nested errors include the complete field path:
|
|
|
557
672
|
```js
|
|
558
673
|
|
|
559
674
|
{
|
|
675
|
+
|
|
560
676
|
path: "address.location.latitude",
|
|
677
|
+
|
|
561
678
|
code: "INVALID_TYPE",
|
|
679
|
+
|
|
562
680
|
message:
|
|
681
|
+
|
|
563
682
|
"Invalid type for attribute address.location.latitude, required number value"
|
|
683
|
+
|
|
564
684
|
}
|
|
565
685
|
```
|
|
566
686
|
|
|
@@ -576,11 +696,14 @@ Example:
|
|
|
576
696
|
const rules = {
|
|
577
697
|
minSalary: {
|
|
578
698
|
type: "number",
|
|
699
|
+
|
|
579
700
|
dependency: {
|
|
580
701
|
maxSalary: {
|
|
581
702
|
setDependencyRule: (minSalary, maxSalary) => ({
|
|
582
703
|
type: "number",
|
|
704
|
+
|
|
583
705
|
min: minSalary + 1,
|
|
706
|
+
|
|
584
707
|
minError: "maxSalary must be more than minSalary",
|
|
585
708
|
}),
|
|
586
709
|
},
|
|
@@ -596,171 +719,540 @@ Example error:
|
|
|
596
719
|
{
|
|
597
720
|
|
|
598
721
|
path: "maxSalary",
|
|
722
|
+
|
|
599
723
|
code: "MIN_VALUE",
|
|
724
|
+
|
|
600
725
|
message:
|
|
726
|
+
|
|
601
727
|
"maxSalary must be more than minSalary"
|
|
728
|
+
|
|
602
729
|
}
|
|
603
730
|
```
|
|
604
731
|
|
|
605
|
-
|
|
732
|
+
---
|
|
606
733
|
|
|
607
|
-
|
|
608
|
-
validation error codes:
|
|
734
|
+
### Transformations and Sanitization
|
|
609
735
|
|
|
610
|
-
|
|
736
|
+
`perfectPayload()` can transform a field before its validation rules
|
|
737
|
+
run. The transformed value is returned in `validatedPayload`, while the
|
|
738
|
+
original input object is not mutated.
|
|
611
739
|
|
|
612
|
-
|
|
613
|
-
NULL_NOT_ALLOWED
|
|
614
|
-
EMPTY_OBJECT_NOT_ALLOWED
|
|
615
|
-
EMPTY_ARRAY_NOT_ALLOWED
|
|
616
|
-
INVALID_ARRAY_ELEMENT
|
|
617
|
-
REGEX_MISMATCH
|
|
618
|
-
INVALID_TYPE
|
|
619
|
-
INVALID_EMAIL
|
|
620
|
-
INVALID_URL
|
|
621
|
-
INVALID_ENUM
|
|
622
|
-
INVALID_UUID
|
|
623
|
-
INVALID_UUID_V1
|
|
624
|
-
INVALID_UUID_V3
|
|
625
|
-
INVALID_UUID_V4
|
|
626
|
-
INVALID_UUID_V5
|
|
627
|
-
INVALID_OBJECT_ID
|
|
628
|
-
MIN_LENGTH
|
|
629
|
-
MAX_LENGTH
|
|
630
|
-
DECIMAL_NOT_ALLOWED
|
|
631
|
-
MIN_VALUE
|
|
632
|
-
MAX_VALUE
|
|
633
|
-
OUT_OF_RANGE
|
|
634
|
-
```
|
|
740
|
+
Supported transformation rules:
|
|
635
741
|
|
|
636
|
-
|
|
637
|
-
remains suitable for human-readable API responses.
|
|
742
|
+
Rule Purpose
|
|
638
743
|
|
|
639
|
-
|
|
744
|
+
---
|
|
640
745
|
|
|
641
|
-
|
|
642
|
-
|
|
746
|
+
`trim` Removes leading and trailing whitespace from strings
|
|
747
|
+
`lowercase` Converts strings to lowercase
|
|
748
|
+
`uppercase` Converts strings to uppercase
|
|
749
|
+
`transform` Runs a custom synchronous transformation function
|
|
643
750
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
(error) => error.code === "INVALID_EMAIL",
|
|
647
|
-
);
|
|
751
|
+
Transformations always run in this fixed order, regardless of the order
|
|
752
|
+
in which the rule properties are written:
|
|
648
753
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
754
|
+
```text
|
|
755
|
+
trim
|
|
756
|
+
↓
|
|
757
|
+
lowercase
|
|
758
|
+
↓
|
|
759
|
+
uppercase
|
|
760
|
+
↓
|
|
761
|
+
transform(value, payload)
|
|
762
|
+
↓
|
|
763
|
+
validation rules
|
|
764
|
+
↓
|
|
765
|
+
customValidator
|
|
766
|
+
↓
|
|
767
|
+
validatedPayload
|
|
653
768
|
```
|
|
654
769
|
|
|
655
|
-
|
|
770
|
+
#### `trim`
|
|
656
771
|
|
|
657
|
-
|
|
772
|
+
```js
|
|
773
|
+
const payload = {
|
|
774
|
+
name: " Kiran Poojary ",
|
|
775
|
+
};
|
|
658
776
|
|
|
659
|
-
|
|
660
|
-
|
|
777
|
+
const rules = {
|
|
778
|
+
name: {
|
|
779
|
+
type: "string",
|
|
780
|
+
trim: true,
|
|
781
|
+
},
|
|
782
|
+
};
|
|
661
783
|
|
|
662
|
-
|
|
784
|
+
const result = perfectPayload(payload, rules);
|
|
663
785
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
786
|
+
console.log(result.validatedPayload.name);
|
|
787
|
+
// "Kiran Poojary"
|
|
788
|
+
|
|
789
|
+
console.log(payload.name);
|
|
790
|
+
// " Kiran Poojary "
|
|
669
791
|
```
|
|
670
792
|
|
|
671
|
-
|
|
793
|
+
`trim` applies only to string values. Non-string values are left
|
|
794
|
+
unchanged.
|
|
795
|
+
|
|
796
|
+
#### `lowercase`
|
|
672
797
|
|
|
673
798
|
```js
|
|
674
799
|
const rules = {
|
|
675
800
|
email: {
|
|
676
|
-
|
|
801
|
+
trim: true,
|
|
802
|
+
lowercase: true,
|
|
677
803
|
type: "email",
|
|
678
|
-
mandatoryError: "Email is required",
|
|
679
|
-
typeError: "Email address is invalid",
|
|
680
804
|
},
|
|
681
805
|
};
|
|
682
806
|
```
|
|
683
807
|
|
|
684
|
-
|
|
808
|
+
For `" KIRAN@EXAMPLE.COM "`, the validated value becomes
|
|
809
|
+
`"kiran@example.com"`.
|
|
685
810
|
|
|
686
|
-
|
|
811
|
+
#### `uppercase`
|
|
687
812
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
813
|
+
```js
|
|
814
|
+
const rules = {
|
|
815
|
+
countryCode: {
|
|
816
|
+
type: "string",
|
|
817
|
+
uppercase: true,
|
|
818
|
+
},
|
|
819
|
+
};
|
|
693
820
|
```
|
|
694
821
|
|
|
695
|
-
|
|
822
|
+
For `"in"`, the validated value becomes `"IN"`.
|
|
696
823
|
|
|
697
|
-
|
|
824
|
+
`lowercase: true` and `uppercase: true` cannot be enabled together for
|
|
825
|
+
the same field. Doing so throws a schema configuration error.
|
|
698
826
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
827
|
+
#### `transform`
|
|
828
|
+
|
|
829
|
+
Use `transform` when the built-in string transformations are not enough.
|
|
830
|
+
|
|
831
|
+
```js
|
|
832
|
+
const rules = {
|
|
833
|
+
phone: {
|
|
834
|
+
type: "string",
|
|
835
|
+
transform: (value) => value.replace(/\s+/g, ""),
|
|
836
|
+
},
|
|
837
|
+
};
|
|
704
838
|
```
|
|
705
839
|
|
|
706
|
-
|
|
840
|
+
For `"98765 43210"`, the validated value becomes `"9876543210"`.
|
|
707
841
|
|
|
708
|
-
|
|
842
|
+
The transformer receives two arguments:
|
|
709
843
|
|
|
710
|
-
|
|
844
|
+
```js
|
|
845
|
+
transform: (value, payload) => {
|
|
846
|
+
return value;
|
|
847
|
+
};
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
- `value` is the field value after the built-in transformations have
|
|
851
|
+
run.
|
|
852
|
+
- `payload` is the current payload/object being validated.
|
|
711
853
|
|
|
712
|
-
|
|
854
|
+
This makes cross-field transformations possible:
|
|
713
855
|
|
|
714
|
-
|
|
856
|
+
```js
|
|
857
|
+
const payload = {
|
|
858
|
+
amount: 100,
|
|
859
|
+
multiplier: 2,
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
const rules = {
|
|
863
|
+
amount: {
|
|
864
|
+
transform: (value, payload) => value * payload.multiplier,
|
|
865
|
+
type: "number",
|
|
866
|
+
},
|
|
867
|
+
multiplier: {
|
|
868
|
+
type: "number",
|
|
869
|
+
},
|
|
870
|
+
};
|
|
715
871
|
|
|
716
|
-
|
|
872
|
+
const result = perfectPayload(payload, rules);
|
|
717
873
|
|
|
718
|
-
|
|
874
|
+
console.log(result.validatedPayload.amount);
|
|
875
|
+
// 200
|
|
876
|
+
```
|
|
719
877
|
|
|
720
|
-
|
|
878
|
+
A custom transformer may also change the data type before validation:
|
|
721
879
|
|
|
722
|
-
|
|
880
|
+
```js
|
|
881
|
+
const rules = {
|
|
882
|
+
quantity: {
|
|
883
|
+
transform: (value) => Number(value),
|
|
884
|
+
type: "number",
|
|
885
|
+
min: 1,
|
|
886
|
+
max: 100,
|
|
887
|
+
},
|
|
888
|
+
};
|
|
889
|
+
```
|
|
723
890
|
|
|
724
|
-
|
|
891
|
+
The transformed value is validated by the normal validation rules and is
|
|
892
|
+
also the value received by `customValidator`.
|
|
725
893
|
|
|
726
|
-
|
|
894
|
+
Transformations work inside `objectAttr` and `elementConstraints`, and
|
|
895
|
+
transformed nested/array values are preserved in `validatedPayload`.
|
|
727
896
|
|
|
728
|
-
|
|
897
|
+
```js
|
|
898
|
+
const rules = {
|
|
899
|
+
profile: {
|
|
900
|
+
type: "object",
|
|
901
|
+
objectAttr: {
|
|
902
|
+
name: {
|
|
903
|
+
trim: true,
|
|
904
|
+
uppercase: true,
|
|
905
|
+
type: "string",
|
|
906
|
+
},
|
|
907
|
+
},
|
|
908
|
+
},
|
|
909
|
+
tags: {
|
|
910
|
+
type: "array",
|
|
911
|
+
elementConstraints: {
|
|
912
|
+
trim: true,
|
|
913
|
+
lowercase: true,
|
|
914
|
+
type: "string",
|
|
915
|
+
},
|
|
916
|
+
},
|
|
917
|
+
};
|
|
918
|
+
```
|
|
729
919
|
|
|
730
|
-
|
|
920
|
+
Missing optional fields are not transformed. An input value of `null` is
|
|
921
|
+
not passed to transformation functions; null handling remains controlled
|
|
922
|
+
by `allowNull`.
|
|
731
923
|
|
|
732
|
-
|
|
924
|
+
**Important:** `transform` is synchronous. A non-function transformer,
|
|
925
|
+
an `async` transformer, or a transformer that returns a Promise is not
|
|
926
|
+
supported and throws an error. Exceptions thrown inside the transformer
|
|
927
|
+
propagate to the caller.
|
|
733
928
|
|
|
734
|
-
|
|
929
|
+
### `customValidator`
|
|
735
930
|
|
|
736
|
-
|
|
931
|
+
Allows you to define custom synchronous validation logic for a field
|
|
932
|
+
when the built-in validation rules are not enough.
|
|
737
933
|
|
|
738
|
-
|
|
934
|
+
The validator receives the field value and the current payload/object
|
|
935
|
+
being validated:
|
|
739
936
|
|
|
740
937
|
```js
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
age: 15,
|
|
744
|
-
score: 120,
|
|
938
|
+
customValidator: (value, payload) => {
|
|
939
|
+
return true;
|
|
745
940
|
};
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
The validator must return `true` to pass validation. Any other return
|
|
944
|
+
value causes validation to fail.
|
|
945
|
+
|
|
946
|
+
Example:
|
|
746
947
|
|
|
948
|
+
```js
|
|
747
949
|
const rules = {
|
|
748
950
|
username: {
|
|
749
951
|
mandatory: true,
|
|
952
|
+
|
|
750
953
|
type: "string",
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
954
|
+
|
|
955
|
+
customValidator: (value) => {
|
|
956
|
+
return !value.toLowerCase().includes("admin");
|
|
957
|
+
},
|
|
958
|
+
|
|
959
|
+
customValidatorCode: "RESERVED_USERNAME",
|
|
960
|
+
|
|
961
|
+
customValidatorError: "Username cannot contain admin",
|
|
755
962
|
},
|
|
756
|
-
|
|
757
|
-
|
|
963
|
+
};
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
For this payload:
|
|
967
|
+
|
|
968
|
+
```js
|
|
969
|
+
const payload = {
|
|
970
|
+
username: "admin_kiran",
|
|
971
|
+
};
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
The validation error is:
|
|
975
|
+
|
|
976
|
+
```js
|
|
977
|
+
|
|
978
|
+
{
|
|
979
|
+
|
|
980
|
+
path: "username",
|
|
981
|
+
|
|
982
|
+
code: "RESERVED_USERNAME",
|
|
983
|
+
|
|
984
|
+
message: "Username cannot contain admin"
|
|
985
|
+
|
|
986
|
+
}
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
If `customValidatorCode` and `customValidatorError` are not provided,
|
|
990
|
+
the default error is:
|
|
991
|
+
|
|
992
|
+
```js
|
|
993
|
+
|
|
994
|
+
{
|
|
995
|
+
|
|
996
|
+
path: "username",
|
|
997
|
+
|
|
998
|
+
code: "CUSTOM_VALIDATION_FAILED",
|
|
999
|
+
|
|
1000
|
+
message: "Custom validation failed for attribute username"
|
|
1001
|
+
|
|
1002
|
+
}
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
The current payload/object being validated can be used as the second
|
|
1006
|
+
argument when required:
|
|
1007
|
+
|
|
1008
|
+
```js
|
|
1009
|
+
const rules = {
|
|
1010
|
+
limit: {
|
|
1011
|
+
type: "number",
|
|
1012
|
+
},
|
|
1013
|
+
|
|
1014
|
+
amount: {
|
|
1015
|
+
type: "number",
|
|
1016
|
+
|
|
1017
|
+
customValidator: (value, payload) => {
|
|
1018
|
+
return value <= payload.limit;
|
|
1019
|
+
},
|
|
1020
|
+
|
|
1021
|
+
customValidatorCode: "LIMIT_EXCEEDED",
|
|
1022
|
+
|
|
1023
|
+
customValidatorError: "Amount cannot exceed limit",
|
|
1024
|
+
},
|
|
1025
|
+
};
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
`customValidator` also works with nested objects and array
|
|
1029
|
+
`elementConstraints`. The generated structured error automatically
|
|
1030
|
+
contains the corresponding nested or array path.
|
|
1031
|
+
|
|
1032
|
+
Important: `customValidator` is synchronous. An `async`
|
|
1033
|
+
validator or a validator that returns a Promise is not supported and
|
|
1034
|
+
throws an error. Asynchronous validation is not part of this feature.
|
|
1035
|
+
|
|
1036
|
+
Error code when no custom code is provided: `CUSTOM_VALIDATION_FAILED`
|
|
1037
|
+
|
|
1038
|
+
## Error Codes
|
|
1039
|
+
|
|
1040
|
+
`perfectPayload()` currently exposes the following machine-readable
|
|
1041
|
+
|
|
1042
|
+
validation error codes:
|
|
1043
|
+
|
|
1044
|
+
```text
|
|
1045
|
+
|
|
1046
|
+
REQUIRED
|
|
1047
|
+
|
|
1048
|
+
NULL_NOT_ALLOWED
|
|
1049
|
+
|
|
1050
|
+
EMPTY_OBJECT_NOT_ALLOWED
|
|
1051
|
+
|
|
1052
|
+
EMPTY_ARRAY_NOT_ALLOWED
|
|
1053
|
+
|
|
1054
|
+
INVALID_ARRAY_ELEMENT
|
|
1055
|
+
|
|
1056
|
+
REGEX_MISMATCH
|
|
1057
|
+
|
|
1058
|
+
INVALID_TYPE
|
|
1059
|
+
|
|
1060
|
+
INVALID_EMAIL
|
|
1061
|
+
|
|
1062
|
+
INVALID_URL
|
|
1063
|
+
|
|
1064
|
+
INVALID_ENUM
|
|
1065
|
+
|
|
1066
|
+
INVALID_UUID
|
|
1067
|
+
|
|
1068
|
+
INVALID_UUID_V1
|
|
1069
|
+
|
|
1070
|
+
INVALID_UUID_V3
|
|
1071
|
+
|
|
1072
|
+
INVALID_UUID_V4
|
|
1073
|
+
|
|
1074
|
+
INVALID_UUID_V5
|
|
1075
|
+
|
|
1076
|
+
INVALID_OBJECT_ID
|
|
1077
|
+
|
|
1078
|
+
MIN_LENGTH
|
|
1079
|
+
|
|
1080
|
+
MAX_LENGTH
|
|
1081
|
+
|
|
1082
|
+
DECIMAL_NOT_ALLOWED
|
|
1083
|
+
|
|
1084
|
+
MIN_VALUE
|
|
1085
|
+
|
|
1086
|
+
MAX_VALUE
|
|
1087
|
+
|
|
1088
|
+
OUT_OF_RANGE
|
|
1089
|
+
```
|
|
1090
|
+
|
|
1091
|
+
These codes are designed for programmatic handling while `message`
|
|
1092
|
+
|
|
1093
|
+
remains suitable for human-readable API responses.
|
|
1094
|
+
|
|
1095
|
+
For example:
|
|
1096
|
+
|
|
1097
|
+
```js
|
|
1098
|
+
|
|
1099
|
+
const result = perfectPayload(payload, validationRules);
|
|
1100
|
+
|
|
1101
|
+
if (!result.valid) {
|
|
1102
|
+
|
|
1103
|
+
const emailError = result.errors.find(
|
|
1104
|
+
|
|
1105
|
+
(error) => error.code === "INVALID_EMAIL",
|
|
1106
|
+
|
|
1107
|
+
);
|
|
1108
|
+
|
|
1109
|
+
if (emailError) {
|
|
1110
|
+
|
|
1111
|
+
**// Handle invalid email**
|
|
1112
|
+
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
}
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
## Custom Error Messages
|
|
1119
|
+
|
|
1120
|
+
Every validation rule can use its corresponding custom error message.
|
|
1121
|
+
|
|
1122
|
+
Custom messages replace the default human-readable `message` while
|
|
1123
|
+
|
|
1124
|
+
keeping the same structured error format:
|
|
1125
|
+
|
|
1126
|
+
```js
|
|
1127
|
+
|
|
1128
|
+
{
|
|
1129
|
+
|
|
1130
|
+
path: "email",
|
|
1131
|
+
|
|
1132
|
+
code: "INVALID_EMAIL",
|
|
1133
|
+
|
|
1134
|
+
message: "Email address is invalid"
|
|
1135
|
+
|
|
1136
|
+
}
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
Example:
|
|
1140
|
+
|
|
1141
|
+
```js
|
|
1142
|
+
const rules = {
|
|
1143
|
+
email: {
|
|
1144
|
+
mandatory: true,
|
|
1145
|
+
|
|
1146
|
+
type: "email",
|
|
1147
|
+
|
|
1148
|
+
mandatoryError: "Email is required",
|
|
1149
|
+
|
|
1150
|
+
typeError: "Email address is invalid",
|
|
1151
|
+
},
|
|
1152
|
+
};
|
|
1153
|
+
```
|
|
1154
|
+
|
|
1155
|
+
If `email` is missing:
|
|
1156
|
+
|
|
1157
|
+
```js
|
|
1158
|
+
|
|
1159
|
+
{
|
|
1160
|
+
|
|
1161
|
+
path: "email",
|
|
1162
|
+
|
|
1163
|
+
code: "REQUIRED",
|
|
1164
|
+
|
|
1165
|
+
message: "Email is required"
|
|
1166
|
+
|
|
1167
|
+
}
|
|
1168
|
+
```
|
|
1169
|
+
|
|
1170
|
+
If `email` is present but invalid:
|
|
1171
|
+
|
|
1172
|
+
```js
|
|
1173
|
+
|
|
1174
|
+
{
|
|
1175
|
+
|
|
1176
|
+
path: "email",
|
|
1177
|
+
|
|
1178
|
+
code: "INVALID_EMAIL",
|
|
1179
|
+
|
|
1180
|
+
message: "Email address is invalid"
|
|
1181
|
+
|
|
1182
|
+
}
|
|
1183
|
+
```
|
|
1184
|
+
|
|
1185
|
+
### Supported Custom Error Properties
|
|
1186
|
+
|
|
1187
|
+
\| Validation Rule \| Custom Error Property \|
|
|
1188
|
+
|
|
1189
|
+
\| -------------------- \| ------------------------- \|
|
|
1190
|
+
|
|
1191
|
+
\| `mandatory` \| `mandatoryError` \|
|
|
1192
|
+
|
|
1193
|
+
\| `allowNull` \| `allowNullError` \|
|
|
1194
|
+
|
|
1195
|
+
\| `allowEmptyObject` \| `emptyObjectError` \|
|
|
1196
|
+
|
|
1197
|
+
\| `allowEmptyArray` \| `emptyArrayError` \|
|
|
1198
|
+
|
|
1199
|
+
\| `elementConstraints` \| `elementConstraintsError` \|
|
|
1200
|
+
|
|
1201
|
+
\| `regex` \| `regexError` \|
|
|
1202
|
+
|
|
1203
|
+
\| `type` \| `typeError` \|
|
|
1204
|
+
|
|
1205
|
+
\| `minLength` \| `minLengthError` \|
|
|
1206
|
+
|
|
1207
|
+
\| `maxLength` \| `maxLengthError` \|
|
|
1208
|
+
|
|
1209
|
+
\| `preventDecimal` \| `preventDecimalError` \|
|
|
1210
|
+
|
|
1211
|
+
\| `min` \| `minError` \|
|
|
1212
|
+
|
|
1213
|
+
\| `max` \| `maxError` \|
|
|
1214
|
+
|
|
1215
|
+
\| `range` \| `rangeError` \|
|
|
1216
|
+
|
|
1217
|
+
### Example with Multiple Custom Errors
|
|
1218
|
+
|
|
1219
|
+
```js
|
|
1220
|
+
const payload = {
|
|
1221
|
+
username: "ab",
|
|
1222
|
+
|
|
1223
|
+
age: 15,
|
|
1224
|
+
|
|
1225
|
+
score: 120,
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
const rules = {
|
|
1229
|
+
username: {
|
|
1230
|
+
mandatory: true,
|
|
1231
|
+
|
|
1232
|
+
type: "string",
|
|
1233
|
+
|
|
1234
|
+
minLength: 3,
|
|
1235
|
+
|
|
1236
|
+
mandatoryError: "Username is required",
|
|
1237
|
+
|
|
1238
|
+
typeError: "Username must be a string",
|
|
1239
|
+
|
|
1240
|
+
minLengthError: "Username must contain at least 3 characters",
|
|
1241
|
+
},
|
|
1242
|
+
|
|
1243
|
+
age: {
|
|
1244
|
+
type: "number",
|
|
1245
|
+
|
|
758
1246
|
min: 18,
|
|
1247
|
+
|
|
759
1248
|
minError: "Age must be at least 18",
|
|
760
1249
|
},
|
|
1250
|
+
|
|
761
1251
|
score: {
|
|
762
1252
|
type: "number",
|
|
1253
|
+
|
|
763
1254
|
range: "0-100",
|
|
1255
|
+
|
|
764
1256
|
rangeError: "Score must be between 0 and 100",
|
|
765
1257
|
},
|
|
766
1258
|
};
|
|
@@ -773,30 +1265,55 @@ Example result:
|
|
|
773
1265
|
```js
|
|
774
1266
|
|
|
775
1267
|
{
|
|
1268
|
+
|
|
776
1269
|
statusCode: 400,
|
|
1270
|
+
|
|
777
1271
|
valid: false,
|
|
1272
|
+
|
|
778
1273
|
message:
|
|
1274
|
+
|
|
779
1275
|
"One or more attribute values are invalid",
|
|
1276
|
+
|
|
780
1277
|
errors: [
|
|
1278
|
+
|
|
781
1279
|
{
|
|
1280
|
+
|
|
782
1281
|
path: "username",
|
|
1282
|
+
|
|
783
1283
|
code: "MIN_LENGTH",
|
|
1284
|
+
|
|
784
1285
|
message:
|
|
1286
|
+
|
|
785
1287
|
"Username must contain at least 3 characters"
|
|
1288
|
+
|
|
786
1289
|
},
|
|
1290
|
+
|
|
787
1291
|
{
|
|
1292
|
+
|
|
788
1293
|
path: "age",
|
|
1294
|
+
|
|
789
1295
|
code: "MIN_VALUE",
|
|
1296
|
+
|
|
790
1297
|
message:
|
|
1298
|
+
|
|
791
1299
|
"Age must be at least 18"
|
|
1300
|
+
|
|
792
1301
|
},
|
|
1302
|
+
|
|
793
1303
|
{
|
|
1304
|
+
|
|
794
1305
|
path: "score",
|
|
1306
|
+
|
|
795
1307
|
code: "OUT_OF_RANGE",
|
|
1308
|
+
|
|
796
1309
|
message:
|
|
1310
|
+
|
|
797
1311
|
"Score must be between 0 and 100"
|
|
1312
|
+
|
|
798
1313
|
}
|
|
1314
|
+
|
|
799
1315
|
]
|
|
1316
|
+
|
|
800
1317
|
}
|
|
801
1318
|
```
|
|
802
1319
|
|
|
@@ -812,7 +1329,9 @@ For example:
|
|
|
812
1329
|
const rules = {
|
|
813
1330
|
age: {
|
|
814
1331
|
type: "number",
|
|
1332
|
+
|
|
815
1333
|
min: 18,
|
|
1334
|
+
|
|
816
1335
|
minError: "You must be 18 or older",
|
|
817
1336
|
},
|
|
818
1337
|
};
|
|
@@ -823,22 +1342,30 @@ Still returns:
|
|
|
823
1342
|
```js
|
|
824
1343
|
|
|
825
1344
|
{
|
|
1345
|
+
|
|
826
1346
|
path: "age",
|
|
1347
|
+
|
|
827
1348
|
code: "MIN_VALUE",
|
|
1349
|
+
|
|
828
1350
|
message: "You must be 18 or older"
|
|
1351
|
+
|
|
829
1352
|
}
|
|
830
1353
|
```
|
|
831
1354
|
|
|
832
1355
|
This makes it possible to:
|
|
833
1356
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1357
|
+
\- show custom messages to API consumers
|
|
1358
|
+
|
|
1359
|
+
\- use stable error codes in application logic
|
|
1360
|
+
|
|
1361
|
+
\- change user-facing wording without changing programmatic error
|
|
1362
|
+
|
|
1363
|
+
handling
|
|
838
1364
|
|
|
839
1365
|
## Custom Response Objects
|
|
840
1366
|
|
|
841
1367
|
`perfectPayload()` allows you to customize both the valid and invalid
|
|
1368
|
+
|
|
842
1369
|
response objects.
|
|
843
1370
|
|
|
844
1371
|
The third argument is the custom valid response.
|
|
@@ -852,7 +1379,9 @@ Example:
|
|
|
852
1379
|
```js
|
|
853
1380
|
const customValidResponse = {
|
|
854
1381
|
statusCode: 201,
|
|
1382
|
+
|
|
855
1383
|
valid: true,
|
|
1384
|
+
|
|
856
1385
|
message: "Payload validated successfully",
|
|
857
1386
|
};
|
|
858
1387
|
|
|
@@ -864,14 +1393,23 @@ When validation succeeds, `validatedPayload` is automatically added:
|
|
|
864
1393
|
```js
|
|
865
1394
|
|
|
866
1395
|
{
|
|
1396
|
+
|
|
867
1397
|
statusCode: 201,
|
|
1398
|
+
|
|
868
1399
|
valid: true,
|
|
1400
|
+
|
|
869
1401
|
message: "Payload validated successfully",
|
|
1402
|
+
|
|
870
1403
|
validatedPayload: {
|
|
1404
|
+
|
|
871
1405
|
name: "Kiran",
|
|
1406
|
+
|
|
872
1407
|
email: "kiran@example.com",
|
|
1408
|
+
|
|
873
1409
|
age: 29
|
|
1410
|
+
|
|
874
1411
|
}
|
|
1412
|
+
|
|
875
1413
|
}
|
|
876
1414
|
```
|
|
877
1415
|
|
|
@@ -882,14 +1420,19 @@ Example:
|
|
|
882
1420
|
```js
|
|
883
1421
|
const customInvalidResponse = {
|
|
884
1422
|
statusCode: 422,
|
|
1423
|
+
|
|
885
1424
|
valid: false,
|
|
1425
|
+
|
|
886
1426
|
message: "Payload validation failed",
|
|
887
1427
|
};
|
|
888
1428
|
|
|
889
1429
|
const result = perfectPayload(
|
|
890
1430
|
payload,
|
|
1431
|
+
|
|
891
1432
|
validationRules,
|
|
1433
|
+
|
|
892
1434
|
undefined,
|
|
1435
|
+
|
|
893
1436
|
customInvalidResponse,
|
|
894
1437
|
);
|
|
895
1438
|
```
|
|
@@ -901,16 +1444,27 @@ When validation fails, `errors` is automatically added:
|
|
|
901
1444
|
{
|
|
902
1445
|
|
|
903
1446
|
statusCode: 422,
|
|
1447
|
+
|
|
904
1448
|
valid: false,
|
|
1449
|
+
|
|
905
1450
|
message: "Payload validation failed",
|
|
1451
|
+
|
|
906
1452
|
errors: [
|
|
1453
|
+
|
|
907
1454
|
{
|
|
1455
|
+
|
|
908
1456
|
path: "email",
|
|
1457
|
+
|
|
909
1458
|
code: "INVALID_EMAIL",
|
|
1459
|
+
|
|
910
1460
|
message:
|
|
1461
|
+
|
|
911
1462
|
"Invalid email format for attribute email"
|
|
1463
|
+
|
|
912
1464
|
}
|
|
1465
|
+
|
|
913
1466
|
]
|
|
1467
|
+
|
|
914
1468
|
}
|
|
915
1469
|
```
|
|
916
1470
|
|
|
@@ -919,25 +1473,33 @@ When validation fails, `errors` is automatically added:
|
|
|
919
1473
|
```js
|
|
920
1474
|
const customValidResponse = {
|
|
921
1475
|
statusCode: 201,
|
|
1476
|
+
|
|
922
1477
|
valid: true,
|
|
1478
|
+
|
|
923
1479
|
message: "CUSTOM_VALID_RESPONSE",
|
|
924
1480
|
};
|
|
925
1481
|
|
|
926
1482
|
const customInvalidResponse = {
|
|
927
1483
|
statusCode: 422,
|
|
1484
|
+
|
|
928
1485
|
valid: false,
|
|
1486
|
+
|
|
929
1487
|
message: "CUSTOM_INVALID_RESPONSE",
|
|
930
1488
|
};
|
|
931
1489
|
|
|
932
1490
|
const result = perfectPayload(
|
|
933
1491
|
payload,
|
|
1492
|
+
|
|
934
1493
|
validationRules,
|
|
1494
|
+
|
|
935
1495
|
customValidResponse,
|
|
1496
|
+
|
|
936
1497
|
customInvalidResponse,
|
|
937
1498
|
);
|
|
938
1499
|
```
|
|
939
1500
|
|
|
940
1501
|
The response object you provide is preserved, while `perfectPayload()`
|
|
1502
|
+
|
|
941
1503
|
automatically adds either:
|
|
942
1504
|
|
|
943
1505
|
```text
|
|
@@ -957,16 +1519,23 @@ for failed validation.
|
|
|
957
1519
|
## Default Responses
|
|
958
1520
|
|
|
959
1521
|
If no custom response objects are provided, the default valid response
|
|
1522
|
+
|
|
960
1523
|
is:
|
|
961
1524
|
|
|
962
1525
|
```js
|
|
963
1526
|
|
|
964
1527
|
{
|
|
1528
|
+
|
|
965
1529
|
statusCode: 200,
|
|
1530
|
+
|
|
966
1531
|
valid: true,
|
|
1532
|
+
|
|
967
1533
|
validatedPayload: {
|
|
968
|
-
|
|
1534
|
+
|
|
1535
|
+
**// validated fields**
|
|
1536
|
+
|
|
969
1537
|
}
|
|
1538
|
+
|
|
970
1539
|
}
|
|
971
1540
|
```
|
|
972
1541
|
|
|
@@ -975,25 +1544,38 @@ The default invalid response is:
|
|
|
975
1544
|
```js
|
|
976
1545
|
|
|
977
1546
|
{
|
|
1547
|
+
|
|
978
1548
|
statusCode: 400,
|
|
1549
|
+
|
|
979
1550
|
valid: false,
|
|
1551
|
+
|
|
980
1552
|
message: "One or more attribute values are invalid",
|
|
1553
|
+
|
|
981
1554
|
errors: [
|
|
1555
|
+
|
|
982
1556
|
{
|
|
1557
|
+
|
|
983
1558
|
path: "field",
|
|
1559
|
+
|
|
984
1560
|
code: "ERROR_CODE",
|
|
1561
|
+
|
|
985
1562
|
message: "Validation error message"
|
|
1563
|
+
|
|
986
1564
|
}
|
|
1565
|
+
|
|
987
1566
|
]
|
|
1567
|
+
|
|
988
1568
|
}
|
|
989
1569
|
```
|
|
990
1570
|
|
|
991
1571
|
## Nested Objects and Array Field Paths
|
|
992
1572
|
|
|
993
1573
|
`perfectPayload()` returns the exact location of a validation failure
|
|
1574
|
+
|
|
994
1575
|
through the `path` property.
|
|
995
1576
|
|
|
996
1577
|
This makes validation errors easier to map to API fields, forms, logs,
|
|
1578
|
+
|
|
997
1579
|
and frontend components.
|
|
998
1580
|
|
|
999
1581
|
### Top-Level Field
|
|
@@ -1011,9 +1593,13 @@ An error can be returned as:
|
|
|
1011
1593
|
```js
|
|
1012
1594
|
|
|
1013
1595
|
{
|
|
1596
|
+
|
|
1014
1597
|
path: "email",
|
|
1598
|
+
|
|
1015
1599
|
code: "INVALID_EMAIL",
|
|
1600
|
+
|
|
1016
1601
|
message: "Invalid email format for attribute email"
|
|
1602
|
+
|
|
1017
1603
|
}
|
|
1018
1604
|
```
|
|
1019
1605
|
|
|
@@ -1025,8 +1611,10 @@ Use `objectAttr` to validate properties inside an object.
|
|
|
1025
1611
|
const payload = {
|
|
1026
1612
|
address: {
|
|
1027
1613
|
city: "Bengaluru",
|
|
1614
|
+
|
|
1028
1615
|
location: {
|
|
1029
1616
|
latitude: "12.9716",
|
|
1617
|
+
|
|
1030
1618
|
longitude: 77.5946,
|
|
1031
1619
|
},
|
|
1032
1620
|
},
|
|
@@ -1035,16 +1623,20 @@ const payload = {
|
|
|
1035
1623
|
const rules = {
|
|
1036
1624
|
address: {
|
|
1037
1625
|
type: "object",
|
|
1626
|
+
|
|
1038
1627
|
objectAttr: {
|
|
1039
1628
|
city: {
|
|
1040
1629
|
type: "string",
|
|
1041
1630
|
},
|
|
1631
|
+
|
|
1042
1632
|
location: {
|
|
1043
1633
|
type: "object",
|
|
1634
|
+
|
|
1044
1635
|
objectAttr: {
|
|
1045
1636
|
latitude: {
|
|
1046
1637
|
type: "number",
|
|
1047
1638
|
},
|
|
1639
|
+
|
|
1048
1640
|
longitude: {
|
|
1049
1641
|
type: "number",
|
|
1050
1642
|
},
|
|
@@ -1058,15 +1650,21 @@ const result = perfectPayload(payload, rules);
|
|
|
1058
1650
|
```
|
|
1059
1651
|
|
|
1060
1652
|
Because `latitude` is a string instead of a number, the error contains
|
|
1653
|
+
|
|
1061
1654
|
its complete nested path:
|
|
1062
1655
|
|
|
1063
1656
|
```js
|
|
1064
1657
|
|
|
1065
1658
|
{
|
|
1659
|
+
|
|
1066
1660
|
path: "address.location.latitude",
|
|
1661
|
+
|
|
1067
1662
|
code: "INVALID_TYPE",
|
|
1663
|
+
|
|
1068
1664
|
message:
|
|
1665
|
+
|
|
1069
1666
|
"Invalid type for attribute address.location.latitude, required number value"
|
|
1667
|
+
|
|
1070
1668
|
}
|
|
1071
1669
|
```
|
|
1072
1670
|
|
|
@@ -1075,13 +1673,16 @@ Nested paths use dot notation:
|
|
|
1075
1673
|
```text
|
|
1076
1674
|
|
|
1077
1675
|
address.city
|
|
1676
|
+
|
|
1078
1677
|
address.location.latitude
|
|
1678
|
+
|
|
1079
1679
|
address.location.longitude
|
|
1080
1680
|
```
|
|
1081
1681
|
|
|
1082
1682
|
### Array Elements
|
|
1083
1683
|
|
|
1084
1684
|
When `elementConstraints` validation fails, the array index is included
|
|
1685
|
+
|
|
1085
1686
|
in the error path.
|
|
1086
1687
|
|
|
1087
1688
|
```js
|
|
@@ -1092,8 +1693,10 @@ const payload = {
|
|
|
1092
1693
|
const rules = {
|
|
1093
1694
|
marks: {
|
|
1094
1695
|
type: "array",
|
|
1696
|
+
|
|
1095
1697
|
elementConstraints: {
|
|
1096
1698
|
type: "number",
|
|
1699
|
+
|
|
1097
1700
|
range: "0-100",
|
|
1098
1701
|
},
|
|
1099
1702
|
},
|
|
@@ -1107,10 +1710,15 @@ The invalid third element is reported as:
|
|
|
1107
1710
|
```js
|
|
1108
1711
|
|
|
1109
1712
|
{
|
|
1713
|
+
|
|
1110
1714
|
path: "marks[2]",
|
|
1715
|
+
|
|
1111
1716
|
code: "OUT_OF_RANGE",
|
|
1717
|
+
|
|
1112
1718
|
message:
|
|
1719
|
+
|
|
1113
1720
|
"Attribute marks[2] should have a value between 0 and 100"
|
|
1721
|
+
|
|
1114
1722
|
}
|
|
1115
1723
|
```
|
|
1116
1724
|
|
|
@@ -1119,7 +1727,9 @@ Array paths use zero-based indexes:
|
|
|
1119
1727
|
```text
|
|
1120
1728
|
|
|
1121
1729
|
marks[0]
|
|
1730
|
+
|
|
1122
1731
|
marks[1]
|
|
1732
|
+
|
|
1123
1733
|
marks[2]
|
|
1124
1734
|
```
|
|
1125
1735
|
|
|
@@ -1132,16 +1742,20 @@ For example:
|
|
|
1132
1742
|
```text
|
|
1133
1743
|
|
|
1134
1744
|
products[0].quantity
|
|
1745
|
+
|
|
1135
1746
|
products[1].quantity
|
|
1747
|
+
|
|
1136
1748
|
products[2].price
|
|
1137
1749
|
```
|
|
1138
1750
|
|
|
1139
1751
|
This provides enough information for consumers to identify the exact
|
|
1752
|
+
|
|
1140
1753
|
field that caused the validation error.
|
|
1141
1754
|
|
|
1142
1755
|
### Why Structured Paths Are Useful
|
|
1143
1756
|
|
|
1144
1757
|
Instead of parsing an error message to determine which field failed,
|
|
1758
|
+
|
|
1145
1759
|
applications can directly use:
|
|
1146
1760
|
|
|
1147
1761
|
```js
|
|
@@ -1177,246 +1791,421 @@ Result:
|
|
|
1177
1791
|
{
|
|
1178
1792
|
|
|
1179
1793
|
"email": "Invalid email format for attribute email",
|
|
1794
|
+
|
|
1180
1795
|
"address.location.latitude": "Invalid type for attribute address.location.latitude, required number value",
|
|
1796
|
+
|
|
1181
1797
|
"marks[2]": "Attribute marks[2] should have a value between 0 and 100"
|
|
1182
1798
|
|
|
1183
1799
|
}
|
|
1184
1800
|
```
|
|
1185
1801
|
|
|
1186
|
-
## Examples
|
|
1802
|
+
## Examples and Usage
|
|
1187
1803
|
|
|
1188
1804
|
### Sample Validation Rule
|
|
1189
1805
|
|
|
1190
1806
|
sample-1
|
|
1191
1807
|
|
|
1192
|
-
```
|
|
1808
|
+
```js
|
|
1193
1809
|
|
|
1194
1810
|
{
|
|
1195
1811
|
|
|
1196
1812
|
firstName: {
|
|
1813
|
+
|
|
1197
1814
|
mandatory: true,
|
|
1815
|
+
|
|
1198
1816
|
allowNull: false,
|
|
1817
|
+
|
|
1199
1818
|
type: "string",
|
|
1819
|
+
|
|
1200
1820
|
minLength: 3,
|
|
1201
|
-
|
|
1821
|
+
|
|
1822
|
+
minLengthError: "First name must have minimum 3 characters."
|
|
1823
|
+
|
|
1202
1824
|
},
|
|
1825
|
+
|
|
1203
1826
|
lastName: {
|
|
1827
|
+
|
|
1204
1828
|
mandatory: false,
|
|
1829
|
+
|
|
1205
1830
|
allowNull: true,
|
|
1831
|
+
|
|
1206
1832
|
type: "string",
|
|
1833
|
+
|
|
1207
1834
|
},
|
|
1835
|
+
|
|
1208
1836
|
email: {
|
|
1837
|
+
|
|
1209
1838
|
mandatory: true,
|
|
1839
|
+
|
|
1210
1840
|
allowNull: false,
|
|
1841
|
+
|
|
1211
1842
|
type: "email",
|
|
1843
|
+
|
|
1212
1844
|
},
|
|
1845
|
+
|
|
1213
1846
|
phone: {
|
|
1847
|
+
|
|
1214
1848
|
mandatory: true,
|
|
1849
|
+
|
|
1215
1850
|
allowNull: false,
|
|
1851
|
+
|
|
1216
1852
|
type: "string",
|
|
1853
|
+
|
|
1217
1854
|
},
|
|
1855
|
+
|
|
1218
1856
|
age: {
|
|
1857
|
+
|
|
1219
1858
|
mandatory: false,
|
|
1859
|
+
|
|
1220
1860
|
type: "number",
|
|
1861
|
+
|
|
1221
1862
|
min: 1,
|
|
1863
|
+
|
|
1222
1864
|
max: 120,
|
|
1865
|
+
|
|
1223
1866
|
},
|
|
1867
|
+
|
|
1224
1868
|
};
|
|
1225
1869
|
```
|
|
1226
1870
|
|
|
1227
1871
|
sample-2
|
|
1228
1872
|
|
|
1229
|
-
```
|
|
1873
|
+
```js
|
|
1230
1874
|
|
|
1231
1875
|
{
|
|
1876
|
+
|
|
1232
1877
|
id: {
|
|
1878
|
+
|
|
1233
1879
|
mandatory: true,
|
|
1880
|
+
|
|
1234
1881
|
allowNull: true,
|
|
1882
|
+
|
|
1235
1883
|
type: "uuidv4",
|
|
1884
|
+
|
|
1236
1885
|
},
|
|
1886
|
+
|
|
1237
1887
|
batchId: {
|
|
1888
|
+
|
|
1238
1889
|
mandatory: true,
|
|
1890
|
+
|
|
1239
1891
|
allowNull: true,
|
|
1892
|
+
|
|
1240
1893
|
type: "objectId",
|
|
1894
|
+
|
|
1241
1895
|
},
|
|
1896
|
+
|
|
1242
1897
|
firstName: {
|
|
1898
|
+
|
|
1243
1899
|
mandatory: true,
|
|
1900
|
+
|
|
1244
1901
|
type: "string",
|
|
1902
|
+
|
|
1245
1903
|
minLength: 3,
|
|
1904
|
+
|
|
1246
1905
|
},
|
|
1906
|
+
|
|
1247
1907
|
lastName: {
|
|
1908
|
+
|
|
1248
1909
|
mandatory: false,
|
|
1910
|
+
|
|
1249
1911
|
allowNull: true,
|
|
1912
|
+
|
|
1250
1913
|
type: "string",
|
|
1914
|
+
|
|
1251
1915
|
},
|
|
1916
|
+
|
|
1252
1917
|
age: {
|
|
1918
|
+
|
|
1253
1919
|
type: "number",
|
|
1920
|
+
|
|
1254
1921
|
min: 0.1,
|
|
1922
|
+
|
|
1255
1923
|
max: 120,
|
|
1924
|
+
|
|
1256
1925
|
},
|
|
1926
|
+
|
|
1257
1927
|
isAdult: {
|
|
1928
|
+
|
|
1258
1929
|
type: "boolean",
|
|
1930
|
+
|
|
1259
1931
|
},
|
|
1932
|
+
|
|
1260
1933
|
totalWins: {
|
|
1934
|
+
|
|
1261
1935
|
type: "number",
|
|
1936
|
+
|
|
1262
1937
|
min: 0,
|
|
1938
|
+
|
|
1263
1939
|
preventDecimal: true,
|
|
1940
|
+
|
|
1264
1941
|
},
|
|
1942
|
+
|
|
1265
1943
|
email: {
|
|
1266
|
-
|
|
1944
|
+
|
|
1945
|
+
regex: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$/,
|
|
1946
|
+
|
|
1267
1947
|
},
|
|
1948
|
+
|
|
1268
1949
|
githubLink: {
|
|
1950
|
+
|
|
1269
1951
|
type: "url",
|
|
1952
|
+
|
|
1270
1953
|
},
|
|
1954
|
+
|
|
1271
1955
|
accountStatus: {
|
|
1956
|
+
|
|
1272
1957
|
type: "enum",
|
|
1958
|
+
|
|
1273
1959
|
enumValues: ["Active", "Inactive", 200],
|
|
1960
|
+
|
|
1274
1961
|
},
|
|
1962
|
+
|
|
1275
1963
|
marks: {
|
|
1964
|
+
|
|
1276
1965
|
range: "0-100",
|
|
1966
|
+
|
|
1277
1967
|
},
|
|
1968
|
+
|
|
1278
1969
|
allMarks: {
|
|
1970
|
+
|
|
1279
1971
|
type: "array",
|
|
1972
|
+
|
|
1280
1973
|
allowEmptyArray: false,
|
|
1974
|
+
|
|
1281
1975
|
elementConstraints: {
|
|
1976
|
+
|
|
1282
1977
|
type: "number",
|
|
1978
|
+
|
|
1283
1979
|
allowNull: false,
|
|
1980
|
+
|
|
1284
1981
|
range: "0-100",
|
|
1982
|
+
|
|
1285
1983
|
},
|
|
1984
|
+
|
|
1286
1985
|
},
|
|
1986
|
+
|
|
1287
1987
|
totalScore: {
|
|
1988
|
+
|
|
1288
1989
|
type: "number",
|
|
1990
|
+
|
|
1289
1991
|
dependency: {
|
|
1992
|
+
|
|
1290
1993
|
result: {
|
|
1994
|
+
|
|
1291
1995
|
setDependencyRule: (totalScore, result) => {
|
|
1996
|
+
|
|
1292
1997
|
return { mandatory: true, allowNull: false, type: "string" };
|
|
1998
|
+
|
|
1293
1999
|
},
|
|
2000
|
+
|
|
1294
2001
|
},
|
|
2002
|
+
|
|
1295
2003
|
},
|
|
2004
|
+
|
|
1296
2005
|
},
|
|
2006
|
+
|
|
1297
2007
|
result: {
|
|
2008
|
+
|
|
1298
2009
|
type: "string",
|
|
2010
|
+
|
|
1299
2011
|
dependency: {
|
|
2012
|
+
|
|
1300
2013
|
totalScore: {
|
|
2014
|
+
|
|
1301
2015
|
setDependencyRule: (result, totalScore) => {
|
|
2016
|
+
|
|
1302
2017
|
return { mandatory: true, allowNull: false, type: "number" };
|
|
2018
|
+
|
|
1303
2019
|
},
|
|
2020
|
+
|
|
1304
2021
|
},
|
|
2022
|
+
|
|
1305
2023
|
},
|
|
2024
|
+
|
|
1306
2025
|
},
|
|
2026
|
+
|
|
1307
2027
|
minSalary: {
|
|
2028
|
+
|
|
1308
2029
|
mandatory: true,
|
|
2030
|
+
|
|
1309
2031
|
min: 1,
|
|
2032
|
+
|
|
1310
2033
|
type: "number",
|
|
2034
|
+
|
|
1311
2035
|
dependency: {
|
|
2036
|
+
|
|
1312
2037
|
maxSalary: {
|
|
2038
|
+
|
|
1313
2039
|
setDependencyRule: (minSalary, maxSalary) => {
|
|
2040
|
+
|
|
1314
2041
|
return {
|
|
2042
|
+
|
|
1315
2043
|
mandatory: true,
|
|
2044
|
+
|
|
1316
2045
|
min: minSalary + 1,
|
|
2046
|
+
|
|
1317
2047
|
minError: "maxSalary must be more than minSalary",
|
|
2048
|
+
|
|
1318
2049
|
};
|
|
2050
|
+
|
|
1319
2051
|
},
|
|
2052
|
+
|
|
1320
2053
|
},
|
|
2054
|
+
|
|
1321
2055
|
},
|
|
2056
|
+
|
|
1322
2057
|
},
|
|
2058
|
+
|
|
1323
2059
|
maxSalary: {
|
|
2060
|
+
|
|
1324
2061
|
dependency: {
|
|
2062
|
+
|
|
1325
2063
|
minSalary: {
|
|
2064
|
+
|
|
1326
2065
|
setDependencyRule: (maxSalary, minSalary) => {
|
|
2066
|
+
|
|
1327
2067
|
return {
|
|
2068
|
+
|
|
1328
2069
|
mandatory: true,
|
|
2070
|
+
|
|
1329
2071
|
max: maxSalary - 1,
|
|
2072
|
+
|
|
1330
2073
|
maxError: "minSalary must be less than maxSalary",
|
|
2074
|
+
|
|
1331
2075
|
};
|
|
2076
|
+
|
|
1332
2077
|
},
|
|
2078
|
+
|
|
1333
2079
|
},
|
|
2080
|
+
|
|
1334
2081
|
},
|
|
2082
|
+
|
|
1335
2083
|
},
|
|
2084
|
+
|
|
1336
2085
|
address: {
|
|
2086
|
+
|
|
1337
2087
|
mandatory: true,
|
|
2088
|
+
|
|
1338
2089
|
type: "object",
|
|
2090
|
+
|
|
1339
2091
|
allowEmptyObject: false,
|
|
2092
|
+
|
|
1340
2093
|
objectAttr: {
|
|
2094
|
+
|
|
1341
2095
|
country: { mandatory: true, type: "string" },
|
|
2096
|
+
|
|
1342
2097
|
state: {
|
|
2098
|
+
|
|
1343
2099
|
mandatory: true,
|
|
2100
|
+
|
|
1344
2101
|
type: "string",
|
|
2102
|
+
|
|
1345
2103
|
},
|
|
2104
|
+
|
|
1346
2105
|
city: {},
|
|
2106
|
+
|
|
1347
2107
|
zip: {
|
|
2108
|
+
|
|
1348
2109
|
mandatory: true,
|
|
2110
|
+
|
|
1349
2111
|
type: "string",
|
|
2112
|
+
|
|
1350
2113
|
},
|
|
2114
|
+
|
|
1351
2115
|
position: {
|
|
2116
|
+
|
|
1352
2117
|
mandatory: true,
|
|
2118
|
+
|
|
1353
2119
|
type: "object",
|
|
2120
|
+
|
|
1354
2121
|
allowEmptyObject: false,
|
|
2122
|
+
|
|
1355
2123
|
objectAttr: {
|
|
2124
|
+
|
|
1356
2125
|
lattitude: { mandatory: true, type: "number" },
|
|
2126
|
+
|
|
1357
2127
|
longitude: {
|
|
2128
|
+
|
|
1358
2129
|
mandatory: true,
|
|
2130
|
+
|
|
1359
2131
|
type: "number",
|
|
2132
|
+
|
|
1360
2133
|
},
|
|
2134
|
+
|
|
1361
2135
|
},
|
|
2136
|
+
|
|
1362
2137
|
},
|
|
2138
|
+
|
|
1363
2139
|
},
|
|
2140
|
+
|
|
1364
2141
|
},
|
|
2142
|
+
|
|
1365
2143
|
}
|
|
1366
2144
|
```
|
|
1367
2145
|
|
|
1368
2146
|
### Usage
|
|
1369
2147
|
|
|
1370
|
-
####
|
|
2148
|
+
#### Creating a route with payload validation middleware
|
|
1371
2149
|
|
|
1372
|
-
```
|
|
2150
|
+
```js
|
|
1373
2151
|
|
|
1374
|
-
|
|
2152
|
+
**// validatePayload is the middleware that invokes perfectPayload()**
|
|
1375
2153
|
|
|
1376
2154
|
router.post(
|
|
2155
|
+
|
|
1377
2156
|
"/payload-validation",
|
|
2157
|
+
|
|
1378
2158
|
validatePayload({ rule: <your validation rule json object> }),
|
|
2159
|
+
|
|
1379
2160
|
(req, res) => res.send("OK")
|
|
2161
|
+
|
|
1380
2162
|
);
|
|
1381
2163
|
```
|
|
1382
2164
|
|
|
1383
|
-
####
|
|
2165
|
+
#### ES Modules middleware example
|
|
1384
2166
|
|
|
1385
|
-
```
|
|
1386
|
-
import {
|
|
2167
|
+
```js
|
|
2168
|
+
import { perfectPayload } from "perfect-payload";
|
|
1387
2169
|
|
|
1388
2170
|
export const validatePayload = ({ rule }) => {
|
|
1389
2171
|
return (req, res, next) => {
|
|
1390
2172
|
try {
|
|
1391
|
-
const { statusCode, ...response } =
|
|
2173
|
+
const { statusCode, ...response } = perfectPayload(req?.body, rule);
|
|
2174
|
+
|
|
1392
2175
|
if (+statusCode >= 200 && +statusCode <= 299) {
|
|
1393
2176
|
req.validatedBody = response?.validatedPayload;
|
|
2177
|
+
|
|
1394
2178
|
next();
|
|
1395
2179
|
} else res.status(statusCode).json(response);
|
|
1396
2180
|
} catch (error) {
|
|
1397
2181
|
console.error("Error validating payload", error);
|
|
2182
|
+
|
|
1398
2183
|
res.status(500).json({ error: "Internal Server Error" });
|
|
1399
2184
|
}
|
|
1400
2185
|
};
|
|
1401
2186
|
};
|
|
1402
2187
|
```
|
|
1403
2188
|
|
|
1404
|
-
####
|
|
2189
|
+
#### CommonJS middleware example
|
|
1405
2190
|
|
|
1406
|
-
```
|
|
2191
|
+
```js
|
|
1407
2192
|
function validatePayload({ rule }) {
|
|
1408
2193
|
return async (req, res, next) => {
|
|
1409
2194
|
try {
|
|
1410
|
-
const {
|
|
1411
|
-
|
|
2195
|
+
const { perfectPayload } = await import("perfect-payload");
|
|
2196
|
+
|
|
2197
|
+
const { statusCode, ...response } = perfectPayload(req?.body, rule);
|
|
2198
|
+
|
|
1412
2199
|
if (+statusCode >= 200 && +statusCode <= 299) {
|
|
1413
2200
|
req.validatedBody = response?.validatedPayload;
|
|
2201
|
+
|
|
1414
2202
|
next();
|
|
1415
2203
|
} else {
|
|
1416
2204
|
res.status(statusCode).json(response);
|
|
1417
2205
|
}
|
|
1418
2206
|
} catch (error) {
|
|
1419
2207
|
console.error("Error validating payload", error);
|
|
2208
|
+
|
|
1420
2209
|
res.status(500).json({ error: "Internal Server Error" });
|
|
1421
2210
|
}
|
|
1422
2211
|
};
|
|
@@ -1428,5 +2217,7 @@ module.exports = { validatePayload };
|
|
|
1428
2217
|
---
|
|
1429
2218
|
|
|
1430
2219
|
This documentation provides a comprehensive guide to using the data
|
|
2220
|
+
|
|
1431
2221
|
validation module effectively. Ensure to define your validation rules
|
|
2222
|
+
|
|
1432
2223
|
clearly to maintain data quality and consistency in your applications.
|