perfect-payload 1.6.0-beta.0 → 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 +501 -99
- package/index.js +81 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,13 +24,16 @@ lightweight.
|
|
|
24
24
|
`perfectPayloadAsync()`
|
|
25
25
|
- Transformed values returned through `validatedPayload`
|
|
26
26
|
- Original input payload is not mutated
|
|
27
|
-
-
|
|
27
|
+
- Configurable unknown-field handling: `strip`, `allow`, or `reject`
|
|
28
|
+
- Clean three-argument API with validation options in one object
|
|
28
29
|
- Legacy `perfectPayloadV1()` retained during the migration period
|
|
29
30
|
|
|
30
31
|
## Quick Links
|
|
31
32
|
|
|
32
33
|
- [Installation](#installation)
|
|
33
34
|
- [Basic Usage](#basic-usage)
|
|
35
|
+
- [Public API](#public-api)
|
|
36
|
+
- [Unknown Field Handling](#unknown-field-handling)
|
|
34
37
|
- [Synchronous vs Asynchronous
|
|
35
38
|
Validation](#synchronous-vs-asynchronous-validation)
|
|
36
39
|
- [Validation Rules](#validation-rules)
|
|
@@ -47,6 +50,31 @@ lightweight.
|
|
|
47
50
|
- [Examples and Usage](#examples-and-usage)
|
|
48
51
|
- [Legacy API](#legacy-api)
|
|
49
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
|
+
|
|
50
78
|
## Installation
|
|
51
79
|
|
|
52
80
|
```bash
|
|
@@ -119,13 +147,11 @@ console.log(result);
|
|
|
119
147
|
}
|
|
120
148
|
```
|
|
121
149
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
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"`.
|
|
127
153
|
|
|
128
|
-
|
|
154
|
+
The original input payload is not mutated.
|
|
129
155
|
|
|
130
156
|
(such as validatedBody, sanitisedData or parsedBody).
|
|
131
157
|
|
|
@@ -183,6 +209,375 @@ failure.
|
|
|
183
209
|
|
|
184
210
|
\- Submitted payload values are not included in default error messages.
|
|
185
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
|
+
|
|
186
581
|
## Synchronous vs Asynchronous Validation
|
|
187
582
|
|
|
188
583
|
For normal synchronous validation, use `perfectPayload()`:
|
|
@@ -190,7 +585,7 @@ For normal synchronous validation, use `perfectPayload()`:
|
|
|
190
585
|
```js
|
|
191
586
|
import { perfectPayload } from "perfect-payload";
|
|
192
587
|
|
|
193
|
-
const result = perfectPayload(payload, validationRules);
|
|
588
|
+
const result = perfectPayload(payload, validationRules, options);
|
|
194
589
|
```
|
|
195
590
|
|
|
196
591
|
When any `customValidator` needs to perform asynchronous work, use
|
|
@@ -199,15 +594,15 @@ When any `customValidator` needs to perform asynchronous work, use
|
|
|
199
594
|
```js
|
|
200
595
|
import { perfectPayloadAsync } from "perfect-payload";
|
|
201
596
|
|
|
202
|
-
const result = await perfectPayloadAsync(payload, validationRules);
|
|
597
|
+
const result = await perfectPayloadAsync(payload, validationRules, options);
|
|
203
598
|
```
|
|
204
599
|
|
|
205
600
|
The public APIs are:
|
|
206
601
|
|
|
207
602
|
```text
|
|
208
|
-
perfectPayloadV1()
|
|
209
|
-
perfectPayload() synchronous validation
|
|
210
|
-
perfectPayloadAsync() synchronous + asynchronous customValidator
|
|
603
|
+
perfectPayloadV1() legacy API; deprecated
|
|
604
|
+
perfectPayload(data, rules, options?) synchronous validation
|
|
605
|
+
perfectPayloadAsync(data, rules, options?) synchronous + asynchronous customValidator
|
|
211
606
|
```
|
|
212
607
|
|
|
213
608
|
`perfectPayload()` remains synchronous and intentionally rejects a
|
|
@@ -246,10 +641,16 @@ March 31, 2027.
|
|
|
246
641
|
|
|
247
642
|
Existing applications can continue using it during the migration period,
|
|
248
643
|
|
|
249
|
-
but all new implementations should use:
|
|
644
|
+
but all new implementations should use the current API:
|
|
250
645
|
|
|
251
646
|
```js
|
|
252
|
-
perfectPayload();
|
|
647
|
+
perfectPayload(data, validationRules, options?);
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
For asynchronous custom validation:
|
|
651
|
+
|
|
652
|
+
```js
|
|
653
|
+
await perfectPayloadAsync(data, validationRules, options?);
|
|
253
654
|
```
|
|
254
655
|
|
|
255
656
|
The legacy API continues to return validation errors as:
|
|
@@ -1661,6 +2062,8 @@ MAX_VALUE
|
|
|
1661
2062
|
OUT_OF_RANGE
|
|
1662
2063
|
|
|
1663
2064
|
CUSTOM_VALIDATION_FAILED
|
|
2065
|
+
|
|
2066
|
+
UNKNOWN_FIELD
|
|
1664
2067
|
```
|
|
1665
2068
|
|
|
1666
2069
|
These codes are designed for programmatic handling while `message`
|
|
@@ -1939,157 +2342,156 @@ handling
|
|
|
1939
2342
|
|
|
1940
2343
|
## Custom Response Objects
|
|
1941
2344
|
|
|
1942
|
-
|
|
2345
|
+
Custom valid and invalid response objects are configured inside the
|
|
2346
|
+
optional third `options` argument.
|
|
1943
2347
|
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
2348
|
+
```js
|
|
2349
|
+
perfectPayload(data, validationRules, options?)
|
|
2350
|
+
await perfectPayloadAsync(data, validationRules, options?)
|
|
2351
|
+
```
|
|
1947
2352
|
|
|
1948
|
-
|
|
2353
|
+
This keeps API-level configuration in one place and avoids positional
|
|
2354
|
+
`undefined` arguments.
|
|
1949
2355
|
|
|
1950
2356
|
### Custom Valid Response
|
|
1951
2357
|
|
|
1952
|
-
Example:
|
|
1953
|
-
|
|
1954
2358
|
```js
|
|
1955
|
-
const
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
};
|
|
1962
|
-
|
|
1963
|
-
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
|
+
});
|
|
1964
2366
|
```
|
|
1965
2367
|
|
|
1966
2368
|
When validation succeeds, `validatedPayload` is automatically added:
|
|
1967
2369
|
|
|
1968
2370
|
```js
|
|
1969
|
-
|
|
1970
2371
|
{
|
|
1971
|
-
|
|
1972
2372
|
statusCode: 201,
|
|
1973
|
-
|
|
1974
2373
|
valid: true,
|
|
1975
|
-
|
|
1976
2374
|
message: "Payload validated successfully",
|
|
1977
|
-
|
|
1978
2375
|
validatedPayload: {
|
|
1979
|
-
|
|
1980
2376
|
name: "Kiran",
|
|
1981
|
-
|
|
1982
2377
|
email: "kiran@example.com",
|
|
1983
|
-
|
|
1984
2378
|
age: 29
|
|
1985
|
-
|
|
1986
2379
|
}
|
|
1987
|
-
|
|
1988
2380
|
}
|
|
1989
2381
|
```
|
|
1990
2382
|
|
|
1991
2383
|
### Custom Invalid Response
|
|
1992
2384
|
|
|
1993
|
-
Example:
|
|
1994
|
-
|
|
1995
2385
|
```js
|
|
1996
|
-
const
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
};
|
|
2003
|
-
|
|
2004
|
-
const result = perfectPayload(
|
|
2005
|
-
payload,
|
|
2006
|
-
|
|
2007
|
-
validationRules,
|
|
2008
|
-
|
|
2009
|
-
undefined,
|
|
2010
|
-
|
|
2011
|
-
customInvalidResponse,
|
|
2012
|
-
);
|
|
2386
|
+
const result = perfectPayload(payload, validationRules, {
|
|
2387
|
+
inValidPayloadResponse: {
|
|
2388
|
+
statusCode: 422,
|
|
2389
|
+
valid: false,
|
|
2390
|
+
message: "Payload validation failed",
|
|
2391
|
+
},
|
|
2392
|
+
});
|
|
2013
2393
|
```
|
|
2014
2394
|
|
|
2015
2395
|
When validation fails, `errors` is automatically added:
|
|
2016
2396
|
|
|
2017
2397
|
```js
|
|
2018
|
-
|
|
2019
2398
|
{
|
|
2020
|
-
|
|
2021
2399
|
statusCode: 422,
|
|
2022
|
-
|
|
2023
2400
|
valid: false,
|
|
2024
|
-
|
|
2025
2401
|
message: "Payload validation failed",
|
|
2026
|
-
|
|
2027
2402
|
errors: [
|
|
2028
|
-
|
|
2029
2403
|
{
|
|
2030
|
-
|
|
2031
2404
|
path: "email",
|
|
2032
|
-
|
|
2033
2405
|
code: "INVALID_EMAIL",
|
|
2034
|
-
|
|
2035
|
-
message:
|
|
2036
|
-
|
|
2037
|
-
"Invalid email format for attribute email"
|
|
2038
|
-
|
|
2406
|
+
message: "Invalid email format for attribute email"
|
|
2039
2407
|
}
|
|
2040
|
-
|
|
2041
2408
|
]
|
|
2042
|
-
|
|
2043
2409
|
}
|
|
2044
2410
|
```
|
|
2045
2411
|
|
|
2046
2412
|
### Custom Valid and Invalid Responses Together
|
|
2047
2413
|
|
|
2048
2414
|
```js
|
|
2049
|
-
const
|
|
2050
|
-
|
|
2415
|
+
const result = perfectPayload(payload, validationRules, {
|
|
2416
|
+
validPayloadResponse: {
|
|
2417
|
+
statusCode: 201,
|
|
2418
|
+
valid: true,
|
|
2419
|
+
message: "CUSTOM_VALID_RESPONSE",
|
|
2420
|
+
},
|
|
2051
2421
|
|
|
2052
|
-
|
|
2422
|
+
inValidPayloadResponse: {
|
|
2423
|
+
statusCode: 422,
|
|
2424
|
+
valid: false,
|
|
2425
|
+
message: "CUSTOM_INVALID_RESPONSE",
|
|
2426
|
+
},
|
|
2427
|
+
});
|
|
2428
|
+
```
|
|
2053
2429
|
|
|
2054
|
-
|
|
2055
|
-
};
|
|
2430
|
+
You can combine response customization with other API options:
|
|
2056
2431
|
|
|
2057
|
-
|
|
2058
|
-
|
|
2432
|
+
```js
|
|
2433
|
+
const result = perfectPayload(payload, validationRules, {
|
|
2434
|
+
unknownFields: "reject",
|
|
2059
2435
|
|
|
2060
|
-
|
|
2436
|
+
validPayloadResponse: {
|
|
2437
|
+
statusCode: 201,
|
|
2438
|
+
valid: true,
|
|
2439
|
+
},
|
|
2061
2440
|
|
|
2062
|
-
|
|
2063
|
-
|
|
2441
|
+
inValidPayloadResponse: {
|
|
2442
|
+
statusCode: 422,
|
|
2443
|
+
valid: false,
|
|
2444
|
+
message: "Payload validation failed",
|
|
2445
|
+
},
|
|
2446
|
+
});
|
|
2447
|
+
```
|
|
2064
2448
|
|
|
2065
|
-
|
|
2066
|
-
|
|
2449
|
+
The response object you provide is preserved while `perfectPayload()`
|
|
2450
|
+
automatically adds `validatedPayload` for successful validation or
|
|
2451
|
+
`errors` for failed validation.
|
|
2067
2452
|
|
|
2068
|
-
|
|
2453
|
+
The same response options are supported by `perfectPayloadAsync()`.
|
|
2069
2454
|
|
|
2070
|
-
|
|
2455
|
+
## v1.7 API Migration
|
|
2071
2456
|
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
```
|
|
2457
|
+
The current `perfectPayload()` and `perfectPayloadAsync()` APIs use one
|
|
2458
|
+
optional third argument for configuration:
|
|
2075
2459
|
|
|
2076
|
-
|
|
2460
|
+
```js
|
|
2461
|
+
perfectPayload(data, validationRules, options?)
|
|
2462
|
+
perfectPayloadAsync(data, validationRules, options?)
|
|
2463
|
+
```
|
|
2077
2464
|
|
|
2078
|
-
|
|
2465
|
+
Custom response objects now belong inside `options`.
|
|
2079
2466
|
|
|
2080
|
-
|
|
2467
|
+
Use:
|
|
2081
2468
|
|
|
2082
|
-
|
|
2469
|
+
```js
|
|
2470
|
+
perfectPayload(payload, rules, {
|
|
2471
|
+
validPayloadResponse: customValidResponse,
|
|
2472
|
+
inValidPayloadResponse: customInvalidResponse,
|
|
2473
|
+
});
|
|
2083
2474
|
```
|
|
2084
2475
|
|
|
2085
|
-
|
|
2476
|
+
instead of passing custom response objects as separate positional
|
|
2477
|
+
arguments.
|
|
2086
2478
|
|
|
2087
|
-
|
|
2479
|
+
This also makes it possible to combine response customization with
|
|
2480
|
+
`unknownFields` without placeholder arguments:
|
|
2088
2481
|
|
|
2089
|
-
|
|
2482
|
+
```js
|
|
2483
|
+
perfectPayload(payload, rules, {
|
|
2484
|
+
unknownFields: "reject",
|
|
2485
|
+
inValidPayloadResponse: {
|
|
2486
|
+
statusCode: 422,
|
|
2487
|
+
valid: false,
|
|
2488
|
+
message: "Payload validation failed",
|
|
2489
|
+
},
|
|
2490
|
+
});
|
|
2090
2491
|
```
|
|
2091
2492
|
|
|
2092
|
-
|
|
2493
|
+
`perfectPayloadV1()` is unchanged and retains its legacy signature
|
|
2494
|
+
during its deprecation period.
|
|
2093
2495
|
|
|
2094
2496
|
## Default Responses
|
|
2095
2497
|
|
package/index.js
CHANGED
|
@@ -522,31 +522,66 @@ export function perfectPayloadV1(
|
|
|
522
522
|
export function perfectPayload(
|
|
523
523
|
data = {},
|
|
524
524
|
dataValidationRule = {},
|
|
525
|
-
|
|
526
|
-
inValidPayloadResponse = {
|
|
527
|
-
statusCode: 400,
|
|
528
|
-
valid: false,
|
|
529
|
-
message: "One or more attribute values are invalid",
|
|
530
|
-
},
|
|
525
|
+
options = {},
|
|
531
526
|
) {
|
|
527
|
+
const {
|
|
528
|
+
unknownFields = "strip",
|
|
529
|
+
|
|
530
|
+
validPayloadResponse = {
|
|
531
|
+
statusCode: 200,
|
|
532
|
+
valid: true,
|
|
533
|
+
},
|
|
534
|
+
|
|
535
|
+
inValidPayloadResponse = {
|
|
536
|
+
statusCode: 400,
|
|
537
|
+
valid: false,
|
|
538
|
+
message: "One or more attribute values are invalid",
|
|
539
|
+
},
|
|
540
|
+
} = options ?? {};
|
|
541
|
+
|
|
542
|
+
if (!["strip", "allow", "reject"].includes(unknownFields)) {
|
|
543
|
+
throw new Error(
|
|
544
|
+
`perfect-payload:- unknownFields must be one of strip, allow, reject`,
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
|
|
532
548
|
return perfectPayloadStructured(
|
|
533
549
|
data,
|
|
534
550
|
dataValidationRule,
|
|
535
551
|
validPayloadResponse,
|
|
536
552
|
inValidPayloadResponse,
|
|
553
|
+
"",
|
|
554
|
+
{
|
|
555
|
+
unknownFields,
|
|
556
|
+
},
|
|
537
557
|
);
|
|
538
558
|
}
|
|
539
559
|
|
|
540
560
|
export async function perfectPayloadAsync(
|
|
541
561
|
data = {},
|
|
542
562
|
dataValidationRule = {},
|
|
543
|
-
|
|
544
|
-
inValidPayloadResponse = {
|
|
545
|
-
statusCode: 400,
|
|
546
|
-
valid: false,
|
|
547
|
-
message: "One or more attribute values are invalid",
|
|
548
|
-
},
|
|
563
|
+
options = {},
|
|
549
564
|
) {
|
|
565
|
+
const {
|
|
566
|
+
validPayloadResponse = {
|
|
567
|
+
statusCode: 200,
|
|
568
|
+
valid: true,
|
|
569
|
+
},
|
|
570
|
+
|
|
571
|
+
inValidPayloadResponse = {
|
|
572
|
+
statusCode: 400,
|
|
573
|
+
valid: false,
|
|
574
|
+
message: "One or more attribute values are invalid",
|
|
575
|
+
},
|
|
576
|
+
unknownFields = "strip",
|
|
577
|
+
} = options ?? {};
|
|
578
|
+
|
|
579
|
+
if (!["strip", "allow", "reject"].includes(unknownFields)) {
|
|
580
|
+
throw new Error(
|
|
581
|
+
`perfect-payload:- unknownFields must be one of strip, allow, reject`,
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
550
585
|
const validationResult = perfectPayloadStructured(
|
|
551
586
|
data,
|
|
552
587
|
dataValidationRule,
|
|
@@ -555,6 +590,7 @@ export async function perfectPayloadAsync(
|
|
|
555
590
|
"",
|
|
556
591
|
{
|
|
557
592
|
skipCustomValidator: true,
|
|
593
|
+
unknownFields,
|
|
558
594
|
},
|
|
559
595
|
);
|
|
560
596
|
|
|
@@ -566,6 +602,7 @@ export async function perfectPayloadAsync(
|
|
|
566
602
|
validationResult?.validatedPayload ?? {},
|
|
567
603
|
dataValidationRule,
|
|
568
604
|
);
|
|
605
|
+
|
|
569
606
|
if (rowErrors.length > 0) {
|
|
570
607
|
return {
|
|
571
608
|
...inValidPayloadResponse,
|
|
@@ -591,6 +628,7 @@ function perfectPayloadStructured(
|
|
|
591
628
|
let validatedPayload = {};
|
|
592
629
|
let rowErrors = [];
|
|
593
630
|
const skipCustomValidator = options?.skipCustomValidator === true;
|
|
631
|
+
const unknownFields = options?.unknownFields ?? "strip";
|
|
594
632
|
|
|
595
633
|
for (const attributeName in dataValidationRule) {
|
|
596
634
|
let addNextError = true;
|
|
@@ -1520,6 +1558,36 @@ function perfectPayloadStructured(
|
|
|
1520
1558
|
}
|
|
1521
1559
|
}
|
|
1522
1560
|
|
|
1561
|
+
// UNKNOWN FIELD HANDLING
|
|
1562
|
+
if (unknownFields === "allow") {
|
|
1563
|
+
for (const attributeName in data) {
|
|
1564
|
+
if (
|
|
1565
|
+
Object.prototype.hasOwnProperty.call(data, attributeName) &&
|
|
1566
|
+
!Object.prototype.hasOwnProperty.call(dataValidationRule, attributeName)
|
|
1567
|
+
) {
|
|
1568
|
+
validatedPayload[attributeName] = data[attributeName];
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
if (unknownFields === "reject") {
|
|
1574
|
+
for (const attributeName in data) {
|
|
1575
|
+
if (
|
|
1576
|
+
Object.prototype.hasOwnProperty.call(data, attributeName) &&
|
|
1577
|
+
!Object.prototype.hasOwnProperty.call(dataValidationRule, attributeName)
|
|
1578
|
+
) {
|
|
1579
|
+
const path = basePath ? `${basePath}.${attributeName}` : attributeName;
|
|
1580
|
+
|
|
1581
|
+
rowErrors.push({
|
|
1582
|
+
path,
|
|
1583
|
+
code: "UNKNOWN_FIELD",
|
|
1584
|
+
message: `Unknown field ${path} is not allowed`,
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// INVALID RESPONSE
|
|
1523
1591
|
if (rowErrors.length > 0) {
|
|
1524
1592
|
return {
|
|
1525
1593
|
...inValidPayloadResponse,
|
|
@@ -1527,6 +1595,7 @@ function perfectPayloadStructured(
|
|
|
1527
1595
|
};
|
|
1528
1596
|
}
|
|
1529
1597
|
|
|
1598
|
+
// VALID RESPONSE
|
|
1530
1599
|
return {
|
|
1531
1600
|
...validPayloadResponse,
|
|
1532
1601
|
validatedPayload,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "perfect-payload",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0-beta.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lightweight JSON payload validation library with structured errors, nested validation, field paths, and customizable validation rules.",
|
|
6
6
|
"main": "index.js",
|