perfect-payload 1.5.0-beta.1 → 1.7.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 +1295 -452
- package/index.js +247 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,22 +1,48 @@
|
|
|
1
1
|
# perfect-payload
|
|
2
2
|
|
|
3
|
-
A lightweight JavaScript payload validation
|
|
4
|
-
and JSON payloads
|
|
5
|
-
|
|
6
|
-
`perfect-payload`
|
|
7
|
-
paths, synchronous custom
|
|
8
|
-
transformation/sanitization, array
|
|
9
|
-
array
|
|
3
|
+
A lightweight JavaScript payload validation and transformation utility
|
|
4
|
+
for API and JSON payloads.
|
|
5
|
+
|
|
6
|
+
`perfect-payload` provides structured validation errors, exact nested
|
|
7
|
+
field paths, synchronous and asynchronous custom validation, synchronous
|
|
8
|
+
transformation/sanitization, array constraints, and deeply nested
|
|
9
|
+
object/array validation while keeping schemas simple and the package
|
|
10
|
+
lightweight.
|
|
11
|
+
|
|
12
|
+
## Highlights
|
|
13
|
+
|
|
14
|
+
- Lightweight, rule-based payload validation
|
|
15
|
+
- Structured errors with stable machine-readable codes
|
|
16
|
+
- Exact nested paths such as `profile.email` and
|
|
17
|
+
`products[1].quantity`
|
|
18
|
+
- Recursive `objectAttr` and `elementConstraints` validation
|
|
19
|
+
- `minItems` and `maxItems` array constraints
|
|
20
|
+
- Built-in `trim`, `lowercase`, and `uppercase` sanitization
|
|
21
|
+
- Custom synchronous `transform(value, payload)`
|
|
22
|
+
- Custom synchronous validators with `perfectPayload()`
|
|
23
|
+
- Custom synchronous or asynchronous validators with
|
|
24
|
+
`perfectPayloadAsync()`
|
|
25
|
+
- Transformed values returned through `validatedPayload`
|
|
26
|
+
- Original input payload is not mutated
|
|
27
|
+
- Configurable unknown-field handling: `strip`, `allow`, or `reject`
|
|
28
|
+
- Clean three-argument API with validation options in one object
|
|
29
|
+
- Legacy `perfectPayloadV1()` retained during the migration period
|
|
10
30
|
|
|
11
31
|
## Quick Links
|
|
12
32
|
|
|
13
33
|
- [Installation](#installation)
|
|
14
34
|
- [Basic Usage](#basic-usage)
|
|
35
|
+
- [Public API](#public-api)
|
|
36
|
+
- [Unknown Field Handling](#unknown-field-handling)
|
|
37
|
+
- [Synchronous vs Asynchronous
|
|
38
|
+
Validation](#synchronous-vs-asynchronous-validation)
|
|
15
39
|
- [Validation Rules](#validation-rules)
|
|
16
|
-
- [Array Size and Nested
|
|
40
|
+
- [Array Size and Nested
|
|
41
|
+
Validation](#array-size-and-nested-validation)
|
|
17
42
|
- [Transformations and
|
|
18
43
|
Sanitization](#transformations-and-sanitization)
|
|
19
44
|
- [Custom Validators](#customvalidator)
|
|
45
|
+
- [Asynchronous Validation](#asynchronous-validation)
|
|
20
46
|
- [Error Codes](#error-codes)
|
|
21
47
|
- [Custom Error Messages](#custom-error-messages)
|
|
22
48
|
- [Nested Objects and Array Field
|
|
@@ -24,6 +50,31 @@ array/object validation while keeping the validation schema simple.
|
|
|
24
50
|
- [Examples and Usage](#examples-and-usage)
|
|
25
51
|
- [Legacy API](#legacy-api)
|
|
26
52
|
|
|
53
|
+
## What's New in v1.7.0
|
|
54
|
+
|
|
55
|
+
v1.7.0 introduces two API-level improvements:
|
|
56
|
+
|
|
57
|
+
1. `unknownFields` gives explicit control over fields that are not
|
|
58
|
+
declared in the validation schema: `"strip"`, `"allow"`, or
|
|
59
|
+
`"reject"`.
|
|
60
|
+
2. `perfectPayload()` and `perfectPayloadAsync()` now use a clean
|
|
61
|
+
three-argument API where custom response objects and other API
|
|
62
|
+
options live inside one `options` object.
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
const result = perfectPayload(payload, rules, {
|
|
66
|
+
unknownFields: "reject",
|
|
67
|
+
inValidPayloadResponse: {
|
|
68
|
+
statusCode: 422,
|
|
69
|
+
valid: false,
|
|
70
|
+
message: "Payload validation failed",
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The default `unknownFields` mode is `"strip"`, preserving the previous
|
|
76
|
+
validated-payload filtering behavior when no option is supplied.
|
|
77
|
+
|
|
27
78
|
## Installation
|
|
28
79
|
|
|
29
80
|
```bash
|
|
@@ -79,29 +130,28 @@ console.log(result);
|
|
|
79
130
|
|
|
80
131
|
{
|
|
81
132
|
|
|
82
|
-
|
|
133
|
+
statusCode: 200,
|
|
83
134
|
|
|
84
|
-
|
|
135
|
+
valid: true,
|
|
85
136
|
|
|
86
|
-
|
|
137
|
+
validatedPayload: {
|
|
87
138
|
|
|
88
|
-
|
|
139
|
+
name: "Kiran",
|
|
89
140
|
|
|
90
|
-
|
|
141
|
+
email: "kiran@example.com",
|
|
91
142
|
|
|
92
|
-
|
|
143
|
+
age: 29
|
|
93
144
|
|
|
94
|
-
|
|
145
|
+
}
|
|
95
146
|
|
|
96
147
|
}
|
|
97
148
|
```
|
|
98
149
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
schema, automatically filtering out any extra attributes. You can use it
|
|
150
|
+
By default, `validatedPayload` contains only fields defined in the
|
|
151
|
+
validation schema. Extra payload fields are stripped unless
|
|
152
|
+
`unknownFields` is explicitly configured as `"allow"` or `"reject"`.
|
|
103
153
|
|
|
104
|
-
|
|
154
|
+
The original input payload is not mutated.
|
|
105
155
|
|
|
106
156
|
(such as validatedBody, sanitisedData or parsedBody).
|
|
107
157
|
|
|
@@ -111,25 +161,25 @@ to safely overwrite request.body or assign it to a new request property
|
|
|
111
161
|
|
|
112
162
|
{
|
|
113
163
|
|
|
114
|
-
|
|
164
|
+
statusCode: 400,
|
|
115
165
|
|
|
116
|
-
|
|
166
|
+
valid: false,
|
|
117
167
|
|
|
118
|
-
|
|
168
|
+
message: "One or more attribute values are invalid",
|
|
119
169
|
|
|
120
|
-
|
|
170
|
+
errors: [
|
|
121
171
|
|
|
122
|
-
|
|
172
|
+
{
|
|
123
173
|
|
|
124
|
-
|
|
174
|
+
path: "email",
|
|
125
175
|
|
|
126
|
-
|
|
176
|
+
code: "INVALID_EMAIL",
|
|
127
177
|
|
|
128
|
-
|
|
178
|
+
message: "Invalid email format for attribute email"
|
|
129
179
|
|
|
130
|
-
|
|
180
|
+
}
|
|
131
181
|
|
|
132
|
-
|
|
182
|
+
]
|
|
133
183
|
|
|
134
184
|
}
|
|
135
185
|
```
|
|
@@ -140,11 +190,11 @@ Each error returned by `perfectPayload()` contains:
|
|
|
140
190
|
|
|
141
191
|
{
|
|
142
192
|
|
|
143
|
-
|
|
193
|
+
path: "field.path",
|
|
144
194
|
|
|
145
|
-
|
|
195
|
+
code: "ERROR_CODE",
|
|
146
196
|
|
|
147
|
-
|
|
197
|
+
message: "Human readable validation message"
|
|
148
198
|
|
|
149
199
|
}
|
|
150
200
|
```
|
|
@@ -159,6 +209,424 @@ failure.
|
|
|
159
209
|
|
|
160
210
|
\- Submitted payload values are not included in default error messages.
|
|
161
211
|
|
|
212
|
+
## Public API
|
|
213
|
+
|
|
214
|
+
For new implementations, both supported APIs use the same clean
|
|
215
|
+
three-argument signature:
|
|
216
|
+
|
|
217
|
+
```js
|
|
218
|
+
perfectPayload(data, validationRules, options?)
|
|
219
|
+
await perfectPayloadAsync(data, validationRules, options?)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The arguments are:
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
Argument Required Description
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
`data` No Payload/object to
|
|
231
|
+
validate. Defaults to
|
|
232
|
+
`{}`.
|
|
233
|
+
|
|
234
|
+
`validationRules` No Validation schema.
|
|
235
|
+
Defaults to `{}`.
|
|
236
|
+
|
|
237
|
+
`options` No API-level configuration
|
|
238
|
+
such as unknown-field
|
|
239
|
+
handling and custom
|
|
240
|
+
response objects.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
The third argument is a single options object. You no longer need to
|
|
245
|
+
pass separate positional arguments for custom valid and invalid
|
|
246
|
+
responses.
|
|
247
|
+
|
|
248
|
+
### Options
|
|
249
|
+
|
|
250
|
+
```js
|
|
251
|
+
{
|
|
252
|
+
unknownFields: "strip" | "allow" | "reject",
|
|
253
|
+
|
|
254
|
+
validPayloadResponse: {
|
|
255
|
+
statusCode: 200,
|
|
256
|
+
valid: true,
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
inValidPayloadResponse: {
|
|
260
|
+
statusCode: 400,
|
|
261
|
+
valid: false,
|
|
262
|
+
message: "One or more attribute values are invalid",
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
All properties are optional.
|
|
268
|
+
|
|
269
|
+
The defaults are equivalent to:
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
{
|
|
273
|
+
unknownFields: "strip",
|
|
274
|
+
|
|
275
|
+
validPayloadResponse: {
|
|
276
|
+
statusCode: 200,
|
|
277
|
+
valid: true,
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
inValidPayloadResponse: {
|
|
281
|
+
statusCode: 400,
|
|
282
|
+
valid: false,
|
|
283
|
+
message: "One or more attribute values are invalid",
|
|
284
|
+
},
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Example:
|
|
289
|
+
|
|
290
|
+
```js
|
|
291
|
+
const result = perfectPayload(payload, validationRules, {
|
|
292
|
+
unknownFields: "reject",
|
|
293
|
+
|
|
294
|
+
validPayloadResponse: {
|
|
295
|
+
statusCode: 201,
|
|
296
|
+
valid: true,
|
|
297
|
+
message: "Payload accepted",
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
inValidPayloadResponse: {
|
|
301
|
+
statusCode: 422,
|
|
302
|
+
valid: false,
|
|
303
|
+
message: "Payload validation failed",
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
The same options object is supported by `perfectPayloadAsync()`:
|
|
309
|
+
|
|
310
|
+
```js
|
|
311
|
+
const result = await perfectPayloadAsync(payload, validationRules, {
|
|
312
|
+
unknownFields: "reject",
|
|
313
|
+
inValidPayloadResponse: {
|
|
314
|
+
statusCode: 422,
|
|
315
|
+
valid: false,
|
|
316
|
+
message: "Payload validation failed",
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Unknown Field Handling
|
|
322
|
+
|
|
323
|
+
`unknownFields` controls what happens when the input payload contains a
|
|
324
|
+
field that is not defined in the validation schema.
|
|
325
|
+
|
|
326
|
+
Supported values:
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
Value Behavior
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
`"strip"` Removes unknown fields from
|
|
335
|
+
`validatedPayload`. This is the
|
|
336
|
+
default and preserves the existing
|
|
337
|
+
behavior.
|
|
338
|
+
|
|
339
|
+
`"allow"` Preserves unknown fields in
|
|
340
|
+
`validatedPayload`.
|
|
341
|
+
|
|
342
|
+
`"reject"` Rejects unknown fields with
|
|
343
|
+
structured `UNKNOWN_FIELD`
|
|
344
|
+
validation errors.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
### `strip` --- default
|
|
349
|
+
|
|
350
|
+
```js
|
|
351
|
+
const payload = {
|
|
352
|
+
name: "Kiran",
|
|
353
|
+
role: "developer",
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const rules = {
|
|
357
|
+
name: {
|
|
358
|
+
type: "string",
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const result = perfectPayload(payload, rules);
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Result:
|
|
366
|
+
|
|
367
|
+
```js
|
|
368
|
+
{
|
|
369
|
+
statusCode: 200,
|
|
370
|
+
valid: true,
|
|
371
|
+
validatedPayload: {
|
|
372
|
+
name: "Kiran"
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
`role` is not part of the schema, so it is removed from
|
|
378
|
+
`validatedPayload`.
|
|
379
|
+
|
|
380
|
+
You can also set the default behavior explicitly:
|
|
381
|
+
|
|
382
|
+
```js
|
|
383
|
+
perfectPayload(payload, rules, {
|
|
384
|
+
unknownFields: "strip",
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### `allow` --- preserve unknown fields
|
|
389
|
+
|
|
390
|
+
```js
|
|
391
|
+
const result = perfectPayload(payload, rules, {
|
|
392
|
+
unknownFields: "allow",
|
|
393
|
+
});
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Result:
|
|
397
|
+
|
|
398
|
+
```js
|
|
399
|
+
{
|
|
400
|
+
statusCode: 200,
|
|
401
|
+
valid: true,
|
|
402
|
+
validatedPayload: {
|
|
403
|
+
name: "Kiran",
|
|
404
|
+
role: "developer"
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Schema-defined fields are still validated normally. Unknown fields are
|
|
410
|
+
simply preserved.
|
|
411
|
+
|
|
412
|
+
### `reject` --- reject unknown fields
|
|
413
|
+
|
|
414
|
+
```js
|
|
415
|
+
const result = perfectPayload(payload, rules, {
|
|
416
|
+
unknownFields: "reject",
|
|
417
|
+
});
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Result:
|
|
421
|
+
|
|
422
|
+
```js
|
|
423
|
+
{
|
|
424
|
+
statusCode: 400,
|
|
425
|
+
valid: false,
|
|
426
|
+
message: "One or more attribute values are invalid",
|
|
427
|
+
errors: [
|
|
428
|
+
{
|
|
429
|
+
path: "role",
|
|
430
|
+
code: "UNKNOWN_FIELD",
|
|
431
|
+
message: "Unknown field role is not allowed"
|
|
432
|
+
}
|
|
433
|
+
]
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Unknown-field errors use the same structured error format as all other
|
|
438
|
+
validation errors.
|
|
439
|
+
|
|
440
|
+
### Nested objects
|
|
441
|
+
|
|
442
|
+
Unknown-field handling is recursive for schemas using `objectAttr`.
|
|
443
|
+
|
|
444
|
+
```js
|
|
445
|
+
const payload = {
|
|
446
|
+
profile: {
|
|
447
|
+
city: "Bengaluru",
|
|
448
|
+
role: "developer",
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
const rules = {
|
|
453
|
+
profile: {
|
|
454
|
+
type: "object",
|
|
455
|
+
objectAttr: {
|
|
456
|
+
city: {
|
|
457
|
+
type: "string",
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
const result = perfectPayload(payload, rules, {
|
|
464
|
+
unknownFields: "reject",
|
|
465
|
+
});
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
|
|
470
|
+
```js
|
|
471
|
+
{
|
|
472
|
+
statusCode: 400,
|
|
473
|
+
valid: false,
|
|
474
|
+
message: "One or more attribute values are invalid",
|
|
475
|
+
errors: [
|
|
476
|
+
{
|
|
477
|
+
path: "profile.role",
|
|
478
|
+
code: "UNKNOWN_FIELD",
|
|
479
|
+
message: "Unknown field profile.role is not allowed"
|
|
480
|
+
}
|
|
481
|
+
]
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Arrays and deep paths
|
|
486
|
+
|
|
487
|
+
`unknownFields` also applies recursively through `elementConstraints`.
|
|
488
|
+
|
|
489
|
+
For an unknown field inside an array element, the error path includes
|
|
490
|
+
the array index:
|
|
491
|
+
|
|
492
|
+
```js
|
|
493
|
+
{
|
|
494
|
+
path: "products[0].internalId",
|
|
495
|
+
code: "UNKNOWN_FIELD",
|
|
496
|
+
message: "Unknown field products[0].internalId is not allowed"
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
This continues through deeply nested combinations of objects and arrays,
|
|
501
|
+
for example:
|
|
502
|
+
|
|
503
|
+
```text
|
|
504
|
+
profile.teams[0].members[0].role
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Normal validation errors and unknown fields
|
|
508
|
+
|
|
509
|
+
With `"reject"`, unknown-field errors can be returned together with
|
|
510
|
+
normal validation errors.
|
|
511
|
+
|
|
512
|
+
For example, an invalid email plus two unknown fields can produce:
|
|
513
|
+
|
|
514
|
+
```js
|
|
515
|
+
{
|
|
516
|
+
statusCode: 400,
|
|
517
|
+
valid: false,
|
|
518
|
+
message: "One or more attribute values are invalid",
|
|
519
|
+
errors: [
|
|
520
|
+
{
|
|
521
|
+
path: "email",
|
|
522
|
+
code: "INVALID_EMAIL",
|
|
523
|
+
message: "Invalid email format for attribute email"
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
path: "role",
|
|
527
|
+
code: "UNKNOWN_FIELD",
|
|
528
|
+
message: "Unknown field role is not allowed"
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
path: "active",
|
|
532
|
+
code: "UNKNOWN_FIELD",
|
|
533
|
+
message: "Unknown field active is not allowed"
|
|
534
|
+
}
|
|
535
|
+
]
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
With `"allow"`, unknown fields do not create validation errors. Normal
|
|
540
|
+
schema validation continues unchanged.
|
|
541
|
+
|
|
542
|
+
### Async behavior
|
|
543
|
+
|
|
544
|
+
`perfectPayloadAsync()` supports the same `unknownFields` option:
|
|
545
|
+
|
|
546
|
+
```js
|
|
547
|
+
const result = await perfectPayloadAsync(payload, rules, {
|
|
548
|
+
unknownFields: "reject",
|
|
549
|
+
});
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
Unknown-field checking is part of the synchronous validation phase. If
|
|
553
|
+
`"reject"` finds an unknown field, asynchronous `customValidator`
|
|
554
|
+
functions are not executed for that payload. This follows the normal
|
|
555
|
+
two-phase contract of `perfectPayloadAsync()`.
|
|
556
|
+
|
|
557
|
+
### Own properties only
|
|
558
|
+
|
|
559
|
+
Unknown-field handling considers only the payload object's own
|
|
560
|
+
enumerable properties. Enumerable properties inherited through the
|
|
561
|
+
prototype chain are ignored.
|
|
562
|
+
|
|
563
|
+
### Invalid option values
|
|
564
|
+
|
|
565
|
+
Only these values are accepted:
|
|
566
|
+
|
|
567
|
+
```text
|
|
568
|
+
strip
|
|
569
|
+
allow
|
|
570
|
+
reject
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
Any other value throws a configuration error:
|
|
574
|
+
|
|
575
|
+
```text
|
|
576
|
+
perfect-payload:- unknownFields must be one of strip, allow, reject
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
This is a configuration error, not a payload validation error.
|
|
580
|
+
|
|
581
|
+
## Synchronous vs Asynchronous Validation
|
|
582
|
+
|
|
583
|
+
For normal synchronous validation, use `perfectPayload()`:
|
|
584
|
+
|
|
585
|
+
```js
|
|
586
|
+
import { perfectPayload } from "perfect-payload";
|
|
587
|
+
|
|
588
|
+
const result = perfectPayload(payload, validationRules, options);
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
When any `customValidator` needs to perform asynchronous work, use
|
|
592
|
+
`perfectPayloadAsync()` and `await` the result:
|
|
593
|
+
|
|
594
|
+
```js
|
|
595
|
+
import { perfectPayloadAsync } from "perfect-payload";
|
|
596
|
+
|
|
597
|
+
const result = await perfectPayloadAsync(payload, validationRules, options);
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
The public APIs are:
|
|
601
|
+
|
|
602
|
+
```text
|
|
603
|
+
perfectPayloadV1() legacy API; deprecated
|
|
604
|
+
perfectPayload(data, rules, options?) synchronous validation
|
|
605
|
+
perfectPayloadAsync(data, rules, options?) synchronous + asynchronous customValidator
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
`perfectPayload()` remains synchronous and intentionally rejects a
|
|
609
|
+
`customValidator` that returns a Promise. This preserves the existing
|
|
610
|
+
synchronous API contract.
|
|
611
|
+
|
|
612
|
+
`perfectPayloadAsync()` first performs transformations and normal
|
|
613
|
+
synchronous validation. If synchronous validation fails, the result is
|
|
614
|
+
returned immediately and asynchronous validators are not executed. This
|
|
615
|
+
avoids unnecessary asynchronous work for payloads that are already
|
|
616
|
+
invalid.
|
|
617
|
+
|
|
618
|
+
```text
|
|
619
|
+
transformations
|
|
620
|
+
↓
|
|
621
|
+
synchronous validation
|
|
622
|
+
↓
|
|
623
|
+
sync errors? ── yes ──→ return validation errors
|
|
624
|
+
↓ no
|
|
625
|
+
async customValidator
|
|
626
|
+
↓
|
|
627
|
+
return result
|
|
628
|
+
```
|
|
629
|
+
|
|
162
630
|
## Legacy API
|
|
163
631
|
|
|
164
632
|
`perfectPayloadV1()` is still available for backward compatibility.
|
|
@@ -173,10 +641,16 @@ March 31, 2027.
|
|
|
173
641
|
|
|
174
642
|
Existing applications can continue using it during the migration period,
|
|
175
643
|
|
|
176
|
-
but all new implementations should use:
|
|
644
|
+
but all new implementations should use the current API:
|
|
177
645
|
|
|
178
646
|
```js
|
|
179
|
-
perfectPayload();
|
|
647
|
+
perfectPayload(data, validationRules, options?);
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
For asynchronous custom validation:
|
|
651
|
+
|
|
652
|
+
```js
|
|
653
|
+
await perfectPayloadAsync(data, validationRules, options?);
|
|
180
654
|
```
|
|
181
655
|
|
|
182
656
|
The legacy API continues to return validation errors as:
|
|
@@ -200,6 +674,7 @@ errors: [
|
|
|
200
674
|
```
|
|
201
675
|
|
|
202
676
|
Note: If an inValidPayloadResponse is provided, the
|
|
677
|
+
|
|
203
678
|
system returns
|
|
204
679
|
|
|
205
680
|
it alongside an automatically generated errors property. Do not include
|
|
@@ -211,6 +686,7 @@ object.
|
|
|
211
686
|
## Validation Rules
|
|
212
687
|
|
|
213
688
|
`perfectPayload()` supports validation, nested-schema,
|
|
689
|
+
|
|
214
690
|
custom-validation, and transformation rules.
|
|
215
691
|
|
|
216
692
|
### `mandatory`
|
|
@@ -305,6 +781,7 @@ Default: Not applied when omitted.
|
|
|
305
781
|
const rules = {
|
|
306
782
|
tags: {
|
|
307
783
|
type: "array",
|
|
784
|
+
|
|
308
785
|
minItems: 2,
|
|
309
786
|
},
|
|
310
787
|
};
|
|
@@ -313,14 +790,20 @@ const rules = {
|
|
|
313
790
|
An array with fewer than 2 items returns `MIN_ITEMS`.
|
|
314
791
|
|
|
315
792
|
```js
|
|
793
|
+
|
|
316
794
|
{
|
|
795
|
+
|
|
317
796
|
path: "tags",
|
|
797
|
+
|
|
318
798
|
code: "MIN_ITEMS",
|
|
799
|
+
|
|
319
800
|
message: "Attribute tags must contain at least 2 item(s)"
|
|
801
|
+
|
|
320
802
|
}
|
|
321
803
|
```
|
|
322
804
|
|
|
323
|
-
`minItems` is enforced even when `allowEmptyArray: true` is set. For
|
|
805
|
+
`minItems` is enforced even when `allowEmptyArray: true` is set. For
|
|
806
|
+
example, `minItems: 2` still rejects `[]`.
|
|
324
807
|
|
|
325
808
|
Error code: `MIN_ITEMS`
|
|
326
809
|
|
|
@@ -336,6 +819,7 @@ Default: Not applied when omitted.
|
|
|
336
819
|
const rules = {
|
|
337
820
|
tags: {
|
|
338
821
|
type: "array",
|
|
822
|
+
|
|
339
823
|
maxItems: 5,
|
|
340
824
|
},
|
|
341
825
|
};
|
|
@@ -344,10 +828,15 @@ const rules = {
|
|
|
344
828
|
An array with more than 5 items returns `MAX_ITEMS`.
|
|
345
829
|
|
|
346
830
|
```js
|
|
831
|
+
|
|
347
832
|
{
|
|
833
|
+
|
|
348
834
|
path: "tags",
|
|
835
|
+
|
|
349
836
|
code: "MAX_ITEMS",
|
|
837
|
+
|
|
350
838
|
message: "Attribute tags must contain at most 5 item(s)"
|
|
839
|
+
|
|
351
840
|
}
|
|
352
841
|
```
|
|
353
842
|
|
|
@@ -554,6 +1043,7 @@ Error code: `MAX_LENGTH`
|
|
|
554
1043
|
Prevents decimal numbers.
|
|
555
1044
|
|
|
556
1045
|
Default: `false`; both integer and decimal numbers are
|
|
1046
|
+
|
|
557
1047
|
allowed.
|
|
558
1048
|
|
|
559
1049
|
Example:
|
|
@@ -664,13 +1154,13 @@ Example error:
|
|
|
664
1154
|
|
|
665
1155
|
{
|
|
666
1156
|
|
|
667
|
-
|
|
1157
|
+
path: "marks[2]",
|
|
668
1158
|
|
|
669
|
-
|
|
1159
|
+
code: "OUT_OF_RANGE",
|
|
670
1160
|
|
|
671
|
-
|
|
1161
|
+
message:
|
|
672
1162
|
|
|
673
|
-
|
|
1163
|
+
"Attribute marks[2] should have a value between 0 and 100"
|
|
674
1164
|
|
|
675
1165
|
}
|
|
676
1166
|
```
|
|
@@ -739,13 +1229,13 @@ Nested errors include the complete field path:
|
|
|
739
1229
|
|
|
740
1230
|
{
|
|
741
1231
|
|
|
742
|
-
|
|
1232
|
+
path: "address.location.latitude",
|
|
743
1233
|
|
|
744
|
-
|
|
1234
|
+
code: "INVALID_TYPE",
|
|
745
1235
|
|
|
746
|
-
|
|
1236
|
+
message:
|
|
747
1237
|
|
|
748
|
-
|
|
1238
|
+
"Invalid type for attribute address.location.latitude, required number value"
|
|
749
1239
|
|
|
750
1240
|
}
|
|
751
1241
|
```
|
|
@@ -784,13 +1274,13 @@ Example error:
|
|
|
784
1274
|
|
|
785
1275
|
{
|
|
786
1276
|
|
|
787
|
-
|
|
1277
|
+
path: "maxSalary",
|
|
788
1278
|
|
|
789
|
-
|
|
1279
|
+
code: "MIN_VALUE",
|
|
790
1280
|
|
|
791
|
-
|
|
1281
|
+
message:
|
|
792
1282
|
|
|
793
|
-
|
|
1283
|
+
"maxSalary must be more than minSalary"
|
|
794
1284
|
|
|
795
1285
|
}
|
|
796
1286
|
```
|
|
@@ -799,7 +1289,9 @@ Example error:
|
|
|
799
1289
|
|
|
800
1290
|
## Array Size and Nested Validation
|
|
801
1291
|
|
|
802
|
-
`perfectPayload()` supports array size constraints and recursive
|
|
1292
|
+
`perfectPayload()` supports array size constraints and recursive
|
|
1293
|
+
validation of arrays and objects at multiple depths. Array indexes and
|
|
1294
|
+
nested object keys are preserved in structured error paths.
|
|
803
1295
|
|
|
804
1296
|
### Array size constraints
|
|
805
1297
|
|
|
@@ -809,12 +1301,17 @@ Use `minItems` and `maxItems` with `type: "array"`:
|
|
|
809
1301
|
const rules = {
|
|
810
1302
|
products: {
|
|
811
1303
|
type: "array",
|
|
1304
|
+
|
|
812
1305
|
minItems: 1,
|
|
1306
|
+
|
|
813
1307
|
maxItems: 3,
|
|
1308
|
+
|
|
814
1309
|
elementConstraints: {
|
|
815
1310
|
type: "object",
|
|
1311
|
+
|
|
816
1312
|
objectAttr: {
|
|
817
1313
|
productId: { mandatory: true, type: "string" },
|
|
1314
|
+
|
|
818
1315
|
quantity: { mandatory: true, type: "number", min: 1 },
|
|
819
1316
|
},
|
|
820
1317
|
},
|
|
@@ -825,18 +1322,26 @@ const rules = {
|
|
|
825
1322
|
If the array is empty, `minItems` reports the array path itself:
|
|
826
1323
|
|
|
827
1324
|
```js
|
|
1325
|
+
|
|
828
1326
|
{
|
|
1327
|
+
|
|
829
1328
|
path: "products",
|
|
1329
|
+
|
|
830
1330
|
code: "MIN_ITEMS",
|
|
1331
|
+
|
|
831
1332
|
message: "Attribute products must contain at least 1 item(s)"
|
|
1333
|
+
|
|
832
1334
|
}
|
|
833
1335
|
```
|
|
834
1336
|
|
|
835
1337
|
### Arrays of objects
|
|
836
1338
|
|
|
837
|
-
`elementConstraints` can contain `objectAttr`, allowing every object in
|
|
1339
|
+
`elementConstraints` can contain `objectAttr`, allowing every object in
|
|
1340
|
+
an array to use a nested schema. An invalid quantity in the second
|
|
1341
|
+
product is reported as:
|
|
838
1342
|
|
|
839
1343
|
```text
|
|
1344
|
+
|
|
840
1345
|
products[1].quantity
|
|
841
1346
|
```
|
|
842
1347
|
|
|
@@ -848,21 +1353,32 @@ products[1].quantity
|
|
|
848
1353
|
const rules = {
|
|
849
1354
|
orders: {
|
|
850
1355
|
type: "array",
|
|
1356
|
+
|
|
851
1357
|
minItems: 1,
|
|
1358
|
+
|
|
852
1359
|
maxItems: 2,
|
|
1360
|
+
|
|
853
1361
|
elementConstraints: {
|
|
854
1362
|
type: "object",
|
|
1363
|
+
|
|
855
1364
|
objectAttr: {
|
|
856
1365
|
orderId: { mandatory: true, type: "string" },
|
|
1366
|
+
|
|
857
1367
|
items: {
|
|
858
1368
|
mandatory: true,
|
|
1369
|
+
|
|
859
1370
|
type: "array",
|
|
1371
|
+
|
|
860
1372
|
minItems: 1,
|
|
1373
|
+
|
|
861
1374
|
maxItems: 2,
|
|
1375
|
+
|
|
862
1376
|
elementConstraints: {
|
|
863
1377
|
type: "object",
|
|
1378
|
+
|
|
864
1379
|
objectAttr: {
|
|
865
1380
|
productId: { mandatory: true, type: "string" },
|
|
1381
|
+
|
|
866
1382
|
quantity: { mandatory: true, type: "number", min: 1 },
|
|
867
1383
|
},
|
|
868
1384
|
},
|
|
@@ -873,31 +1389,41 @@ const rules = {
|
|
|
873
1389
|
};
|
|
874
1390
|
```
|
|
875
1391
|
|
|
876
|
-
A deep validation failure preserves the complete indexed path, for
|
|
1392
|
+
A deep validation failure preserves the complete indexed path, for
|
|
1393
|
+
example:
|
|
877
1394
|
|
|
878
1395
|
```text
|
|
1396
|
+
|
|
879
1397
|
orders[1].items[2].quantity
|
|
880
1398
|
```
|
|
881
1399
|
|
|
882
|
-
Array constraints work at nested levels too. A nested array can report
|
|
1400
|
+
Array constraints work at nested levels too. A nested array can report
|
|
1401
|
+
paths such as:
|
|
883
1402
|
|
|
884
1403
|
```text
|
|
1404
|
+
|
|
885
1405
|
orders[1].items
|
|
886
1406
|
```
|
|
887
1407
|
|
|
888
1408
|
Nested arrays are supported and every array index is preserved:
|
|
889
1409
|
|
|
890
1410
|
```text
|
|
1411
|
+
|
|
891
1412
|
matrix[1][1]
|
|
1413
|
+
|
|
892
1414
|
matrix[1][1][1]
|
|
893
1415
|
```
|
|
894
1416
|
|
|
895
|
-
Transformations applied inside nested objects or array elements are
|
|
1417
|
+
Transformations applied inside nested objects or array elements are
|
|
1418
|
+
preserved in `validatedPayload`, while the original input remains
|
|
1419
|
+
unchanged.
|
|
896
1420
|
|
|
897
1421
|
### Transformations and Sanitization
|
|
898
1422
|
|
|
899
1423
|
`perfectPayload()` can transform a field before its validation rules
|
|
1424
|
+
|
|
900
1425
|
run. The transformed value is returned in `validatedPayload`, while the
|
|
1426
|
+
|
|
901
1427
|
original input object is not mutated.
|
|
902
1428
|
|
|
903
1429
|
Supported transformation rules:
|
|
@@ -907,53 +1433,81 @@ Rule Purpose
|
|
|
907
1433
|
---
|
|
908
1434
|
|
|
909
1435
|
`trim` Removes leading and trailing whitespace from strings
|
|
1436
|
+
|
|
910
1437
|
`lowercase` Converts strings to lowercase
|
|
1438
|
+
|
|
911
1439
|
`uppercase` Converts strings to uppercase
|
|
1440
|
+
|
|
912
1441
|
`transform` Runs a custom synchronous transformation function
|
|
913
1442
|
|
|
914
1443
|
Transformations always run in this fixed order, regardless of the order
|
|
1444
|
+
|
|
915
1445
|
in which the rule properties are written:
|
|
916
1446
|
|
|
917
1447
|
```text
|
|
1448
|
+
|
|
918
1449
|
trim
|
|
1450
|
+
|
|
919
1451
|
↓
|
|
1452
|
+
|
|
920
1453
|
lowercase
|
|
1454
|
+
|
|
921
1455
|
↓
|
|
1456
|
+
|
|
922
1457
|
uppercase
|
|
1458
|
+
|
|
923
1459
|
↓
|
|
1460
|
+
|
|
924
1461
|
transform(value, payload)
|
|
1462
|
+
|
|
925
1463
|
↓
|
|
1464
|
+
|
|
926
1465
|
validation rules
|
|
1466
|
+
|
|
927
1467
|
↓
|
|
1468
|
+
|
|
928
1469
|
customValidator
|
|
1470
|
+
|
|
929
1471
|
↓
|
|
1472
|
+
|
|
930
1473
|
validatedPayload
|
|
931
1474
|
```
|
|
932
1475
|
|
|
933
1476
|
#### `trim`
|
|
934
1477
|
|
|
935
1478
|
```js
|
|
1479
|
+
|
|
936
1480
|
const payload = {
|
|
1481
|
+
|
|
937
1482
|
name: " Kiran Poojary ",
|
|
1483
|
+
|
|
938
1484
|
};
|
|
939
1485
|
|
|
940
1486
|
const rules = {
|
|
1487
|
+
|
|
941
1488
|
name: {
|
|
1489
|
+
|
|
942
1490
|
type: "string",
|
|
1491
|
+
|
|
943
1492
|
trim: true,
|
|
1493
|
+
|
|
944
1494
|
},
|
|
1495
|
+
|
|
945
1496
|
};
|
|
946
1497
|
|
|
947
1498
|
const result = perfectPayload(payload, rules);
|
|
948
1499
|
|
|
949
1500
|
console.log(result.validatedPayload.name);
|
|
950
|
-
|
|
1501
|
+
|
|
1502
|
+
*// "Kiran Poojary"*
|
|
951
1503
|
|
|
952
1504
|
console.log(payload.name);
|
|
953
|
-
|
|
1505
|
+
|
|
1506
|
+
*// " Kiran Poojary "*
|
|
954
1507
|
```
|
|
955
1508
|
|
|
956
1509
|
`trim` applies only to string values. Non-string values are left
|
|
1510
|
+
|
|
957
1511
|
unchanged.
|
|
958
1512
|
|
|
959
1513
|
#### `lowercase`
|
|
@@ -962,13 +1516,16 @@ unchanged.
|
|
|
962
1516
|
const rules = {
|
|
963
1517
|
email: {
|
|
964
1518
|
trim: true,
|
|
1519
|
+
|
|
965
1520
|
lowercase: true,
|
|
1521
|
+
|
|
966
1522
|
type: "email",
|
|
967
1523
|
},
|
|
968
1524
|
};
|
|
969
1525
|
```
|
|
970
1526
|
|
|
971
1527
|
For `" KIRAN@EXAMPLE.COM "`, the validated value becomes
|
|
1528
|
+
|
|
972
1529
|
`"kiran@example.com"`.
|
|
973
1530
|
|
|
974
1531
|
#### `uppercase`
|
|
@@ -977,6 +1534,7 @@ For `" KIRAN@EXAMPLE.COM "`, the validated value becomes
|
|
|
977
1534
|
const rules = {
|
|
978
1535
|
countryCode: {
|
|
979
1536
|
type: "string",
|
|
1537
|
+
|
|
980
1538
|
uppercase: true,
|
|
981
1539
|
},
|
|
982
1540
|
};
|
|
@@ -985,6 +1543,7 @@ const rules = {
|
|
|
985
1543
|
For `"in"`, the validated value becomes `"IN"`.
|
|
986
1544
|
|
|
987
1545
|
`lowercase: true` and `uppercase: true` cannot be enabled together for
|
|
1546
|
+
|
|
988
1547
|
the same field. Doing so throws a schema configuration error.
|
|
989
1548
|
|
|
990
1549
|
#### `transform`
|
|
@@ -995,6 +1554,7 @@ Use `transform` when the built-in string transformations are not enough.
|
|
|
995
1554
|
const rules = {
|
|
996
1555
|
phone: {
|
|
997
1556
|
type: "string",
|
|
1557
|
+
|
|
998
1558
|
transform: (value) => value.replace(/\s+/g, ""),
|
|
999
1559
|
},
|
|
1000
1560
|
};
|
|
@@ -1011,31 +1571,46 @@ transform: (value, payload) => {
|
|
|
1011
1571
|
```
|
|
1012
1572
|
|
|
1013
1573
|
- `value` is the field value after the built-in transformations have
|
|
1574
|
+
|
|
1014
1575
|
run.
|
|
1576
|
+
|
|
1015
1577
|
- `payload` is the current payload/object being validated.
|
|
1016
1578
|
|
|
1017
1579
|
This makes cross-field transformations possible:
|
|
1018
1580
|
|
|
1019
1581
|
```js
|
|
1582
|
+
|
|
1020
1583
|
const payload = {
|
|
1584
|
+
|
|
1021
1585
|
amount: 100,
|
|
1586
|
+
|
|
1022
1587
|
multiplier: 2,
|
|
1588
|
+
|
|
1023
1589
|
};
|
|
1024
1590
|
|
|
1025
1591
|
const rules = {
|
|
1592
|
+
|
|
1026
1593
|
amount: {
|
|
1594
|
+
|
|
1027
1595
|
transform: (value, payload) => value * payload.multiplier,
|
|
1596
|
+
|
|
1028
1597
|
type: "number",
|
|
1598
|
+
|
|
1029
1599
|
},
|
|
1600
|
+
|
|
1030
1601
|
multiplier: {
|
|
1602
|
+
|
|
1031
1603
|
type: "number",
|
|
1604
|
+
|
|
1032
1605
|
},
|
|
1606
|
+
|
|
1033
1607
|
};
|
|
1034
1608
|
|
|
1035
1609
|
const result = perfectPayload(payload, rules);
|
|
1036
1610
|
|
|
1037
1611
|
console.log(result.validatedPayload.amount);
|
|
1038
|
-
|
|
1612
|
+
|
|
1613
|
+
*// 200*
|
|
1039
1614
|
```
|
|
1040
1615
|
|
|
1041
1616
|
A custom transformer may also change the data type before validation:
|
|
@@ -1044,36 +1619,48 @@ A custom transformer may also change the data type before validation:
|
|
|
1044
1619
|
const rules = {
|
|
1045
1620
|
quantity: {
|
|
1046
1621
|
transform: (value) => Number(value),
|
|
1622
|
+
|
|
1047
1623
|
type: "number",
|
|
1624
|
+
|
|
1048
1625
|
min: 1,
|
|
1626
|
+
|
|
1049
1627
|
max: 100,
|
|
1050
1628
|
},
|
|
1051
1629
|
};
|
|
1052
1630
|
```
|
|
1053
1631
|
|
|
1054
1632
|
The transformed value is validated by the normal validation rules and is
|
|
1633
|
+
|
|
1055
1634
|
also the value received by `customValidator`.
|
|
1056
1635
|
|
|
1057
1636
|
Transformations work inside `objectAttr` and `elementConstraints`, and
|
|
1637
|
+
|
|
1058
1638
|
transformed nested/array values are preserved in `validatedPayload`.
|
|
1059
1639
|
|
|
1060
1640
|
```js
|
|
1061
1641
|
const rules = {
|
|
1062
1642
|
profile: {
|
|
1063
1643
|
type: "object",
|
|
1644
|
+
|
|
1064
1645
|
objectAttr: {
|
|
1065
1646
|
name: {
|
|
1066
1647
|
trim: true,
|
|
1648
|
+
|
|
1067
1649
|
uppercase: true,
|
|
1650
|
+
|
|
1068
1651
|
type: "string",
|
|
1069
1652
|
},
|
|
1070
1653
|
},
|
|
1071
1654
|
},
|
|
1655
|
+
|
|
1072
1656
|
tags: {
|
|
1073
1657
|
type: "array",
|
|
1658
|
+
|
|
1074
1659
|
elementConstraints: {
|
|
1075
1660
|
trim: true,
|
|
1661
|
+
|
|
1076
1662
|
lowercase: true,
|
|
1663
|
+
|
|
1077
1664
|
type: "string",
|
|
1078
1665
|
},
|
|
1079
1666
|
},
|
|
@@ -1081,29 +1668,39 @@ const rules = {
|
|
|
1081
1668
|
```
|
|
1082
1669
|
|
|
1083
1670
|
Missing optional fields are not transformed. An input value of `null` is
|
|
1671
|
+
|
|
1084
1672
|
not passed to transformation functions; null handling remains controlled
|
|
1673
|
+
|
|
1085
1674
|
by `allowNull`.
|
|
1086
1675
|
|
|
1087
|
-
|
|
1676
|
+
\*\*\*\*Important:\*\*\*\* `transform` is synchronous. A non-function
|
|
1677
|
+
transformer,
|
|
1678
|
+
|
|
1088
1679
|
an `async` transformer, a transformer that returns a Promise, or a
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1680
|
+
|
|
1681
|
+
transformer that returns `undefined` is not supported and throws an
|
|
1682
|
+
error.
|
|
1683
|
+
|
|
1684
|
+
Returning `null`, `""`, `0`, or `false` is allowed; the transformed
|
|
1685
|
+
value is
|
|
1686
|
+
|
|
1687
|
+
then processed by the normal validation rules. Exceptions thrown inside
|
|
1688
|
+
the
|
|
1689
|
+
|
|
1092
1690
|
transformer propagate to the caller.
|
|
1093
1691
|
|
|
1094
1692
|
For example, returning `undefined` throws:
|
|
1095
1693
|
|
|
1096
1694
|
```text
|
|
1695
|
+
|
|
1097
1696
|
perfect-payload:- transform must not return undefined for attribute username
|
|
1098
1697
|
```
|
|
1099
1698
|
|
|
1100
1699
|
### `customValidator`
|
|
1101
1700
|
|
|
1102
|
-
|
|
1103
|
-
when the built-in validation rules are not enough.
|
|
1701
|
+
Defines custom validation logic when the built-in rules are not enough.
|
|
1104
1702
|
|
|
1105
|
-
The validator receives
|
|
1106
|
-
being validated:
|
|
1703
|
+
The validator receives:
|
|
1107
1704
|
|
|
1108
1705
|
```js
|
|
1109
1706
|
customValidator: (value, payload) => {
|
|
@@ -1111,100 +1708,302 @@ customValidator: (value, payload) => {
|
|
|
1111
1708
|
};
|
|
1112
1709
|
```
|
|
1113
1710
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1711
|
+
- `value` is the field value after transformations have been applied.
|
|
1712
|
+
- `payload` is the current payload/object being validated.
|
|
1713
|
+
- Return `true` to pass.
|
|
1714
|
+
- Any value other than `true` fails validation.
|
|
1715
|
+
- Exceptions thrown by the validator propagate to the caller.
|
|
1716
|
+
|
|
1717
|
+
For nested validation, `payload` means the current nested object rather
|
|
1718
|
+
than the root request body.
|
|
1116
1719
|
|
|
1117
|
-
|
|
1720
|
+
#### Synchronous custom validator
|
|
1721
|
+
|
|
1722
|
+
Use a synchronous validator with `perfectPayload()`:
|
|
1118
1723
|
|
|
1119
1724
|
```js
|
|
1120
1725
|
const rules = {
|
|
1121
1726
|
username: {
|
|
1122
1727
|
mandatory: true,
|
|
1123
|
-
|
|
1124
1728
|
type: "string",
|
|
1729
|
+
trim: true,
|
|
1125
1730
|
|
|
1126
1731
|
customValidator: (value) => {
|
|
1127
1732
|
return !value.toLowerCase().includes("admin");
|
|
1128
1733
|
},
|
|
1129
1734
|
|
|
1130
1735
|
customValidatorCode: "RESERVED_USERNAME",
|
|
1131
|
-
|
|
1132
1736
|
customValidatorError: "Username cannot contain admin",
|
|
1133
1737
|
},
|
|
1134
1738
|
};
|
|
1739
|
+
|
|
1740
|
+
const result = perfectPayload({ username: " admin_kiran " }, rules);
|
|
1135
1741
|
```
|
|
1136
1742
|
|
|
1137
|
-
|
|
1743
|
+
A failure returns:
|
|
1138
1744
|
|
|
1139
1745
|
```js
|
|
1140
|
-
|
|
1141
|
-
|
|
1746
|
+
{
|
|
1747
|
+
statusCode: 400,
|
|
1748
|
+
valid: false,
|
|
1749
|
+
message: "One or more attribute values are invalid",
|
|
1750
|
+
errors: [
|
|
1751
|
+
{
|
|
1752
|
+
path: "username",
|
|
1753
|
+
code: "RESERVED_USERNAME",
|
|
1754
|
+
message: "Username cannot contain admin"
|
|
1755
|
+
}
|
|
1756
|
+
]
|
|
1757
|
+
}
|
|
1758
|
+
```
|
|
1759
|
+
|
|
1760
|
+
The current payload/object can be used for cross-field validation:
|
|
1761
|
+
|
|
1762
|
+
```js
|
|
1763
|
+
const rules = {
|
|
1764
|
+
limit: {
|
|
1765
|
+
type: "number",
|
|
1766
|
+
},
|
|
1767
|
+
|
|
1768
|
+
amount: {
|
|
1769
|
+
type: "number",
|
|
1770
|
+
|
|
1771
|
+
customValidator: (value, payload) => {
|
|
1772
|
+
return value <= payload.limit;
|
|
1773
|
+
},
|
|
1774
|
+
|
|
1775
|
+
customValidatorCode: "LIMIT_EXCEEDED",
|
|
1776
|
+
customValidatorError: "Amount cannot exceed limit",
|
|
1777
|
+
},
|
|
1142
1778
|
};
|
|
1143
1779
|
```
|
|
1144
1780
|
|
|
1145
|
-
|
|
1781
|
+
If `customValidatorCode` and `customValidatorError` are omitted, the
|
|
1782
|
+
default error is:
|
|
1146
1783
|
|
|
1147
1784
|
```js
|
|
1785
|
+
{
|
|
1786
|
+
path: "username",
|
|
1787
|
+
code: "CUSTOM_VALIDATION_FAILED",
|
|
1788
|
+
message: "Custom validation failed for attribute username"
|
|
1789
|
+
}
|
|
1790
|
+
```
|
|
1791
|
+
|
|
1792
|
+
`customValidator` works recursively inside `objectAttr` and
|
|
1793
|
+
`elementConstraints`. Structured errors preserve the corresponding
|
|
1794
|
+
nested and array paths.
|
|
1795
|
+
|
|
1796
|
+
When using `perfectPayload()`, `customValidator` must remain
|
|
1797
|
+
synchronous. A Promise-returning validator throws:
|
|
1798
|
+
|
|
1799
|
+
```text
|
|
1800
|
+
perfect-payload:- customValidator must be synchronous for attribute username
|
|
1801
|
+
```
|
|
1802
|
+
|
|
1803
|
+
For asynchronous custom validation, use `perfectPayloadAsync()`.
|
|
1804
|
+
|
|
1805
|
+
## Asynchronous Validation
|
|
1806
|
+
|
|
1807
|
+
`perfectPayloadAsync()` supports both synchronous and asynchronous
|
|
1808
|
+
`customValidator` functions without changing the behavior of
|
|
1809
|
+
`perfectPayload()`.
|
|
1148
1810
|
|
|
1811
|
+
```js
|
|
1812
|
+
import { perfectPayloadAsync } from "perfect-payload";
|
|
1813
|
+
|
|
1814
|
+
const rules = {
|
|
1815
|
+
username: {
|
|
1816
|
+
mandatory: true,
|
|
1817
|
+
type: "string",
|
|
1818
|
+
trim: true,
|
|
1819
|
+
|
|
1820
|
+
customValidator: async (value) => {
|
|
1821
|
+
const available = await checkUsernameAvailability(value);
|
|
1822
|
+
return available;
|
|
1823
|
+
},
|
|
1824
|
+
|
|
1825
|
+
customValidatorCode: "USERNAME_TAKEN",
|
|
1826
|
+
customValidatorError: "Username is already taken",
|
|
1827
|
+
},
|
|
1828
|
+
};
|
|
1829
|
+
|
|
1830
|
+
const result = await perfectPayloadAsync(
|
|
1831
|
+
{
|
|
1832
|
+
username: " kiran ",
|
|
1833
|
+
},
|
|
1834
|
+
rules,
|
|
1835
|
+
);
|
|
1836
|
+
```
|
|
1837
|
+
|
|
1838
|
+
On success, transformations are preserved:
|
|
1839
|
+
|
|
1840
|
+
```js
|
|
1149
1841
|
{
|
|
1842
|
+
statusCode: 200,
|
|
1843
|
+
valid: true,
|
|
1844
|
+
validatedPayload: {
|
|
1845
|
+
username: "kiran"
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
```
|
|
1849
|
+
|
|
1850
|
+
On asynchronous validation failure:
|
|
1851
|
+
|
|
1852
|
+
```js
|
|
1853
|
+
{
|
|
1854
|
+
statusCode: 400,
|
|
1855
|
+
valid: false,
|
|
1856
|
+
message: "One or more attribute values are invalid",
|
|
1857
|
+
errors: [
|
|
1858
|
+
{
|
|
1859
|
+
path: "username",
|
|
1860
|
+
code: "USERNAME_TAKEN",
|
|
1861
|
+
message: "Username is already taken"
|
|
1862
|
+
}
|
|
1863
|
+
]
|
|
1864
|
+
}
|
|
1865
|
+
```
|
|
1866
|
+
|
|
1867
|
+
### Async validator contract
|
|
1868
|
+
|
|
1869
|
+
For `perfectPayloadAsync()`:
|
|
1870
|
+
|
|
1871
|
+
```text
|
|
1872
|
+
true → pass
|
|
1873
|
+
false → validation failure
|
|
1874
|
+
anything != true → validation failure
|
|
1875
|
+
throw → exception propagates
|
|
1876
|
+
rejected Promise → rejection propagates
|
|
1877
|
+
```
|
|
1878
|
+
|
|
1879
|
+
A normal synchronous validator is also valid when using the asynchronous
|
|
1880
|
+
API:
|
|
1881
|
+
|
|
1882
|
+
```js
|
|
1883
|
+
const rules = {
|
|
1884
|
+
username: {
|
|
1885
|
+
type: "string",
|
|
1886
|
+
customValidator: (value) => value !== "admin",
|
|
1887
|
+
},
|
|
1888
|
+
};
|
|
1889
|
+
|
|
1890
|
+
const result = await perfectPayloadAsync(payload, rules);
|
|
1891
|
+
```
|
|
1892
|
+
|
|
1893
|
+
A configured `customValidator` must be a function. Otherwise an error is
|
|
1894
|
+
thrown:
|
|
1895
|
+
|
|
1896
|
+
```text
|
|
1897
|
+
perfect-payload:- customValidator must be a function for attribute username
|
|
1898
|
+
```
|
|
1899
|
+
|
|
1900
|
+
### Validation order
|
|
1901
|
+
|
|
1902
|
+
`perfectPayloadAsync()` uses two phases:
|
|
1903
|
+
|
|
1904
|
+
1. Transform the payload and run normal synchronous validation.
|
|
1905
|
+
2. If phase 1 succeeds, run custom validators with `await`.
|
|
1906
|
+
|
|
1907
|
+
If any synchronous validation error exists, phase 2 is skipped and the
|
|
1908
|
+
synchronous validation result is returned immediately.
|
|
1909
|
+
|
|
1910
|
+
This means asynchronous validators can assume the payload has already
|
|
1911
|
+
passed its normal synchronous validation rules.
|
|
1912
|
+
|
|
1913
|
+
### Nested async validation
|
|
1914
|
+
|
|
1915
|
+
Async custom validators work recursively inside `objectAttr`:
|
|
1916
|
+
|
|
1917
|
+
```js
|
|
1918
|
+
const rules = {
|
|
1919
|
+
profile: {
|
|
1920
|
+
type: "object",
|
|
1921
|
+
|
|
1922
|
+
objectAttr: {
|
|
1923
|
+
username: {
|
|
1924
|
+
type: "string",
|
|
1925
|
+
trim: true,
|
|
1926
|
+
|
|
1927
|
+
customValidator: async (value) => {
|
|
1928
|
+
return await isUsernameAvailable(value);
|
|
1929
|
+
},
|
|
1930
|
+
|
|
1931
|
+
customValidatorCode: "USERNAME_TAKEN",
|
|
1932
|
+
customValidatorError: "Username is already taken",
|
|
1933
|
+
},
|
|
1934
|
+
},
|
|
1935
|
+
},
|
|
1936
|
+
};
|
|
1937
|
+
```
|
|
1938
|
+
|
|
1939
|
+
A failure produces the complete path:
|
|
1940
|
+
|
|
1941
|
+
```text
|
|
1942
|
+
profile.username
|
|
1943
|
+
```
|
|
1944
|
+
|
|
1945
|
+
They also work inside `elementConstraints`:
|
|
1946
|
+
|
|
1947
|
+
```js
|
|
1948
|
+
const rules = {
|
|
1949
|
+
usernames: {
|
|
1950
|
+
type: "array",
|
|
1951
|
+
|
|
1952
|
+
elementConstraints: {
|
|
1953
|
+
type: "string",
|
|
1954
|
+
trim: true,
|
|
1955
|
+
|
|
1956
|
+
customValidator: async (value) => {
|
|
1957
|
+
return await isUsernameAvailable(value);
|
|
1958
|
+
},
|
|
1959
|
+
|
|
1960
|
+
customValidatorCode: "USERNAME_TAKEN",
|
|
1961
|
+
customValidatorError: "Username is already taken",
|
|
1962
|
+
},
|
|
1963
|
+
},
|
|
1964
|
+
};
|
|
1965
|
+
```
|
|
1150
1966
|
|
|
1151
|
-
|
|
1967
|
+
For an invalid second element:
|
|
1152
1968
|
|
|
1153
|
-
|
|
1969
|
+
```text
|
|
1970
|
+
usernames[1]
|
|
1971
|
+
```
|
|
1154
1972
|
|
|
1155
|
-
|
|
1973
|
+
Deep combinations of objects and arrays preserve every level of the
|
|
1974
|
+
path:
|
|
1156
1975
|
|
|
1157
|
-
|
|
1976
|
+
```text
|
|
1977
|
+
products[1].seller.username
|
|
1978
|
+
profile.teams[1].members[1].username
|
|
1158
1979
|
```
|
|
1159
1980
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1981
|
+
Default async custom-validation messages also use the final indexed
|
|
1982
|
+
path:
|
|
1162
1983
|
|
|
1163
1984
|
```js
|
|
1164
|
-
|
|
1165
1985
|
{
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
code: "CUSTOM_VALIDATION_FAILED",
|
|
1170
|
-
|
|
1171
|
-
message: "Custom validation failed for attribute username"
|
|
1172
|
-
|
|
1986
|
+
path: "users[1].username",
|
|
1987
|
+
code: "CUSTOM_VALIDATION_FAILED",
|
|
1988
|
+
message: "Custom validation failed for attribute users[1].username"
|
|
1173
1989
|
}
|
|
1174
1990
|
```
|
|
1175
1991
|
|
|
1176
|
-
|
|
1177
|
-
argument when required:
|
|
1178
|
-
|
|
1179
|
-
```js
|
|
1180
|
-
const rules = {
|
|
1181
|
-
limit: {
|
|
1182
|
-
type: "number",
|
|
1183
|
-
},
|
|
1184
|
-
|
|
1185
|
-
amount: {
|
|
1186
|
-
type: "number",
|
|
1992
|
+
### Transform remains synchronous
|
|
1187
1993
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
},
|
|
1994
|
+
`perfectPayloadAsync()` makes custom validation asynchronous; it does
|
|
1995
|
+
not make `transform` asynchronous.
|
|
1191
1996
|
|
|
1192
|
-
|
|
1997
|
+
`transform` must still be synchronous:
|
|
1193
1998
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1999
|
+
```js
|
|
2000
|
+
transform: (value, payload) => {
|
|
2001
|
+
return value;
|
|
1196
2002
|
};
|
|
1197
2003
|
```
|
|
1198
2004
|
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
contains the corresponding nested or array path.
|
|
1202
|
-
|
|
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.
|
|
1206
|
-
|
|
1207
|
-
Error code when no custom code is provided: `CUSTOM_VALIDATION_FAILED`
|
|
2005
|
+
An async transformer or a transformer that returns a Promise is not
|
|
2006
|
+
supported.
|
|
1208
2007
|
|
|
1209
2008
|
## Error Codes
|
|
1210
2009
|
|
|
@@ -1263,6 +2062,8 @@ MAX_VALUE
|
|
|
1263
2062
|
OUT_OF_RANGE
|
|
1264
2063
|
|
|
1265
2064
|
CUSTOM_VALIDATION_FAILED
|
|
2065
|
+
|
|
2066
|
+
UNKNOWN_FIELD
|
|
1266
2067
|
```
|
|
1267
2068
|
|
|
1268
2069
|
These codes are designed for programmatic handling while `message`
|
|
@@ -1277,17 +2078,17 @@ const result = perfectPayload(payload, validationRules);
|
|
|
1277
2078
|
|
|
1278
2079
|
if (!result.valid) {
|
|
1279
2080
|
|
|
1280
|
-
|
|
2081
|
+
const emailError = result.errors.find(
|
|
1281
2082
|
|
|
1282
|
-
|
|
2083
|
+
(error) => error.code === "INVALID_EMAIL",
|
|
1283
2084
|
|
|
1284
|
-
|
|
2085
|
+
);
|
|
1285
2086
|
|
|
1286
|
-
|
|
2087
|
+
if (emailError) {
|
|
1287
2088
|
|
|
1288
|
-
|
|
2089
|
+
***// Handle invalid email***
|
|
1289
2090
|
|
|
1290
|
-
|
|
2091
|
+
}
|
|
1291
2092
|
|
|
1292
2093
|
}
|
|
1293
2094
|
```
|
|
@@ -1304,11 +2105,11 @@ keeping the same structured error format:
|
|
|
1304
2105
|
|
|
1305
2106
|
{
|
|
1306
2107
|
|
|
1307
|
-
|
|
2108
|
+
path: "email",
|
|
1308
2109
|
|
|
1309
|
-
|
|
2110
|
+
code: "INVALID_EMAIL",
|
|
1310
2111
|
|
|
1311
|
-
|
|
2112
|
+
message: "Email address is invalid"
|
|
1312
2113
|
|
|
1313
2114
|
}
|
|
1314
2115
|
```
|
|
@@ -1335,11 +2136,11 @@ If `email` is missing:
|
|
|
1335
2136
|
|
|
1336
2137
|
{
|
|
1337
2138
|
|
|
1338
|
-
|
|
2139
|
+
path: "email",
|
|
1339
2140
|
|
|
1340
|
-
|
|
2141
|
+
code: "REQUIRED",
|
|
1341
2142
|
|
|
1342
|
-
|
|
2143
|
+
message: "Email is required"
|
|
1343
2144
|
|
|
1344
2145
|
}
|
|
1345
2146
|
```
|
|
@@ -1350,11 +2151,11 @@ If `email` is present but invalid:
|
|
|
1350
2151
|
|
|
1351
2152
|
{
|
|
1352
2153
|
|
|
1353
|
-
|
|
2154
|
+
path: "email",
|
|
1354
2155
|
|
|
1355
|
-
|
|
2156
|
+
code: "INVALID_EMAIL",
|
|
1356
2157
|
|
|
1357
|
-
|
|
2158
|
+
message: "Email address is invalid"
|
|
1358
2159
|
|
|
1359
2160
|
}
|
|
1360
2161
|
```
|
|
@@ -1443,53 +2244,53 @@ Example result:
|
|
|
1443
2244
|
|
|
1444
2245
|
{
|
|
1445
2246
|
|
|
1446
|
-
|
|
2247
|
+
statusCode: 400,
|
|
1447
2248
|
|
|
1448
|
-
|
|
2249
|
+
valid: false,
|
|
1449
2250
|
|
|
1450
|
-
|
|
2251
|
+
message:
|
|
1451
2252
|
|
|
1452
|
-
|
|
2253
|
+
"One or more attribute values are invalid",
|
|
1453
2254
|
|
|
1454
|
-
|
|
2255
|
+
errors: [
|
|
1455
2256
|
|
|
1456
|
-
|
|
2257
|
+
{
|
|
1457
2258
|
|
|
1458
|
-
|
|
2259
|
+
path: "username",
|
|
1459
2260
|
|
|
1460
|
-
|
|
2261
|
+
code: "MIN_LENGTH",
|
|
1461
2262
|
|
|
1462
|
-
|
|
2263
|
+
message:
|
|
1463
2264
|
|
|
1464
|
-
|
|
2265
|
+
"Username must contain at least 3 characters"
|
|
1465
2266
|
|
|
1466
|
-
|
|
2267
|
+
},
|
|
1467
2268
|
|
|
1468
|
-
|
|
2269
|
+
{
|
|
1469
2270
|
|
|
1470
|
-
|
|
2271
|
+
path: "age",
|
|
1471
2272
|
|
|
1472
|
-
|
|
2273
|
+
code: "MIN_VALUE",
|
|
1473
2274
|
|
|
1474
|
-
|
|
2275
|
+
message:
|
|
1475
2276
|
|
|
1476
|
-
|
|
2277
|
+
"Age must be at least 18"
|
|
1477
2278
|
|
|
1478
|
-
|
|
2279
|
+
},
|
|
1479
2280
|
|
|
1480
|
-
|
|
2281
|
+
{
|
|
1481
2282
|
|
|
1482
|
-
|
|
2283
|
+
path: "score",
|
|
1483
2284
|
|
|
1484
|
-
|
|
2285
|
+
code: "OUT_OF_RANGE",
|
|
1485
2286
|
|
|
1486
|
-
|
|
2287
|
+
message:
|
|
1487
2288
|
|
|
1488
|
-
|
|
2289
|
+
"Score must be between 0 and 100"
|
|
1489
2290
|
|
|
1490
|
-
|
|
2291
|
+
}
|
|
1491
2292
|
|
|
1492
|
-
|
|
2293
|
+
]
|
|
1493
2294
|
|
|
1494
2295
|
}
|
|
1495
2296
|
```
|
|
@@ -1520,11 +2321,11 @@ Still returns:
|
|
|
1520
2321
|
|
|
1521
2322
|
{
|
|
1522
2323
|
|
|
1523
|
-
|
|
2324
|
+
path: "age",
|
|
1524
2325
|
|
|
1525
|
-
|
|
2326
|
+
code: "MIN_VALUE",
|
|
1526
2327
|
|
|
1527
|
-
|
|
2328
|
+
message: "You must be 18 or older"
|
|
1528
2329
|
|
|
1529
2330
|
}
|
|
1530
2331
|
```
|
|
@@ -1541,157 +2342,156 @@ handling
|
|
|
1541
2342
|
|
|
1542
2343
|
## Custom Response Objects
|
|
1543
2344
|
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
response objects.
|
|
2345
|
+
Custom valid and invalid response objects are configured inside the
|
|
2346
|
+
optional third `options` argument.
|
|
1547
2347
|
|
|
1548
|
-
|
|
2348
|
+
```js
|
|
2349
|
+
perfectPayload(data, validationRules, options?)
|
|
2350
|
+
await perfectPayloadAsync(data, validationRules, options?)
|
|
2351
|
+
```
|
|
1549
2352
|
|
|
1550
|
-
|
|
2353
|
+
This keeps API-level configuration in one place and avoids positional
|
|
2354
|
+
`undefined` arguments.
|
|
1551
2355
|
|
|
1552
2356
|
### Custom Valid Response
|
|
1553
2357
|
|
|
1554
|
-
Example:
|
|
1555
|
-
|
|
1556
2358
|
```js
|
|
1557
|
-
const
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
};
|
|
1564
|
-
|
|
1565
|
-
const result = perfectPayload(payload, validationRules, customValidResponse);
|
|
2359
|
+
const result = perfectPayload(payload, validationRules, {
|
|
2360
|
+
validPayloadResponse: {
|
|
2361
|
+
statusCode: 201,
|
|
2362
|
+
valid: true,
|
|
2363
|
+
message: "Payload validated successfully",
|
|
2364
|
+
},
|
|
2365
|
+
});
|
|
1566
2366
|
```
|
|
1567
2367
|
|
|
1568
2368
|
When validation succeeds, `validatedPayload` is automatically added:
|
|
1569
2369
|
|
|
1570
2370
|
```js
|
|
1571
|
-
|
|
1572
2371
|
{
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
name: "Kiran",
|
|
1583
|
-
|
|
1584
|
-
email: "kiran@example.com",
|
|
1585
|
-
|
|
1586
|
-
age: 29
|
|
1587
|
-
|
|
1588
|
-
}
|
|
1589
|
-
|
|
2372
|
+
statusCode: 201,
|
|
2373
|
+
valid: true,
|
|
2374
|
+
message: "Payload validated successfully",
|
|
2375
|
+
validatedPayload: {
|
|
2376
|
+
name: "Kiran",
|
|
2377
|
+
email: "kiran@example.com",
|
|
2378
|
+
age: 29
|
|
2379
|
+
}
|
|
1590
2380
|
}
|
|
1591
2381
|
```
|
|
1592
2382
|
|
|
1593
2383
|
### Custom Invalid Response
|
|
1594
2384
|
|
|
1595
|
-
Example:
|
|
1596
|
-
|
|
1597
2385
|
```js
|
|
1598
|
-
const
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
};
|
|
1605
|
-
|
|
1606
|
-
const result = perfectPayload(
|
|
1607
|
-
payload,
|
|
1608
|
-
|
|
1609
|
-
validationRules,
|
|
1610
|
-
|
|
1611
|
-
undefined,
|
|
1612
|
-
|
|
1613
|
-
customInvalidResponse,
|
|
1614
|
-
);
|
|
2386
|
+
const result = perfectPayload(payload, validationRules, {
|
|
2387
|
+
inValidPayloadResponse: {
|
|
2388
|
+
statusCode: 422,
|
|
2389
|
+
valid: false,
|
|
2390
|
+
message: "Payload validation failed",
|
|
2391
|
+
},
|
|
2392
|
+
});
|
|
1615
2393
|
```
|
|
1616
2394
|
|
|
1617
2395
|
When validation fails, `errors` is automatically added:
|
|
1618
2396
|
|
|
1619
2397
|
```js
|
|
1620
|
-
|
|
1621
2398
|
{
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
path: "email",
|
|
1634
|
-
|
|
1635
|
-
code: "INVALID_EMAIL",
|
|
1636
|
-
|
|
1637
|
-
message:
|
|
1638
|
-
|
|
1639
|
-
"Invalid email format for attribute email"
|
|
1640
|
-
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
]
|
|
1644
|
-
|
|
2399
|
+
statusCode: 422,
|
|
2400
|
+
valid: false,
|
|
2401
|
+
message: "Payload validation failed",
|
|
2402
|
+
errors: [
|
|
2403
|
+
{
|
|
2404
|
+
path: "email",
|
|
2405
|
+
code: "INVALID_EMAIL",
|
|
2406
|
+
message: "Invalid email format for attribute email"
|
|
2407
|
+
}
|
|
2408
|
+
]
|
|
1645
2409
|
}
|
|
1646
2410
|
```
|
|
1647
2411
|
|
|
1648
2412
|
### Custom Valid and Invalid Responses Together
|
|
1649
2413
|
|
|
1650
2414
|
```js
|
|
1651
|
-
const
|
|
1652
|
-
|
|
2415
|
+
const result = perfectPayload(payload, validationRules, {
|
|
2416
|
+
validPayloadResponse: {
|
|
2417
|
+
statusCode: 201,
|
|
2418
|
+
valid: true,
|
|
2419
|
+
message: "CUSTOM_VALID_RESPONSE",
|
|
2420
|
+
},
|
|
1653
2421
|
|
|
1654
|
-
|
|
2422
|
+
inValidPayloadResponse: {
|
|
2423
|
+
statusCode: 422,
|
|
2424
|
+
valid: false,
|
|
2425
|
+
message: "CUSTOM_INVALID_RESPONSE",
|
|
2426
|
+
},
|
|
2427
|
+
});
|
|
2428
|
+
```
|
|
1655
2429
|
|
|
1656
|
-
|
|
1657
|
-
};
|
|
2430
|
+
You can combine response customization with other API options:
|
|
1658
2431
|
|
|
1659
|
-
|
|
1660
|
-
|
|
2432
|
+
```js
|
|
2433
|
+
const result = perfectPayload(payload, validationRules, {
|
|
2434
|
+
unknownFields: "reject",
|
|
1661
2435
|
|
|
1662
|
-
|
|
2436
|
+
validPayloadResponse: {
|
|
2437
|
+
statusCode: 201,
|
|
2438
|
+
valid: true,
|
|
2439
|
+
},
|
|
1663
2440
|
|
|
1664
|
-
|
|
1665
|
-
|
|
2441
|
+
inValidPayloadResponse: {
|
|
2442
|
+
statusCode: 422,
|
|
2443
|
+
valid: false,
|
|
2444
|
+
message: "Payload validation failed",
|
|
2445
|
+
},
|
|
2446
|
+
});
|
|
2447
|
+
```
|
|
1666
2448
|
|
|
1667
|
-
|
|
1668
|
-
|
|
2449
|
+
The response object you provide is preserved while `perfectPayload()`
|
|
2450
|
+
automatically adds `validatedPayload` for successful validation or
|
|
2451
|
+
`errors` for failed validation.
|
|
1669
2452
|
|
|
1670
|
-
|
|
2453
|
+
The same response options are supported by `perfectPayloadAsync()`.
|
|
1671
2454
|
|
|
1672
|
-
|
|
2455
|
+
## v1.7 API Migration
|
|
1673
2456
|
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
```
|
|
2457
|
+
The current `perfectPayload()` and `perfectPayloadAsync()` APIs use one
|
|
2458
|
+
optional third argument for configuration:
|
|
1677
2459
|
|
|
1678
|
-
|
|
2460
|
+
```js
|
|
2461
|
+
perfectPayload(data, validationRules, options?)
|
|
2462
|
+
perfectPayloadAsync(data, validationRules, options?)
|
|
2463
|
+
```
|
|
1679
2464
|
|
|
1680
|
-
|
|
2465
|
+
Custom response objects now belong inside `options`.
|
|
1681
2466
|
|
|
1682
|
-
|
|
2467
|
+
Use:
|
|
1683
2468
|
|
|
1684
|
-
|
|
2469
|
+
```js
|
|
2470
|
+
perfectPayload(payload, rules, {
|
|
2471
|
+
validPayloadResponse: customValidResponse,
|
|
2472
|
+
inValidPayloadResponse: customInvalidResponse,
|
|
2473
|
+
});
|
|
1685
2474
|
```
|
|
1686
2475
|
|
|
1687
|
-
|
|
2476
|
+
instead of passing custom response objects as separate positional
|
|
2477
|
+
arguments.
|
|
1688
2478
|
|
|
1689
|
-
|
|
2479
|
+
This also makes it possible to combine response customization with
|
|
2480
|
+
`unknownFields` without placeholder arguments:
|
|
1690
2481
|
|
|
1691
|
-
|
|
2482
|
+
```js
|
|
2483
|
+
perfectPayload(payload, rules, {
|
|
2484
|
+
unknownFields: "reject",
|
|
2485
|
+
inValidPayloadResponse: {
|
|
2486
|
+
statusCode: 422,
|
|
2487
|
+
valid: false,
|
|
2488
|
+
message: "Payload validation failed",
|
|
2489
|
+
},
|
|
2490
|
+
});
|
|
1692
2491
|
```
|
|
1693
2492
|
|
|
1694
|
-
|
|
2493
|
+
`perfectPayloadV1()` is unchanged and retains its legacy signature
|
|
2494
|
+
during its deprecation period.
|
|
1695
2495
|
|
|
1696
2496
|
## Default Responses
|
|
1697
2497
|
|
|
@@ -1703,15 +2503,15 @@ is:
|
|
|
1703
2503
|
|
|
1704
2504
|
{
|
|
1705
2505
|
|
|
1706
|
-
|
|
2506
|
+
statusCode: 200,
|
|
1707
2507
|
|
|
1708
|
-
|
|
2508
|
+
valid: true,
|
|
1709
2509
|
|
|
1710
|
-
|
|
2510
|
+
validatedPayload: {
|
|
1711
2511
|
|
|
1712
|
-
|
|
2512
|
+
***// validated fields***
|
|
1713
2513
|
|
|
1714
|
-
|
|
2514
|
+
}
|
|
1715
2515
|
|
|
1716
2516
|
}
|
|
1717
2517
|
```
|
|
@@ -1722,25 +2522,25 @@ The default invalid response is:
|
|
|
1722
2522
|
|
|
1723
2523
|
{
|
|
1724
2524
|
|
|
1725
|
-
|
|
2525
|
+
statusCode: 400,
|
|
1726
2526
|
|
|
1727
|
-
|
|
2527
|
+
valid: false,
|
|
1728
2528
|
|
|
1729
|
-
|
|
2529
|
+
message: "One or more attribute values are invalid",
|
|
1730
2530
|
|
|
1731
|
-
|
|
2531
|
+
errors: [
|
|
1732
2532
|
|
|
1733
|
-
|
|
2533
|
+
{
|
|
1734
2534
|
|
|
1735
|
-
|
|
2535
|
+
path: "field",
|
|
1736
2536
|
|
|
1737
|
-
|
|
2537
|
+
code: "ERROR_CODE",
|
|
1738
2538
|
|
|
1739
|
-
|
|
2539
|
+
message: "Validation error message"
|
|
1740
2540
|
|
|
1741
|
-
|
|
2541
|
+
}
|
|
1742
2542
|
|
|
1743
|
-
|
|
2543
|
+
]
|
|
1744
2544
|
|
|
1745
2545
|
}
|
|
1746
2546
|
```
|
|
@@ -1771,11 +2571,11 @@ An error can be returned as:
|
|
|
1771
2571
|
|
|
1772
2572
|
{
|
|
1773
2573
|
|
|
1774
|
-
|
|
2574
|
+
path: "email",
|
|
1775
2575
|
|
|
1776
|
-
|
|
2576
|
+
code: "INVALID_EMAIL",
|
|
1777
2577
|
|
|
1778
|
-
|
|
2578
|
+
message: "Invalid email format for attribute email"
|
|
1779
2579
|
|
|
1780
2580
|
}
|
|
1781
2581
|
```
|
|
@@ -1834,13 +2634,13 @@ its complete nested path:
|
|
|
1834
2634
|
|
|
1835
2635
|
{
|
|
1836
2636
|
|
|
1837
|
-
|
|
2637
|
+
path: "address.location.latitude",
|
|
1838
2638
|
|
|
1839
|
-
|
|
2639
|
+
code: "INVALID_TYPE",
|
|
1840
2640
|
|
|
1841
|
-
|
|
2641
|
+
message:
|
|
1842
2642
|
|
|
1843
|
-
|
|
2643
|
+
"Invalid type for attribute address.location.latitude, required number value"
|
|
1844
2644
|
|
|
1845
2645
|
}
|
|
1846
2646
|
```
|
|
@@ -1888,13 +2688,13 @@ The invalid third element is reported as:
|
|
|
1888
2688
|
|
|
1889
2689
|
{
|
|
1890
2690
|
|
|
1891
|
-
|
|
2691
|
+
path: "marks[2]",
|
|
1892
2692
|
|
|
1893
|
-
|
|
2693
|
+
code: "OUT_OF_RANGE",
|
|
1894
2694
|
|
|
1895
|
-
|
|
2695
|
+
message:
|
|
1896
2696
|
|
|
1897
|
-
|
|
2697
|
+
"Attribute marks[2] should have a value between 0 and 100"
|
|
1898
2698
|
|
|
1899
2699
|
}
|
|
1900
2700
|
```
|
|
@@ -1910,7 +2710,9 @@ marks[1]
|
|
|
1910
2710
|
marks[2]
|
|
1911
2711
|
```
|
|
1912
2712
|
|
|
1913
|
-
Array-level constraints such as `minItems` and `maxItems` report the
|
|
2713
|
+
Array-level constraints such as `minItems` and `maxItems` report the
|
|
2714
|
+
path of the array itself. For nested arrays, the complete parent path is
|
|
2715
|
+
retained, for example `orders[1].items`.
|
|
1914
2716
|
|
|
1915
2717
|
### Nested Fields Inside Arrays
|
|
1916
2718
|
|
|
@@ -1969,11 +2771,11 @@ Result:
|
|
|
1969
2771
|
|
|
1970
2772
|
{
|
|
1971
2773
|
|
|
1972
|
-
|
|
2774
|
+
"email": "Invalid email format for attribute email",
|
|
1973
2775
|
|
|
1974
|
-
|
|
2776
|
+
"address.location.latitude": "Invalid type for attribute address.location.latitude, required number value",
|
|
1975
2777
|
|
|
1976
|
-
|
|
2778
|
+
"marks[2]": "Attribute marks[2] should have a value between 0 and 100"
|
|
1977
2779
|
|
|
1978
2780
|
}
|
|
1979
2781
|
```
|
|
@@ -1988,61 +2790,61 @@ sample-1
|
|
|
1988
2790
|
|
|
1989
2791
|
{
|
|
1990
2792
|
|
|
1991
|
-
|
|
2793
|
+
firstName: {
|
|
1992
2794
|
|
|
1993
|
-
|
|
2795
|
+
mandatory: true,
|
|
1994
2796
|
|
|
1995
|
-
|
|
2797
|
+
allowNull: false,
|
|
1996
2798
|
|
|
1997
|
-
|
|
2799
|
+
type: "string",
|
|
1998
2800
|
|
|
1999
|
-
|
|
2801
|
+
minLength: 3,
|
|
2000
2802
|
|
|
2001
|
-
|
|
2803
|
+
minLengthError: "First name must have minimum 3 characters."
|
|
2002
2804
|
|
|
2003
|
-
|
|
2805
|
+
},
|
|
2004
2806
|
|
|
2005
|
-
|
|
2807
|
+
lastName: {
|
|
2006
2808
|
|
|
2007
|
-
|
|
2809
|
+
mandatory: false,
|
|
2008
2810
|
|
|
2009
|
-
|
|
2811
|
+
allowNull: true,
|
|
2010
2812
|
|
|
2011
|
-
|
|
2813
|
+
type: "string",
|
|
2012
2814
|
|
|
2013
|
-
|
|
2815
|
+
},
|
|
2014
2816
|
|
|
2015
|
-
|
|
2817
|
+
email: {
|
|
2016
2818
|
|
|
2017
|
-
|
|
2819
|
+
mandatory: true,
|
|
2018
2820
|
|
|
2019
|
-
|
|
2821
|
+
allowNull: false,
|
|
2020
2822
|
|
|
2021
|
-
|
|
2823
|
+
type: "email",
|
|
2022
2824
|
|
|
2023
|
-
|
|
2825
|
+
},
|
|
2024
2826
|
|
|
2025
|
-
|
|
2827
|
+
phone: {
|
|
2026
2828
|
|
|
2027
|
-
|
|
2829
|
+
mandatory: true,
|
|
2028
2830
|
|
|
2029
|
-
|
|
2831
|
+
allowNull: false,
|
|
2030
2832
|
|
|
2031
|
-
|
|
2833
|
+
type: "string",
|
|
2032
2834
|
|
|
2033
|
-
|
|
2835
|
+
},
|
|
2034
2836
|
|
|
2035
|
-
|
|
2837
|
+
age: {
|
|
2036
2838
|
|
|
2037
|
-
|
|
2839
|
+
mandatory: false,
|
|
2038
2840
|
|
|
2039
|
-
|
|
2841
|
+
type: "number",
|
|
2040
2842
|
|
|
2041
|
-
|
|
2843
|
+
min: 1,
|
|
2042
2844
|
|
|
2043
|
-
|
|
2845
|
+
max: 120,
|
|
2044
2846
|
|
|
2045
|
-
|
|
2847
|
+
},
|
|
2046
2848
|
|
|
2047
2849
|
};
|
|
2048
2850
|
```
|
|
@@ -2053,271 +2855,271 @@ sample-2
|
|
|
2053
2855
|
|
|
2054
2856
|
{
|
|
2055
2857
|
|
|
2056
|
-
|
|
2858
|
+
id: {
|
|
2057
2859
|
|
|
2058
|
-
|
|
2860
|
+
mandatory: true,
|
|
2059
2861
|
|
|
2060
|
-
|
|
2862
|
+
allowNull: true,
|
|
2061
2863
|
|
|
2062
|
-
|
|
2864
|
+
type: "uuidv4",
|
|
2063
2865
|
|
|
2064
|
-
|
|
2866
|
+
},
|
|
2065
2867
|
|
|
2066
|
-
|
|
2868
|
+
batchId: {
|
|
2067
2869
|
|
|
2068
|
-
|
|
2870
|
+
mandatory: true,
|
|
2069
2871
|
|
|
2070
|
-
|
|
2872
|
+
allowNull: true,
|
|
2071
2873
|
|
|
2072
|
-
|
|
2874
|
+
type: "objectId",
|
|
2073
2875
|
|
|
2074
|
-
|
|
2876
|
+
},
|
|
2075
2877
|
|
|
2076
|
-
|
|
2878
|
+
firstName: {
|
|
2077
2879
|
|
|
2078
|
-
|
|
2880
|
+
mandatory: true,
|
|
2079
2881
|
|
|
2080
|
-
|
|
2882
|
+
type: "string",
|
|
2081
2883
|
|
|
2082
|
-
|
|
2884
|
+
minLength: 3,
|
|
2083
2885
|
|
|
2084
|
-
|
|
2886
|
+
},
|
|
2085
2887
|
|
|
2086
|
-
|
|
2888
|
+
lastName: {
|
|
2087
2889
|
|
|
2088
|
-
|
|
2890
|
+
mandatory: false,
|
|
2089
2891
|
|
|
2090
|
-
|
|
2892
|
+
allowNull: true,
|
|
2091
2893
|
|
|
2092
|
-
|
|
2894
|
+
type: "string",
|
|
2093
2895
|
|
|
2094
|
-
|
|
2896
|
+
},
|
|
2095
2897
|
|
|
2096
|
-
|
|
2898
|
+
age: {
|
|
2097
2899
|
|
|
2098
|
-
|
|
2900
|
+
type: "number",
|
|
2099
2901
|
|
|
2100
|
-
|
|
2902
|
+
min: 0.1,
|
|
2101
2903
|
|
|
2102
|
-
|
|
2904
|
+
max: 120,
|
|
2103
2905
|
|
|
2104
|
-
|
|
2906
|
+
},
|
|
2105
2907
|
|
|
2106
|
-
|
|
2908
|
+
isAdult: {
|
|
2107
2909
|
|
|
2108
|
-
|
|
2910
|
+
type: "boolean",
|
|
2109
2911
|
|
|
2110
|
-
|
|
2912
|
+
},
|
|
2111
2913
|
|
|
2112
|
-
|
|
2914
|
+
totalWins: {
|
|
2113
2915
|
|
|
2114
|
-
|
|
2916
|
+
type: "number",
|
|
2115
2917
|
|
|
2116
|
-
|
|
2918
|
+
min: 0,
|
|
2117
2919
|
|
|
2118
|
-
|
|
2920
|
+
preventDecimal: true,
|
|
2119
2921
|
|
|
2120
|
-
|
|
2922
|
+
},
|
|
2121
2923
|
|
|
2122
|
-
|
|
2924
|
+
email: {
|
|
2123
2925
|
|
|
2124
|
-
|
|
2926
|
+
regex: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\\\\\.[a-zA-Z]{2,}$/,
|
|
2125
2927
|
|
|
2126
|
-
|
|
2928
|
+
},
|
|
2127
2929
|
|
|
2128
|
-
|
|
2930
|
+
githubLink: {
|
|
2129
2931
|
|
|
2130
|
-
|
|
2932
|
+
type: "url",
|
|
2131
2933
|
|
|
2132
|
-
|
|
2934
|
+
},
|
|
2133
2935
|
|
|
2134
|
-
|
|
2936
|
+
accountStatus: {
|
|
2135
2937
|
|
|
2136
|
-
|
|
2938
|
+
type: "enum",
|
|
2137
2939
|
|
|
2138
|
-
|
|
2940
|
+
enumValues: ["Active", "Inactive", 200],
|
|
2139
2941
|
|
|
2140
|
-
|
|
2942
|
+
},
|
|
2141
2943
|
|
|
2142
|
-
|
|
2944
|
+
marks: {
|
|
2143
2945
|
|
|
2144
|
-
|
|
2946
|
+
range: "0-100",
|
|
2145
2947
|
|
|
2146
|
-
|
|
2948
|
+
},
|
|
2147
2949
|
|
|
2148
|
-
|
|
2950
|
+
allMarks: {
|
|
2149
2951
|
|
|
2150
|
-
|
|
2952
|
+
type: "array",
|
|
2151
2953
|
|
|
2152
|
-
|
|
2954
|
+
allowEmptyArray: false,
|
|
2153
2955
|
|
|
2154
|
-
|
|
2956
|
+
elementConstraints: {
|
|
2155
2957
|
|
|
2156
|
-
|
|
2958
|
+
type: "number",
|
|
2157
2959
|
|
|
2158
|
-
|
|
2960
|
+
allowNull: false,
|
|
2159
2961
|
|
|
2160
|
-
|
|
2962
|
+
range: "0-100",
|
|
2161
2963
|
|
|
2162
|
-
|
|
2964
|
+
},
|
|
2163
2965
|
|
|
2164
|
-
|
|
2966
|
+
},
|
|
2165
2967
|
|
|
2166
|
-
|
|
2968
|
+
totalScore: {
|
|
2167
2969
|
|
|
2168
|
-
|
|
2970
|
+
type: "number",
|
|
2169
2971
|
|
|
2170
|
-
|
|
2972
|
+
dependency: {
|
|
2171
2973
|
|
|
2172
|
-
|
|
2974
|
+
result: {
|
|
2173
2975
|
|
|
2174
|
-
|
|
2976
|
+
setDependencyRule: (totalScore, result) => {
|
|
2175
2977
|
|
|
2176
|
-
|
|
2978
|
+
return { mandatory: true, allowNull: false, type: "string" };
|
|
2177
2979
|
|
|
2178
|
-
|
|
2980
|
+
},
|
|
2179
2981
|
|
|
2180
|
-
|
|
2982
|
+
},
|
|
2181
2983
|
|
|
2182
|
-
|
|
2984
|
+
},
|
|
2183
2985
|
|
|
2184
|
-
|
|
2986
|
+
},
|
|
2185
2987
|
|
|
2186
|
-
|
|
2988
|
+
result: {
|
|
2187
2989
|
|
|
2188
|
-
|
|
2990
|
+
type: "string",
|
|
2189
2991
|
|
|
2190
|
-
|
|
2992
|
+
dependency: {
|
|
2191
2993
|
|
|
2192
|
-
|
|
2994
|
+
totalScore: {
|
|
2193
2995
|
|
|
2194
|
-
|
|
2996
|
+
setDependencyRule: (result, totalScore) => {
|
|
2195
2997
|
|
|
2196
|
-
|
|
2998
|
+
return { mandatory: true, allowNull: false, type: "number" };
|
|
2197
2999
|
|
|
2198
|
-
|
|
3000
|
+
},
|
|
2199
3001
|
|
|
2200
|
-
|
|
3002
|
+
},
|
|
2201
3003
|
|
|
2202
|
-
|
|
3004
|
+
},
|
|
2203
3005
|
|
|
2204
|
-
|
|
3006
|
+
},
|
|
2205
3007
|
|
|
2206
|
-
|
|
3008
|
+
minSalary: {
|
|
2207
3009
|
|
|
2208
|
-
|
|
3010
|
+
mandatory: true,
|
|
2209
3011
|
|
|
2210
|
-
|
|
3012
|
+
min: 1,
|
|
2211
3013
|
|
|
2212
|
-
|
|
3014
|
+
type: "number",
|
|
2213
3015
|
|
|
2214
|
-
|
|
3016
|
+
dependency: {
|
|
2215
3017
|
|
|
2216
|
-
|
|
3018
|
+
maxSalary: {
|
|
2217
3019
|
|
|
2218
|
-
|
|
3020
|
+
setDependencyRule: (minSalary, maxSalary) => {
|
|
2219
3021
|
|
|
2220
|
-
|
|
3022
|
+
return {
|
|
2221
3023
|
|
|
2222
|
-
|
|
3024
|
+
mandatory: true,
|
|
2223
3025
|
|
|
2224
|
-
|
|
3026
|
+
min: minSalary + 1,
|
|
2225
3027
|
|
|
2226
|
-
|
|
3028
|
+
minError: "maxSalary must be more than minSalary",
|
|
2227
3029
|
|
|
2228
|
-
|
|
3030
|
+
};
|
|
2229
3031
|
|
|
2230
|
-
|
|
3032
|
+
},
|
|
2231
3033
|
|
|
2232
|
-
|
|
3034
|
+
},
|
|
2233
3035
|
|
|
2234
|
-
|
|
3036
|
+
},
|
|
2235
3037
|
|
|
2236
|
-
|
|
3038
|
+
},
|
|
2237
3039
|
|
|
2238
|
-
|
|
3040
|
+
maxSalary: {
|
|
2239
3041
|
|
|
2240
|
-
|
|
3042
|
+
dependency: {
|
|
2241
3043
|
|
|
2242
|
-
|
|
3044
|
+
minSalary: {
|
|
2243
3045
|
|
|
2244
|
-
|
|
3046
|
+
setDependencyRule: (maxSalary, minSalary) => {
|
|
2245
3047
|
|
|
2246
|
-
|
|
3048
|
+
return {
|
|
2247
3049
|
|
|
2248
|
-
|
|
3050
|
+
mandatory: true,
|
|
2249
3051
|
|
|
2250
|
-
|
|
3052
|
+
max: maxSalary - 1,
|
|
2251
3053
|
|
|
2252
|
-
|
|
3054
|
+
maxError: "minSalary must be less than maxSalary",
|
|
2253
3055
|
|
|
2254
|
-
|
|
3056
|
+
};
|
|
2255
3057
|
|
|
2256
|
-
|
|
3058
|
+
},
|
|
2257
3059
|
|
|
2258
|
-
|
|
3060
|
+
},
|
|
2259
3061
|
|
|
2260
|
-
|
|
3062
|
+
},
|
|
2261
3063
|
|
|
2262
|
-
|
|
3064
|
+
},
|
|
2263
3065
|
|
|
2264
|
-
|
|
3066
|
+
address: {
|
|
2265
3067
|
|
|
2266
|
-
|
|
3068
|
+
mandatory: true,
|
|
2267
3069
|
|
|
2268
|
-
|
|
3070
|
+
type: "object",
|
|
2269
3071
|
|
|
2270
|
-
|
|
3072
|
+
allowEmptyObject: false,
|
|
2271
3073
|
|
|
2272
|
-
|
|
3074
|
+
objectAttr: {
|
|
2273
3075
|
|
|
2274
|
-
|
|
3076
|
+
country: { mandatory: true, type: "string" },
|
|
2275
3077
|
|
|
2276
|
-
|
|
3078
|
+
state: {
|
|
2277
3079
|
|
|
2278
|
-
|
|
3080
|
+
mandatory: true,
|
|
2279
3081
|
|
|
2280
|
-
|
|
3082
|
+
type: "string",
|
|
2281
3083
|
|
|
2282
|
-
|
|
3084
|
+
},
|
|
2283
3085
|
|
|
2284
|
-
|
|
3086
|
+
city: {},
|
|
2285
3087
|
|
|
2286
|
-
|
|
3088
|
+
zip: {
|
|
2287
3089
|
|
|
2288
|
-
|
|
3090
|
+
mandatory: true,
|
|
2289
3091
|
|
|
2290
|
-
|
|
3092
|
+
type: "string",
|
|
2291
3093
|
|
|
2292
|
-
|
|
3094
|
+
},
|
|
2293
3095
|
|
|
2294
|
-
|
|
3096
|
+
position: {
|
|
2295
3097
|
|
|
2296
|
-
|
|
3098
|
+
mandatory: true,
|
|
2297
3099
|
|
|
2298
|
-
|
|
3100
|
+
type: "object",
|
|
2299
3101
|
|
|
2300
|
-
|
|
3102
|
+
allowEmptyObject: false,
|
|
2301
3103
|
|
|
2302
|
-
|
|
3104
|
+
objectAttr: {
|
|
2303
3105
|
|
|
2304
|
-
|
|
3106
|
+
lattitude: { mandatory: true, type: "number" },
|
|
2305
3107
|
|
|
2306
|
-
|
|
3108
|
+
longitude: {
|
|
2307
3109
|
|
|
2308
|
-
|
|
3110
|
+
mandatory: true,
|
|
2309
3111
|
|
|
2310
|
-
|
|
3112
|
+
type: "number",
|
|
2311
3113
|
|
|
2312
|
-
|
|
3114
|
+
},
|
|
2313
3115
|
|
|
2314
|
-
|
|
3116
|
+
},
|
|
2315
3117
|
|
|
2316
|
-
|
|
3118
|
+
},
|
|
2317
3119
|
|
|
2318
|
-
|
|
3120
|
+
},
|
|
2319
3121
|
|
|
2320
|
-
|
|
3122
|
+
},
|
|
2321
3123
|
|
|
2322
3124
|
}
|
|
2323
3125
|
```
|
|
@@ -2328,15 +3130,15 @@ sample-2
|
|
|
2328
3130
|
|
|
2329
3131
|
```js
|
|
2330
3132
|
|
|
2331
|
-
|
|
3133
|
+
***// validatePayload is the middleware that invokes perfectPayload()***
|
|
2332
3134
|
|
|
2333
3135
|
router.post(
|
|
2334
3136
|
|
|
2335
|
-
|
|
3137
|
+
"/payload-validation",
|
|
2336
3138
|
|
|
2337
|
-
|
|
3139
|
+
validatePayload({ rule: <your validation rule json object> }),
|
|
2338
3140
|
|
|
2339
|
-
|
|
3141
|
+
(req, res) => res.send("OK")
|
|
2340
3142
|
|
|
2341
3143
|
);
|
|
2342
3144
|
```
|
|
@@ -2365,6 +3167,47 @@ export const validatePayload = ({ rule }) => {
|
|
|
2365
3167
|
};
|
|
2366
3168
|
```
|
|
2367
3169
|
|
|
3170
|
+
#### Async ES Modules middleware example
|
|
3171
|
+
|
|
3172
|
+
When your schema contains an asynchronous `customValidator`, the
|
|
3173
|
+
middleware itself must be `async` and `perfectPayloadAsync()` must be
|
|
3174
|
+
awaited:
|
|
3175
|
+
|
|
3176
|
+
```js
|
|
3177
|
+
import { perfectPayloadAsync } from "perfect-payload";
|
|
3178
|
+
|
|
3179
|
+
export const validatePayloadAsync = ({ rule }) => {
|
|
3180
|
+
return async (req, res, next) => {
|
|
3181
|
+
try {
|
|
3182
|
+
const { statusCode, ...response } = await perfectPayloadAsync(
|
|
3183
|
+
req?.body,
|
|
3184
|
+
rule,
|
|
3185
|
+
);
|
|
3186
|
+
|
|
3187
|
+
if (+statusCode >= 200 && +statusCode <= 299) {
|
|
3188
|
+
req.validatedBody = response?.validatedPayload;
|
|
3189
|
+
next();
|
|
3190
|
+
} else {
|
|
3191
|
+
res.status(statusCode).json(response);
|
|
3192
|
+
}
|
|
3193
|
+
} catch (error) {
|
|
3194
|
+
console.error("Error validating payload", error);
|
|
3195
|
+
res.status(500).json({ error: "Internal Server Error" });
|
|
3196
|
+
}
|
|
3197
|
+
};
|
|
3198
|
+
};
|
|
3199
|
+
```
|
|
3200
|
+
|
|
3201
|
+
Route usage:
|
|
3202
|
+
|
|
3203
|
+
```js
|
|
3204
|
+
router.post(
|
|
3205
|
+
"/payload-validation",
|
|
3206
|
+
validatePayloadAsync({ rule: <your validation rule json object> }),
|
|
3207
|
+
(req, res) => res.send("OK"),
|
|
3208
|
+
);
|
|
3209
|
+
```
|
|
3210
|
+
|
|
2368
3211
|
#### CommonJS middleware example
|
|
2369
3212
|
|
|
2370
3213
|
```js
|