perfect-payload 2.0.0-beta.3 → 2.0.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 +314 -1117
- package/dist/framework.js +5 -21
- package/dist/index.d.ts +6 -3
- package/dist/index.js +108 -68
- package/dist/types/framework.d.ts +2 -2
- package/dist/types/options.d.ts +3 -3
- package/dist/types/rules.d.ts +4 -3
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,29 +1,21 @@
|
|
|
1
1
|
# perfect-payload
|
|
2
2
|
|
|
3
|
-
A lightweight JavaScript library for validating, transforming, and
|
|
4
|
-
sanitizing API and JSON payloads.
|
|
5
|
-
|
|
6
|
-
`perfect-payload` provides structured validation errors, exact nested
|
|
7
|
-
field paths, synchronous and asynchronous custom validation,
|
|
8
|
-
transformations, nested object/array validation, unknown-field handling,
|
|
9
|
-
and ready-to-use Express and Fastify integrations.
|
|
10
|
-
|
|
11
|
-
The package is designed to stay simple and lightweight, with no Express
|
|
12
|
-
or Fastify runtime dependency.
|
|
3
|
+
A lightweight JavaScript library for validating, transforming, and sanitizing API and JSON payloads. `perfect-payload` provides structured validation errors, exact nested field paths, synchronous and asynchronous custom validation, transformations, nested object/array validation, unknown-field handling, and ready-to-use Express and Fastify integrations. The package is designed to stay simple and lightweight, with no Express or Fastify runtime dependency.
|
|
13
4
|
|
|
14
5
|
## Highlights
|
|
15
6
|
|
|
16
7
|
- Lightweight, rule-based payload validation
|
|
8
|
+
- TypeScript-first package with built-in declarations — no separate `@types/perfect-payload`
|
|
17
9
|
- Structured errors with stable machine-readable error codes
|
|
18
|
-
- Exact nested paths such as `profile.email` and
|
|
19
|
-
`products[1].quantity`
|
|
10
|
+
- Exact nested paths such as `profile.email` and `products[1].quantity`
|
|
20
11
|
- Recursive `objectAttr` and `elementConstraints` validation
|
|
21
12
|
- Array constraints with `minItems` and `maxItems`
|
|
22
13
|
- Built-in `trim`, `lowercase`, and `uppercase` transformations
|
|
23
14
|
- Custom synchronous `transform(value, payload)`
|
|
15
|
+
- Array-element transforms with `transform(value, index, payload)`
|
|
16
|
+
- Deterministic validation precedence independent of rule-property order
|
|
24
17
|
- Synchronous custom validators with `perfectPayload()`
|
|
25
|
-
- Synchronous or asynchronous custom validators with
|
|
26
|
-
`perfectPayloadAsync()`
|
|
18
|
+
- Synchronous or asynchronous custom validators with `perfectPayloadAsync()`
|
|
27
19
|
- Configurable unknown-field handling: `strip`, `allow`, or `reject`
|
|
28
20
|
- Optional simplified errors with `prettyErrors`
|
|
29
21
|
- Express middleware integration
|
|
@@ -33,7 +25,7 @@ or Fastify runtime dependency.
|
|
|
33
25
|
- Framework-aware error paths such as `body.email` and `params.userId`
|
|
34
26
|
- Transformed values returned through `validatedPayload`
|
|
35
27
|
- Original input payload/request data is not mutated
|
|
36
|
-
-
|
|
28
|
+
- Zero Express or Fastify runtime dependency
|
|
37
29
|
- Legacy `perfectPayloadV1()` retained during the migration period
|
|
38
30
|
|
|
39
31
|
## Quick Links
|
|
@@ -41,38 +33,49 @@ or Fastify runtime dependency.
|
|
|
41
33
|
- [Installation](#installation)
|
|
42
34
|
- [Basic Usage](#basic-usage)
|
|
43
35
|
- [Public API](#public-api)
|
|
36
|
+
- [TypeScript](#typescript)
|
|
44
37
|
- [Options](#options)
|
|
45
|
-
- [Pretty Errors](#
|
|
38
|
+
- [Pretty Errors](#prettyerrors)
|
|
46
39
|
- [Express Integration](#express-integration)
|
|
47
40
|
- [Fastify Integration](#fastify-integration)
|
|
48
41
|
- [Framework Request Validation](#framework-request-validation)
|
|
49
42
|
- [Unknown Field Handling](#unknown-field-handling)
|
|
50
|
-
- [Synchronous vs Asynchronous
|
|
51
|
-
Validation](#synchronous-vs-asynchronous-validation)
|
|
43
|
+
- [Synchronous vs Asynchronous Validation](#synchronous-vs-asynchronous-validation)
|
|
52
44
|
- [Validation Rules](#validation-rules)
|
|
53
|
-
- [
|
|
54
|
-
|
|
55
|
-
- [Transformations and
|
|
56
|
-
Sanitization](#transformations-and-sanitization)
|
|
45
|
+
- [Validation Precedence](#validation-precedence)
|
|
46
|
+
- [Array Size and Nested Validation](#array-size-and-nested-validation)
|
|
47
|
+
- [Transformations and Sanitization](#transformations-and-sanitization)
|
|
57
48
|
- [Custom Validators](#customvalidator)
|
|
58
49
|
- [Asynchronous Validation](#asynchronous-validation)
|
|
59
50
|
- [Error Codes](#error-codes)
|
|
60
51
|
- [Custom Error Messages](#custom-error-messages)
|
|
61
|
-
- [Nested Objects and Array Field
|
|
62
|
-
Paths](#nested-objects-and-array-field-paths)
|
|
52
|
+
- [Nested Objects and Array Field Paths](#nested-objects-and-array-field-paths)
|
|
63
53
|
- [Examples and Usage](#examples-and-usage)
|
|
64
54
|
- [Legacy API](#legacy-api)
|
|
65
55
|
|
|
66
|
-
## What's New in
|
|
56
|
+
## What's New in v2.0.0
|
|
67
57
|
|
|
68
|
-
|
|
69
|
-
while keeping the core validation API framework-independent.
|
|
58
|
+
v2.0.0 establishes the stable TypeScript-first architecture for `perfect-payload` while keeping the package lightweight and framework-independent.
|
|
70
59
|
|
|
71
|
-
|
|
60
|
+
Major v2 additions and hardening include:
|
|
61
|
+
|
|
62
|
+
- Built-in TypeScript declarations generated from the source — no separate `@types/perfect-payload` package.
|
|
63
|
+
- Public validation types such as `ValidationRules` and `ValidationOptions`.
|
|
64
|
+
- Structured validation errors with stable `path`, `code`, and `message` fields.
|
|
65
|
+
- `prettyErrors` for consumers that prefer a simple string array.
|
|
66
|
+
- Recursive unknown-field handling with `strip`, `allow`, and `reject`.
|
|
67
|
+
- Exact nested and indexed paths across `objectAttr` and `elementConstraints`.
|
|
68
|
+
- Synchronous transforms with a complete root-payload context.
|
|
69
|
+
- Array-element transforms with `(value, index, payload)`.
|
|
70
|
+
- Deterministic validation precedence that does not depend on the order in which rule properties are written.
|
|
71
|
+
- `perfectPayloadAsync()` with async `customValidator` support and sync-error pruning.
|
|
72
|
+
- Lightweight Express and Fastify adapters for `headers`, `params`, `query`, and `body`.
|
|
73
|
+
- Framework-aware generated error paths/messages while preserving custom error messages exactly as configured.
|
|
74
|
+
- Legacy `perfectPayloadV1()` retained during the migration period.
|
|
72
75
|
|
|
73
|
-
|
|
76
|
+
## Express Integration
|
|
74
77
|
|
|
75
|
-
Express is **not** installed as a dependency of `perfect-payload`.
|
|
78
|
+
`perfect-payload` provides a lightweight Express adapter so request validation can be added directly as middleware. Express is **not** installed as a dependency of `perfect-payload`.
|
|
76
79
|
|
|
77
80
|
### Import
|
|
78
81
|
|
|
@@ -87,11 +90,8 @@ Use `validatePayload()` when your validation rules are synchronous.
|
|
|
87
90
|
```js
|
|
88
91
|
import express from "express";
|
|
89
92
|
import { validatePayload } from "perfect-payload/express";
|
|
90
|
-
|
|
91
93
|
const app = express();
|
|
92
|
-
|
|
93
94
|
app.use(express.json());
|
|
94
|
-
|
|
95
95
|
const userRules = {
|
|
96
96
|
email: {
|
|
97
97
|
mandatory: true,
|
|
@@ -105,7 +105,6 @@ const userRules = {
|
|
|
105
105
|
min: 18,
|
|
106
106
|
},
|
|
107
107
|
};
|
|
108
|
-
|
|
109
108
|
app.post(
|
|
110
109
|
"/users",
|
|
111
110
|
validatePayload({
|
|
@@ -115,7 +114,6 @@ app.post(
|
|
|
115
114
|
}),
|
|
116
115
|
(req, res) => {
|
|
117
116
|
const user = req.validatedPayload.body;
|
|
118
|
-
|
|
119
117
|
res.json({
|
|
120
118
|
message: "User created",
|
|
121
119
|
user,
|
|
@@ -158,20 +156,17 @@ app.post(
|
|
|
158
156
|
type: "string",
|
|
159
157
|
},
|
|
160
158
|
},
|
|
161
|
-
|
|
162
159
|
params: {
|
|
163
160
|
userId: {
|
|
164
161
|
mandatory: true,
|
|
165
162
|
type: "string",
|
|
166
163
|
},
|
|
167
164
|
},
|
|
168
|
-
|
|
169
165
|
query: {
|
|
170
166
|
notify: {
|
|
171
167
|
type: "boolean",
|
|
172
168
|
},
|
|
173
169
|
},
|
|
174
|
-
|
|
175
170
|
body: {
|
|
176
171
|
email: {
|
|
177
172
|
mandatory: true,
|
|
@@ -184,7 +179,6 @@ app.post(
|
|
|
184
179
|
}),
|
|
185
180
|
(req, res) => {
|
|
186
181
|
const { headers, params, query, body } = req.validatedPayload;
|
|
187
|
-
|
|
188
182
|
res.json({
|
|
189
183
|
headers,
|
|
190
184
|
params,
|
|
@@ -212,13 +206,13 @@ Structured error paths include the request source:
|
|
|
212
206
|
{
|
|
213
207
|
path: "body.email",
|
|
214
208
|
code: "INVALID_EMAIL",
|
|
215
|
-
message: "Invalid email format for attribute email"
|
|
209
|
+
message: "Invalid email format for attribute body.email"
|
|
216
210
|
}
|
|
217
211
|
]
|
|
218
212
|
}
|
|
219
213
|
```
|
|
220
214
|
|
|
221
|
-
|
|
215
|
+
For framework-generated default errors, the request source is part of the generated field path/message. Custom messages such as `mandatoryError`, `typeError`, `regexError`, and `customValidatorError` are returned exactly as configured and are not prefixed or rewritten by the adapter.
|
|
222
216
|
|
|
223
217
|
### Adapter Options
|
|
224
218
|
|
|
@@ -247,7 +241,6 @@ Use `validatePayloadAsync()` when the schema contains asynchronous `customValida
|
|
|
247
241
|
|
|
248
242
|
```js
|
|
249
243
|
import { validatePayloadAsync } from "perfect-payload/express";
|
|
250
|
-
|
|
251
244
|
app.post(
|
|
252
245
|
"/users",
|
|
253
246
|
validatePayloadAsync({
|
|
@@ -257,11 +250,9 @@ app.post(
|
|
|
257
250
|
mandatory: true,
|
|
258
251
|
type: "string",
|
|
259
252
|
trim: true,
|
|
260
|
-
|
|
261
253
|
customValidator: async (value) => {
|
|
262
254
|
return await isUsernameAvailable(value);
|
|
263
255
|
},
|
|
264
|
-
|
|
265
256
|
customValidatorCode: "USERNAME_TAKEN",
|
|
266
257
|
customValidatorError: "Username is already taken",
|
|
267
258
|
},
|
|
@@ -283,9 +274,7 @@ Validation failures are handled by the middleware automatically. Unexpected erro
|
|
|
283
274
|
|
|
284
275
|
## Fastify Integration
|
|
285
276
|
|
|
286
|
-
`perfect-payload` provides a lightweight Fastify adapter that can be used directly as a route hook.
|
|
287
|
-
|
|
288
|
-
Fastify is **not** installed as a dependency of `perfect-payload`.
|
|
277
|
+
`perfect-payload` provides a lightweight Fastify adapter that can be used directly as a route hook. Fastify is **not** installed as a dependency of `perfect-payload`.
|
|
289
278
|
|
|
290
279
|
### Import
|
|
291
280
|
|
|
@@ -300,9 +289,7 @@ Use the adapter as a Fastify `preValidation` hook:
|
|
|
300
289
|
```js
|
|
301
290
|
import Fastify from "fastify";
|
|
302
291
|
import { validatePayload } from "perfect-payload/fastify";
|
|
303
|
-
|
|
304
292
|
const fastify = Fastify();
|
|
305
|
-
|
|
306
293
|
const userRules = {
|
|
307
294
|
email: {
|
|
308
295
|
mandatory: true,
|
|
@@ -316,7 +303,6 @@ const userRules = {
|
|
|
316
303
|
min: 18,
|
|
317
304
|
},
|
|
318
305
|
};
|
|
319
|
-
|
|
320
306
|
fastify.post(
|
|
321
307
|
"/users",
|
|
322
308
|
{
|
|
@@ -328,7 +314,6 @@ fastify.post(
|
|
|
328
314
|
},
|
|
329
315
|
async (request, reply) => {
|
|
330
316
|
const user = request.validatedPayload.body;
|
|
331
|
-
|
|
332
317
|
return {
|
|
333
318
|
message: "User created",
|
|
334
319
|
user,
|
|
@@ -372,20 +357,17 @@ fastify.post(
|
|
|
372
357
|
type: "string",
|
|
373
358
|
},
|
|
374
359
|
},
|
|
375
|
-
|
|
376
360
|
params: {
|
|
377
361
|
userId: {
|
|
378
362
|
mandatory: true,
|
|
379
363
|
type: "string",
|
|
380
364
|
},
|
|
381
365
|
},
|
|
382
|
-
|
|
383
366
|
query: {
|
|
384
367
|
notify: {
|
|
385
368
|
type: "boolean",
|
|
386
369
|
},
|
|
387
370
|
},
|
|
388
|
-
|
|
389
371
|
body: {
|
|
390
372
|
email: {
|
|
391
373
|
mandatory: true,
|
|
@@ -399,7 +381,6 @@ fastify.post(
|
|
|
399
381
|
},
|
|
400
382
|
async (request, reply) => {
|
|
401
383
|
const { headers, params, query, body } = request.validatedPayload;
|
|
402
|
-
|
|
403
384
|
return {
|
|
404
385
|
headers,
|
|
405
386
|
params,
|
|
@@ -427,13 +408,13 @@ Structured error paths include the request source:
|
|
|
427
408
|
{
|
|
428
409
|
path: "body.email",
|
|
429
410
|
code: "INVALID_EMAIL",
|
|
430
|
-
message: "Invalid email format for attribute email"
|
|
411
|
+
message: "Invalid email format for attribute body.email"
|
|
431
412
|
}
|
|
432
413
|
]
|
|
433
414
|
}
|
|
434
415
|
```
|
|
435
416
|
|
|
436
|
-
|
|
417
|
+
For framework-generated default errors, the request source is part of the generated field path/message. Custom messages such as `mandatoryError`, `typeError`, `regexError`, and `customValidatorError` are returned exactly as configured and are not prefixed or rewritten by the adapter.
|
|
437
418
|
|
|
438
419
|
### Adapter Options
|
|
439
420
|
|
|
@@ -462,7 +443,6 @@ Use `validatePayloadAsync()` when the schema contains asynchronous `customValida
|
|
|
462
443
|
|
|
463
444
|
```js
|
|
464
445
|
import { validatePayloadAsync } from "perfect-payload/fastify";
|
|
465
|
-
|
|
466
446
|
fastify.post(
|
|
467
447
|
"/users",
|
|
468
448
|
{
|
|
@@ -473,11 +453,9 @@ fastify.post(
|
|
|
473
453
|
mandatory: true,
|
|
474
454
|
type: "string",
|
|
475
455
|
trim: true,
|
|
476
|
-
|
|
477
456
|
customValidator: async (value) => {
|
|
478
457
|
return await isUsernameAvailable(value);
|
|
479
458
|
},
|
|
480
|
-
|
|
481
459
|
customValidatorCode: "USERNAME_TAKEN",
|
|
482
460
|
customValidatorError: "Username is already taken",
|
|
483
461
|
},
|
|
@@ -500,24 +478,48 @@ Validation failures are handled by the hook automatically. Unexpected errors fro
|
|
|
500
478
|
|
|
501
479
|
### Framework-aware error paths
|
|
502
480
|
|
|
503
|
-
Structured validation errors include the request source
|
|
481
|
+
Structured framework validation errors include the request source:
|
|
504
482
|
|
|
505
483
|
```js
|
|
506
484
|
{
|
|
507
485
|
path: "body.email",
|
|
508
486
|
code: "INVALID_EMAIL",
|
|
509
|
-
message: "Invalid email format for attribute email"
|
|
487
|
+
message: "Invalid email format for attribute body.email"
|
|
510
488
|
}
|
|
511
489
|
```
|
|
512
490
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
491
|
+
Generated default errors use source-aware paths such as:
|
|
492
|
+
|
|
493
|
+
```text
|
|
494
|
+
headers.x-request-id
|
|
495
|
+
params.userId
|
|
496
|
+
query.page
|
|
497
|
+
body.email
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
A generated pretty error can therefore look like:
|
|
501
|
+
|
|
502
|
+
```text
|
|
503
|
+
Custom validation failed for attribute headers.x-test-id
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
Custom developer-defined messages are preserved exactly. For example:
|
|
507
|
+
|
|
508
|
+
```js
|
|
509
|
+
customValidatorError: "Invalid x-request-id in headers";
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
returns:
|
|
513
|
+
|
|
514
|
+
```text
|
|
515
|
+
Invalid x-request-id in headers
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
The framework adapter does not prepend `headers.`, `params.`, `query.`, or `body.` to a custom message.
|
|
516
519
|
|
|
517
520
|
### `prettyErrors`
|
|
518
521
|
|
|
519
|
-
|
|
520
|
-
the default.
|
|
522
|
+
`prettyErrors` provides a simplified string-error mode. Structured errors remain the default.
|
|
521
523
|
|
|
522
524
|
```js
|
|
523
525
|
const result = perfectPayload(payload, rules, {
|
|
@@ -525,26 +527,16 @@ const result = perfectPayload(payload, rules, {
|
|
|
525
527
|
});
|
|
526
528
|
```
|
|
527
529
|
|
|
528
|
-
With `prettyErrors: true`, the `errors` array contains human-readable
|
|
529
|
-
message strings instead of structured error objects.
|
|
530
|
-
|
|
531
|
-
`prettyErrors` is also supported by `perfectPayloadAsync()` and the
|
|
532
|
-
Express/Fastify integrations.
|
|
530
|
+
With `prettyErrors: true`, the `errors` array contains human-readable message strings instead of structured error objects. `prettyErrors` is also supported by `perfectPayloadAsync()` and the Express/Fastify integrations.
|
|
533
531
|
|
|
534
532
|
### Error privacy
|
|
535
533
|
|
|
536
|
-
Default validation messages do not include submitted payload values.
|
|
537
|
-
|
|
538
|
-
Schema constraints such as allowed enum values, minimums, maximums, and
|
|
539
|
-
ranges may still appear in validation messages. Custom error messages
|
|
540
|
-
are controlled by the application and are returned as configured.
|
|
534
|
+
Default validation messages do not include submitted payload values. Schema constraints such as allowed enum values, minimums, maximums, and ranges may still appear in validation messages. Custom error messages are controlled by the application and are returned as configured.
|
|
541
535
|
|
|
542
536
|
### Lightweight framework integrations
|
|
543
537
|
|
|
544
|
-
Express and Fastify are **not installed as dependencies of
|
|
545
|
-
`perfect-payload`**.
|
|
538
|
+
Express and Fastify are **not installed as dependencies of `perfect-payload`**. The framework integrations are thin adapters around the same validation
|
|
546
539
|
|
|
547
|
-
The framework integrations are thin adapters around the same validation
|
|
548
540
|
engine used by:
|
|
549
541
|
|
|
550
542
|
```js
|
|
@@ -552,13 +544,11 @@ perfectPayload();
|
|
|
552
544
|
perfectPayloadAsync();
|
|
553
545
|
```
|
|
554
546
|
|
|
555
|
-
This keeps the package lightweight while allowing framework users to
|
|
556
|
-
integrate validation without writing their own middleware or hooks.
|
|
547
|
+
This keeps the package lightweight while allowing framework users to integrate validation without writing their own middleware or hooks.
|
|
557
548
|
|
|
558
549
|
## Installation
|
|
559
550
|
|
|
560
551
|
```bash
|
|
561
|
-
|
|
562
552
|
npm install perfect-payload
|
|
563
553
|
```
|
|
564
554
|
|
|
@@ -568,117 +558,75 @@ Use `perfectPayload()` for all new implementations.
|
|
|
568
558
|
|
|
569
559
|
```js
|
|
570
560
|
import { perfectPayload } from "perfect-payload";
|
|
571
|
-
|
|
572
561
|
const payload = {
|
|
573
562
|
name: "Kiran",
|
|
574
|
-
|
|
575
563
|
email: "kiran@example.com",
|
|
576
|
-
|
|
577
564
|
age: 29,
|
|
578
565
|
};
|
|
579
|
-
|
|
580
566
|
const validationRules = {
|
|
581
567
|
name: {
|
|
582
568
|
mandatory: true,
|
|
583
|
-
|
|
584
569
|
type: "string",
|
|
585
570
|
},
|
|
586
|
-
|
|
587
571
|
email: {
|
|
588
572
|
mandatory: true,
|
|
589
|
-
|
|
590
573
|
type: "email",
|
|
591
574
|
},
|
|
592
|
-
|
|
593
575
|
age: {
|
|
594
576
|
mandatory: true,
|
|
595
|
-
|
|
596
577
|
type: "number",
|
|
597
|
-
|
|
598
578
|
min: 18,
|
|
599
579
|
},
|
|
600
580
|
};
|
|
601
|
-
|
|
602
581
|
const result = perfectPayload(payload, validationRules);
|
|
603
|
-
|
|
604
582
|
console.log(result);
|
|
605
583
|
```
|
|
606
584
|
|
|
607
585
|
### Valid Response
|
|
608
586
|
|
|
609
587
|
```js
|
|
610
|
-
|
|
611
588
|
{
|
|
612
|
-
|
|
613
589
|
statusCode: 200,
|
|
614
|
-
|
|
615
590
|
valid: true,
|
|
616
|
-
|
|
617
591
|
validatedPayload: {
|
|
618
|
-
|
|
619
592
|
name: "Kiran",
|
|
620
|
-
|
|
621
593
|
email: "kiran@example.com",
|
|
622
|
-
|
|
623
594
|
age: 29
|
|
624
|
-
|
|
625
595
|
}
|
|
626
|
-
|
|
627
596
|
}
|
|
628
597
|
```
|
|
629
598
|
|
|
630
|
-
By default, `validatedPayload` contains only fields defined in the validation schema. Extra payload fields are stripped unless `unknownFields` is explicitly configured as `"allow"` or `"reject"`.
|
|
631
|
-
|
|
632
|
-
The original input payload is not mutated. (such as validatedBody, sanitisedData or parsedBody).
|
|
599
|
+
By default, `validatedPayload` contains only fields defined in the validation schema. Extra payload fields are stripped unless `unknownFields` is explicitly configured as `"allow"` or `"reject"`. The original input payload is not mutated. (such as validatedBody, sanitisedData or parsedBody).
|
|
633
600
|
|
|
634
601
|
### Invalid Response
|
|
635
602
|
|
|
636
603
|
```js
|
|
637
|
-
|
|
638
604
|
{
|
|
639
|
-
|
|
640
605
|
statusCode: 400,
|
|
641
|
-
|
|
642
606
|
valid: false,
|
|
643
|
-
|
|
644
607
|
message: "One or more attribute values are invalid",
|
|
645
|
-
|
|
646
608
|
errors: [
|
|
647
|
-
|
|
648
609
|
{
|
|
649
|
-
|
|
650
610
|
path: "email",
|
|
651
|
-
|
|
652
611
|
code: "INVALID_EMAIL",
|
|
653
|
-
|
|
654
612
|
message: "Invalid email format for attribute email"
|
|
655
|
-
|
|
656
613
|
}
|
|
657
|
-
|
|
658
614
|
]
|
|
659
|
-
|
|
660
615
|
}
|
|
661
616
|
```
|
|
662
617
|
|
|
663
618
|
Each error returned by `perfectPayload()` contains:
|
|
664
619
|
|
|
665
620
|
```js
|
|
666
|
-
|
|
667
621
|
{
|
|
668
|
-
|
|
669
622
|
path: "field.path",
|
|
670
|
-
|
|
671
623
|
code: "ERROR_CODE",
|
|
672
|
-
|
|
673
624
|
message: "Human readable validation message"
|
|
674
|
-
|
|
675
625
|
}
|
|
676
626
|
```
|
|
677
627
|
|
|
678
628
|
- `path` identifies the exact field that failed validation.
|
|
679
|
-
|
|
680
629
|
- `code` provides a stable machine-readable validation error code.
|
|
681
|
-
|
|
682
630
|
- `message` provides a human-readable description of the validation
|
|
683
631
|
|
|
684
632
|
failure.
|
|
@@ -687,12 +635,10 @@ failure.
|
|
|
687
635
|
|
|
688
636
|
## Public API
|
|
689
637
|
|
|
690
|
-
For new implementations, both supported APIs use the same clean three-argument signature:
|
|
638
|
+
For v2 and all new implementations, both supported APIs use the same clean three-argument signature:
|
|
691
639
|
|
|
692
640
|
```js
|
|
693
|
-
|
|
694
641
|
perfectPayload(data, validationRules, options?)
|
|
695
|
-
|
|
696
642
|
await perfectPayloadAsync(data, validationRules, options?)
|
|
697
643
|
```
|
|
698
644
|
|
|
@@ -700,67 +646,99 @@ The arguments are:
|
|
|
700
646
|
|
|
701
647
|
| Argument | Required | Description |
|
|
702
648
|
| ----------------- | -------- | ----------------------------------------------------------------------------------- |
|
|
703
|
-
| `data` | No | Payload/object to validate. Defaults to
|
|
704
|
-
| `validationRules` | No | Validation schema. Defaults to
|
|
649
|
+
| `data` | No | Payload/object to validate. Defaults to`{}`. |
|
|
650
|
+
| `validationRules` | No | Validation schema. Defaults to`{}`. |
|
|
705
651
|
| `options` | No | API-level configuration such as unknown-field handling and custom response objects. |
|
|
706
652
|
|
|
707
|
-
The third argument is a single options object. You no longer need to pass separate positional arguments for custom valid and invalid responses.
|
|
708
|
-
|
|
709
|
-
### Options
|
|
710
|
-
|
|
711
|
-
```js
|
|
712
|
-
|
|
713
|
-
{
|
|
653
|
+
The third argument is a single options object. You no longer need to pass separate positional arguments for custom valid and invalid responses. In TypeScript, this object is typed as `ValidationOptions`.
|
|
714
654
|
|
|
715
|
-
|
|
655
|
+
### TypeScript
|
|
716
656
|
|
|
717
|
-
|
|
657
|
+
`perfect-payload` ships its TypeScript declarations with the package. No separate `@types/perfect-payload` installation is required.
|
|
718
658
|
|
|
719
|
-
|
|
659
|
+
```ts
|
|
660
|
+
import { perfectPayload, perfectPayloadAsync } from "perfect-payload";
|
|
661
|
+
import type {
|
|
662
|
+
ValidationOptions,
|
|
663
|
+
ValidationRules,
|
|
664
|
+
AttributeValidationRules,
|
|
665
|
+
ValidationValueType,
|
|
666
|
+
TransformFunction,
|
|
667
|
+
ArrayElementTransformFunction,
|
|
668
|
+
CustomValidator,
|
|
669
|
+
DependencyRule,
|
|
670
|
+
DependencyRules,
|
|
671
|
+
} from "perfect-payload";
|
|
672
|
+
```
|
|
720
673
|
|
|
721
|
-
|
|
674
|
+
Example:
|
|
722
675
|
|
|
676
|
+
```ts
|
|
677
|
+
const rules: ValidationRules = {
|
|
678
|
+
email: {
|
|
679
|
+
mandatory: true,
|
|
680
|
+
type: "email",
|
|
681
|
+
trim: true,
|
|
682
|
+
lowercase: true,
|
|
723
683
|
},
|
|
684
|
+
};
|
|
685
|
+
const options: ValidationOptions = {
|
|
686
|
+
unknownFields: "reject",
|
|
687
|
+
prettyErrors: false,
|
|
688
|
+
};
|
|
689
|
+
const result = perfectPayload(
|
|
690
|
+
{
|
|
691
|
+
email: " KIRAN@EXAMPLE.COM ",
|
|
692
|
+
},
|
|
693
|
+
rules,
|
|
694
|
+
options,
|
|
695
|
+
);
|
|
696
|
+
if (result.valid === true) {
|
|
697
|
+
console.log(result.validatedPayload);
|
|
698
|
+
} else {
|
|
699
|
+
console.log(result.errors);
|
|
700
|
+
}
|
|
701
|
+
```
|
|
724
702
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
statusCode: 400,
|
|
728
|
-
|
|
729
|
-
valid: false,
|
|
703
|
+
`prettyErrors: true` changes the error collection to strings, while the default structured mode returns objects containing `path`, `code`, and `message`. The runtime package remains the same for JavaScript and TypeScript consumers.
|
|
730
704
|
|
|
731
|
-
|
|
705
|
+
### Options
|
|
732
706
|
|
|
733
|
-
|
|
707
|
+
The third argument uses the `ValidationOptions` type in TypeScript.
|
|
734
708
|
|
|
709
|
+
```ts
|
|
710
|
+
interface ValidationOptions {
|
|
711
|
+
unknownFields?: "strip" | "allow" | "reject";
|
|
712
|
+
prettyErrors?: boolean;
|
|
713
|
+
validPayloadResponse?: {
|
|
714
|
+
statusCode: number;
|
|
715
|
+
valid: true;
|
|
716
|
+
[key: string]: unknown;
|
|
717
|
+
};
|
|
718
|
+
inValidPayloadResponse?: {
|
|
719
|
+
statusCode: number;
|
|
720
|
+
valid: false;
|
|
721
|
+
message: string;
|
|
722
|
+
[key: string]: unknown;
|
|
723
|
+
};
|
|
735
724
|
}
|
|
736
725
|
```
|
|
737
726
|
|
|
738
727
|
All properties are optional. The defaults are equivalent to:
|
|
739
728
|
|
|
740
729
|
```js
|
|
741
|
-
|
|
742
730
|
{
|
|
743
|
-
|
|
744
731
|
unknownFields: "strip",
|
|
745
|
-
|
|
732
|
+
prettyErrors: false,
|
|
746
733
|
validPayloadResponse: {
|
|
747
|
-
|
|
748
734
|
statusCode: 200,
|
|
749
|
-
|
|
750
735
|
valid: true,
|
|
751
|
-
|
|
752
736
|
},
|
|
753
|
-
|
|
754
737
|
inValidPayloadResponse: {
|
|
755
|
-
|
|
756
738
|
statusCode: 400,
|
|
757
|
-
|
|
758
739
|
valid: false,
|
|
759
|
-
|
|
760
740
|
message: "One or more attribute values are invalid",
|
|
761
|
-
|
|
762
741
|
},
|
|
763
|
-
|
|
764
742
|
}
|
|
765
743
|
```
|
|
766
744
|
|
|
@@ -769,20 +747,15 @@ Example:
|
|
|
769
747
|
```js
|
|
770
748
|
const result = perfectPayload(payload, validationRules, {
|
|
771
749
|
unknownFields: "reject",
|
|
772
|
-
|
|
750
|
+
prettyErrors: false,
|
|
773
751
|
validPayloadResponse: {
|
|
774
752
|
statusCode: 201,
|
|
775
|
-
|
|
776
753
|
valid: true,
|
|
777
|
-
|
|
778
754
|
message: "Payload accepted",
|
|
779
755
|
},
|
|
780
|
-
|
|
781
756
|
inValidPayloadResponse: {
|
|
782
757
|
statusCode: 422,
|
|
783
|
-
|
|
784
758
|
valid: false,
|
|
785
|
-
|
|
786
759
|
message: "Payload validation failed",
|
|
787
760
|
},
|
|
788
761
|
});
|
|
@@ -793,12 +766,10 @@ The same options object is supported by `perfectPayloadAsync()`:
|
|
|
793
766
|
```js
|
|
794
767
|
const result = await perfectPayloadAsync(payload, validationRules, {
|
|
795
768
|
unknownFields: "reject",
|
|
796
|
-
|
|
769
|
+
prettyErrors: false,
|
|
797
770
|
inValidPayloadResponse: {
|
|
798
771
|
statusCode: 422,
|
|
799
|
-
|
|
800
772
|
valid: false,
|
|
801
|
-
|
|
802
773
|
message: "Payload validation failed",
|
|
803
774
|
},
|
|
804
775
|
});
|
|
@@ -810,46 +781,36 @@ const result = await perfectPayloadAsync(payload, validationRules, {
|
|
|
810
781
|
|
|
811
782
|
Supported values:
|
|
812
783
|
|
|
813
|
-
| Value | Behavior
|
|
814
|
-
| ---------- |
|
|
815
|
-
| `"strip"` | Removes unknown fields from
|
|
816
|
-
| `"allow"` | Preserves unknown fields in
|
|
817
|
-
| `"reject"` | Rejects unknown fields with structured
|
|
784
|
+
| Value | Behavior |
|
|
785
|
+
| ---------- | ------------------------------------------------------------------------------------------------------- |
|
|
786
|
+
| `"strip"` | Removes unknown fields from`validatedPayload`. This is the default and preserves the existing behavior. |
|
|
787
|
+
| `"allow"` | Preserves unknown fields in`validatedPayload`. |
|
|
788
|
+
| `"reject"` | Rejects unknown fields with structured`UNKNOWN_FIELD` validation errors. |
|
|
818
789
|
|
|
819
790
|
### `strip` — default
|
|
820
791
|
|
|
821
792
|
```js
|
|
822
793
|
const payload = {
|
|
823
794
|
name: "Kiran",
|
|
824
|
-
|
|
825
795
|
role: "developer",
|
|
826
796
|
};
|
|
827
|
-
|
|
828
797
|
const rules = {
|
|
829
798
|
name: {
|
|
830
799
|
type: "string",
|
|
831
800
|
},
|
|
832
801
|
};
|
|
833
|
-
|
|
834
802
|
const result = perfectPayload(payload, rules);
|
|
835
803
|
```
|
|
836
804
|
|
|
837
805
|
Result:
|
|
838
806
|
|
|
839
807
|
```js
|
|
840
|
-
|
|
841
808
|
{
|
|
842
|
-
|
|
843
809
|
statusCode: 200,
|
|
844
|
-
|
|
845
810
|
valid: true,
|
|
846
|
-
|
|
847
811
|
validatedPayload: {
|
|
848
|
-
|
|
849
812
|
name: "Kiran"
|
|
850
|
-
|
|
851
813
|
}
|
|
852
|
-
|
|
853
814
|
}
|
|
854
815
|
```
|
|
855
816
|
|
|
@@ -863,7 +824,7 @@ perfectPayload(payload, rules, {
|
|
|
863
824
|
});
|
|
864
825
|
```
|
|
865
826
|
|
|
866
|
-
### `allow`
|
|
827
|
+
### `allow` — preserve unknown fields
|
|
867
828
|
|
|
868
829
|
```js
|
|
869
830
|
const result = perfectPayload(payload, rules, {
|
|
@@ -874,29 +835,19 @@ const result = perfectPayload(payload, rules, {
|
|
|
874
835
|
Result:
|
|
875
836
|
|
|
876
837
|
```js
|
|
877
|
-
|
|
878
838
|
{
|
|
879
|
-
|
|
880
839
|
statusCode: 200,
|
|
881
|
-
|
|
882
840
|
valid: true,
|
|
883
|
-
|
|
884
841
|
validatedPayload: {
|
|
885
|
-
|
|
886
842
|
name: "Kiran",
|
|
887
|
-
|
|
888
843
|
role: "developer"
|
|
889
|
-
|
|
890
844
|
}
|
|
891
|
-
|
|
892
845
|
}
|
|
893
846
|
```
|
|
894
847
|
|
|
895
|
-
Schema-defined fields are still validated normally. Unknown fields are
|
|
896
|
-
|
|
897
|
-
simply preserved.
|
|
848
|
+
Schema-defined fields are still validated normally. Unknown fields are simply preserved.
|
|
898
849
|
|
|
899
|
-
### `reject`
|
|
850
|
+
### `reject` — reject unknown fields
|
|
900
851
|
|
|
901
852
|
```js
|
|
902
853
|
const result = perfectPayload(payload, rules, {
|
|
@@ -907,29 +858,17 @@ const result = perfectPayload(payload, rules, {
|
|
|
907
858
|
Result:
|
|
908
859
|
|
|
909
860
|
```js
|
|
910
|
-
|
|
911
861
|
{
|
|
912
|
-
|
|
913
862
|
statusCode: 400,
|
|
914
|
-
|
|
915
863
|
valid: false,
|
|
916
|
-
|
|
917
864
|
message: "One or more attribute values are invalid",
|
|
918
|
-
|
|
919
865
|
errors: [
|
|
920
|
-
|
|
921
866
|
{
|
|
922
|
-
|
|
923
867
|
path: "role",
|
|
924
|
-
|
|
925
868
|
code: "UNKNOWN_FIELD",
|
|
926
|
-
|
|
927
869
|
message: "Unknown field role is not allowed"
|
|
928
|
-
|
|
929
870
|
}
|
|
930
|
-
|
|
931
871
|
]
|
|
932
|
-
|
|
933
872
|
}
|
|
934
873
|
```
|
|
935
874
|
|
|
@@ -943,15 +882,12 @@ Unknown-field handling is recursive for schemas using `objectAttr`.
|
|
|
943
882
|
const payload = {
|
|
944
883
|
profile: {
|
|
945
884
|
city: "Bengaluru",
|
|
946
|
-
|
|
947
885
|
role: "developer",
|
|
948
886
|
},
|
|
949
887
|
};
|
|
950
|
-
|
|
951
888
|
const rules = {
|
|
952
889
|
profile: {
|
|
953
890
|
type: "object",
|
|
954
|
-
|
|
955
891
|
objectAttr: {
|
|
956
892
|
city: {
|
|
957
893
|
type: "string",
|
|
@@ -959,7 +895,6 @@ const rules = {
|
|
|
959
895
|
},
|
|
960
896
|
},
|
|
961
897
|
};
|
|
962
|
-
|
|
963
898
|
const result = perfectPayload(payload, rules, {
|
|
964
899
|
unknownFields: "reject",
|
|
965
900
|
});
|
|
@@ -968,29 +903,17 @@ const result = perfectPayload(payload, rules, {
|
|
|
968
903
|
Returns:
|
|
969
904
|
|
|
970
905
|
```js
|
|
971
|
-
|
|
972
906
|
{
|
|
973
|
-
|
|
974
907
|
statusCode: 400,
|
|
975
|
-
|
|
976
908
|
valid: false,
|
|
977
|
-
|
|
978
909
|
message: "One or more attribute values are invalid",
|
|
979
|
-
|
|
980
910
|
errors: [
|
|
981
|
-
|
|
982
911
|
{
|
|
983
|
-
|
|
984
912
|
path: "profile.role",
|
|
985
|
-
|
|
986
913
|
code: "UNKNOWN_FIELD",
|
|
987
|
-
|
|
988
914
|
message: "Unknown field profile.role is not allowed"
|
|
989
|
-
|
|
990
915
|
}
|
|
991
|
-
|
|
992
916
|
]
|
|
993
|
-
|
|
994
917
|
}
|
|
995
918
|
```
|
|
996
919
|
|
|
@@ -1001,15 +924,10 @@ Returns:
|
|
|
1001
924
|
the array index:
|
|
1002
925
|
|
|
1003
926
|
```js
|
|
1004
|
-
|
|
1005
927
|
{
|
|
1006
|
-
|
|
1007
928
|
path: "products[0].internalId",
|
|
1008
|
-
|
|
1009
929
|
code: "UNKNOWN_FIELD",
|
|
1010
|
-
|
|
1011
930
|
message: "Unknown field products[0].internalId is not allowed"
|
|
1012
|
-
|
|
1013
931
|
}
|
|
1014
932
|
```
|
|
1015
933
|
|
|
@@ -1018,7 +936,6 @@ This continues through deeply nested combinations of objects and arrays,
|
|
|
1018
936
|
for example:
|
|
1019
937
|
|
|
1020
938
|
```text
|
|
1021
|
-
|
|
1022
939
|
profile.teams[0].members[0].role
|
|
1023
940
|
```
|
|
1024
941
|
|
|
@@ -1029,49 +946,27 @@ With `"reject"`, unknown-field errors can be returned together with normal valid
|
|
|
1029
946
|
For example, an invalid email plus two unknown fields can produce:
|
|
1030
947
|
|
|
1031
948
|
```js
|
|
1032
|
-
|
|
1033
949
|
{
|
|
1034
|
-
|
|
1035
950
|
statusCode: 400,
|
|
1036
|
-
|
|
1037
951
|
valid: false,
|
|
1038
|
-
|
|
1039
952
|
message: "One or more attribute values are invalid",
|
|
1040
|
-
|
|
1041
953
|
errors: [
|
|
1042
|
-
|
|
1043
954
|
{
|
|
1044
|
-
|
|
1045
955
|
path: "email",
|
|
1046
|
-
|
|
1047
956
|
code: "INVALID_EMAIL",
|
|
1048
|
-
|
|
1049
957
|
message: "Invalid email format for attribute email"
|
|
1050
|
-
|
|
1051
958
|
},
|
|
1052
|
-
|
|
1053
959
|
{
|
|
1054
|
-
|
|
1055
960
|
path: "role",
|
|
1056
|
-
|
|
1057
961
|
code: "UNKNOWN_FIELD",
|
|
1058
|
-
|
|
1059
962
|
message: "Unknown field role is not allowed"
|
|
1060
|
-
|
|
1061
963
|
},
|
|
1062
|
-
|
|
1063
964
|
{
|
|
1064
|
-
|
|
1065
965
|
path: "active",
|
|
1066
|
-
|
|
1067
966
|
code: "UNKNOWN_FIELD",
|
|
1068
|
-
|
|
1069
967
|
message: "Unknown field active is not allowed"
|
|
1070
|
-
|
|
1071
968
|
}
|
|
1072
|
-
|
|
1073
969
|
]
|
|
1074
|
-
|
|
1075
970
|
}
|
|
1076
971
|
```
|
|
1077
972
|
|
|
@@ -1098,18 +993,14 @@ Unknown-field handling considers only the payload object's own enumerable proper
|
|
|
1098
993
|
Only these values are accepted:
|
|
1099
994
|
|
|
1100
995
|
```text
|
|
1101
|
-
|
|
1102
996
|
strip
|
|
1103
|
-
|
|
1104
997
|
allow
|
|
1105
|
-
|
|
1106
998
|
reject
|
|
1107
999
|
```
|
|
1108
1000
|
|
|
1109
1001
|
Any other value throws a configuration error:
|
|
1110
1002
|
|
|
1111
1003
|
```text
|
|
1112
|
-
|
|
1113
1004
|
perfect-payload:- unknownFields must be one of strip, allow, reject
|
|
1114
1005
|
```
|
|
1115
1006
|
|
|
@@ -1121,7 +1012,6 @@ For normal synchronous validation, use `perfectPayload()`:
|
|
|
1121
1012
|
|
|
1122
1013
|
```js
|
|
1123
1014
|
import { perfectPayload } from "perfect-payload";
|
|
1124
|
-
|
|
1125
1015
|
const result = perfectPayload(payload, validationRules, options);
|
|
1126
1016
|
```
|
|
1127
1017
|
|
|
@@ -1129,50 +1019,29 @@ When any `customValidator` needs to perform asynchronous work, use `perfectPaylo
|
|
|
1129
1019
|
|
|
1130
1020
|
```js
|
|
1131
1021
|
import { perfectPayloadAsync } from "perfect-payload";
|
|
1132
|
-
|
|
1133
|
-
const result = await perfectPayloadAsync(
|
|
1134
|
-
payload,
|
|
1135
|
-
validationRules,
|
|
1136
|
-
|
|
1137
|
-
options,
|
|
1138
|
-
);
|
|
1022
|
+
const result = await perfectPayloadAsync(payload, validationRules, options);
|
|
1139
1023
|
```
|
|
1140
1024
|
|
|
1141
1025
|
The public APIs are:
|
|
1142
1026
|
|
|
1143
1027
|
```text
|
|
1144
|
-
|
|
1145
1028
|
perfectPayloadV1() legacy API; deprecated
|
|
1146
|
-
|
|
1147
1029
|
perfectPayload(data, rules, options?) synchronous validation
|
|
1148
|
-
|
|
1149
1030
|
perfectPayloadAsync(data, rules, options?) synchronous + asynchronous
|
|
1150
|
-
|
|
1151
1031
|
customValidator
|
|
1152
1032
|
```
|
|
1153
1033
|
|
|
1154
|
-
`perfectPayload()` remains synchronous and intentionally rejects a `customValidator` that returns a Promise. This preserves the existing synchronous API contract.
|
|
1155
|
-
|
|
1156
|
-
`perfectPayloadAsync()` first performs transformations and normal synchronous validation. If synchronous validation fails, the result is returned immediately and asynchronous validators are not executed. This avoids unnecessary asynchronous work for payloads that are already invalid.
|
|
1034
|
+
`perfectPayload()` remains synchronous and intentionally rejects a `customValidator` that returns a Promise. This preserves the existing synchronous API contract. `perfectPayloadAsync()` first performs transformations and normal synchronous validation. If synchronous validation fails, the result is returned immediately and asynchronous validators are not executed. This avoids unnecessary asynchronous work for payloads that are already invalid.
|
|
1157
1035
|
|
|
1158
1036
|
```text
|
|
1159
|
-
|
|
1160
1037
|
transformations
|
|
1161
|
-
|
|
1162
1038
|
↓
|
|
1163
|
-
|
|
1164
1039
|
synchronous validation
|
|
1165
|
-
|
|
1166
1040
|
↓
|
|
1167
|
-
|
|
1168
1041
|
sync errors? ── yes ──→ return validation errors
|
|
1169
|
-
|
|
1170
1042
|
↓ no
|
|
1171
|
-
|
|
1172
1043
|
async customValidator
|
|
1173
|
-
|
|
1174
1044
|
↓
|
|
1175
|
-
|
|
1176
1045
|
return result
|
|
1177
1046
|
```
|
|
1178
1047
|
|
|
@@ -1184,25 +1053,17 @@ return result
|
|
|
1184
1053
|
import { perfectPayloadV1 } from "perfect-payload";
|
|
1185
1054
|
```
|
|
1186
1055
|
|
|
1187
|
-
`perfectPayloadV1()` is deprecated and will no longer be supported
|
|
1188
|
-
|
|
1189
|
-
after
|
|
1190
|
-
|
|
1191
|
-
March 31, 2027.
|
|
1192
|
-
|
|
1193
|
-
Existing applications can continue using it during the migration period,
|
|
1056
|
+
`perfectPayloadV1()` is deprecated and will no longer be supported after March 31, 2027. Existing applications can continue using it during the migration period,
|
|
1194
1057
|
|
|
1195
1058
|
but all new implementations should use the current API:
|
|
1196
1059
|
|
|
1197
1060
|
```js
|
|
1198
|
-
|
|
1199
1061
|
perfectPayload(data, validationRules, options?);
|
|
1200
1062
|
```
|
|
1201
1063
|
|
|
1202
1064
|
For asynchronous custom validation:
|
|
1203
1065
|
|
|
1204
1066
|
```js
|
|
1205
|
-
|
|
1206
1067
|
await perfectPayloadAsync(data, validationRules, options?);
|
|
1207
1068
|
```
|
|
1208
1069
|
|
|
@@ -1218,9 +1079,7 @@ while the new `perfectPayload()` API returns structured errors:
|
|
|
1218
1079
|
errors: [
|
|
1219
1080
|
{
|
|
1220
1081
|
path: "email",
|
|
1221
|
-
|
|
1222
1082
|
code: "INVALID_EMAIL",
|
|
1223
|
-
|
|
1224
1083
|
message: "Invalid email format for attribute email",
|
|
1225
1084
|
},
|
|
1226
1085
|
];
|
|
@@ -1234,9 +1093,7 @@ Note: If an inValidPayloadResponse is provided in the options, the system return
|
|
|
1234
1093
|
|
|
1235
1094
|
### `mandatory`
|
|
1236
1095
|
|
|
1237
|
-
Marks a field as required. An empty string is also treated as missing.
|
|
1238
|
-
|
|
1239
|
-
Default: `false`, the field is not required.
|
|
1096
|
+
Marks a field as required. An empty string is also treated as missing. Default: `false`, the field is not required.
|
|
1240
1097
|
|
|
1241
1098
|
```js
|
|
1242
1099
|
const rules = {
|
|
@@ -1246,15 +1103,11 @@ const rules = {
|
|
|
1246
1103
|
};
|
|
1247
1104
|
```
|
|
1248
1105
|
|
|
1249
|
-
Error code: `REQUIRED`
|
|
1250
|
-
|
|
1251
|
-
---
|
|
1106
|
+
## Error code: `REQUIRED`
|
|
1252
1107
|
|
|
1253
1108
|
### `allowNull`
|
|
1254
1109
|
|
|
1255
|
-
Controls whether `null` values are accepted.
|
|
1256
|
-
|
|
1257
|
-
Default: `true`, `null` values are allowed.
|
|
1110
|
+
Controls whether `null` values are accepted. Default: `true`, `null` values are allowed.
|
|
1258
1111
|
|
|
1259
1112
|
Example:
|
|
1260
1113
|
|
|
@@ -1266,15 +1119,11 @@ const rules = {
|
|
|
1266
1119
|
};
|
|
1267
1120
|
```
|
|
1268
1121
|
|
|
1269
|
-
Error code: `NULL_NOT_ALLOWED`
|
|
1270
|
-
|
|
1271
|
-
---
|
|
1122
|
+
## Error code: `NULL_NOT_ALLOWED`
|
|
1272
1123
|
|
|
1273
1124
|
### `allowEmptyObject`
|
|
1274
1125
|
|
|
1275
|
-
Controls whether an empty object `{}` is accepted.
|
|
1276
|
-
|
|
1277
|
-
Default: `true`, empty objects are allowed.
|
|
1126
|
+
Controls whether an empty object `{}` is accepted. Default: `true`, empty objects are allowed.
|
|
1278
1127
|
|
|
1279
1128
|
Example:
|
|
1280
1129
|
|
|
@@ -1282,21 +1131,16 @@ Example:
|
|
|
1282
1131
|
const rules = {
|
|
1283
1132
|
address: {
|
|
1284
1133
|
type: "object",
|
|
1285
|
-
|
|
1286
1134
|
allowEmptyObject: false,
|
|
1287
1135
|
},
|
|
1288
1136
|
};
|
|
1289
1137
|
```
|
|
1290
1138
|
|
|
1291
|
-
Error code: `EMPTY_OBJECT_NOT_ALLOWED`
|
|
1292
|
-
|
|
1293
|
-
---
|
|
1139
|
+
## Error code: `EMPTY_OBJECT_NOT_ALLOWED`
|
|
1294
1140
|
|
|
1295
1141
|
### `allowEmptyArray`
|
|
1296
1142
|
|
|
1297
|
-
Controls whether an empty array `[]` is accepted.
|
|
1298
|
-
|
|
1299
|
-
Default: `true`, empty arrays are allowed.
|
|
1143
|
+
Controls whether an empty array `[]` is accepted. Default: `true`, empty arrays are allowed.
|
|
1300
1144
|
|
|
1301
1145
|
Example:
|
|
1302
1146
|
|
|
@@ -1304,27 +1148,21 @@ Example:
|
|
|
1304
1148
|
const rules = {
|
|
1305
1149
|
products: {
|
|
1306
1150
|
type: "array",
|
|
1307
|
-
|
|
1308
1151
|
allowEmptyArray: false,
|
|
1309
1152
|
},
|
|
1310
1153
|
};
|
|
1311
1154
|
```
|
|
1312
1155
|
|
|
1313
|
-
Error code: `EMPTY_ARRAY_NOT_ALLOWED`
|
|
1314
|
-
|
|
1315
|
-
---
|
|
1156
|
+
## Error code: `EMPTY_ARRAY_NOT_ALLOWED`
|
|
1316
1157
|
|
|
1317
1158
|
### `minItems`
|
|
1318
1159
|
|
|
1319
|
-
Defines the minimum number of items required in an array.
|
|
1320
|
-
|
|
1321
|
-
Default: `Not applied when omitted.`
|
|
1160
|
+
Defines the minimum number of items required in an array. Default: `Not applied when omitted.`
|
|
1322
1161
|
|
|
1323
1162
|
```js
|
|
1324
1163
|
const rules = {
|
|
1325
1164
|
tags: {
|
|
1326
1165
|
type: "array",
|
|
1327
|
-
|
|
1328
1166
|
minItems: 2,
|
|
1329
1167
|
},
|
|
1330
1168
|
};
|
|
@@ -1333,37 +1171,23 @@ const rules = {
|
|
|
1333
1171
|
An array with fewer than 2 items returns `MIN_ITEMS`.
|
|
1334
1172
|
|
|
1335
1173
|
```js
|
|
1336
|
-
|
|
1337
1174
|
{
|
|
1338
|
-
|
|
1339
1175
|
path: "tags",
|
|
1340
|
-
|
|
1341
1176
|
code: "MIN_ITEMS",
|
|
1342
|
-
|
|
1343
1177
|
message: "Attribute tags must contain at least 2 item(s)"
|
|
1344
|
-
|
|
1345
1178
|
}
|
|
1346
1179
|
```
|
|
1347
1180
|
|
|
1348
|
-
`minItems` is enforced even when `allowEmptyArray: true` is set. For
|
|
1349
|
-
|
|
1350
|
-
example, `minItems: 2` still rejects `[]`.
|
|
1351
|
-
|
|
1352
|
-
Error code: `MIN_ITEMS`
|
|
1353
|
-
|
|
1354
|
-
---
|
|
1181
|
+
## `minItems` is enforced even when `allowEmptyArray: true` is set. For example, `minItems: 2` still rejects `[]`. Error code: `MIN_ITEMS`
|
|
1355
1182
|
|
|
1356
1183
|
### `maxItems`
|
|
1357
1184
|
|
|
1358
|
-
Defines the maximum number of items allowed in an array.
|
|
1359
|
-
|
|
1360
|
-
Default: `Not applied when omitted`.
|
|
1185
|
+
Defines the maximum number of items allowed in an array. Default: `Not applied when omitted`.
|
|
1361
1186
|
|
|
1362
1187
|
```js
|
|
1363
1188
|
const rules = {
|
|
1364
1189
|
tags: {
|
|
1365
1190
|
type: "array",
|
|
1366
|
-
|
|
1367
1191
|
maxItems: 5,
|
|
1368
1192
|
},
|
|
1369
1193
|
};
|
|
@@ -1372,23 +1196,14 @@ const rules = {
|
|
|
1372
1196
|
An array with more than 5 items returns `MAX_ITEMS`.
|
|
1373
1197
|
|
|
1374
1198
|
```js
|
|
1375
|
-
|
|
1376
1199
|
{
|
|
1377
|
-
|
|
1378
1200
|
path: "tags",
|
|
1379
|
-
|
|
1380
1201
|
code: "MAX_ITEMS",
|
|
1381
|
-
|
|
1382
1202
|
message: "Attribute tags must contain at most 5 item(s)"
|
|
1383
|
-
|
|
1384
1203
|
}
|
|
1385
1204
|
```
|
|
1386
1205
|
|
|
1387
|
-
`minItems` and `maxItems` can be used together.
|
|
1388
|
-
|
|
1389
|
-
Error code: `MAX_ITEMS`
|
|
1390
|
-
|
|
1391
|
-
---
|
|
1206
|
+
## `minItems` and `maxItems` can be used together. Error code: `MAX_ITEMS`
|
|
1392
1207
|
|
|
1393
1208
|
### `type`
|
|
1394
1209
|
|
|
@@ -1397,33 +1212,19 @@ Validates the expected data type.
|
|
|
1397
1212
|
Supported values:
|
|
1398
1213
|
|
|
1399
1214
|
```text
|
|
1400
|
-
|
|
1401
1215
|
number
|
|
1402
|
-
|
|
1403
1216
|
string
|
|
1404
|
-
|
|
1405
1217
|
boolean
|
|
1406
|
-
|
|
1407
1218
|
email
|
|
1408
|
-
|
|
1409
1219
|
url
|
|
1410
|
-
|
|
1411
1220
|
enum
|
|
1412
|
-
|
|
1413
1221
|
uuid
|
|
1414
|
-
|
|
1415
1222
|
uuidv1
|
|
1416
|
-
|
|
1417
1223
|
uuidv3
|
|
1418
|
-
|
|
1419
1224
|
uuidv4
|
|
1420
|
-
|
|
1421
1225
|
uuidv5
|
|
1422
|
-
|
|
1423
1226
|
objectId
|
|
1424
|
-
|
|
1425
1227
|
array
|
|
1426
|
-
|
|
1427
1228
|
object
|
|
1428
1229
|
```
|
|
1429
1230
|
|
|
@@ -1434,11 +1235,9 @@ const rules = {
|
|
|
1434
1235
|
age: {
|
|
1435
1236
|
type: "number",
|
|
1436
1237
|
},
|
|
1437
|
-
|
|
1438
1238
|
email: {
|
|
1439
1239
|
type: "email",
|
|
1440
1240
|
},
|
|
1441
|
-
|
|
1442
1241
|
active: {
|
|
1443
1242
|
type: "boolean",
|
|
1444
1243
|
},
|
|
@@ -1459,7 +1258,6 @@ Example:
|
|
|
1459
1258
|
const rules = {
|
|
1460
1259
|
status: {
|
|
1461
1260
|
type: "enum",
|
|
1462
|
-
|
|
1463
1261
|
enumValues: ["active", "inactive", "blocked", 1, 0],
|
|
1464
1262
|
},
|
|
1465
1263
|
};
|
|
@@ -1479,7 +1277,6 @@ Example:
|
|
|
1479
1277
|
const rules = {
|
|
1480
1278
|
status: {
|
|
1481
1279
|
type: "enum",
|
|
1482
|
-
|
|
1483
1280
|
enumValues: ["active", "inactive", "blocked"],
|
|
1484
1281
|
},
|
|
1485
1282
|
};
|
|
@@ -1490,37 +1287,23 @@ Error code: `INVALID_ENUM`
|
|
|
1490
1287
|
Possible error codes for types:
|
|
1491
1288
|
|
|
1492
1289
|
```text
|
|
1493
|
-
|
|
1494
1290
|
INVALID_TYPE
|
|
1495
|
-
|
|
1496
1291
|
INVALID_EMAIL
|
|
1497
|
-
|
|
1498
1292
|
INVALID_URL
|
|
1499
|
-
|
|
1500
1293
|
INVALID_ENUM
|
|
1501
|
-
|
|
1502
1294
|
INVALID_UUID
|
|
1503
|
-
|
|
1504
1295
|
INVALID_UUID_V1
|
|
1505
|
-
|
|
1506
1296
|
INVALID_UUID_V3
|
|
1507
|
-
|
|
1508
1297
|
INVALID_UUID_V4
|
|
1509
|
-
|
|
1510
1298
|
INVALID_UUID_V5
|
|
1511
|
-
|
|
1512
1299
|
INVALID_OBJECT_ID
|
|
1513
1300
|
```
|
|
1514
1301
|
|
|
1515
|
-
For `type: "number"`, `NaN` is rejected as `INVALID_TYPE`.
|
|
1516
|
-
|
|
1517
|
-
---
|
|
1302
|
+
## For `type: "number"`, `NaN` is rejected as `INVALID_TYPE`.
|
|
1518
1303
|
|
|
1519
1304
|
### `regex`
|
|
1520
1305
|
|
|
1521
|
-
Validates a value using a regular expression.
|
|
1522
|
-
|
|
1523
|
-
Default: `Not applied when omitted.`
|
|
1306
|
+
Validates a value using a regular expression. Default: `Not applied when omitted.`
|
|
1524
1307
|
|
|
1525
1308
|
Example:
|
|
1526
1309
|
|
|
@@ -1528,21 +1311,16 @@ Example:
|
|
|
1528
1311
|
const rules = {
|
|
1529
1312
|
employeeCode: {
|
|
1530
1313
|
type: "string",
|
|
1531
|
-
|
|
1532
1314
|
regex: /[^1]{3}[0-9]{3}$/,
|
|
1533
1315
|
},
|
|
1534
1316
|
};
|
|
1535
1317
|
```
|
|
1536
1318
|
|
|
1537
|
-
Error code: `REGEX_MISMATCH`
|
|
1538
|
-
|
|
1539
|
-
---
|
|
1319
|
+
## Error code: `REGEX_MISMATCH`
|
|
1540
1320
|
|
|
1541
1321
|
### `minLength`
|
|
1542
1322
|
|
|
1543
|
-
Defines the minimum allowed string length.
|
|
1544
|
-
|
|
1545
|
-
Default: `Not applied when omitted.`
|
|
1323
|
+
Defines the minimum allowed string length. Default: `Not applied when omitted.`
|
|
1546
1324
|
|
|
1547
1325
|
Example:
|
|
1548
1326
|
|
|
@@ -1550,21 +1328,16 @@ Example:
|
|
|
1550
1328
|
const rules = {
|
|
1551
1329
|
username: {
|
|
1552
1330
|
type: "string",
|
|
1553
|
-
|
|
1554
1331
|
minLength: 5,
|
|
1555
1332
|
},
|
|
1556
1333
|
};
|
|
1557
1334
|
```
|
|
1558
1335
|
|
|
1559
|
-
Error code: `MIN_LENGTH`
|
|
1560
|
-
|
|
1561
|
-
---
|
|
1336
|
+
## Error code: `MIN_LENGTH`
|
|
1562
1337
|
|
|
1563
1338
|
### `maxLength`
|
|
1564
1339
|
|
|
1565
|
-
Defines the maximum allowed string length.
|
|
1566
|
-
|
|
1567
|
-
Default: `Not applied when omitted.`
|
|
1340
|
+
Defines the maximum allowed string length. Default: `Not applied when omitted.`
|
|
1568
1341
|
|
|
1569
1342
|
Example:
|
|
1570
1343
|
|
|
@@ -1572,21 +1345,16 @@ Example:
|
|
|
1572
1345
|
const rules = {
|
|
1573
1346
|
username: {
|
|
1574
1347
|
type: "string",
|
|
1575
|
-
|
|
1576
1348
|
maxLength: 20,
|
|
1577
1349
|
},
|
|
1578
1350
|
};
|
|
1579
1351
|
```
|
|
1580
1352
|
|
|
1581
|
-
Error code: `MAX_LENGTH`
|
|
1582
|
-
|
|
1583
|
-
---
|
|
1353
|
+
## Error code: `MAX_LENGTH`
|
|
1584
1354
|
|
|
1585
1355
|
### `preventDecimal`
|
|
1586
1356
|
|
|
1587
|
-
Prevents decimal numbers.
|
|
1588
|
-
|
|
1589
|
-
Default: `false`; both integer and decimal numbers are allowed.
|
|
1357
|
+
Prevents decimal numbers. Default: `false`; both integer and decimal numbers are allowed.
|
|
1590
1358
|
|
|
1591
1359
|
Example:
|
|
1592
1360
|
|
|
@@ -1594,21 +1362,16 @@ Example:
|
|
|
1594
1362
|
const rules = {
|
|
1595
1363
|
quantity: {
|
|
1596
1364
|
type: "number",
|
|
1597
|
-
|
|
1598
1365
|
preventDecimal: true,
|
|
1599
1366
|
},
|
|
1600
1367
|
};
|
|
1601
1368
|
```
|
|
1602
1369
|
|
|
1603
|
-
Error code: `DECIMAL_NOT_ALLOWED`
|
|
1604
|
-
|
|
1605
|
-
---
|
|
1370
|
+
## Error code: `DECIMAL_NOT_ALLOWED`
|
|
1606
1371
|
|
|
1607
1372
|
### `min`
|
|
1608
1373
|
|
|
1609
|
-
Defines the minimum allowed numeric value.
|
|
1610
|
-
|
|
1611
|
-
Default: `Not applied when omitted.`
|
|
1374
|
+
Defines the minimum allowed numeric value. Default: `Not applied when omitted.`
|
|
1612
1375
|
|
|
1613
1376
|
Example:
|
|
1614
1377
|
|
|
@@ -1616,21 +1379,16 @@ Example:
|
|
|
1616
1379
|
const rules = {
|
|
1617
1380
|
age: {
|
|
1618
1381
|
type: "number",
|
|
1619
|
-
|
|
1620
1382
|
min: 18,
|
|
1621
1383
|
},
|
|
1622
1384
|
};
|
|
1623
1385
|
```
|
|
1624
1386
|
|
|
1625
|
-
Error code: `MIN_VALUE`
|
|
1626
|
-
|
|
1627
|
-
---
|
|
1387
|
+
## Error code: `MIN_VALUE`
|
|
1628
1388
|
|
|
1629
1389
|
### `max`
|
|
1630
1390
|
|
|
1631
|
-
Defines the maximum allowed numeric value.
|
|
1632
|
-
|
|
1633
|
-
Default: `Not applied when omitted.`
|
|
1391
|
+
Defines the maximum allowed numeric value. Default: `Not applied when omitted.`
|
|
1634
1392
|
|
|
1635
1393
|
Example:
|
|
1636
1394
|
|
|
@@ -1638,21 +1396,16 @@ Example:
|
|
|
1638
1396
|
const rules = {
|
|
1639
1397
|
quantity: {
|
|
1640
1398
|
type: "number",
|
|
1641
|
-
|
|
1642
1399
|
max: 100,
|
|
1643
1400
|
},
|
|
1644
1401
|
};
|
|
1645
1402
|
```
|
|
1646
1403
|
|
|
1647
|
-
Error code: `MAX_VALUE`
|
|
1648
|
-
|
|
1649
|
-
---
|
|
1404
|
+
## Error code: `MAX_VALUE`
|
|
1650
1405
|
|
|
1651
1406
|
### `range`
|
|
1652
1407
|
|
|
1653
|
-
Defines the allowed numeric range.
|
|
1654
|
-
|
|
1655
|
-
Default: `Not applied when omitted.`
|
|
1408
|
+
Defines the allowed numeric range. Default: `Not applied when omitted.`
|
|
1656
1409
|
|
|
1657
1410
|
Example:
|
|
1658
1411
|
|
|
@@ -1660,15 +1413,12 @@ Example:
|
|
|
1660
1413
|
const rules = {
|
|
1661
1414
|
marks: {
|
|
1662
1415
|
type: "number",
|
|
1663
|
-
|
|
1664
1416
|
range: "0-100",
|
|
1665
1417
|
},
|
|
1666
1418
|
};
|
|
1667
1419
|
```
|
|
1668
1420
|
|
|
1669
|
-
Error code: `OUT_OF_RANGE`
|
|
1670
|
-
|
|
1671
|
-
---
|
|
1421
|
+
## Error code: `OUT_OF_RANGE`
|
|
1672
1422
|
|
|
1673
1423
|
### `elementConstraints`
|
|
1674
1424
|
|
|
@@ -1680,10 +1430,8 @@ Example:
|
|
|
1680
1430
|
const rules = {
|
|
1681
1431
|
marks: {
|
|
1682
1432
|
type: "array",
|
|
1683
|
-
|
|
1684
1433
|
elementConstraints: {
|
|
1685
1434
|
type: "number",
|
|
1686
|
-
|
|
1687
1435
|
range: "0-100",
|
|
1688
1436
|
},
|
|
1689
1437
|
},
|
|
@@ -1693,23 +1441,36 @@ const rules = {
|
|
|
1693
1441
|
Example error:
|
|
1694
1442
|
|
|
1695
1443
|
```js
|
|
1696
|
-
|
|
1697
1444
|
{
|
|
1698
|
-
|
|
1699
1445
|
path: "marks[2]",
|
|
1700
|
-
|
|
1701
1446
|
code: "OUT_OF_RANGE",
|
|
1447
|
+
message: "Attribute marks[2] should have a value between 0 and 100"
|
|
1448
|
+
}
|
|
1449
|
+
```
|
|
1702
1450
|
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
"Attribute marks[2] should have a value between 0 and 100"
|
|
1451
|
+
Array-element transformations receive three arguments:
|
|
1706
1452
|
|
|
1707
|
-
|
|
1453
|
+
```js
|
|
1454
|
+
const rules = {
|
|
1455
|
+
tags: {
|
|
1456
|
+
type: "array",
|
|
1457
|
+
elementConstraints: {
|
|
1458
|
+
transform: (value, index, payload) => {
|
|
1459
|
+
return String(value).trim().toLowerCase();
|
|
1460
|
+
},
|
|
1461
|
+
type: "string",
|
|
1462
|
+
},
|
|
1463
|
+
},
|
|
1464
|
+
};
|
|
1708
1465
|
```
|
|
1709
1466
|
|
|
1710
|
-
|
|
1467
|
+
For an `elementConstraints` transform:
|
|
1468
|
+
|
|
1469
|
+
- `value` is the current array element.
|
|
1470
|
+
- `index` is the zero-based index of that element.
|
|
1471
|
+
- `payload` is the complete root payload/source object currently being validated.
|
|
1711
1472
|
|
|
1712
|
-
is
|
|
1473
|
+
The same root `payload` object is provided even when `elementConstraints` is nested inside another object or array. When `elementConstraintsError` is explicitly provided, the error code is `INVALID_ARRAY_ELEMENT`.
|
|
1713
1474
|
|
|
1714
1475
|
Example:
|
|
1715
1476
|
|
|
@@ -1717,18 +1478,14 @@ Example:
|
|
|
1717
1478
|
const rules = {
|
|
1718
1479
|
marks: {
|
|
1719
1480
|
type: "array",
|
|
1720
|
-
|
|
1721
1481
|
elementConstraints: {
|
|
1722
1482
|
type: "number",
|
|
1723
1483
|
},
|
|
1724
|
-
|
|
1725
1484
|
elementConstraintsError: "Every marks element must be a number",
|
|
1726
1485
|
},
|
|
1727
1486
|
};
|
|
1728
1487
|
```
|
|
1729
1488
|
|
|
1730
|
-
---
|
|
1731
|
-
|
|
1732
1489
|
### `objectAttr`
|
|
1733
1490
|
|
|
1734
1491
|
Validates fields inside a nested object.
|
|
@@ -1739,22 +1496,17 @@ Example:
|
|
|
1739
1496
|
const rules = {
|
|
1740
1497
|
address: {
|
|
1741
1498
|
type: "object",
|
|
1742
|
-
|
|
1743
1499
|
objectAttr: {
|
|
1744
1500
|
city: {
|
|
1745
1501
|
mandatory: true,
|
|
1746
|
-
|
|
1747
1502
|
type: "string",
|
|
1748
1503
|
},
|
|
1749
|
-
|
|
1750
1504
|
location: {
|
|
1751
1505
|
type: "object",
|
|
1752
|
-
|
|
1753
1506
|
objectAttr: {
|
|
1754
1507
|
latitude: {
|
|
1755
1508
|
type: "number",
|
|
1756
1509
|
},
|
|
1757
|
-
|
|
1758
1510
|
longitude: {
|
|
1759
1511
|
type: "number",
|
|
1760
1512
|
},
|
|
@@ -1768,17 +1520,11 @@ const rules = {
|
|
|
1768
1520
|
Nested errors include the complete field path:
|
|
1769
1521
|
|
|
1770
1522
|
```js
|
|
1771
|
-
|
|
1772
1523
|
{
|
|
1773
|
-
|
|
1774
1524
|
path: "address.location.latitude",
|
|
1775
|
-
|
|
1776
1525
|
code: "INVALID_TYPE",
|
|
1777
|
-
|
|
1778
1526
|
message:
|
|
1779
|
-
|
|
1780
1527
|
"Invalid type for attribute address.location.latitude, required number value"
|
|
1781
|
-
|
|
1782
1528
|
}
|
|
1783
1529
|
```
|
|
1784
1530
|
|
|
@@ -1794,14 +1540,11 @@ Example:
|
|
|
1794
1540
|
const rules = {
|
|
1795
1541
|
minSalary: {
|
|
1796
1542
|
type: "number",
|
|
1797
|
-
|
|
1798
1543
|
dependency: {
|
|
1799
1544
|
maxSalary: {
|
|
1800
1545
|
setDependencyRule: (minSalary, maxSalary) => ({
|
|
1801
1546
|
type: "number",
|
|
1802
|
-
|
|
1803
1547
|
min: minSalary + 1,
|
|
1804
|
-
|
|
1805
1548
|
minError: "maxSalary must be more than minSalary",
|
|
1806
1549
|
}),
|
|
1807
1550
|
},
|
|
@@ -1813,21 +1556,61 @@ const rules = {
|
|
|
1813
1556
|
Example error:
|
|
1814
1557
|
|
|
1815
1558
|
```js
|
|
1816
|
-
|
|
1817
1559
|
{
|
|
1818
|
-
|
|
1819
1560
|
path: "maxSalary",
|
|
1820
|
-
|
|
1821
1561
|
code: "MIN_VALUE",
|
|
1822
|
-
|
|
1823
1562
|
message:
|
|
1824
|
-
|
|
1825
1563
|
"maxSalary must be more than minSalary"
|
|
1826
|
-
|
|
1827
1564
|
}
|
|
1828
1565
|
```
|
|
1829
1566
|
|
|
1830
|
-
|
|
1567
|
+
## Validation Precedence
|
|
1568
|
+
|
|
1569
|
+
v2 uses a fixed validation order. The order in which properties are written inside a rule object does not change runtime behavior.
|
|
1570
|
+
|
|
1571
|
+
```text
|
|
1572
|
+
1. mandatory / presence
|
|
1573
|
+
2. transformations
|
|
1574
|
+
trim
|
|
1575
|
+
lowercase
|
|
1576
|
+
uppercase
|
|
1577
|
+
transform
|
|
1578
|
+
3. allowNull
|
|
1579
|
+
4. type
|
|
1580
|
+
5. empty-container checks
|
|
1581
|
+
allowEmptyObject
|
|
1582
|
+
allowEmptyArray
|
|
1583
|
+
6. structural rules
|
|
1584
|
+
objectAttr
|
|
1585
|
+
elementConstraints
|
|
1586
|
+
minItems
|
|
1587
|
+
maxItems
|
|
1588
|
+
7. value constraints
|
|
1589
|
+
regex
|
|
1590
|
+
minLength
|
|
1591
|
+
maxLength
|
|
1592
|
+
preventDecimal
|
|
1593
|
+
min
|
|
1594
|
+
max
|
|
1595
|
+
range
|
|
1596
|
+
8. dependency
|
|
1597
|
+
9. customValidator
|
|
1598
|
+
```
|
|
1599
|
+
|
|
1600
|
+
For a single field, once an earlier validation fails, later validations for that field are pruned.
|
|
1601
|
+
|
|
1602
|
+
Examples:
|
|
1603
|
+
|
|
1604
|
+
```text
|
|
1605
|
+
mandatory fails
|
|
1606
|
+
→ transform/type/customValidator do not run
|
|
1607
|
+
type fails
|
|
1608
|
+
→ objectAttr/elementConstraints/regex/customValidator do not run
|
|
1609
|
+
regex fails
|
|
1610
|
+
→ customValidator does not run
|
|
1611
|
+
```
|
|
1612
|
+
|
|
1613
|
+
This behavior applies to the modern core API and therefore also to the Express and Fastify adapters. `perfectPayloadV1()` keeps its legacy behavior. `perfectPayloadAsync()` follows the same synchronous precedence first. Async custom validators run only when the synchronous phase completes without validation errors.
|
|
1831
1614
|
|
|
1832
1615
|
## Array Size and Nested Validation
|
|
1833
1616
|
|
|
@@ -1841,17 +1624,12 @@ Use `minItems` and `maxItems` with `type: "array"`:
|
|
|
1841
1624
|
const rules = {
|
|
1842
1625
|
products: {
|
|
1843
1626
|
type: "array",
|
|
1844
|
-
|
|
1845
1627
|
minItems: 1,
|
|
1846
|
-
|
|
1847
1628
|
maxItems: 3,
|
|
1848
|
-
|
|
1849
1629
|
elementConstraints: {
|
|
1850
1630
|
type: "object",
|
|
1851
|
-
|
|
1852
1631
|
objectAttr: {
|
|
1853
1632
|
productId: { mandatory: true, type: "string" },
|
|
1854
|
-
|
|
1855
1633
|
quantity: { mandatory: true, type: "number", min: 1 },
|
|
1856
1634
|
},
|
|
1857
1635
|
},
|
|
@@ -1862,15 +1640,10 @@ const rules = {
|
|
|
1862
1640
|
If the array is empty, `minItems` reports the array path itself:
|
|
1863
1641
|
|
|
1864
1642
|
```js
|
|
1865
|
-
|
|
1866
1643
|
{
|
|
1867
|
-
|
|
1868
1644
|
path: "products",
|
|
1869
|
-
|
|
1870
1645
|
code: "MIN_ITEMS",
|
|
1871
|
-
|
|
1872
1646
|
message: "Attribute products must contain at least 1 item(s)"
|
|
1873
|
-
|
|
1874
1647
|
}
|
|
1875
1648
|
```
|
|
1876
1649
|
|
|
@@ -1879,7 +1652,6 @@ If the array is empty, `minItems` reports the array path itself:
|
|
|
1879
1652
|
`elementConstraints` can contain `objectAttr`, allowing every object in an array to use a nested schema. An invalid quantity in the second product is reported as:
|
|
1880
1653
|
|
|
1881
1654
|
```text
|
|
1882
|
-
|
|
1883
1655
|
products[1].quantity
|
|
1884
1656
|
```
|
|
1885
1657
|
|
|
@@ -1891,32 +1663,21 @@ products[1].quantity
|
|
|
1891
1663
|
const rules = {
|
|
1892
1664
|
orders: {
|
|
1893
1665
|
type: "array",
|
|
1894
|
-
|
|
1895
1666
|
minItems: 1,
|
|
1896
|
-
|
|
1897
1667
|
maxItems: 2,
|
|
1898
|
-
|
|
1899
1668
|
elementConstraints: {
|
|
1900
1669
|
type: "object",
|
|
1901
|
-
|
|
1902
1670
|
objectAttr: {
|
|
1903
1671
|
orderId: { mandatory: true, type: "string" },
|
|
1904
|
-
|
|
1905
1672
|
items: {
|
|
1906
1673
|
mandatory: true,
|
|
1907
|
-
|
|
1908
1674
|
type: "array",
|
|
1909
|
-
|
|
1910
1675
|
minItems: 1,
|
|
1911
|
-
|
|
1912
1676
|
maxItems: 2,
|
|
1913
|
-
|
|
1914
1677
|
elementConstraints: {
|
|
1915
1678
|
type: "object",
|
|
1916
|
-
|
|
1917
1679
|
objectAttr: {
|
|
1918
1680
|
productId: { mandatory: true, type: "string" },
|
|
1919
|
-
|
|
1920
1681
|
quantity: { mandatory: true, type: "number", min: 1 },
|
|
1921
1682
|
},
|
|
1922
1683
|
},
|
|
@@ -1932,23 +1693,19 @@ A deep validation failure preserves the complete indexed path, for
|
|
|
1932
1693
|
example:
|
|
1933
1694
|
|
|
1934
1695
|
```text
|
|
1935
|
-
|
|
1936
1696
|
orders[1].items[2].quantity
|
|
1937
1697
|
```
|
|
1938
1698
|
|
|
1939
1699
|
Array constraints work at nested levels too. A nested array can report paths such as:
|
|
1940
1700
|
|
|
1941
1701
|
```text
|
|
1942
|
-
|
|
1943
1702
|
orders[1].items
|
|
1944
1703
|
```
|
|
1945
1704
|
|
|
1946
1705
|
Nested arrays are supported and every array index is preserved:
|
|
1947
1706
|
|
|
1948
1707
|
```text
|
|
1949
|
-
|
|
1950
1708
|
matrix[1][1]
|
|
1951
|
-
|
|
1952
1709
|
matrix[1][1][1]
|
|
1953
1710
|
```
|
|
1954
1711
|
|
|
@@ -1956,7 +1713,7 @@ Transformations applied inside nested objects or array elements are preserved in
|
|
|
1956
1713
|
|
|
1957
1714
|
### Transformations and Sanitization
|
|
1958
1715
|
|
|
1959
|
-
`perfectPayload()` can transform a field before its validation rules run. The transformed value is returned in `validatedPayload`, while the original input object is not mutated.
|
|
1716
|
+
`perfectPayload()` can transform a field before its remaining validation rules run. The transformed value is returned in `validatedPayload`, while the original input object is not mutated.
|
|
1960
1717
|
|
|
1961
1718
|
Supported transformation rules:
|
|
1962
1719
|
|
|
@@ -1967,66 +1724,58 @@ Supported transformation rules:
|
|
|
1967
1724
|
| `uppercase` | Converts strings to uppercase. |
|
|
1968
1725
|
| `transform` | Runs a custom synchronous transformation function. |
|
|
1969
1726
|
|
|
1970
|
-
|
|
1727
|
+
The v2 validation flow is deterministic and does not depend on the order in which rule properties are written:
|
|
1971
1728
|
|
|
1972
1729
|
```text
|
|
1973
|
-
|
|
1730
|
+
mandatory / presence
|
|
1731
|
+
↓
|
|
1974
1732
|
trim
|
|
1975
|
-
|
|
1976
|
-
↓
|
|
1977
|
-
|
|
1733
|
+
↓
|
|
1978
1734
|
lowercase
|
|
1979
|
-
|
|
1980
|
-
↓
|
|
1981
|
-
|
|
1735
|
+
↓
|
|
1982
1736
|
uppercase
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1737
|
+
↓
|
|
1738
|
+
transform
|
|
1739
|
+
↓
|
|
1740
|
+
allowNull
|
|
1741
|
+
↓
|
|
1742
|
+
type
|
|
1743
|
+
↓
|
|
1744
|
+
empty-container checks
|
|
1745
|
+
↓
|
|
1746
|
+
structural validation
|
|
1747
|
+
↓
|
|
1748
|
+
value constraints
|
|
1749
|
+
↓
|
|
1750
|
+
dependency
|
|
1751
|
+
↓
|
|
1994
1752
|
customValidator
|
|
1995
|
-
|
|
1996
|
-
↓
|
|
1997
|
-
|
|
1753
|
+
↓
|
|
1998
1754
|
validatedPayload
|
|
1999
1755
|
```
|
|
2000
1756
|
|
|
1757
|
+
If `mandatory` fails, transformations and all later rules for that field are skipped.
|
|
1758
|
+
|
|
2001
1759
|
#### `trim`
|
|
2002
1760
|
|
|
2003
1761
|
```js
|
|
2004
1762
|
const payload = {
|
|
2005
1763
|
name: " Kiran Poojary ",
|
|
2006
1764
|
};
|
|
2007
|
-
|
|
2008
1765
|
const rules = {
|
|
2009
1766
|
name: {
|
|
2010
1767
|
type: "string",
|
|
2011
|
-
|
|
2012
1768
|
trim: true,
|
|
2013
1769
|
},
|
|
2014
1770
|
};
|
|
2015
|
-
|
|
2016
1771
|
const result = perfectPayload(payload, rules);
|
|
2017
|
-
|
|
2018
1772
|
console.log(result.validatedPayload.name);
|
|
2019
|
-
|
|
2020
1773
|
// "Kiran Poojary"
|
|
2021
|
-
|
|
2022
1774
|
console.log(payload.name);
|
|
2023
|
-
|
|
2024
1775
|
// " Kiran Poojary "
|
|
2025
1776
|
```
|
|
2026
1777
|
|
|
2027
|
-
`trim` applies only to string values. Non-string values are left
|
|
2028
|
-
|
|
2029
|
-
unchanged.
|
|
1778
|
+
`trim` applies only to string values. Non-string values are left unchanged.
|
|
2030
1779
|
|
|
2031
1780
|
#### `lowercase`
|
|
2032
1781
|
|
|
@@ -2034,9 +1783,7 @@ unchanged.
|
|
|
2034
1783
|
const rules = {
|
|
2035
1784
|
email: {
|
|
2036
1785
|
trim: true,
|
|
2037
|
-
|
|
2038
1786
|
lowercase: true,
|
|
2039
|
-
|
|
2040
1787
|
type: "email",
|
|
2041
1788
|
},
|
|
2042
1789
|
};
|
|
@@ -2050,7 +1797,6 @@ For `" KIRAN@EXAMPLE.COM "`, the validated value becomes `"kiran@example.com"`
|
|
|
2050
1797
|
const rules = {
|
|
2051
1798
|
countryCode: {
|
|
2052
1799
|
type: "string",
|
|
2053
|
-
|
|
2054
1800
|
uppercase: true,
|
|
2055
1801
|
},
|
|
2056
1802
|
};
|
|
@@ -2066,15 +1812,14 @@ Use `transform` when the built-in string transformations are not enough.
|
|
|
2066
1812
|
const rules = {
|
|
2067
1813
|
phone: {
|
|
2068
1814
|
type: "string",
|
|
2069
|
-
|
|
2070
|
-
transform: (value) => value.replace(/`\s`{=tex}+/g, ""),
|
|
1815
|
+
transform: (value) => String(value).replace(/\s+/g, ""),
|
|
2071
1816
|
},
|
|
2072
1817
|
};
|
|
2073
1818
|
```
|
|
2074
1819
|
|
|
2075
1820
|
For `"98765 43210"`, the validated value becomes `"9876543210"`.
|
|
2076
1821
|
|
|
2077
|
-
|
|
1822
|
+
A normal transformer receives two arguments:
|
|
2078
1823
|
|
|
2079
1824
|
```js
|
|
2080
1825
|
transform: (value, payload) => {
|
|
@@ -2083,35 +1828,38 @@ transform: (value, payload) => {
|
|
|
2083
1828
|
```
|
|
2084
1829
|
|
|
2085
1830
|
- `value` is the field value after the built-in transformations have run.
|
|
1831
|
+
- `payload` is the **complete root payload/source object currently being validated**.
|
|
2086
1832
|
|
|
2087
|
-
|
|
1833
|
+
For direct core usage, `payload` is the complete `data` object passed to `perfectPayload()` or `perfectPayloadAsync()`. For framework adapters, the transform receives the complete request source being validated:
|
|
1834
|
+
|
|
1835
|
+
```text
|
|
1836
|
+
body rule → request.body
|
|
1837
|
+
headers rule → request.headers
|
|
1838
|
+
params rule → request.params
|
|
1839
|
+
query rule → request.query
|
|
1840
|
+
```
|
|
2088
1841
|
|
|
2089
|
-
This
|
|
1842
|
+
This root-payload contract is preserved inside nested `objectAttr` and `elementConstraints` validation.
|
|
1843
|
+
|
|
1844
|
+
Example cross-field transformation:
|
|
2090
1845
|
|
|
2091
1846
|
```js
|
|
2092
1847
|
const payload = {
|
|
2093
1848
|
amount: 100,
|
|
2094
|
-
|
|
2095
1849
|
multiplier: 2,
|
|
2096
1850
|
};
|
|
2097
|
-
|
|
2098
1851
|
const rules = {
|
|
2099
1852
|
amount: {
|
|
2100
1853
|
transform: (value, payload) => value * payload.multiplier,
|
|
2101
|
-
|
|
2102
1854
|
type: "number",
|
|
2103
1855
|
},
|
|
2104
|
-
|
|
2105
1856
|
multiplier: {
|
|
2106
1857
|
type: "number",
|
|
2107
1858
|
},
|
|
2108
1859
|
};
|
|
2109
|
-
|
|
2110
1860
|
const result = perfectPayload(payload, rules);
|
|
2111
|
-
|
|
2112
1861
|
console.log(result.validatedPayload.amount);
|
|
2113
|
-
|
|
2114
|
-
// 200***
|
|
1862
|
+
// 200
|
|
2115
1863
|
```
|
|
2116
1864
|
|
|
2117
1865
|
A custom transformer may also change the data type before validation:
|
|
@@ -2120,61 +1868,30 @@ A custom transformer may also change the data type before validation:
|
|
|
2120
1868
|
const rules = {
|
|
2121
1869
|
quantity: {
|
|
2122
1870
|
transform: (value) => Number(value),
|
|
2123
|
-
|
|
2124
1871
|
type: "number",
|
|
2125
|
-
|
|
2126
1872
|
min: 1,
|
|
2127
|
-
|
|
2128
1873
|
max: 100,
|
|
2129
1874
|
},
|
|
2130
1875
|
};
|
|
2131
1876
|
```
|
|
2132
1877
|
|
|
2133
|
-
|
|
1878
|
+
For `elementConstraints`, the callback additionally receives the zero-based array index:
|
|
2134
1879
|
|
|
2135
1880
|
```js
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
objectAttr: {
|
|
2141
|
-
name: {
|
|
2142
|
-
trim: true,
|
|
2143
|
-
|
|
2144
|
-
uppercase: true,
|
|
2145
|
-
|
|
2146
|
-
type: "string",
|
|
2147
|
-
},
|
|
2148
|
-
},
|
|
2149
|
-
},
|
|
2150
|
-
|
|
2151
|
-
tags: {
|
|
2152
|
-
type: "array",
|
|
2153
|
-
|
|
2154
|
-
elementConstraints: {
|
|
2155
|
-
trim: true,
|
|
2156
|
-
|
|
2157
|
-
lowercase: true,
|
|
2158
|
-
|
|
2159
|
-
type: "string",
|
|
2160
|
-
},
|
|
1881
|
+
elementConstraints: {
|
|
1882
|
+
transform: (value, index, payload) => {
|
|
1883
|
+
return String(value).trim();
|
|
2161
1884
|
},
|
|
2162
|
-
|
|
1885
|
+
type: "string",
|
|
1886
|
+
}
|
|
2163
1887
|
```
|
|
2164
1888
|
|
|
2165
|
-
Missing optional fields are not transformed. An input value of `null` is not passed to transformation functions; null handling remains controlled by `allowNull`.
|
|
2166
|
-
|
|
2167
|
-
**Important:** `transform` is synchronous. A non-function transformer, an `async` transformer, a transformer that returns a Promise, or a transformer that returns `undefined` is not supported and throws an error.
|
|
2168
|
-
|
|
2169
|
-
Returning `null`, `""`, `0`, or `false` is allowed; the transformed value is then processed by the normal validation rules. Exceptions thrown inside the transformer propagate to the caller.
|
|
1889
|
+
The transformed value is validated by the normal validation rules and is also the value received by `customValidator`. Transformed nested/array values are preserved in `validatedPayload`. Missing optional fields are not transformed. An input value of `null` is not passed to transformation functions; null handling remains controlled by `allowNull`. **Important:** `transform` is synchronous. A non-function transformer, an `async` transformer, a transformer that returns a Promise, or a transformer that returns `undefined` is not supported and throws an error. Returning `null`, `""`, `0`, or `false` is allowed; the transformed value is then processed by the remaining validation rules. Exceptions thrown inside the transformer propagate to the caller.
|
|
2170
1890
|
|
|
2171
1891
|
For example, returning `undefined` throws:
|
|
2172
1892
|
|
|
2173
1893
|
```text
|
|
2174
|
-
|
|
2175
|
-
perfect-payload:- transform must not return undefined for attribute
|
|
2176
|
-
|
|
2177
|
-
username
|
|
1894
|
+
perfect-payload:- transform must not return undefined for attribute username
|
|
2178
1895
|
```
|
|
2179
1896
|
|
|
2180
1897
|
### `customValidator`
|
|
@@ -2190,13 +1907,9 @@ customValidator: (value, payload) => {
|
|
|
2190
1907
|
```
|
|
2191
1908
|
|
|
2192
1909
|
- `value` is the field value after transformations have been applied.
|
|
2193
|
-
|
|
2194
1910
|
- `payload` is the current payload/object being validated.
|
|
2195
|
-
|
|
2196
1911
|
- Return `true` to pass.
|
|
2197
|
-
|
|
2198
1912
|
- Any value other than `true` fails validation.
|
|
2199
|
-
|
|
2200
1913
|
- Exceptions thrown by the validator propagate to the caller.
|
|
2201
1914
|
|
|
2202
1915
|
For nested validation, `payload` means the current nested object rather than the root request body.
|
|
@@ -2209,50 +1922,32 @@ Use a synchronous validator with `perfectPayload()`:
|
|
|
2209
1922
|
const rules = {
|
|
2210
1923
|
username: {
|
|
2211
1924
|
mandatory: true,
|
|
2212
|
-
|
|
2213
1925
|
type: "string",
|
|
2214
|
-
|
|
2215
1926
|
trim: true,
|
|
2216
|
-
|
|
2217
1927
|
customValidator: (value) => {
|
|
2218
1928
|
return !value.toLowerCase().includes("admin");
|
|
2219
1929
|
},
|
|
2220
|
-
|
|
2221
1930
|
customValidatorCode: "RESERVED_USERNAME",
|
|
2222
|
-
|
|
2223
1931
|
customValidatorError: "Username cannot contain admin",
|
|
2224
1932
|
},
|
|
2225
1933
|
};
|
|
2226
|
-
|
|
2227
1934
|
const result = perfectPayload({ username: " admin_kiran " }, rules);
|
|
2228
1935
|
```
|
|
2229
1936
|
|
|
2230
1937
|
A failure returns:
|
|
2231
1938
|
|
|
2232
1939
|
```js
|
|
2233
|
-
|
|
2234
1940
|
{
|
|
2235
|
-
|
|
2236
1941
|
statusCode: 400,
|
|
2237
|
-
|
|
2238
1942
|
valid: false,
|
|
2239
|
-
|
|
2240
1943
|
message: "One or more attribute values are invalid",
|
|
2241
|
-
|
|
2242
1944
|
errors: [
|
|
2243
|
-
|
|
2244
1945
|
{
|
|
2245
|
-
|
|
2246
1946
|
path: "username",
|
|
2247
|
-
|
|
2248
1947
|
code: "RESERVED_USERNAME",
|
|
2249
|
-
|
|
2250
1948
|
message: "Username cannot contain admin"
|
|
2251
|
-
|
|
2252
1949
|
}
|
|
2253
|
-
|
|
2254
1950
|
]
|
|
2255
|
-
|
|
2256
1951
|
}
|
|
2257
1952
|
```
|
|
2258
1953
|
|
|
@@ -2263,16 +1958,12 @@ const rules = {
|
|
|
2263
1958
|
limit: {
|
|
2264
1959
|
type: "number",
|
|
2265
1960
|
},
|
|
2266
|
-
|
|
2267
1961
|
amount: {
|
|
2268
1962
|
type: "number",
|
|
2269
|
-
|
|
2270
1963
|
customValidator: (value, payload) => {
|
|
2271
1964
|
return value <= payload.limit;
|
|
2272
1965
|
},
|
|
2273
|
-
|
|
2274
1966
|
customValidatorCode: "LIMIT_EXCEEDED",
|
|
2275
|
-
|
|
2276
1967
|
customValidatorError: "Amount cannot exceed limit",
|
|
2277
1968
|
},
|
|
2278
1969
|
};
|
|
@@ -2283,26 +1974,17 @@ If `customValidatorCode` and `customValidatorError` are omitted, the
|
|
|
2283
1974
|
default error is:
|
|
2284
1975
|
|
|
2285
1976
|
```js
|
|
2286
|
-
|
|
2287
1977
|
{
|
|
2288
|
-
|
|
2289
1978
|
path: "username",
|
|
2290
|
-
|
|
2291
1979
|
code: "CUSTOM_VALIDATION_FAILED",
|
|
2292
|
-
|
|
2293
1980
|
message: "Custom validation failed for attribute username"
|
|
2294
|
-
|
|
2295
1981
|
}
|
|
2296
1982
|
```
|
|
2297
1983
|
|
|
2298
|
-
`customValidator` works recursively inside `objectAttr` and `elementConstraints`. Structured errors preserve the corresponding nested and array paths.
|
|
2299
|
-
|
|
2300
|
-
When using `perfectPayload()`, `customValidator` must remain synchronous. A Promise-returning validator throws:
|
|
1984
|
+
`customValidator` works recursively inside `objectAttr` and `elementConstraints`. Structured errors preserve the corresponding nested and array paths. When using `perfectPayload()`, `customValidator` must remain synchronous. A Promise-returning validator throws:
|
|
2301
1985
|
|
|
2302
1986
|
```text
|
|
2303
|
-
|
|
2304
1987
|
perfect-payload:- customValidator must be synchronous for attribute
|
|
2305
|
-
|
|
2306
1988
|
username
|
|
2307
1989
|
```
|
|
2308
1990
|
|
|
@@ -2310,38 +1992,27 @@ For asynchronous custom validation, use `perfectPayloadAsync()`.
|
|
|
2310
1992
|
|
|
2311
1993
|
## Asynchronous Validation
|
|
2312
1994
|
|
|
2313
|
-
`perfectPayloadAsync()` supports both synchronous and asynchronous `customValidator` functions without changing the behavior of
|
|
2314
|
-
|
|
2315
|
-
`perfectPayload()`.
|
|
1995
|
+
`perfectPayloadAsync()` supports both synchronous and asynchronous `customValidator` functions without changing the behavior of `perfectPayload()`.
|
|
2316
1996
|
|
|
2317
1997
|
```js
|
|
2318
1998
|
import { perfectPayloadAsync } from "perfect-payload";
|
|
2319
|
-
|
|
2320
1999
|
const rules = {
|
|
2321
2000
|
username: {
|
|
2322
2001
|
mandatory: true,
|
|
2323
|
-
|
|
2324
2002
|
type: "string",
|
|
2325
|
-
|
|
2326
2003
|
trim: true,
|
|
2327
|
-
|
|
2328
2004
|
customValidator: async (value) => {
|
|
2329
2005
|
const available = await checkUsernameAvailability(value);
|
|
2330
|
-
|
|
2331
2006
|
return available;
|
|
2332
2007
|
},
|
|
2333
|
-
|
|
2334
2008
|
customValidatorCode: "USERNAME_TAKEN",
|
|
2335
|
-
|
|
2336
2009
|
customValidatorError: "Username is already taken",
|
|
2337
2010
|
},
|
|
2338
2011
|
};
|
|
2339
|
-
|
|
2340
2012
|
const result = await perfectPayloadAsync(
|
|
2341
2013
|
{
|
|
2342
2014
|
username: " kiran ",
|
|
2343
2015
|
},
|
|
2344
|
-
|
|
2345
2016
|
rules,
|
|
2346
2017
|
);
|
|
2347
2018
|
```
|
|
@@ -2349,48 +2020,29 @@ const result = await perfectPayloadAsync(
|
|
|
2349
2020
|
On success, transformations are preserved:
|
|
2350
2021
|
|
|
2351
2022
|
```js
|
|
2352
|
-
|
|
2353
2023
|
{
|
|
2354
|
-
|
|
2355
2024
|
statusCode: 200,
|
|
2356
|
-
|
|
2357
2025
|
valid: true,
|
|
2358
|
-
|
|
2359
2026
|
validatedPayload: {
|
|
2360
|
-
|
|
2361
2027
|
username: "kiran"
|
|
2362
|
-
|
|
2363
2028
|
}
|
|
2364
|
-
|
|
2365
2029
|
}
|
|
2366
2030
|
```
|
|
2367
2031
|
|
|
2368
2032
|
On asynchronous validation failure:
|
|
2369
2033
|
|
|
2370
2034
|
```js
|
|
2371
|
-
|
|
2372
2035
|
{
|
|
2373
|
-
|
|
2374
2036
|
statusCode: 400,
|
|
2375
|
-
|
|
2376
2037
|
valid: false,
|
|
2377
|
-
|
|
2378
2038
|
message: "One or more attribute values are invalid",
|
|
2379
|
-
|
|
2380
2039
|
errors: [
|
|
2381
|
-
|
|
2382
2040
|
{
|
|
2383
|
-
|
|
2384
2041
|
path: "username",
|
|
2385
|
-
|
|
2386
2042
|
code: "USERNAME_TAKEN",
|
|
2387
|
-
|
|
2388
2043
|
message: "Username is already taken"
|
|
2389
|
-
|
|
2390
2044
|
}
|
|
2391
|
-
|
|
2392
2045
|
]
|
|
2393
|
-
|
|
2394
2046
|
}
|
|
2395
2047
|
```
|
|
2396
2048
|
|
|
@@ -2399,15 +2051,10 @@ On asynchronous validation failure:
|
|
|
2399
2051
|
For `perfectPayloadAsync()`:
|
|
2400
2052
|
|
|
2401
2053
|
```text
|
|
2402
|
-
|
|
2403
2054
|
true → pass
|
|
2404
|
-
|
|
2405
2055
|
false → validation failure
|
|
2406
|
-
|
|
2407
2056
|
anything != true → validation failure
|
|
2408
|
-
|
|
2409
2057
|
throw → exception propagates
|
|
2410
|
-
|
|
2411
2058
|
rejected Promise → rejection propagates
|
|
2412
2059
|
```
|
|
2413
2060
|
|
|
@@ -2419,22 +2066,17 @@ API:
|
|
|
2419
2066
|
const rules = {
|
|
2420
2067
|
username: {
|
|
2421
2068
|
type: "string",
|
|
2422
|
-
|
|
2423
2069
|
customValidator: (value) => value !== "admin",
|
|
2424
2070
|
},
|
|
2425
2071
|
};
|
|
2426
|
-
|
|
2427
2072
|
const result = await perfectPayloadAsync(payload, rules);
|
|
2428
2073
|
```
|
|
2429
2074
|
|
|
2430
|
-
A configured `customValidator` must be a function. Otherwise an error
|
|
2431
|
-
|
|
2432
|
-
is
|
|
2075
|
+
A configured `customValidator` must be a function. Otherwise an error is
|
|
2433
2076
|
|
|
2434
2077
|
thrown:
|
|
2435
2078
|
|
|
2436
2079
|
```text
|
|
2437
|
-
|
|
2438
2080
|
perfect-payload:- customValidator must be a function for attribute username
|
|
2439
2081
|
```
|
|
2440
2082
|
|
|
@@ -2442,11 +2084,10 @@ perfect-payload:- customValidator must be a function for attribute username
|
|
|
2442
2084
|
|
|
2443
2085
|
`perfectPayloadAsync()` uses two phases:
|
|
2444
2086
|
|
|
2445
|
-
1.
|
|
2446
|
-
|
|
2087
|
+
1. Run presence checks, transformations, and the normal deterministic synchronous validation pipeline.
|
|
2447
2088
|
2. If phase 1 succeeds, run custom validators with `await`.
|
|
2448
2089
|
|
|
2449
|
-
If any synchronous validation error exists, phase 2 is skipped and the synchronous validation result is returned immediately. This means
|
|
2090
|
+
If any synchronous validation error exists, phase 2 is skipped and the synchronous validation result is returned immediately. This means an async `customValidator` is pruned when an earlier rule such as `type`, `regex`, or another synchronous constraint has already failed.
|
|
2450
2091
|
|
|
2451
2092
|
### Nested async validation
|
|
2452
2093
|
|
|
@@ -2456,19 +2097,14 @@ Async custom validators work recursively inside `objectAttr`:
|
|
|
2456
2097
|
const rules = {
|
|
2457
2098
|
profile: {
|
|
2458
2099
|
type: "object",
|
|
2459
|
-
|
|
2460
2100
|
objectAttr: {
|
|
2461
2101
|
username: {
|
|
2462
2102
|
type: "string",
|
|
2463
|
-
|
|
2464
2103
|
trim: true,
|
|
2465
|
-
|
|
2466
2104
|
customValidator: async (value) => {
|
|
2467
2105
|
return await isUsernameAvailable(value);
|
|
2468
2106
|
},
|
|
2469
|
-
|
|
2470
2107
|
customValidatorCode: "USERNAME_TAKEN",
|
|
2471
|
-
|
|
2472
2108
|
customValidatorError: "Username is already taken",
|
|
2473
2109
|
},
|
|
2474
2110
|
},
|
|
@@ -2479,7 +2115,6 @@ const rules = {
|
|
|
2479
2115
|
A failure produces the complete path:
|
|
2480
2116
|
|
|
2481
2117
|
```text
|
|
2482
|
-
|
|
2483
2118
|
profile.username
|
|
2484
2119
|
```
|
|
2485
2120
|
|
|
@@ -2489,18 +2124,13 @@ They also work inside `elementConstraints`:
|
|
|
2489
2124
|
const rules = {
|
|
2490
2125
|
usernames: {
|
|
2491
2126
|
type: "array",
|
|
2492
|
-
|
|
2493
2127
|
elementConstraints: {
|
|
2494
2128
|
type: "string",
|
|
2495
|
-
|
|
2496
2129
|
trim: true,
|
|
2497
|
-
|
|
2498
2130
|
customValidator: async (value) => {
|
|
2499
2131
|
return await isUsernameAvailable(value);
|
|
2500
2132
|
},
|
|
2501
|
-
|
|
2502
2133
|
customValidatorCode: "USERNAME_TAKEN",
|
|
2503
|
-
|
|
2504
2134
|
customValidatorError: "Username is already taken",
|
|
2505
2135
|
},
|
|
2506
2136
|
},
|
|
@@ -2510,7 +2140,6 @@ const rules = {
|
|
|
2510
2140
|
For an invalid second element:
|
|
2511
2141
|
|
|
2512
2142
|
```text
|
|
2513
|
-
|
|
2514
2143
|
usernames[1]
|
|
2515
2144
|
```
|
|
2516
2145
|
|
|
@@ -2519,9 +2148,7 @@ Deep combinations of objects and arrays preserve every level of the
|
|
|
2519
2148
|
path:
|
|
2520
2149
|
|
|
2521
2150
|
```text
|
|
2522
|
-
|
|
2523
2151
|
products[1].seller.username
|
|
2524
|
-
|
|
2525
2152
|
profile.teams[1].members[1].username
|
|
2526
2153
|
```
|
|
2527
2154
|
|
|
@@ -2530,23 +2157,16 @@ Default async custom-validation messages also use the final indexed
|
|
|
2530
2157
|
path:
|
|
2531
2158
|
|
|
2532
2159
|
```js
|
|
2533
|
-
|
|
2534
2160
|
{
|
|
2535
|
-
|
|
2536
2161
|
path: "users[1].username",
|
|
2537
|
-
|
|
2538
2162
|
code: "CUSTOM_VALIDATION_FAILED",
|
|
2539
|
-
|
|
2540
2163
|
message: "Custom validation failed for attribute users[1].username"
|
|
2541
|
-
|
|
2542
2164
|
}
|
|
2543
2165
|
```
|
|
2544
2166
|
|
|
2545
2167
|
### Transform remains synchronous
|
|
2546
2168
|
|
|
2547
|
-
`perfectPayloadAsync()` makes custom validation asynchronous; it does
|
|
2548
|
-
|
|
2549
|
-
not make `transform` asynchronous.
|
|
2169
|
+
`perfectPayloadAsync()` makes custom validation asynchronous; it does not make `transform` asynchronous.
|
|
2550
2170
|
|
|
2551
2171
|
`transform` must still be synchronous:
|
|
2552
2172
|
|
|
@@ -2556,9 +2176,7 @@ transform: (value, payload) => {
|
|
|
2556
2176
|
};
|
|
2557
2177
|
```
|
|
2558
2178
|
|
|
2559
|
-
An async transformer or a transformer that returns a Promise is not
|
|
2560
|
-
|
|
2561
|
-
supported.
|
|
2179
|
+
An async transformer or a transformer that returns a Promise is not supported.
|
|
2562
2180
|
|
|
2563
2181
|
## Error Codes
|
|
2564
2182
|
|
|
@@ -2567,57 +2185,31 @@ supported.
|
|
|
2567
2185
|
validation error codes:
|
|
2568
2186
|
|
|
2569
2187
|
```text
|
|
2570
|
-
|
|
2571
2188
|
REQUIRED
|
|
2572
|
-
|
|
2573
2189
|
NULL_NOT_ALLOWED
|
|
2574
|
-
|
|
2575
2190
|
EMPTY_OBJECT_NOT_ALLOWED
|
|
2576
|
-
|
|
2577
2191
|
EMPTY_ARRAY_NOT_ALLOWED
|
|
2578
|
-
|
|
2579
2192
|
MIN_ITEMS
|
|
2580
|
-
|
|
2581
2193
|
MAX_ITEMS
|
|
2582
|
-
|
|
2583
2194
|
INVALID_ARRAY_ELEMENT
|
|
2584
|
-
|
|
2585
2195
|
REGEX_MISMATCH
|
|
2586
|
-
|
|
2587
2196
|
INVALID_TYPE
|
|
2588
|
-
|
|
2589
2197
|
INVALID_EMAIL
|
|
2590
|
-
|
|
2591
2198
|
INVALID_URL
|
|
2592
|
-
|
|
2593
2199
|
INVALID_ENUM
|
|
2594
|
-
|
|
2595
2200
|
INVALID_UUID
|
|
2596
|
-
|
|
2597
2201
|
INVALID_UUID_V1
|
|
2598
|
-
|
|
2599
2202
|
INVALID_UUID_V3
|
|
2600
|
-
|
|
2601
2203
|
INVALID_UUID_V4
|
|
2602
|
-
|
|
2603
2204
|
INVALID_UUID_V5
|
|
2604
|
-
|
|
2605
2205
|
INVALID_OBJECT_ID
|
|
2606
|
-
|
|
2607
2206
|
MIN_LENGTH
|
|
2608
|
-
|
|
2609
2207
|
MAX_LENGTH
|
|
2610
|
-
|
|
2611
2208
|
DECIMAL_NOT_ALLOWED
|
|
2612
|
-
|
|
2613
2209
|
MIN_VALUE
|
|
2614
|
-
|
|
2615
2210
|
MAX_VALUE
|
|
2616
|
-
|
|
2617
2211
|
OUT_OF_RANGE
|
|
2618
|
-
|
|
2619
2212
|
CUSTOM_VALIDATION_FAILED
|
|
2620
|
-
|
|
2621
2213
|
UNKNOWN_FIELD
|
|
2622
2214
|
```
|
|
2623
2215
|
|
|
@@ -2626,23 +2218,14 @@ These codes are designed for programmatic handling while `message` remains suita
|
|
|
2626
2218
|
For example:
|
|
2627
2219
|
|
|
2628
2220
|
```js
|
|
2629
|
-
|
|
2630
2221
|
const result = perfectPayload(payload, validationRules);
|
|
2631
|
-
|
|
2632
2222
|
if (!result.valid) {
|
|
2633
|
-
|
|
2634
2223
|
const emailError = result.errors.find(
|
|
2635
|
-
|
|
2636
2224
|
(error) => error.code === "INVALID_EMAIL",
|
|
2637
|
-
|
|
2638
2225
|
);
|
|
2639
|
-
|
|
2640
2226
|
if (emailError) {
|
|
2641
|
-
|
|
2642
|
-
*****// Handle invalid email*****
|
|
2643
|
-
|
|
2227
|
+
// Handle invalid email
|
|
2644
2228
|
}
|
|
2645
|
-
|
|
2646
2229
|
}
|
|
2647
2230
|
```
|
|
2648
2231
|
|
|
@@ -2651,15 +2234,10 @@ if (!result.valid) {
|
|
|
2651
2234
|
Every validation rule can use its corresponding custom error message. Custom messages replace the default human-readable `message` while keeping the same structured error format:
|
|
2652
2235
|
|
|
2653
2236
|
```js
|
|
2654
|
-
|
|
2655
2237
|
{
|
|
2656
|
-
|
|
2657
2238
|
path: "email",
|
|
2658
|
-
|
|
2659
2239
|
code: "INVALID_EMAIL",
|
|
2660
|
-
|
|
2661
2240
|
message: "Email address is invalid"
|
|
2662
|
-
|
|
2663
2241
|
}
|
|
2664
2242
|
```
|
|
2665
2243
|
|
|
@@ -2669,11 +2247,8 @@ Example:
|
|
|
2669
2247
|
const rules = {
|
|
2670
2248
|
email: {
|
|
2671
2249
|
mandatory: true,
|
|
2672
|
-
|
|
2673
2250
|
type: "email",
|
|
2674
|
-
|
|
2675
2251
|
mandatoryError: "Email is required",
|
|
2676
|
-
|
|
2677
2252
|
typeError: "Email address is invalid",
|
|
2678
2253
|
},
|
|
2679
2254
|
};
|
|
@@ -2682,30 +2257,20 @@ const rules = {
|
|
|
2682
2257
|
If `email` is missing:
|
|
2683
2258
|
|
|
2684
2259
|
```js
|
|
2685
|
-
|
|
2686
2260
|
{
|
|
2687
|
-
|
|
2688
2261
|
path: "email",
|
|
2689
|
-
|
|
2690
2262
|
code: "REQUIRED",
|
|
2691
|
-
|
|
2692
2263
|
message: "Email is required"
|
|
2693
|
-
|
|
2694
2264
|
}
|
|
2695
2265
|
```
|
|
2696
2266
|
|
|
2697
2267
|
If `email` is present but invalid:
|
|
2698
2268
|
|
|
2699
2269
|
```js
|
|
2700
|
-
|
|
2701
2270
|
{
|
|
2702
|
-
|
|
2703
2271
|
path: "email",
|
|
2704
|
-
|
|
2705
2272
|
code: "INVALID_EMAIL",
|
|
2706
|
-
|
|
2707
2273
|
message: "Email address is invalid"
|
|
2708
|
-
|
|
2709
2274
|
}
|
|
2710
2275
|
```
|
|
2711
2276
|
|
|
@@ -2732,101 +2297,60 @@ If `email` is present but invalid:
|
|
|
2732
2297
|
```js
|
|
2733
2298
|
const payload = {
|
|
2734
2299
|
username: "ab",
|
|
2735
|
-
|
|
2736
2300
|
age: 15,
|
|
2737
|
-
|
|
2738
2301
|
score: 120,
|
|
2739
2302
|
};
|
|
2740
|
-
|
|
2741
2303
|
const rules = {
|
|
2742
2304
|
username: {
|
|
2743
2305
|
mandatory: true,
|
|
2744
|
-
|
|
2745
2306
|
type: "string",
|
|
2746
|
-
|
|
2747
2307
|
minLength: 3,
|
|
2748
|
-
|
|
2749
2308
|
mandatoryError: "Username is required",
|
|
2750
|
-
|
|
2751
2309
|
typeError: "Username must be a string",
|
|
2752
|
-
|
|
2753
2310
|
minLengthError: "Username must contain at least 3 characters",
|
|
2754
2311
|
},
|
|
2755
|
-
|
|
2756
2312
|
age: {
|
|
2757
2313
|
type: "number",
|
|
2758
|
-
|
|
2759
2314
|
min: 18,
|
|
2760
|
-
|
|
2761
2315
|
minError: "Age must be at least 18",
|
|
2762
2316
|
},
|
|
2763
|
-
|
|
2764
2317
|
score: {
|
|
2765
2318
|
type: "number",
|
|
2766
|
-
|
|
2767
2319
|
range: "0-100",
|
|
2768
|
-
|
|
2769
2320
|
rangeError: "Score must be between 0 and 100",
|
|
2770
2321
|
},
|
|
2771
2322
|
};
|
|
2772
|
-
|
|
2773
2323
|
const result = perfectPayload(payload, rules);
|
|
2774
2324
|
```
|
|
2775
2325
|
|
|
2776
2326
|
Example result:
|
|
2777
2327
|
|
|
2778
2328
|
```js
|
|
2779
|
-
|
|
2780
2329
|
{
|
|
2781
|
-
|
|
2782
2330
|
statusCode: 400,
|
|
2783
|
-
|
|
2784
2331
|
valid: false,
|
|
2785
|
-
|
|
2786
2332
|
message:
|
|
2787
|
-
|
|
2788
2333
|
"One or more attribute values are invalid",
|
|
2789
|
-
|
|
2790
2334
|
errors: [
|
|
2791
|
-
|
|
2792
2335
|
{
|
|
2793
|
-
|
|
2794
2336
|
path: "username",
|
|
2795
|
-
|
|
2796
2337
|
code: "MIN_LENGTH",
|
|
2797
|
-
|
|
2798
2338
|
message:
|
|
2799
|
-
|
|
2800
2339
|
"Username must contain at least 3 characters"
|
|
2801
|
-
|
|
2802
2340
|
},
|
|
2803
|
-
|
|
2804
2341
|
{
|
|
2805
|
-
|
|
2806
2342
|
path: "age",
|
|
2807
|
-
|
|
2808
2343
|
code: "MIN_VALUE",
|
|
2809
|
-
|
|
2810
2344
|
message:
|
|
2811
|
-
|
|
2812
2345
|
"Age must be at least 18"
|
|
2813
|
-
|
|
2814
2346
|
},
|
|
2815
|
-
|
|
2816
2347
|
{
|
|
2817
|
-
|
|
2818
2348
|
path: "score",
|
|
2819
|
-
|
|
2820
2349
|
code: "OUT_OF_RANGE",
|
|
2821
|
-
|
|
2822
2350
|
message:
|
|
2823
|
-
|
|
2824
2351
|
"Score must be between 0 and 100"
|
|
2825
|
-
|
|
2826
2352
|
}
|
|
2827
|
-
|
|
2828
2353
|
]
|
|
2829
|
-
|
|
2830
2354
|
}
|
|
2831
2355
|
```
|
|
2832
2356
|
|
|
@@ -2840,9 +2364,7 @@ For example:
|
|
|
2840
2364
|
const rules = {
|
|
2841
2365
|
age: {
|
|
2842
2366
|
type: "number",
|
|
2843
|
-
|
|
2844
2367
|
min: 18,
|
|
2845
|
-
|
|
2846
2368
|
minError: "You must be 18 or older",
|
|
2847
2369
|
},
|
|
2848
2370
|
};
|
|
@@ -2851,44 +2373,31 @@ const rules = {
|
|
|
2851
2373
|
Still returns:
|
|
2852
2374
|
|
|
2853
2375
|
```js
|
|
2854
|
-
|
|
2855
2376
|
{
|
|
2856
|
-
|
|
2857
2377
|
path: "age",
|
|
2858
|
-
|
|
2859
2378
|
code: "MIN_VALUE",
|
|
2860
|
-
|
|
2861
2379
|
message: "You must be 18 or older"
|
|
2862
|
-
|
|
2863
2380
|
}
|
|
2864
2381
|
```
|
|
2865
2382
|
|
|
2866
2383
|
This makes it possible to:
|
|
2867
2384
|
|
|
2868
2385
|
- show custom messages to API consumers
|
|
2869
|
-
|
|
2870
2386
|
- use stable error codes in application logic
|
|
2871
|
-
|
|
2872
2387
|
- change user-facing wording without changing programmatic error
|
|
2873
2388
|
|
|
2874
2389
|
handling
|
|
2875
2390
|
|
|
2876
2391
|
## Custom Response Objects
|
|
2877
2392
|
|
|
2878
|
-
Custom valid and invalid response objects are configured inside the
|
|
2879
|
-
|
|
2880
|
-
optional third `options` argument.
|
|
2393
|
+
Custom valid and invalid response objects are configured inside the optional third `options` argument.
|
|
2881
2394
|
|
|
2882
2395
|
```js
|
|
2883
|
-
|
|
2884
2396
|
perfectPayload(data, validationRules, options?)
|
|
2885
|
-
|
|
2886
2397
|
await perfectPayloadAsync(data, validationRules, options?)
|
|
2887
2398
|
```
|
|
2888
2399
|
|
|
2889
|
-
This keeps API-level configuration in one place and avoids positional
|
|
2890
|
-
|
|
2891
|
-
`undefined` arguments.
|
|
2400
|
+
This keeps API-level configuration in one place and avoids positional `undefined` arguments.
|
|
2892
2401
|
|
|
2893
2402
|
### Custom Valid Response
|
|
2894
2403
|
|
|
@@ -2896,9 +2405,7 @@ This keeps API-level configuration in one place and avoids positional
|
|
|
2896
2405
|
const result = perfectPayload(payload, validationRules, {
|
|
2897
2406
|
validPayloadResponse: {
|
|
2898
2407
|
statusCode: 201,
|
|
2899
|
-
|
|
2900
2408
|
valid: true,
|
|
2901
|
-
|
|
2902
2409
|
message: "Payload validated successfully",
|
|
2903
2410
|
},
|
|
2904
2411
|
});
|
|
@@ -2907,25 +2414,15 @@ const result = perfectPayload(payload, validationRules, {
|
|
|
2907
2414
|
When validation succeeds, `validatedPayload` is automatically added:
|
|
2908
2415
|
|
|
2909
2416
|
```js
|
|
2910
|
-
|
|
2911
2417
|
{
|
|
2912
|
-
|
|
2913
2418
|
statusCode: 201,
|
|
2914
|
-
|
|
2915
2419
|
valid: true,
|
|
2916
|
-
|
|
2917
2420
|
message: "Payload validated successfully",
|
|
2918
|
-
|
|
2919
2421
|
validatedPayload: {
|
|
2920
|
-
|
|
2921
2422
|
name: "Kiran",
|
|
2922
|
-
|
|
2923
2423
|
email: "kiran@example.com",
|
|
2924
|
-
|
|
2925
2424
|
age: 29
|
|
2926
|
-
|
|
2927
2425
|
}
|
|
2928
|
-
|
|
2929
2426
|
}
|
|
2930
2427
|
```
|
|
2931
2428
|
|
|
@@ -2935,9 +2432,7 @@ When validation succeeds, `validatedPayload` is automatically added:
|
|
|
2935
2432
|
const result = perfectPayload(payload, validationRules, {
|
|
2936
2433
|
inValidPayloadResponse: {
|
|
2937
2434
|
statusCode: 422,
|
|
2938
|
-
|
|
2939
2435
|
valid: false,
|
|
2940
|
-
|
|
2941
2436
|
message: "Payload validation failed",
|
|
2942
2437
|
},
|
|
2943
2438
|
});
|
|
@@ -2946,29 +2441,17 @@ const result = perfectPayload(payload, validationRules, {
|
|
|
2946
2441
|
When validation fails, `errors` is automatically added:
|
|
2947
2442
|
|
|
2948
2443
|
```js
|
|
2949
|
-
|
|
2950
2444
|
{
|
|
2951
|
-
|
|
2952
2445
|
statusCode: 422,
|
|
2953
|
-
|
|
2954
2446
|
valid: false,
|
|
2955
|
-
|
|
2956
2447
|
message: "Payload validation failed",
|
|
2957
|
-
|
|
2958
2448
|
errors: [
|
|
2959
|
-
|
|
2960
2449
|
{
|
|
2961
|
-
|
|
2962
2450
|
path: "email",
|
|
2963
|
-
|
|
2964
2451
|
code: "INVALID_EMAIL",
|
|
2965
|
-
|
|
2966
2452
|
message: "Invalid email format for attribute email"
|
|
2967
|
-
|
|
2968
2453
|
}
|
|
2969
|
-
|
|
2970
2454
|
]
|
|
2971
|
-
|
|
2972
2455
|
}
|
|
2973
2456
|
```
|
|
2974
2457
|
|
|
@@ -2978,17 +2461,12 @@ When validation fails, `errors` is automatically added:
|
|
|
2978
2461
|
const result = perfectPayload(payload, validationRules, {
|
|
2979
2462
|
validPayloadResponse: {
|
|
2980
2463
|
statusCode: 201,
|
|
2981
|
-
|
|
2982
2464
|
valid: true,
|
|
2983
|
-
|
|
2984
2465
|
message: "CUSTOM_VALID_RESPONSE",
|
|
2985
2466
|
},
|
|
2986
|
-
|
|
2987
2467
|
inValidPayloadResponse: {
|
|
2988
2468
|
statusCode: 422,
|
|
2989
|
-
|
|
2990
2469
|
valid: false,
|
|
2991
|
-
|
|
2992
2470
|
message: "CUSTOM_INVALID_RESPONSE",
|
|
2993
2471
|
},
|
|
2994
2472
|
});
|
|
@@ -2999,35 +2477,26 @@ You can combine response customization with other API options:
|
|
|
2999
2477
|
```js
|
|
3000
2478
|
const result = perfectPayload(payload, validationRules, {
|
|
3001
2479
|
unknownFields: "reject",
|
|
3002
|
-
|
|
3003
2480
|
validPayloadResponse: {
|
|
3004
2481
|
statusCode: 201,
|
|
3005
|
-
|
|
3006
2482
|
valid: true,
|
|
3007
2483
|
},
|
|
3008
|
-
|
|
3009
2484
|
inValidPayloadResponse: {
|
|
3010
2485
|
statusCode: 422,
|
|
3011
|
-
|
|
3012
2486
|
valid: false,
|
|
3013
|
-
|
|
3014
2487
|
message: "Payload validation failed",
|
|
3015
2488
|
},
|
|
3016
2489
|
});
|
|
3017
2490
|
```
|
|
3018
2491
|
|
|
3019
|
-
The response object you provide is preserved while `perfectPayload()` automatically adds `validatedPayload` for successful validation or `errors` for failed validation.
|
|
3020
|
-
|
|
3021
|
-
The same response options are supported by `perfectPayloadAsync()`.
|
|
2492
|
+
The response object you provide is preserved while `perfectPayload()` automatically adds `validatedPayload` for successful validation or `errors` for failed validation. The same response options are supported by `perfectPayloadAsync()`.
|
|
3022
2493
|
|
|
3023
|
-
##
|
|
2494
|
+
## Migrating to the v2 API
|
|
3024
2495
|
|
|
3025
|
-
The
|
|
2496
|
+
The v2 `perfectPayload()` and `perfectPayloadAsync()` APIs use one optional third argument for configuration:
|
|
3026
2497
|
|
|
3027
2498
|
```js
|
|
3028
|
-
|
|
3029
2499
|
perfectPayload(data, validationRules, options?)
|
|
3030
|
-
|
|
3031
2500
|
perfectPayloadAsync(data, validationRules, options?)
|
|
3032
2501
|
```
|
|
3033
2502
|
|
|
@@ -3038,7 +2507,6 @@ Use:
|
|
|
3038
2507
|
```js
|
|
3039
2508
|
perfectPayload(payload, rules, {
|
|
3040
2509
|
validPayloadResponse: customValidResponse,
|
|
3041
|
-
|
|
3042
2510
|
inValidPayloadResponse: customInvalidResponse,
|
|
3043
2511
|
});
|
|
3044
2512
|
```
|
|
@@ -3048,12 +2516,9 @@ instead of passing custom response objects as separate positional arguments. Thi
|
|
|
3048
2516
|
```js
|
|
3049
2517
|
perfectPayload(payload, rules, {
|
|
3050
2518
|
unknownFields: "reject",
|
|
3051
|
-
|
|
3052
2519
|
inValidPayloadResponse: {
|
|
3053
2520
|
statusCode: 422,
|
|
3054
|
-
|
|
3055
2521
|
valid: false,
|
|
3056
|
-
|
|
3057
2522
|
message: "Payload validation failed",
|
|
3058
2523
|
},
|
|
3059
2524
|
});
|
|
@@ -3066,48 +2531,29 @@ perfectPayload(payload, rules, {
|
|
|
3066
2531
|
If no custom response objects are provided, the default valid response is:
|
|
3067
2532
|
|
|
3068
2533
|
```js
|
|
3069
|
-
|
|
3070
2534
|
{
|
|
3071
|
-
|
|
3072
2535
|
statusCode: 200,
|
|
3073
|
-
|
|
3074
2536
|
valid: true,
|
|
3075
|
-
|
|
3076
2537
|
validatedPayload: {
|
|
3077
|
-
|
|
3078
|
-
*****// validated fields*****
|
|
3079
|
-
|
|
2538
|
+
// validated fields
|
|
3080
2539
|
}
|
|
3081
|
-
|
|
3082
2540
|
}
|
|
3083
2541
|
```
|
|
3084
2542
|
|
|
3085
2543
|
The default invalid response is:
|
|
3086
2544
|
|
|
3087
2545
|
```js
|
|
3088
|
-
|
|
3089
2546
|
{
|
|
3090
|
-
|
|
3091
2547
|
statusCode: 400,
|
|
3092
|
-
|
|
3093
2548
|
valid: false,
|
|
3094
|
-
|
|
3095
2549
|
message: "One or more attribute values are invalid",
|
|
3096
|
-
|
|
3097
2550
|
errors: [
|
|
3098
|
-
|
|
3099
2551
|
{
|
|
3100
|
-
|
|
3101
2552
|
path: "field",
|
|
3102
|
-
|
|
3103
2553
|
code: "ERROR_CODE",
|
|
3104
|
-
|
|
3105
2554
|
message: "Validation error message"
|
|
3106
|
-
|
|
3107
2555
|
}
|
|
3108
|
-
|
|
3109
2556
|
]
|
|
3110
|
-
|
|
3111
2557
|
}
|
|
3112
2558
|
```
|
|
3113
2559
|
|
|
@@ -3128,15 +2574,10 @@ const payload = {
|
|
|
3128
2574
|
An error can be returned as:
|
|
3129
2575
|
|
|
3130
2576
|
```js
|
|
3131
|
-
|
|
3132
2577
|
{
|
|
3133
|
-
|
|
3134
2578
|
path: "email",
|
|
3135
|
-
|
|
3136
2579
|
code: "INVALID_EMAIL",
|
|
3137
|
-
|
|
3138
2580
|
message: "Invalid email format for attribute email"
|
|
3139
|
-
|
|
3140
2581
|
}
|
|
3141
2582
|
```
|
|
3142
2583
|
|
|
@@ -3148,32 +2589,25 @@ Use `objectAttr` to validate properties inside an object.
|
|
|
3148
2589
|
const payload = {
|
|
3149
2590
|
address: {
|
|
3150
2591
|
city: "Bengaluru",
|
|
3151
|
-
|
|
3152
2592
|
location: {
|
|
3153
2593
|
latitude: "12.9716",
|
|
3154
|
-
|
|
3155
2594
|
longitude: 77.5946,
|
|
3156
2595
|
},
|
|
3157
2596
|
},
|
|
3158
2597
|
};
|
|
3159
|
-
|
|
3160
2598
|
const rules = {
|
|
3161
2599
|
address: {
|
|
3162
2600
|
type: "object",
|
|
3163
|
-
|
|
3164
2601
|
objectAttr: {
|
|
3165
2602
|
city: {
|
|
3166
2603
|
type: "string",
|
|
3167
2604
|
},
|
|
3168
|
-
|
|
3169
2605
|
location: {
|
|
3170
2606
|
type: "object",
|
|
3171
|
-
|
|
3172
2607
|
objectAttr: {
|
|
3173
2608
|
latitude: {
|
|
3174
2609
|
type: "number",
|
|
3175
2610
|
},
|
|
3176
|
-
|
|
3177
2611
|
longitude: {
|
|
3178
2612
|
type: "number",
|
|
3179
2613
|
},
|
|
@@ -3182,35 +2616,25 @@ const rules = {
|
|
|
3182
2616
|
},
|
|
3183
2617
|
},
|
|
3184
2618
|
};
|
|
3185
|
-
|
|
3186
2619
|
const result = perfectPayload(payload, rules);
|
|
3187
2620
|
```
|
|
3188
2621
|
|
|
3189
2622
|
Because `latitude` is a string instead of a number, the error contains its complete nested path:
|
|
3190
2623
|
|
|
3191
2624
|
```js
|
|
3192
|
-
|
|
3193
2625
|
{
|
|
3194
|
-
|
|
3195
2626
|
path: "address.location.latitude",
|
|
3196
|
-
|
|
3197
2627
|
code: "INVALID_TYPE",
|
|
3198
|
-
|
|
3199
2628
|
message:
|
|
3200
|
-
|
|
3201
2629
|
"Invalid type for attribute address.location.latitude, required number value"
|
|
3202
|
-
|
|
3203
2630
|
}
|
|
3204
2631
|
```
|
|
3205
2632
|
|
|
3206
2633
|
Nested paths use dot notation:
|
|
3207
2634
|
|
|
3208
2635
|
```text
|
|
3209
|
-
|
|
3210
2636
|
address.city
|
|
3211
|
-
|
|
3212
2637
|
address.location.latitude
|
|
3213
|
-
|
|
3214
2638
|
address.location.longitude
|
|
3215
2639
|
```
|
|
3216
2640
|
|
|
@@ -3222,47 +2646,34 @@ When `elementConstraints` validation fails, the array index is included in the e
|
|
|
3222
2646
|
const payload = {
|
|
3223
2647
|
marks: [50, 75, 150],
|
|
3224
2648
|
};
|
|
3225
|
-
|
|
3226
2649
|
const rules = {
|
|
3227
2650
|
marks: {
|
|
3228
2651
|
type: "array",
|
|
3229
|
-
|
|
3230
2652
|
elementConstraints: {
|
|
3231
2653
|
type: "number",
|
|
3232
|
-
|
|
3233
2654
|
range: "0-100",
|
|
3234
2655
|
},
|
|
3235
2656
|
},
|
|
3236
2657
|
};
|
|
3237
|
-
|
|
3238
2658
|
const result = perfectPayload(payload, rules);
|
|
3239
2659
|
```
|
|
3240
2660
|
|
|
3241
2661
|
The invalid third element is reported as:
|
|
3242
2662
|
|
|
3243
2663
|
```js
|
|
3244
|
-
|
|
3245
2664
|
{
|
|
3246
|
-
|
|
3247
2665
|
path: "marks[2]",
|
|
3248
|
-
|
|
3249
2666
|
code: "OUT_OF_RANGE",
|
|
3250
|
-
|
|
3251
2667
|
message:
|
|
3252
|
-
|
|
3253
2668
|
"Attribute marks[2] should have a value between 0 and 100"
|
|
3254
|
-
|
|
3255
2669
|
}
|
|
3256
2670
|
```
|
|
3257
2671
|
|
|
3258
2672
|
Array paths use zero-based indexes:
|
|
3259
2673
|
|
|
3260
2674
|
```text
|
|
3261
|
-
|
|
3262
2675
|
marks[0]
|
|
3263
|
-
|
|
3264
2676
|
marks[1]
|
|
3265
|
-
|
|
3266
2677
|
marks[2]
|
|
3267
2678
|
```
|
|
3268
2679
|
|
|
@@ -3275,11 +2686,8 @@ Paths can also identify fields inside array elements.
|
|
|
3275
2686
|
For example:
|
|
3276
2687
|
|
|
3277
2688
|
```text
|
|
3278
|
-
|
|
3279
2689
|
products[0].quantity
|
|
3280
|
-
|
|
3281
2690
|
products[1].quantity
|
|
3282
|
-
|
|
3283
2691
|
products[2].price
|
|
3284
2692
|
```
|
|
3285
2693
|
|
|
@@ -3297,7 +2705,6 @@ For example:
|
|
|
3297
2705
|
|
|
3298
2706
|
```js
|
|
3299
2707
|
const result = perfectPayload(payload, validationRules);
|
|
3300
|
-
|
|
3301
2708
|
if (!result.valid) {
|
|
3302
2709
|
result.errors.forEach((error) => {
|
|
3303
2710
|
console.log(error.path, error.code, error.message);
|
|
@@ -3309,7 +2716,6 @@ A frontend can also map validation errors by path:
|
|
|
3309
2716
|
|
|
3310
2717
|
```js
|
|
3311
2718
|
const fieldErrors = {};
|
|
3312
|
-
|
|
3313
2719
|
result.errors.forEach((error) => {
|
|
3314
2720
|
fieldErrors[error.path] = error.message;
|
|
3315
2721
|
});
|
|
@@ -3318,15 +2724,10 @@ result.errors.forEach((error) => {
|
|
|
3318
2724
|
Result:
|
|
3319
2725
|
|
|
3320
2726
|
```js
|
|
3321
|
-
|
|
3322
2727
|
{
|
|
3323
|
-
|
|
3324
2728
|
"email": "Invalid email format for attribute email",
|
|
3325
|
-
|
|
3326
2729
|
"address.location.latitude": "Invalid type for attribute address.location.latitude, required number value",
|
|
3327
|
-
|
|
3328
2730
|
"marks[2]": "Attribute marks[2] should have a value between 0 and 100"
|
|
3329
|
-
|
|
3330
2731
|
}
|
|
3331
2732
|
```
|
|
3332
2733
|
|
|
@@ -3337,340 +2738,175 @@ Result:
|
|
|
3337
2738
|
sample-1
|
|
3338
2739
|
|
|
3339
2740
|
```js
|
|
3340
|
-
|
|
3341
2741
|
{
|
|
3342
|
-
|
|
3343
2742
|
firstName: {
|
|
3344
|
-
|
|
3345
2743
|
mandatory: true,
|
|
3346
|
-
|
|
3347
2744
|
allowNull: false,
|
|
3348
|
-
|
|
3349
2745
|
type: "string",
|
|
3350
|
-
|
|
3351
2746
|
minLength: 3,
|
|
3352
|
-
|
|
3353
2747
|
minLengthError: "First name must have minimum 3 characters."
|
|
3354
|
-
|
|
3355
2748
|
},
|
|
3356
|
-
|
|
3357
2749
|
lastName: {
|
|
3358
|
-
|
|
3359
2750
|
mandatory: false,
|
|
3360
|
-
|
|
3361
2751
|
allowNull: true,
|
|
3362
|
-
|
|
3363
2752
|
type: "string",
|
|
3364
|
-
|
|
3365
2753
|
},
|
|
3366
|
-
|
|
3367
2754
|
email: {
|
|
3368
|
-
|
|
3369
2755
|
mandatory: true,
|
|
3370
|
-
|
|
3371
2756
|
allowNull: false,
|
|
3372
|
-
|
|
3373
2757
|
type: "email",
|
|
3374
|
-
|
|
3375
2758
|
},
|
|
3376
|
-
|
|
3377
2759
|
phone: {
|
|
3378
|
-
|
|
3379
2760
|
mandatory: true,
|
|
3380
|
-
|
|
3381
2761
|
allowNull: false,
|
|
3382
|
-
|
|
3383
2762
|
type: "string",
|
|
3384
|
-
|
|
3385
2763
|
},
|
|
3386
|
-
|
|
3387
2764
|
age: {
|
|
3388
|
-
|
|
3389
2765
|
mandatory: false,
|
|
3390
|
-
|
|
3391
2766
|
type: "number",
|
|
3392
|
-
|
|
3393
2767
|
min: 1,
|
|
3394
|
-
|
|
3395
2768
|
max: 120,
|
|
3396
|
-
|
|
3397
2769
|
},
|
|
3398
|
-
|
|
3399
2770
|
};
|
|
3400
2771
|
```
|
|
3401
2772
|
|
|
3402
2773
|
sample-2
|
|
3403
2774
|
|
|
3404
2775
|
```js
|
|
3405
|
-
|
|
3406
2776
|
{
|
|
3407
|
-
|
|
3408
2777
|
id: {
|
|
3409
|
-
|
|
3410
2778
|
mandatory: true,
|
|
3411
|
-
|
|
3412
2779
|
allowNull: true,
|
|
3413
|
-
|
|
3414
2780
|
type: "uuidv4",
|
|
3415
|
-
|
|
3416
2781
|
},
|
|
3417
|
-
|
|
3418
2782
|
batchId: {
|
|
3419
|
-
|
|
3420
2783
|
mandatory: true,
|
|
3421
|
-
|
|
3422
2784
|
allowNull: true,
|
|
3423
|
-
|
|
3424
2785
|
type: "objectId",
|
|
3425
|
-
|
|
3426
2786
|
},
|
|
3427
|
-
|
|
3428
2787
|
firstName: {
|
|
3429
|
-
|
|
3430
2788
|
mandatory: true,
|
|
3431
|
-
|
|
3432
2789
|
type: "string",
|
|
3433
|
-
|
|
3434
2790
|
minLength: 3,
|
|
3435
|
-
|
|
3436
2791
|
},
|
|
3437
|
-
|
|
3438
2792
|
lastName: {
|
|
3439
|
-
|
|
3440
2793
|
mandatory: false,
|
|
3441
|
-
|
|
3442
2794
|
allowNull: true,
|
|
3443
|
-
|
|
3444
2795
|
type: "string",
|
|
3445
|
-
|
|
3446
2796
|
},
|
|
3447
|
-
|
|
3448
2797
|
age: {
|
|
3449
|
-
|
|
3450
2798
|
type: "number",
|
|
3451
|
-
|
|
3452
2799
|
min: 0.1,
|
|
3453
|
-
|
|
3454
2800
|
max: 120,
|
|
3455
|
-
|
|
3456
2801
|
},
|
|
3457
|
-
|
|
3458
2802
|
isAdult: {
|
|
3459
|
-
|
|
3460
2803
|
type: "boolean",
|
|
3461
|
-
|
|
3462
2804
|
},
|
|
3463
|
-
|
|
3464
2805
|
totalWins: {
|
|
3465
|
-
|
|
3466
2806
|
type: "number",
|
|
3467
|
-
|
|
3468
2807
|
min: 0,
|
|
3469
|
-
|
|
3470
2808
|
preventDecimal: true,
|
|
3471
|
-
|
|
3472
2809
|
},
|
|
3473
|
-
|
|
3474
2810
|
email: {
|
|
3475
|
-
|
|
3476
2811
|
regex: /[^2]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/,
|
|
3477
|
-
|
|
3478
2812
|
},
|
|
3479
|
-
|
|
3480
2813
|
githubLink: {
|
|
3481
|
-
|
|
3482
2814
|
type: "url",
|
|
3483
|
-
|
|
3484
2815
|
},
|
|
3485
|
-
|
|
3486
2816
|
accountStatus: {
|
|
3487
|
-
|
|
3488
2817
|
type: "enum",
|
|
3489
|
-
|
|
3490
2818
|
enumValues: ["Active", "Inactive", 200],
|
|
3491
|
-
|
|
3492
2819
|
},
|
|
3493
|
-
|
|
3494
2820
|
marks: {
|
|
3495
|
-
|
|
3496
2821
|
range: "0-100",
|
|
3497
|
-
|
|
3498
2822
|
},
|
|
3499
|
-
|
|
3500
2823
|
allMarks: {
|
|
3501
|
-
|
|
3502
2824
|
type: "array",
|
|
3503
|
-
|
|
3504
2825
|
allowEmptyArray: false,
|
|
3505
|
-
|
|
3506
2826
|
elementConstraints: {
|
|
3507
|
-
|
|
3508
2827
|
type: "number",
|
|
3509
|
-
|
|
3510
2828
|
allowNull: false,
|
|
3511
|
-
|
|
3512
2829
|
range: "0-100",
|
|
3513
|
-
|
|
3514
2830
|
},
|
|
3515
|
-
|
|
3516
2831
|
},
|
|
3517
|
-
|
|
3518
2832
|
totalScore: {
|
|
3519
|
-
|
|
3520
2833
|
type: "number",
|
|
3521
|
-
|
|
3522
2834
|
dependency: {
|
|
3523
|
-
|
|
3524
2835
|
result: {
|
|
3525
|
-
|
|
3526
2836
|
setDependencyRule: (totalScore, result) => {
|
|
3527
|
-
|
|
3528
2837
|
return { mandatory: true, allowNull: false, type: "string" };
|
|
3529
|
-
|
|
3530
2838
|
},
|
|
3531
|
-
|
|
3532
2839
|
},
|
|
3533
|
-
|
|
3534
2840
|
},
|
|
3535
|
-
|
|
3536
2841
|
},
|
|
3537
|
-
|
|
3538
2842
|
result: {
|
|
3539
|
-
|
|
3540
2843
|
type: "string",
|
|
3541
|
-
|
|
3542
2844
|
dependency: {
|
|
3543
|
-
|
|
3544
2845
|
totalScore: {
|
|
3545
|
-
|
|
3546
2846
|
setDependencyRule: (result, totalScore) => {
|
|
3547
|
-
|
|
3548
2847
|
return { mandatory: true, allowNull: false, type: "number" };
|
|
3549
|
-
|
|
3550
2848
|
},
|
|
3551
|
-
|
|
3552
2849
|
},
|
|
3553
|
-
|
|
3554
2850
|
},
|
|
3555
|
-
|
|
3556
2851
|
},
|
|
3557
|
-
|
|
3558
2852
|
minSalary: {
|
|
3559
|
-
|
|
3560
2853
|
mandatory: true,
|
|
3561
|
-
|
|
3562
2854
|
min: 1,
|
|
3563
|
-
|
|
3564
2855
|
type: "number",
|
|
3565
|
-
|
|
3566
2856
|
dependency: {
|
|
3567
|
-
|
|
3568
2857
|
maxSalary: {
|
|
3569
|
-
|
|
3570
2858
|
setDependencyRule: (minSalary, maxSalary) => {
|
|
3571
|
-
|
|
3572
2859
|
return {
|
|
3573
|
-
|
|
3574
2860
|
mandatory: true,
|
|
3575
|
-
|
|
3576
2861
|
min: minSalary + 1,
|
|
3577
|
-
|
|
3578
2862
|
minError: "maxSalary must be more than minSalary",
|
|
3579
|
-
|
|
3580
2863
|
};
|
|
3581
|
-
|
|
3582
2864
|
},
|
|
3583
|
-
|
|
3584
2865
|
},
|
|
3585
|
-
|
|
3586
2866
|
},
|
|
3587
|
-
|
|
3588
2867
|
},
|
|
3589
|
-
|
|
3590
2868
|
maxSalary: {
|
|
3591
|
-
|
|
3592
2869
|
dependency: {
|
|
3593
|
-
|
|
3594
2870
|
minSalary: {
|
|
3595
|
-
|
|
3596
2871
|
setDependencyRule: (maxSalary, minSalary) => {
|
|
3597
|
-
|
|
3598
2872
|
return {
|
|
3599
|
-
|
|
3600
2873
|
mandatory: true,
|
|
3601
|
-
|
|
3602
2874
|
max: maxSalary - 1,
|
|
3603
|
-
|
|
3604
2875
|
maxError: "minSalary must be less than maxSalary",
|
|
3605
|
-
|
|
3606
2876
|
};
|
|
3607
|
-
|
|
3608
2877
|
},
|
|
3609
|
-
|
|
3610
2878
|
},
|
|
3611
|
-
|
|
3612
2879
|
},
|
|
3613
|
-
|
|
3614
2880
|
},
|
|
3615
|
-
|
|
3616
2881
|
address: {
|
|
3617
|
-
|
|
3618
2882
|
mandatory: true,
|
|
3619
|
-
|
|
3620
2883
|
type: "object",
|
|
3621
|
-
|
|
3622
2884
|
allowEmptyObject: false,
|
|
3623
|
-
|
|
3624
2885
|
objectAttr: {
|
|
3625
|
-
|
|
3626
2886
|
country: { mandatory: true, type: "string" },
|
|
3627
|
-
|
|
3628
2887
|
state: {
|
|
3629
|
-
|
|
3630
2888
|
mandatory: true,
|
|
3631
|
-
|
|
3632
2889
|
type: "string",
|
|
3633
|
-
|
|
3634
2890
|
},
|
|
3635
|
-
|
|
3636
2891
|
city: {},
|
|
3637
|
-
|
|
3638
2892
|
zip: {
|
|
3639
|
-
|
|
3640
2893
|
mandatory: true,
|
|
3641
|
-
|
|
3642
2894
|
type: "string",
|
|
3643
|
-
|
|
3644
2895
|
},
|
|
3645
|
-
|
|
3646
2896
|
position: {
|
|
3647
|
-
|
|
3648
2897
|
mandatory: true,
|
|
3649
|
-
|
|
3650
2898
|
type: "object",
|
|
3651
|
-
|
|
3652
2899
|
allowEmptyObject: false,
|
|
3653
|
-
|
|
3654
2900
|
objectAttr: {
|
|
3655
|
-
|
|
3656
2901
|
lattitude: { mandatory: true, type: "number" },
|
|
3657
|
-
|
|
3658
2902
|
longitude: {
|
|
3659
|
-
|
|
3660
2903
|
mandatory: true,
|
|
3661
|
-
|
|
3662
2904
|
type: "number",
|
|
3663
|
-
|
|
3664
2905
|
},
|
|
3665
|
-
|
|
3666
2906
|
},
|
|
3667
|
-
|
|
3668
2907
|
},
|
|
3669
|
-
|
|
3670
2908
|
},
|
|
3671
|
-
|
|
3672
2909
|
},
|
|
3673
|
-
|
|
3674
2910
|
}
|
|
3675
2911
|
```
|
|
3676
2912
|
|
|
@@ -3679,19 +2915,11 @@ sample-2
|
|
|
3679
2915
|
#### Creating a route with payload validation middleware
|
|
3680
2916
|
|
|
3681
2917
|
```js
|
|
3682
|
-
|
|
3683
|
-
****// validatePayload is the middleware that invokes
|
|
3684
|
-
|
|
3685
|
-
perfectPayload()****
|
|
3686
|
-
|
|
2918
|
+
// validatePayload is the middleware that invokes perfectPayload()
|
|
3687
2919
|
router.post(
|
|
3688
|
-
|
|
3689
2920
|
"/payload-validation",
|
|
3690
|
-
|
|
3691
2921
|
validatePayload({ rule: <your validation rule json object> }),
|
|
3692
|
-
|
|
3693
2922
|
(req, res) => res.send("OK")
|
|
3694
|
-
|
|
3695
2923
|
);
|
|
3696
2924
|
```
|
|
3697
2925
|
|
|
@@ -3699,24 +2927,16 @@ router.post(
|
|
|
3699
2927
|
|
|
3700
2928
|
```js
|
|
3701
2929
|
import { perfectPayload } from "perfect-payload";
|
|
3702
|
-
|
|
3703
2930
|
export const validatePayload = ({ rule }) => {
|
|
3704
2931
|
return (req, res, next) => {
|
|
3705
2932
|
try {
|
|
3706
|
-
const { statusCode, ...response } = perfectPayload(
|
|
3707
|
-
req?.body,
|
|
3708
|
-
|
|
3709
|
-
rule,
|
|
3710
|
-
);
|
|
3711
|
-
|
|
2933
|
+
const { statusCode, ...response } = perfectPayload(req?.body, rule);
|
|
3712
2934
|
if (+statusCode >= 200 && +statusCode <= 299) {
|
|
3713
2935
|
req.validatedBody = response?.validatedPayload;
|
|
3714
|
-
|
|
3715
2936
|
next();
|
|
3716
2937
|
} else res.status(statusCode).json(response);
|
|
3717
2938
|
} catch (error) {
|
|
3718
2939
|
console.error("Error validating payload", error);
|
|
3719
|
-
|
|
3720
2940
|
res.status(500).json({ error: "Internal Server Error" });
|
|
3721
2941
|
}
|
|
3722
2942
|
};
|
|
@@ -3729,26 +2949,21 @@ When your schema contains an asynchronous `customValidator`, the middleware itse
|
|
|
3729
2949
|
|
|
3730
2950
|
```js
|
|
3731
2951
|
import { perfectPayloadAsync } from "perfect-payload";
|
|
3732
|
-
|
|
3733
2952
|
export const validatePayloadAsync = ({ rule }) => {
|
|
3734
2953
|
return async (req, res, next) => {
|
|
3735
2954
|
try {
|
|
3736
2955
|
const { statusCode, ...response } = await perfectPayloadAsync(
|
|
3737
2956
|
req?.body,
|
|
3738
|
-
|
|
3739
2957
|
rule,
|
|
3740
2958
|
);
|
|
3741
|
-
|
|
3742
2959
|
if (+statusCode >= 200 && +statusCode <= 299) {
|
|
3743
2960
|
req.validatedBody = response?.validatedPayload;
|
|
3744
|
-
|
|
3745
2961
|
next();
|
|
3746
2962
|
} else {
|
|
3747
2963
|
res.status(statusCode).json(response);
|
|
3748
2964
|
}
|
|
3749
2965
|
} catch (error) {
|
|
3750
2966
|
console.error("Error validating payload", error);
|
|
3751
|
-
|
|
3752
2967
|
res.status(500).json({ error: "Internal Server Error" });
|
|
3753
2968
|
}
|
|
3754
2969
|
};
|
|
@@ -3758,15 +2973,10 @@ export const validatePayloadAsync = ({ rule }) => {
|
|
|
3758
2973
|
Route usage:
|
|
3759
2974
|
|
|
3760
2975
|
```js
|
|
3761
|
-
|
|
3762
2976
|
router.post(
|
|
3763
|
-
|
|
3764
2977
|
"/payload-validation",
|
|
3765
|
-
|
|
3766
2978
|
validatePayloadAsync({ rule: <your validation rule json object> }),
|
|
3767
|
-
|
|
3768
2979
|
(req, res) => res.send("OK"),
|
|
3769
|
-
|
|
3770
2980
|
);
|
|
3771
2981
|
```
|
|
3772
2982
|
|
|
@@ -3777,35 +2987,22 @@ function validatePayload({ rule }) {
|
|
|
3777
2987
|
return async (req, res, next) => {
|
|
3778
2988
|
try {
|
|
3779
2989
|
const { perfectPayload } = await import("perfect-payload");
|
|
3780
|
-
|
|
3781
|
-
const { statusCode, ...response } = perfectPayload(
|
|
3782
|
-
req?.body,
|
|
3783
|
-
|
|
3784
|
-
rule,
|
|
3785
|
-
);
|
|
3786
|
-
|
|
2990
|
+
const { statusCode, ...response } = perfectPayload(req?.body, rule);
|
|
3787
2991
|
if (+statusCode >= 200 && +statusCode <= 299) {
|
|
3788
2992
|
req.validatedBody = response?.validatedPayload;
|
|
3789
|
-
|
|
3790
2993
|
next();
|
|
3791
2994
|
} else {
|
|
3792
2995
|
res.status(statusCode).json(response);
|
|
3793
2996
|
}
|
|
3794
2997
|
} catch (error) {
|
|
3795
2998
|
console.error("Error validating payload", error);
|
|
3796
|
-
|
|
3797
2999
|
res.status(500).json({ error: "Internal Server Error" });
|
|
3798
3000
|
}
|
|
3799
3001
|
};
|
|
3800
3002
|
}
|
|
3801
|
-
|
|
3802
3003
|
module.exports = { validatePayload };
|
|
3803
3004
|
```
|
|
3804
3005
|
|
|
3805
3006
|
---
|
|
3806
3007
|
|
|
3807
|
-
This documentation provides a comprehensive guide to using the data
|
|
3808
|
-
|
|
3809
|
-
validation module effectively. Ensure to define your validation rules
|
|
3810
|
-
|
|
3811
|
-
clearly to maintain data quality and consistency in your applications.
|
|
3008
|
+
This documentation provides a comprehensive guide to using the data validation module effectively. Ensure to define your validation rules clearly to maintain data quality and consistency in your applications.
|