perfect-payload 1.7.0-beta.0 → 1.8.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +903 -337
- package/express.js +75 -0
- package/fastify.js +67 -0
- package/framework.js +175 -0
- package/index.js +41 -15
- package/package.json +10 -2
package/README.md
CHANGED
|
@@ -1,31 +1,39 @@
|
|
|
1
1
|
# perfect-payload
|
|
2
2
|
|
|
3
|
-
A lightweight JavaScript
|
|
4
|
-
|
|
3
|
+
A lightweight JavaScript library for validating, transforming, and
|
|
4
|
+
sanitizing API and JSON payloads.
|
|
5
5
|
|
|
6
6
|
`perfect-payload` provides structured validation errors, exact nested
|
|
7
|
-
field paths, synchronous and asynchronous custom validation,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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.
|
|
11
13
|
|
|
12
14
|
## Highlights
|
|
13
15
|
|
|
14
16
|
- Lightweight, rule-based payload validation
|
|
15
|
-
- Structured errors with stable machine-readable codes
|
|
17
|
+
- Structured errors with stable machine-readable error codes
|
|
16
18
|
- Exact nested paths such as `profile.email` and
|
|
17
19
|
`products[1].quantity`
|
|
18
20
|
- Recursive `objectAttr` and `elementConstraints` validation
|
|
19
|
-
- `minItems` and `maxItems`
|
|
20
|
-
- Built-in `trim`, `lowercase`, and `uppercase`
|
|
21
|
+
- Array constraints with `minItems` and `maxItems`
|
|
22
|
+
- Built-in `trim`, `lowercase`, and `uppercase` transformations
|
|
21
23
|
- Custom synchronous `transform(value, payload)`
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
+
- Synchronous custom validators with `perfectPayload()`
|
|
25
|
+
- Synchronous or asynchronous custom validators with
|
|
24
26
|
`perfectPayloadAsync()`
|
|
25
|
-
- Transformed values returned through `validatedPayload`
|
|
26
|
-
- Original input payload is not mutated
|
|
27
27
|
- Configurable unknown-field handling: `strip`, `allow`, or `reject`
|
|
28
|
-
-
|
|
28
|
+
- Optional simplified errors with `prettyErrors`
|
|
29
|
+
- Express middleware integration
|
|
30
|
+
- Fastify hook integration
|
|
31
|
+
- Validate `headers`, `params`, `query`, and `body`
|
|
32
|
+
- Aggregated validation errors across request sources
|
|
33
|
+
- Framework-aware error paths such as `body.email` and `params.userId`
|
|
34
|
+
- Transformed values returned through `validatedPayload`
|
|
35
|
+
- Original input payload/request data is not mutated
|
|
36
|
+
- No Express or Fastify runtime dependency
|
|
29
37
|
- Legacy `perfectPayloadV1()` retained during the migration period
|
|
30
38
|
|
|
31
39
|
## Quick Links
|
|
@@ -33,6 +41,11 @@ lightweight.
|
|
|
33
41
|
- [Installation](#installation)
|
|
34
42
|
- [Basic Usage](#basic-usage)
|
|
35
43
|
- [Public API](#public-api)
|
|
44
|
+
- [Options](#options)
|
|
45
|
+
- [Pretty Errors](#pretty-errors)
|
|
46
|
+
- [Express Integration](#express-integration)
|
|
47
|
+
- [Fastify Integration](#fastify-integration)
|
|
48
|
+
- [Framework Request Validation](#framework-request-validation)
|
|
36
49
|
- [Unknown Field Handling](#unknown-field-handling)
|
|
37
50
|
- [Synchronous vs Asynchronous
|
|
38
51
|
Validation](#synchronous-vs-asynchronous-validation)
|
|
@@ -50,30 +63,497 @@ lightweight.
|
|
|
50
63
|
- [Examples and Usage](#examples-and-usage)
|
|
51
64
|
- [Legacy API](#legacy-api)
|
|
52
65
|
|
|
53
|
-
## What's New in v1.
|
|
66
|
+
## What's New in v1.8.0
|
|
67
|
+
|
|
68
|
+
v1.8.0 adds first-class framework integrations and simpler error output
|
|
69
|
+
while keeping the core validation API framework-independent.
|
|
70
|
+
|
|
71
|
+
## Express Integration
|
|
72
|
+
|
|
73
|
+
`perfect-payload` provides a lightweight Express adapter so request validation can be added directly as middleware.
|
|
54
74
|
|
|
55
|
-
|
|
75
|
+
Express is **not** installed as a dependency of `perfect-payload`.
|
|
56
76
|
|
|
57
|
-
|
|
58
|
-
declared in the validation schema: `"strip"`, `"allow"`, or
|
|
59
|
-
`"reject"`.
|
|
60
|
-
2. `perfectPayload()` and `perfectPayloadAsync()` now use a clean
|
|
61
|
-
three-argument API where custom response objects and other API
|
|
62
|
-
options live inside one `options` object.
|
|
77
|
+
### Import
|
|
63
78
|
|
|
64
79
|
```js
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
80
|
+
import { validatePayload, validatePayloadAsync } from "perfect-payload/express";
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Validate Request Body
|
|
84
|
+
|
|
85
|
+
Use `validatePayload()` when your validation rules are synchronous.
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
import express from "express";
|
|
89
|
+
import { validatePayload } from "perfect-payload/express";
|
|
90
|
+
|
|
91
|
+
const app = express();
|
|
92
|
+
|
|
93
|
+
app.use(express.json());
|
|
94
|
+
|
|
95
|
+
const userRules = {
|
|
96
|
+
email: {
|
|
97
|
+
mandatory: true,
|
|
98
|
+
type: "email",
|
|
99
|
+
trim: true,
|
|
100
|
+
lowercase: true,
|
|
101
|
+
},
|
|
102
|
+
age: {
|
|
103
|
+
mandatory: true,
|
|
104
|
+
type: "number",
|
|
105
|
+
min: 18,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
app.post(
|
|
110
|
+
"/users",
|
|
111
|
+
validatePayload({
|
|
112
|
+
rule: {
|
|
113
|
+
body: userRules,
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
(req, res) => {
|
|
117
|
+
const user = req.validatedPayload.body;
|
|
118
|
+
|
|
119
|
+
res.json({
|
|
120
|
+
message: "User created",
|
|
121
|
+
user,
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
When validation succeeds, the middleware calls `next()` and makes the processed payload available at:
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
req.validatedPayload;
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
For the example above:
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
req.validatedPayload = {
|
|
137
|
+
body: {
|
|
138
|
+
email: "kiran@example.com",
|
|
139
|
+
age: 29,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The original `req.body` is not mutated.
|
|
145
|
+
|
|
146
|
+
### Validate Multiple Request Sources
|
|
147
|
+
|
|
148
|
+
The adapter can validate `headers`, `params`, `query`, and `body` together.
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
app.post(
|
|
152
|
+
"/users/:userId",
|
|
153
|
+
validatePayload({
|
|
154
|
+
rule: {
|
|
155
|
+
headers: {
|
|
156
|
+
authorization: {
|
|
157
|
+
mandatory: true,
|
|
158
|
+
type: "string",
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
params: {
|
|
163
|
+
userId: {
|
|
164
|
+
mandatory: true,
|
|
165
|
+
type: "string",
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
query: {
|
|
170
|
+
notify: {
|
|
171
|
+
type: "boolean",
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
body: {
|
|
176
|
+
email: {
|
|
177
|
+
mandatory: true,
|
|
178
|
+
type: "email",
|
|
179
|
+
trim: true,
|
|
180
|
+
lowercase: true,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
}),
|
|
185
|
+
(req, res) => {
|
|
186
|
+
const { headers, params, query, body } = req.validatedPayload;
|
|
187
|
+
|
|
188
|
+
res.json({
|
|
189
|
+
headers,
|
|
190
|
+
params,
|
|
191
|
+
query,
|
|
192
|
+
body,
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Only request sources configured inside `rule` are included in `req.validatedPayload`.
|
|
199
|
+
|
|
200
|
+
### Validation Errors
|
|
201
|
+
|
|
202
|
+
All configured request sources are validated and their errors are aggregated.
|
|
203
|
+
|
|
204
|
+
Structured error paths include the request source:
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
{
|
|
208
|
+
statusCode: 400,
|
|
209
|
+
valid: false,
|
|
210
|
+
message: "One or more attribute values are invalid",
|
|
211
|
+
errors: [
|
|
212
|
+
{
|
|
213
|
+
path: "body.email",
|
|
214
|
+
code: "INVALID_EMAIL",
|
|
215
|
+
message: "Invalid email format for attribute email"
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The request source is added to the structured `path`, while the validation message itself is preserved.
|
|
222
|
+
|
|
223
|
+
### Adapter Options
|
|
224
|
+
|
|
225
|
+
Core options can be passed through the adapter using `options`:
|
|
226
|
+
|
|
227
|
+
```js
|
|
228
|
+
validatePayload({
|
|
229
|
+
rule: {
|
|
230
|
+
body: userRules,
|
|
231
|
+
},
|
|
232
|
+
options: {
|
|
233
|
+
unknownFields: "reject",
|
|
234
|
+
prettyErrors: false,
|
|
235
|
+
inValidPayloadResponse: {
|
|
236
|
+
statusCode: 422,
|
|
237
|
+
valid: false,
|
|
238
|
+
message: "Request validation failed",
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Async Validation
|
|
245
|
+
|
|
246
|
+
Use `validatePayloadAsync()` when the schema contains asynchronous `customValidator` functions.
|
|
247
|
+
|
|
248
|
+
```js
|
|
249
|
+
import { validatePayloadAsync } from "perfect-payload/express";
|
|
250
|
+
|
|
251
|
+
app.post(
|
|
252
|
+
"/users",
|
|
253
|
+
validatePayloadAsync({
|
|
254
|
+
rule: {
|
|
255
|
+
body: {
|
|
256
|
+
username: {
|
|
257
|
+
mandatory: true,
|
|
258
|
+
type: "string",
|
|
259
|
+
trim: true,
|
|
260
|
+
|
|
261
|
+
customValidator: async (value) => {
|
|
262
|
+
return await isUsernameAvailable(value);
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
customValidatorCode: "USERNAME_TAKEN",
|
|
266
|
+
customValidatorError: "Username is already taken",
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
}),
|
|
271
|
+
(req, res) => {
|
|
272
|
+
res.json(req.validatedPayload.body);
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Use:
|
|
278
|
+
|
|
279
|
+
- `validatePayload()` for synchronous validation.
|
|
280
|
+
- `validatePayloadAsync()` when asynchronous `customValidator` functions are required.
|
|
281
|
+
|
|
282
|
+
Validation failures are handled by the middleware automatically. Unexpected errors from validators, transformations, or configuration are passed to Express through `next(error)`.
|
|
283
|
+
|
|
284
|
+
## Fastify Integration
|
|
285
|
+
|
|
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`.
|
|
289
|
+
|
|
290
|
+
### Import
|
|
291
|
+
|
|
292
|
+
```js
|
|
293
|
+
import { validatePayload, validatePayloadAsync } from "perfect-payload/fastify";
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Validate Request Body
|
|
297
|
+
|
|
298
|
+
Use the adapter as a Fastify `preValidation` hook:
|
|
299
|
+
|
|
300
|
+
```js
|
|
301
|
+
import Fastify from "fastify";
|
|
302
|
+
import { validatePayload } from "perfect-payload/fastify";
|
|
303
|
+
|
|
304
|
+
const fastify = Fastify();
|
|
305
|
+
|
|
306
|
+
const userRules = {
|
|
307
|
+
email: {
|
|
308
|
+
mandatory: true,
|
|
309
|
+
type: "email",
|
|
310
|
+
trim: true,
|
|
311
|
+
lowercase: true,
|
|
312
|
+
},
|
|
313
|
+
age: {
|
|
314
|
+
mandatory: true,
|
|
315
|
+
type: "number",
|
|
316
|
+
min: 18,
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
fastify.post(
|
|
321
|
+
"/users",
|
|
322
|
+
{
|
|
323
|
+
preValidation: validatePayload({
|
|
324
|
+
rule: {
|
|
325
|
+
body: userRules,
|
|
326
|
+
},
|
|
327
|
+
}),
|
|
328
|
+
},
|
|
329
|
+
async (request, reply) => {
|
|
330
|
+
const user = request.validatedPayload.body;
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
message: "User created",
|
|
334
|
+
user,
|
|
335
|
+
};
|
|
336
|
+
},
|
|
337
|
+
);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
When validation succeeds, the processed payload is available at:
|
|
341
|
+
|
|
342
|
+
```js
|
|
343
|
+
request.validatedPayload;
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
For the example above:
|
|
347
|
+
|
|
348
|
+
```js
|
|
349
|
+
request.validatedPayload = {
|
|
350
|
+
body: {
|
|
351
|
+
email: "kiran@example.com",
|
|
352
|
+
age: 29,
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
The original `request.body` is not mutated.
|
|
358
|
+
|
|
359
|
+
### Validate Multiple Request Sources
|
|
360
|
+
|
|
361
|
+
The adapter can validate `headers`, `params`, `query`, and `body` together.
|
|
362
|
+
|
|
363
|
+
```js
|
|
364
|
+
fastify.post(
|
|
365
|
+
"/users/:userId",
|
|
366
|
+
{
|
|
367
|
+
preValidation: validatePayload({
|
|
368
|
+
rule: {
|
|
369
|
+
headers: {
|
|
370
|
+
authorization: {
|
|
371
|
+
mandatory: true,
|
|
372
|
+
type: "string",
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
|
|
376
|
+
params: {
|
|
377
|
+
userId: {
|
|
378
|
+
mandatory: true,
|
|
379
|
+
type: "string",
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
query: {
|
|
384
|
+
notify: {
|
|
385
|
+
type: "boolean",
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
body: {
|
|
390
|
+
email: {
|
|
391
|
+
mandatory: true,
|
|
392
|
+
type: "email",
|
|
393
|
+
trim: true,
|
|
394
|
+
lowercase: true,
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
}),
|
|
399
|
+
},
|
|
400
|
+
async (request, reply) => {
|
|
401
|
+
const { headers, params, query, body } = request.validatedPayload;
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
headers,
|
|
405
|
+
params,
|
|
406
|
+
query,
|
|
407
|
+
body,
|
|
408
|
+
};
|
|
409
|
+
},
|
|
410
|
+
);
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Only request sources configured inside `rule` are included in `request.validatedPayload`.
|
|
414
|
+
|
|
415
|
+
### Validation Errors
|
|
416
|
+
|
|
417
|
+
All configured request sources are validated and their errors are aggregated.
|
|
418
|
+
|
|
419
|
+
Structured error paths include the request source:
|
|
420
|
+
|
|
421
|
+
```js
|
|
422
|
+
{
|
|
423
|
+
statusCode: 400,
|
|
424
|
+
valid: false,
|
|
425
|
+
message: "One or more attribute values are invalid",
|
|
426
|
+
errors: [
|
|
427
|
+
{
|
|
428
|
+
path: "body.email",
|
|
429
|
+
code: "INVALID_EMAIL",
|
|
430
|
+
message: "Invalid email format for attribute email"
|
|
431
|
+
}
|
|
432
|
+
]
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
The request source is added to the structured `path`, while the validation message itself is preserved.
|
|
437
|
+
|
|
438
|
+
### Adapter Options
|
|
439
|
+
|
|
440
|
+
Core options can be passed through the adapter using `options`:
|
|
441
|
+
|
|
442
|
+
```js
|
|
443
|
+
validatePayload({
|
|
444
|
+
rule: {
|
|
445
|
+
body: userRules,
|
|
446
|
+
},
|
|
447
|
+
options: {
|
|
448
|
+
unknownFields: "reject",
|
|
449
|
+
prettyErrors: false,
|
|
450
|
+
inValidPayloadResponse: {
|
|
451
|
+
statusCode: 422,
|
|
452
|
+
valid: false,
|
|
453
|
+
message: "Request validation failed",
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Async Validation
|
|
460
|
+
|
|
461
|
+
Use `validatePayloadAsync()` when the schema contains asynchronous `customValidator` functions.
|
|
462
|
+
|
|
463
|
+
```js
|
|
464
|
+
import { validatePayloadAsync } from "perfect-payload/fastify";
|
|
465
|
+
|
|
466
|
+
fastify.post(
|
|
467
|
+
"/users",
|
|
468
|
+
{
|
|
469
|
+
preValidation: validatePayloadAsync({
|
|
470
|
+
rule: {
|
|
471
|
+
body: {
|
|
472
|
+
username: {
|
|
473
|
+
mandatory: true,
|
|
474
|
+
type: "string",
|
|
475
|
+
trim: true,
|
|
476
|
+
|
|
477
|
+
customValidator: async (value) => {
|
|
478
|
+
return await isUsernameAvailable(value);
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
customValidatorCode: "USERNAME_TAKEN",
|
|
482
|
+
customValidatorError: "Username is already taken",
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
}),
|
|
487
|
+
},
|
|
488
|
+
async (request, reply) => {
|
|
489
|
+
return request.validatedPayload.body;
|
|
71
490
|
},
|
|
491
|
+
);
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
Use:
|
|
495
|
+
|
|
496
|
+
- `validatePayload()` for synchronous validation.
|
|
497
|
+
- `validatePayloadAsync()` when asynchronous `customValidator` functions are required.
|
|
498
|
+
|
|
499
|
+
Validation failures are handled by the hook automatically. Unexpected errors from validators, transformations, or configuration propagate through Fastify's normal error-handling lifecycle.
|
|
500
|
+
|
|
501
|
+
### Framework-aware error paths
|
|
502
|
+
|
|
503
|
+
Structured validation errors include the request source in their path:
|
|
504
|
+
|
|
505
|
+
```js
|
|
506
|
+
{
|
|
507
|
+
path: "body.email",
|
|
508
|
+
code: "INVALID_EMAIL",
|
|
509
|
+
message: "Invalid email format for attribute email"
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
The `path` identifies the exact request source and field. Custom and
|
|
514
|
+
default validation messages are preserved rather than rewritten by the
|
|
515
|
+
framework adapter.
|
|
516
|
+
|
|
517
|
+
### `prettyErrors`
|
|
518
|
+
|
|
519
|
+
v1.8.0 introduces the `prettyErrors` option. Structured errors remain
|
|
520
|
+
the default.
|
|
521
|
+
|
|
522
|
+
```js
|
|
523
|
+
const result = perfectPayload(payload, rules, {
|
|
524
|
+
prettyErrors: true,
|
|
72
525
|
});
|
|
73
526
|
```
|
|
74
527
|
|
|
75
|
-
|
|
76
|
-
|
|
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.
|
|
533
|
+
|
|
534
|
+
### Error privacy
|
|
535
|
+
|
|
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.
|
|
541
|
+
|
|
542
|
+
### Lightweight framework integrations
|
|
543
|
+
|
|
544
|
+
Express and Fastify are **not installed as dependencies of
|
|
545
|
+
`perfect-payload`**.
|
|
546
|
+
|
|
547
|
+
The framework integrations are thin adapters around the same validation
|
|
548
|
+
engine used by:
|
|
549
|
+
|
|
550
|
+
```js
|
|
551
|
+
perfectPayload();
|
|
552
|
+
perfectPayloadAsync();
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
This keeps the package lightweight while allowing framework users to
|
|
556
|
+
integrate validation without writing their own middleware or hooks.
|
|
77
557
|
|
|
78
558
|
## Installation
|
|
79
559
|
|
|
@@ -147,13 +627,9 @@ console.log(result);
|
|
|
147
627
|
}
|
|
148
628
|
```
|
|
149
629
|
|
|
150
|
-
By default, `validatedPayload` contains only fields defined in the
|
|
151
|
-
validation schema. Extra payload fields are stripped unless
|
|
152
|
-
`unknownFields` is explicitly configured as `"allow"` or `"reject"`.
|
|
153
|
-
|
|
154
|
-
The original input payload is not mutated.
|
|
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"`.
|
|
155
631
|
|
|
156
|
-
(such as validatedBody, sanitisedData or parsedBody).
|
|
632
|
+
The original input payload is not mutated. (such as validatedBody, sanitisedData or parsedBody).
|
|
157
633
|
|
|
158
634
|
### Invalid Response
|
|
159
635
|
|
|
@@ -199,89 +675,92 @@ Each error returned by `perfectPayload()` contains:
|
|
|
199
675
|
}
|
|
200
676
|
```
|
|
201
677
|
|
|
202
|
-
|
|
678
|
+
- `path` identifies the exact field that failed validation.
|
|
203
679
|
|
|
204
|
-
|
|
680
|
+
- `code` provides a stable machine-readable validation error code.
|
|
205
681
|
|
|
206
|
-
|
|
682
|
+
- `message` provides a human-readable description of the validation
|
|
207
683
|
|
|
208
684
|
failure.
|
|
209
685
|
|
|
210
|
-
|
|
686
|
+
- Submitted payload values are not included in default error messages.
|
|
211
687
|
|
|
212
688
|
## Public API
|
|
213
689
|
|
|
214
|
-
For new implementations, both supported APIs use the same clean
|
|
215
|
-
three-argument signature:
|
|
690
|
+
For new implementations, both supported APIs use the same clean three-argument signature:
|
|
216
691
|
|
|
217
692
|
```js
|
|
693
|
+
|
|
218
694
|
perfectPayload(data, validationRules, options?)
|
|
695
|
+
|
|
219
696
|
await perfectPayloadAsync(data, validationRules, options?)
|
|
220
697
|
```
|
|
221
698
|
|
|
222
699
|
The arguments are:
|
|
223
700
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
`data` No Payload/object to
|
|
231
|
-
validate. Defaults to
|
|
232
|
-
`{}`.
|
|
233
|
-
|
|
234
|
-
`validationRules` No Validation schema.
|
|
235
|
-
Defaults to `{}`.
|
|
236
|
-
|
|
237
|
-
`options` No API-level configuration
|
|
238
|
-
such as unknown-field
|
|
239
|
-
handling and custom
|
|
240
|
-
response objects.
|
|
241
|
-
|
|
242
|
-
---
|
|
701
|
+
| Argument | Required | Description |
|
|
702
|
+
| ----------------- | -------- | ----------------------------------------------------------------------------------- |
|
|
703
|
+
| `data` | No | Payload/object to validate. Defaults to `{}`. |
|
|
704
|
+
| `validationRules` | No | Validation schema. Defaults to `{}`. |
|
|
705
|
+
| `options` | No | API-level configuration such as unknown-field handling and custom response objects. |
|
|
243
706
|
|
|
244
|
-
The third argument is a single options object. You no longer need to
|
|
245
|
-
pass separate positional arguments for custom valid and invalid
|
|
246
|
-
responses.
|
|
707
|
+
The third argument is a single options object. You no longer need to pass separate positional arguments for custom valid and invalid responses.
|
|
247
708
|
|
|
248
709
|
### Options
|
|
249
710
|
|
|
250
711
|
```js
|
|
712
|
+
|
|
251
713
|
{
|
|
714
|
+
|
|
252
715
|
unknownFields: "strip" | "allow" | "reject",
|
|
253
716
|
|
|
254
717
|
validPayloadResponse: {
|
|
718
|
+
|
|
255
719
|
statusCode: 200,
|
|
720
|
+
|
|
256
721
|
valid: true,
|
|
722
|
+
|
|
257
723
|
},
|
|
258
724
|
|
|
259
725
|
inValidPayloadResponse: {
|
|
726
|
+
|
|
260
727
|
statusCode: 400,
|
|
728
|
+
|
|
261
729
|
valid: false,
|
|
730
|
+
|
|
262
731
|
message: "One or more attribute values are invalid",
|
|
732
|
+
|
|
263
733
|
},
|
|
734
|
+
|
|
264
735
|
}
|
|
265
736
|
```
|
|
266
737
|
|
|
267
|
-
All properties are optional.
|
|
268
|
-
|
|
269
|
-
The defaults are equivalent to:
|
|
738
|
+
All properties are optional. The defaults are equivalent to:
|
|
270
739
|
|
|
271
740
|
```js
|
|
741
|
+
|
|
272
742
|
{
|
|
743
|
+
|
|
273
744
|
unknownFields: "strip",
|
|
274
745
|
|
|
275
746
|
validPayloadResponse: {
|
|
747
|
+
|
|
276
748
|
statusCode: 200,
|
|
749
|
+
|
|
277
750
|
valid: true,
|
|
751
|
+
|
|
278
752
|
},
|
|
279
753
|
|
|
280
754
|
inValidPayloadResponse: {
|
|
755
|
+
|
|
281
756
|
statusCode: 400,
|
|
757
|
+
|
|
282
758
|
valid: false,
|
|
759
|
+
|
|
283
760
|
message: "One or more attribute values are invalid",
|
|
761
|
+
|
|
284
762
|
},
|
|
763
|
+
|
|
285
764
|
}
|
|
286
765
|
```
|
|
287
766
|
|
|
@@ -293,13 +772,17 @@ const result = perfectPayload(payload, validationRules, {
|
|
|
293
772
|
|
|
294
773
|
validPayloadResponse: {
|
|
295
774
|
statusCode: 201,
|
|
775
|
+
|
|
296
776
|
valid: true,
|
|
777
|
+
|
|
297
778
|
message: "Payload accepted",
|
|
298
779
|
},
|
|
299
780
|
|
|
300
781
|
inValidPayloadResponse: {
|
|
301
782
|
statusCode: 422,
|
|
783
|
+
|
|
302
784
|
valid: false,
|
|
785
|
+
|
|
303
786
|
message: "Payload validation failed",
|
|
304
787
|
},
|
|
305
788
|
});
|
|
@@ -310,9 +793,12 @@ The same options object is supported by `perfectPayloadAsync()`:
|
|
|
310
793
|
```js
|
|
311
794
|
const result = await perfectPayloadAsync(payload, validationRules, {
|
|
312
795
|
unknownFields: "reject",
|
|
796
|
+
|
|
313
797
|
inValidPayloadResponse: {
|
|
314
798
|
statusCode: 422,
|
|
799
|
+
|
|
315
800
|
valid: false,
|
|
801
|
+
|
|
316
802
|
message: "Payload validation failed",
|
|
317
803
|
},
|
|
318
804
|
});
|
|
@@ -320,36 +806,22 @@ const result = await perfectPayloadAsync(payload, validationRules, {
|
|
|
320
806
|
|
|
321
807
|
## Unknown Field Handling
|
|
322
808
|
|
|
323
|
-
`unknownFields` controls what happens when the input payload contains a
|
|
324
|
-
field that is not defined in the validation schema.
|
|
809
|
+
`unknownFields` controls what happens when the input payload contains a field that is not defined in the validation schema.
|
|
325
810
|
|
|
326
811
|
Supported values:
|
|
327
812
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
`"strip"` Removes unknown fields from
|
|
335
|
-
`validatedPayload`. This is the
|
|
336
|
-
default and preserves the existing
|
|
337
|
-
behavior.
|
|
338
|
-
|
|
339
|
-
`"allow"` Preserves unknown fields in
|
|
340
|
-
`validatedPayload`.
|
|
813
|
+
| Value | Behavior |
|
|
814
|
+
| ---------- | -------------------------------------------------------------------------------------------------------- |
|
|
815
|
+
| `"strip"` | Removes unknown fields from `validatedPayload`. This is the default and preserves the existing behavior. |
|
|
816
|
+
| `"allow"` | Preserves unknown fields in `validatedPayload`. |
|
|
817
|
+
| `"reject"` | Rejects unknown fields with structured `UNKNOWN_FIELD` validation errors. |
|
|
341
818
|
|
|
342
|
-
`
|
|
343
|
-
structured `UNKNOWN_FIELD`
|
|
344
|
-
validation errors.
|
|
345
|
-
|
|
346
|
-
---
|
|
347
|
-
|
|
348
|
-
### `strip` --- default
|
|
819
|
+
### `strip` — default
|
|
349
820
|
|
|
350
821
|
```js
|
|
351
822
|
const payload = {
|
|
352
823
|
name: "Kiran",
|
|
824
|
+
|
|
353
825
|
role: "developer",
|
|
354
826
|
};
|
|
355
827
|
|
|
@@ -365,17 +837,23 @@ const result = perfectPayload(payload, rules);
|
|
|
365
837
|
Result:
|
|
366
838
|
|
|
367
839
|
```js
|
|
840
|
+
|
|
368
841
|
{
|
|
842
|
+
|
|
369
843
|
statusCode: 200,
|
|
844
|
+
|
|
370
845
|
valid: true,
|
|
846
|
+
|
|
371
847
|
validatedPayload: {
|
|
848
|
+
|
|
372
849
|
name: "Kiran"
|
|
850
|
+
|
|
373
851
|
}
|
|
852
|
+
|
|
374
853
|
}
|
|
375
854
|
```
|
|
376
855
|
|
|
377
|
-
`role` is not part of the schema, so it is removed from
|
|
378
|
-
`validatedPayload`.
|
|
856
|
+
`role` is not part of the schema, so it is removed from `validatedPayload`.
|
|
379
857
|
|
|
380
858
|
You can also set the default behavior explicitly:
|
|
381
859
|
|
|
@@ -396,17 +874,26 @@ const result = perfectPayload(payload, rules, {
|
|
|
396
874
|
Result:
|
|
397
875
|
|
|
398
876
|
```js
|
|
877
|
+
|
|
399
878
|
{
|
|
879
|
+
|
|
400
880
|
statusCode: 200,
|
|
881
|
+
|
|
401
882
|
valid: true,
|
|
883
|
+
|
|
402
884
|
validatedPayload: {
|
|
885
|
+
|
|
403
886
|
name: "Kiran",
|
|
887
|
+
|
|
404
888
|
role: "developer"
|
|
889
|
+
|
|
405
890
|
}
|
|
891
|
+
|
|
406
892
|
}
|
|
407
893
|
```
|
|
408
894
|
|
|
409
895
|
Schema-defined fields are still validated normally. Unknown fields are
|
|
896
|
+
|
|
410
897
|
simply preserved.
|
|
411
898
|
|
|
412
899
|
### `reject` --- reject unknown fields
|
|
@@ -420,22 +907,33 @@ const result = perfectPayload(payload, rules, {
|
|
|
420
907
|
Result:
|
|
421
908
|
|
|
422
909
|
```js
|
|
910
|
+
|
|
423
911
|
{
|
|
912
|
+
|
|
424
913
|
statusCode: 400,
|
|
914
|
+
|
|
425
915
|
valid: false,
|
|
916
|
+
|
|
426
917
|
message: "One or more attribute values are invalid",
|
|
918
|
+
|
|
427
919
|
errors: [
|
|
920
|
+
|
|
428
921
|
{
|
|
922
|
+
|
|
429
923
|
path: "role",
|
|
924
|
+
|
|
430
925
|
code: "UNKNOWN_FIELD",
|
|
926
|
+
|
|
431
927
|
message: "Unknown field role is not allowed"
|
|
928
|
+
|
|
432
929
|
}
|
|
930
|
+
|
|
433
931
|
]
|
|
932
|
+
|
|
434
933
|
}
|
|
435
934
|
```
|
|
436
935
|
|
|
437
|
-
Unknown-field errors use the same structured error format as all other
|
|
438
|
-
validation errors.
|
|
936
|
+
Unknown-field errors use the same structured error format as all other validation errors.
|
|
439
937
|
|
|
440
938
|
### Nested objects
|
|
441
939
|
|
|
@@ -445,6 +943,7 @@ Unknown-field handling is recursive for schemas using `objectAttr`.
|
|
|
445
943
|
const payload = {
|
|
446
944
|
profile: {
|
|
447
945
|
city: "Bengaluru",
|
|
946
|
+
|
|
448
947
|
role: "developer",
|
|
449
948
|
},
|
|
450
949
|
};
|
|
@@ -452,6 +951,7 @@ const payload = {
|
|
|
452
951
|
const rules = {
|
|
453
952
|
profile: {
|
|
454
953
|
type: "object",
|
|
954
|
+
|
|
455
955
|
objectAttr: {
|
|
456
956
|
city: {
|
|
457
957
|
type: "string",
|
|
@@ -468,76 +968,114 @@ const result = perfectPayload(payload, rules, {
|
|
|
468
968
|
Returns:
|
|
469
969
|
|
|
470
970
|
```js
|
|
971
|
+
|
|
471
972
|
{
|
|
973
|
+
|
|
472
974
|
statusCode: 400,
|
|
975
|
+
|
|
473
976
|
valid: false,
|
|
977
|
+
|
|
474
978
|
message: "One or more attribute values are invalid",
|
|
979
|
+
|
|
475
980
|
errors: [
|
|
981
|
+
|
|
476
982
|
{
|
|
983
|
+
|
|
477
984
|
path: "profile.role",
|
|
985
|
+
|
|
478
986
|
code: "UNKNOWN_FIELD",
|
|
987
|
+
|
|
479
988
|
message: "Unknown field profile.role is not allowed"
|
|
989
|
+
|
|
480
990
|
}
|
|
991
|
+
|
|
481
992
|
]
|
|
993
|
+
|
|
482
994
|
}
|
|
483
995
|
```
|
|
484
996
|
|
|
485
997
|
### Arrays and deep paths
|
|
486
998
|
|
|
487
|
-
`unknownFields` also applies recursively through `elementConstraints`.
|
|
999
|
+
`unknownFields` also applies recursively through `elementConstraints`. For an unknown field inside an array element, the error path includes
|
|
488
1000
|
|
|
489
|
-
For an unknown field inside an array element, the error path includes
|
|
490
1001
|
the array index:
|
|
491
1002
|
|
|
492
1003
|
```js
|
|
1004
|
+
|
|
493
1005
|
{
|
|
1006
|
+
|
|
494
1007
|
path: "products[0].internalId",
|
|
1008
|
+
|
|
495
1009
|
code: "UNKNOWN_FIELD",
|
|
1010
|
+
|
|
496
1011
|
message: "Unknown field products[0].internalId is not allowed"
|
|
1012
|
+
|
|
497
1013
|
}
|
|
498
1014
|
```
|
|
499
1015
|
|
|
500
1016
|
This continues through deeply nested combinations of objects and arrays,
|
|
1017
|
+
|
|
501
1018
|
for example:
|
|
502
1019
|
|
|
503
1020
|
```text
|
|
1021
|
+
|
|
504
1022
|
profile.teams[0].members[0].role
|
|
505
1023
|
```
|
|
506
1024
|
|
|
507
1025
|
### Normal validation errors and unknown fields
|
|
508
1026
|
|
|
509
|
-
With `"reject"`, unknown-field errors can be returned together with
|
|
510
|
-
normal validation errors.
|
|
1027
|
+
With `"reject"`, unknown-field errors can be returned together with normal validation errors.
|
|
511
1028
|
|
|
512
1029
|
For example, an invalid email plus two unknown fields can produce:
|
|
513
1030
|
|
|
514
1031
|
```js
|
|
1032
|
+
|
|
515
1033
|
{
|
|
1034
|
+
|
|
516
1035
|
statusCode: 400,
|
|
1036
|
+
|
|
517
1037
|
valid: false,
|
|
1038
|
+
|
|
518
1039
|
message: "One or more attribute values are invalid",
|
|
1040
|
+
|
|
519
1041
|
errors: [
|
|
1042
|
+
|
|
520
1043
|
{
|
|
1044
|
+
|
|
521
1045
|
path: "email",
|
|
1046
|
+
|
|
522
1047
|
code: "INVALID_EMAIL",
|
|
1048
|
+
|
|
523
1049
|
message: "Invalid email format for attribute email"
|
|
1050
|
+
|
|
524
1051
|
},
|
|
1052
|
+
|
|
525
1053
|
{
|
|
1054
|
+
|
|
526
1055
|
path: "role",
|
|
1056
|
+
|
|
527
1057
|
code: "UNKNOWN_FIELD",
|
|
1058
|
+
|
|
528
1059
|
message: "Unknown field role is not allowed"
|
|
1060
|
+
|
|
529
1061
|
},
|
|
1062
|
+
|
|
530
1063
|
{
|
|
1064
|
+
|
|
531
1065
|
path: "active",
|
|
1066
|
+
|
|
532
1067
|
code: "UNKNOWN_FIELD",
|
|
1068
|
+
|
|
533
1069
|
message: "Unknown field active is not allowed"
|
|
1070
|
+
|
|
534
1071
|
}
|
|
1072
|
+
|
|
535
1073
|
]
|
|
1074
|
+
|
|
536
1075
|
}
|
|
537
1076
|
```
|
|
538
1077
|
|
|
539
|
-
With `"allow"`, unknown fields do not create validation errors. Normal
|
|
540
|
-
schema validation continues unchanged.
|
|
1078
|
+
With `"allow"`, unknown fields do not create validation errors. Normal schema validation continues unchanged.
|
|
541
1079
|
|
|
542
1080
|
### Async behavior
|
|
543
1081
|
|
|
@@ -549,30 +1087,29 @@ const result = await perfectPayloadAsync(payload, rules, {
|
|
|
549
1087
|
});
|
|
550
1088
|
```
|
|
551
1089
|
|
|
552
|
-
Unknown-field checking is part of the synchronous validation phase. If
|
|
553
|
-
`"reject"` finds an unknown field, asynchronous `customValidator`
|
|
554
|
-
functions are not executed for that payload. This follows the normal
|
|
555
|
-
two-phase contract of `perfectPayloadAsync()`.
|
|
1090
|
+
Unknown-field checking is part of the synchronous validation phase. If `"reject"` finds an unknown field, asynchronous `customValidator` functions are not executed for that payload. This follows the normal two-phase contract of `perfectPayloadAsync()`.
|
|
556
1091
|
|
|
557
1092
|
### Own properties only
|
|
558
1093
|
|
|
559
|
-
Unknown-field handling considers only the payload object's own
|
|
560
|
-
enumerable properties. Enumerable properties inherited through the
|
|
561
|
-
prototype chain are ignored.
|
|
1094
|
+
Unknown-field handling considers only the payload object's own enumerable properties. Enumerable properties inherited through the prototype chain are ignored.
|
|
562
1095
|
|
|
563
1096
|
### Invalid option values
|
|
564
1097
|
|
|
565
1098
|
Only these values are accepted:
|
|
566
1099
|
|
|
567
1100
|
```text
|
|
1101
|
+
|
|
568
1102
|
strip
|
|
1103
|
+
|
|
569
1104
|
allow
|
|
1105
|
+
|
|
570
1106
|
reject
|
|
571
1107
|
```
|
|
572
1108
|
|
|
573
1109
|
Any other value throws a configuration error:
|
|
574
1110
|
|
|
575
1111
|
```text
|
|
1112
|
+
|
|
576
1113
|
perfect-payload:- unknownFields must be one of strip, allow, reject
|
|
577
1114
|
```
|
|
578
1115
|
|
|
@@ -588,42 +1125,54 @@ import { perfectPayload } from "perfect-payload";
|
|
|
588
1125
|
const result = perfectPayload(payload, validationRules, options);
|
|
589
1126
|
```
|
|
590
1127
|
|
|
591
|
-
When any `customValidator` needs to perform asynchronous work, use
|
|
592
|
-
`perfectPayloadAsync()` and `await` the result:
|
|
1128
|
+
When any `customValidator` needs to perform asynchronous work, use `perfectPayloadAsync()` and `await` the result:
|
|
593
1129
|
|
|
594
1130
|
```js
|
|
595
1131
|
import { perfectPayloadAsync } from "perfect-payload";
|
|
596
1132
|
|
|
597
|
-
const result = await perfectPayloadAsync(
|
|
1133
|
+
const result = await perfectPayloadAsync(
|
|
1134
|
+
payload,
|
|
1135
|
+
validationRules,
|
|
1136
|
+
|
|
1137
|
+
options,
|
|
1138
|
+
);
|
|
598
1139
|
```
|
|
599
1140
|
|
|
600
1141
|
The public APIs are:
|
|
601
1142
|
|
|
602
1143
|
```text
|
|
1144
|
+
|
|
603
1145
|
perfectPayloadV1() legacy API; deprecated
|
|
1146
|
+
|
|
604
1147
|
perfectPayload(data, rules, options?) synchronous validation
|
|
605
|
-
|
|
1148
|
+
|
|
1149
|
+
perfectPayloadAsync(data, rules, options?) synchronous + asynchronous
|
|
1150
|
+
|
|
1151
|
+
customValidator
|
|
606
1152
|
```
|
|
607
1153
|
|
|
608
|
-
`perfectPayload()` remains synchronous and intentionally rejects a
|
|
609
|
-
`customValidator` that returns a Promise. This preserves the existing
|
|
610
|
-
synchronous API contract.
|
|
1154
|
+
`perfectPayload()` remains synchronous and intentionally rejects a `customValidator` that returns a Promise. This preserves the existing synchronous API contract.
|
|
611
1155
|
|
|
612
|
-
`perfectPayloadAsync()` first performs transformations and normal
|
|
613
|
-
synchronous validation. If synchronous validation fails, the result is
|
|
614
|
-
returned immediately and asynchronous validators are not executed. This
|
|
615
|
-
avoids unnecessary asynchronous work for payloads that are already
|
|
616
|
-
invalid.
|
|
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.
|
|
617
1157
|
|
|
618
1158
|
```text
|
|
1159
|
+
|
|
619
1160
|
transformations
|
|
1161
|
+
|
|
620
1162
|
↓
|
|
1163
|
+
|
|
621
1164
|
synchronous validation
|
|
1165
|
+
|
|
622
1166
|
↓
|
|
1167
|
+
|
|
623
1168
|
sync errors? ── yes ──→ return validation errors
|
|
1169
|
+
|
|
624
1170
|
↓ no
|
|
1171
|
+
|
|
625
1172
|
async customValidator
|
|
1173
|
+
|
|
626
1174
|
↓
|
|
1175
|
+
|
|
627
1176
|
return result
|
|
628
1177
|
```
|
|
629
1178
|
|
|
@@ -635,7 +1184,9 @@ return result
|
|
|
635
1184
|
import { perfectPayloadV1 } from "perfect-payload";
|
|
636
1185
|
```
|
|
637
1186
|
|
|
638
|
-
`perfectPayloadV1()` is deprecated and will no longer be supported
|
|
1187
|
+
`perfectPayloadV1()` is deprecated and will no longer be supported
|
|
1188
|
+
|
|
1189
|
+
after
|
|
639
1190
|
|
|
640
1191
|
March 31, 2027.
|
|
641
1192
|
|
|
@@ -644,12 +1195,14 @@ Existing applications can continue using it during the migration period,
|
|
|
644
1195
|
but all new implementations should use the current API:
|
|
645
1196
|
|
|
646
1197
|
```js
|
|
1198
|
+
|
|
647
1199
|
perfectPayload(data, validationRules, options?);
|
|
648
1200
|
```
|
|
649
1201
|
|
|
650
1202
|
For asynchronous custom validation:
|
|
651
1203
|
|
|
652
1204
|
```js
|
|
1205
|
+
|
|
653
1206
|
await perfectPayloadAsync(data, validationRules, options?);
|
|
654
1207
|
```
|
|
655
1208
|
|
|
@@ -673,21 +1226,11 @@ errors: [
|
|
|
673
1226
|
];
|
|
674
1227
|
```
|
|
675
1228
|
|
|
676
|
-
Note: If an inValidPayloadResponse is provided, the
|
|
677
|
-
|
|
678
|
-
system returns
|
|
679
|
-
|
|
680
|
-
it alongside an automatically generated errors property. Do not include
|
|
681
|
-
|
|
682
|
-
your own errors attribute inside the custom inValidPayloadResponse
|
|
683
|
-
|
|
684
|
-
object.
|
|
1229
|
+
Note: If an inValidPayloadResponse is provided in the options, the system returns it alongside an automatically generated errors property. Do not include your own errors attribute inside the custom `options.inValidPayloadResponse` object.
|
|
685
1230
|
|
|
686
1231
|
## Validation Rules
|
|
687
1232
|
|
|
688
|
-
`perfectPayload()` supports validation, nested-schema,
|
|
689
|
-
|
|
690
|
-
custom-validation, and transformation rules.
|
|
1233
|
+
`perfectPayload()` supports validation, nested-schema, custom-validation, and transformation rules.
|
|
691
1234
|
|
|
692
1235
|
### `mandatory`
|
|
693
1236
|
|
|
@@ -775,7 +1318,7 @@ Error code: `EMPTY_ARRAY_NOT_ALLOWED`
|
|
|
775
1318
|
|
|
776
1319
|
Defines the minimum number of items required in an array.
|
|
777
1320
|
|
|
778
|
-
Default: Not applied when omitted
|
|
1321
|
+
Default: `Not applied when omitted.`
|
|
779
1322
|
|
|
780
1323
|
```js
|
|
781
1324
|
const rules = {
|
|
@@ -803,6 +1346,7 @@ An array with fewer than 2 items returns `MIN_ITEMS`.
|
|
|
803
1346
|
```
|
|
804
1347
|
|
|
805
1348
|
`minItems` is enforced even when `allowEmptyArray: true` is set. For
|
|
1349
|
+
|
|
806
1350
|
example, `minItems: 2` still rejects `[]`.
|
|
807
1351
|
|
|
808
1352
|
Error code: `MIN_ITEMS`
|
|
@@ -813,7 +1357,7 @@ Error code: `MIN_ITEMS`
|
|
|
813
1357
|
|
|
814
1358
|
Defines the maximum number of items allowed in an array.
|
|
815
1359
|
|
|
816
|
-
Default: Not applied when omitted
|
|
1360
|
+
Default: `Not applied when omitted`.
|
|
817
1361
|
|
|
818
1362
|
```js
|
|
819
1363
|
const rules = {
|
|
@@ -976,7 +1520,7 @@ For `type: "number"`, `NaN` is rejected as `INVALID_TYPE`.
|
|
|
976
1520
|
|
|
977
1521
|
Validates a value using a regular expression.
|
|
978
1522
|
|
|
979
|
-
Default: Not applied when omitted
|
|
1523
|
+
Default: `Not applied when omitted.`
|
|
980
1524
|
|
|
981
1525
|
Example:
|
|
982
1526
|
|
|
@@ -985,7 +1529,7 @@ const rules = {
|
|
|
985
1529
|
employeeCode: {
|
|
986
1530
|
type: "string",
|
|
987
1531
|
|
|
988
|
-
regex:
|
|
1532
|
+
regex: /[^1]{3}[0-9]{3}$/,
|
|
989
1533
|
},
|
|
990
1534
|
};
|
|
991
1535
|
```
|
|
@@ -998,7 +1542,7 @@ Error code: `REGEX_MISMATCH`
|
|
|
998
1542
|
|
|
999
1543
|
Defines the minimum allowed string length.
|
|
1000
1544
|
|
|
1001
|
-
Default: Not applied when omitted
|
|
1545
|
+
Default: `Not applied when omitted.`
|
|
1002
1546
|
|
|
1003
1547
|
Example:
|
|
1004
1548
|
|
|
@@ -1020,7 +1564,7 @@ Error code: `MIN_LENGTH`
|
|
|
1020
1564
|
|
|
1021
1565
|
Defines the maximum allowed string length.
|
|
1022
1566
|
|
|
1023
|
-
Default: Not applied when omitted
|
|
1567
|
+
Default: `Not applied when omitted.`
|
|
1024
1568
|
|
|
1025
1569
|
Example:
|
|
1026
1570
|
|
|
@@ -1042,9 +1586,7 @@ Error code: `MAX_LENGTH`
|
|
|
1042
1586
|
|
|
1043
1587
|
Prevents decimal numbers.
|
|
1044
1588
|
|
|
1045
|
-
Default: `false`; both integer and decimal numbers are
|
|
1046
|
-
|
|
1047
|
-
allowed.
|
|
1589
|
+
Default: `false`; both integer and decimal numbers are allowed.
|
|
1048
1590
|
|
|
1049
1591
|
Example:
|
|
1050
1592
|
|
|
@@ -1066,7 +1608,7 @@ Error code: `DECIMAL_NOT_ALLOWED`
|
|
|
1066
1608
|
|
|
1067
1609
|
Defines the minimum allowed numeric value.
|
|
1068
1610
|
|
|
1069
|
-
Default: Not applied when omitted
|
|
1611
|
+
Default: `Not applied when omitted.`
|
|
1070
1612
|
|
|
1071
1613
|
Example:
|
|
1072
1614
|
|
|
@@ -1088,7 +1630,7 @@ Error code: `MIN_VALUE`
|
|
|
1088
1630
|
|
|
1089
1631
|
Defines the maximum allowed numeric value.
|
|
1090
1632
|
|
|
1091
|
-
Default: Not applied when omitted
|
|
1633
|
+
Default: `Not applied when omitted.`
|
|
1092
1634
|
|
|
1093
1635
|
Example:
|
|
1094
1636
|
|
|
@@ -1110,7 +1652,7 @@ Error code: `MAX_VALUE`
|
|
|
1110
1652
|
|
|
1111
1653
|
Defines the allowed numeric range.
|
|
1112
1654
|
|
|
1113
|
-
Default: Not applied when omitted
|
|
1655
|
+
Default: `Not applied when omitted.`
|
|
1114
1656
|
|
|
1115
1657
|
Example:
|
|
1116
1658
|
|
|
@@ -1289,9 +1831,7 @@ Example error:
|
|
|
1289
1831
|
|
|
1290
1832
|
## Array Size and Nested Validation
|
|
1291
1833
|
|
|
1292
|
-
`perfectPayload()` supports array size constraints and recursive
|
|
1293
|
-
validation of arrays and objects at multiple depths. Array indexes and
|
|
1294
|
-
nested object keys are preserved in structured error paths.
|
|
1834
|
+
`perfectPayload()` supports array size constraints and recursive validation of arrays and objects at multiple depths. Array indexes and nested object keys are preserved in structured error paths.
|
|
1295
1835
|
|
|
1296
1836
|
### Array size constraints
|
|
1297
1837
|
|
|
@@ -1336,9 +1876,7 @@ If the array is empty, `minItems` reports the array path itself:
|
|
|
1336
1876
|
|
|
1337
1877
|
### Arrays of objects
|
|
1338
1878
|
|
|
1339
|
-
`elementConstraints` can contain `objectAttr`, allowing every object in
|
|
1340
|
-
an array to use a nested schema. An invalid quantity in the second
|
|
1341
|
-
product is reported as:
|
|
1879
|
+
`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:
|
|
1342
1880
|
|
|
1343
1881
|
```text
|
|
1344
1882
|
|
|
@@ -1390,6 +1928,7 @@ const rules = {
|
|
|
1390
1928
|
```
|
|
1391
1929
|
|
|
1392
1930
|
A deep validation failure preserves the complete indexed path, for
|
|
1931
|
+
|
|
1393
1932
|
example:
|
|
1394
1933
|
|
|
1395
1934
|
```text
|
|
@@ -1397,8 +1936,7 @@ example:
|
|
|
1397
1936
|
orders[1].items[2].quantity
|
|
1398
1937
|
```
|
|
1399
1938
|
|
|
1400
|
-
Array constraints work at nested levels too. A nested array can report
|
|
1401
|
-
paths such as:
|
|
1939
|
+
Array constraints work at nested levels too. A nested array can report paths such as:
|
|
1402
1940
|
|
|
1403
1941
|
```text
|
|
1404
1942
|
|
|
@@ -1414,35 +1952,22 @@ matrix[1][1]
|
|
|
1414
1952
|
matrix[1][1][1]
|
|
1415
1953
|
```
|
|
1416
1954
|
|
|
1417
|
-
Transformations applied inside nested objects or array elements are
|
|
1418
|
-
preserved in `validatedPayload`, while the original input remains
|
|
1419
|
-
unchanged.
|
|
1955
|
+
Transformations applied inside nested objects or array elements are preserved in `validatedPayload`, while the original input remains unchanged.
|
|
1420
1956
|
|
|
1421
1957
|
### Transformations and Sanitization
|
|
1422
1958
|
|
|
1423
|
-
`perfectPayload()` can transform a field before its validation rules
|
|
1424
|
-
|
|
1425
|
-
run. The transformed value is returned in `validatedPayload`, while the
|
|
1426
|
-
|
|
1427
|
-
original input object is not mutated.
|
|
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.
|
|
1428
1960
|
|
|
1429
1961
|
Supported transformation rules:
|
|
1430
1962
|
|
|
1431
|
-
Rule Purpose
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
`
|
|
1436
|
-
|
|
1437
|
-
`lowercase` Converts strings to lowercase
|
|
1963
|
+
| Rule | Purpose |
|
|
1964
|
+
| ----------- | ----------------------------------------------------- |
|
|
1965
|
+
| `trim` | Removes leading and trailing whitespace from strings. |
|
|
1966
|
+
| `lowercase` | Converts strings to lowercase. |
|
|
1967
|
+
| `uppercase` | Converts strings to uppercase. |
|
|
1968
|
+
| `transform` | Runs a custom synchronous transformation function. |
|
|
1438
1969
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
`transform` Runs a custom synchronous transformation function
|
|
1442
|
-
|
|
1443
|
-
Transformations always run in this fixed order, regardless of the order
|
|
1444
|
-
|
|
1445
|
-
in which the rule properties are written:
|
|
1970
|
+
Transformations always run in this fixed order, regardless of the order in which the rule properties are written:
|
|
1446
1971
|
|
|
1447
1972
|
```text
|
|
1448
1973
|
|
|
@@ -1476,34 +2001,27 @@ validatedPayload
|
|
|
1476
2001
|
#### `trim`
|
|
1477
2002
|
|
|
1478
2003
|
```js
|
|
1479
|
-
|
|
1480
2004
|
const payload = {
|
|
1481
|
-
|
|
1482
2005
|
name: " Kiran Poojary ",
|
|
1483
|
-
|
|
1484
2006
|
};
|
|
1485
2007
|
|
|
1486
2008
|
const rules = {
|
|
1487
|
-
|
|
1488
2009
|
name: {
|
|
1489
|
-
|
|
1490
2010
|
type: "string",
|
|
1491
2011
|
|
|
1492
2012
|
trim: true,
|
|
1493
|
-
|
|
1494
2013
|
},
|
|
1495
|
-
|
|
1496
2014
|
};
|
|
1497
2015
|
|
|
1498
2016
|
const result = perfectPayload(payload, rules);
|
|
1499
2017
|
|
|
1500
2018
|
console.log(result.validatedPayload.name);
|
|
1501
2019
|
|
|
1502
|
-
|
|
2020
|
+
// "Kiran Poojary"
|
|
1503
2021
|
|
|
1504
2022
|
console.log(payload.name);
|
|
1505
2023
|
|
|
1506
|
-
|
|
2024
|
+
// " Kiran Poojary "
|
|
1507
2025
|
```
|
|
1508
2026
|
|
|
1509
2027
|
`trim` applies only to string values. Non-string values are left
|
|
@@ -1524,9 +2042,7 @@ const rules = {
|
|
|
1524
2042
|
};
|
|
1525
2043
|
```
|
|
1526
2044
|
|
|
1527
|
-
For `" KIRAN@EXAMPLE.COM "`, the validated value becomes
|
|
1528
|
-
|
|
1529
|
-
`"kiran@example.com"`.
|
|
2045
|
+
For `" KIRAN@EXAMPLE.COM "`, the validated value becomes `"kiran@example.com"`.
|
|
1530
2046
|
|
|
1531
2047
|
#### `uppercase`
|
|
1532
2048
|
|
|
@@ -1540,11 +2056,7 @@ const rules = {
|
|
|
1540
2056
|
};
|
|
1541
2057
|
```
|
|
1542
2058
|
|
|
1543
|
-
For `"in"`, the validated value becomes `"IN"`.
|
|
1544
|
-
|
|
1545
|
-
`lowercase: true` and `uppercase: true` cannot be enabled together for
|
|
1546
|
-
|
|
1547
|
-
the same field. Doing so throws a schema configuration error.
|
|
2059
|
+
For `"in"`, the validated value becomes `"IN"`. `lowercase: true` and `uppercase: true` cannot be enabled together for the same field. Doing so throws a schema configuration error.
|
|
1548
2060
|
|
|
1549
2061
|
#### `transform`
|
|
1550
2062
|
|
|
@@ -1555,7 +2067,7 @@ const rules = {
|
|
|
1555
2067
|
phone: {
|
|
1556
2068
|
type: "string",
|
|
1557
2069
|
|
|
1558
|
-
transform: (value) => value.replace(
|
|
2070
|
+
transform: (value) => value.replace(/`\s`{=tex}+/g, ""),
|
|
1559
2071
|
},
|
|
1560
2072
|
};
|
|
1561
2073
|
```
|
|
@@ -1570,47 +2082,36 @@ transform: (value, payload) => {
|
|
|
1570
2082
|
};
|
|
1571
2083
|
```
|
|
1572
2084
|
|
|
1573
|
-
- `value` is the field value after the built-in transformations have
|
|
1574
|
-
|
|
1575
|
-
run.
|
|
2085
|
+
- `value` is the field value after the built-in transformations have run.
|
|
1576
2086
|
|
|
1577
2087
|
- `payload` is the current payload/object being validated.
|
|
1578
2088
|
|
|
1579
2089
|
This makes cross-field transformations possible:
|
|
1580
2090
|
|
|
1581
2091
|
```js
|
|
1582
|
-
|
|
1583
2092
|
const payload = {
|
|
1584
|
-
|
|
1585
2093
|
amount: 100,
|
|
1586
2094
|
|
|
1587
2095
|
multiplier: 2,
|
|
1588
|
-
|
|
1589
2096
|
};
|
|
1590
2097
|
|
|
1591
2098
|
const rules = {
|
|
1592
|
-
|
|
1593
2099
|
amount: {
|
|
1594
|
-
|
|
1595
2100
|
transform: (value, payload) => value * payload.multiplier,
|
|
1596
2101
|
|
|
1597
2102
|
type: "number",
|
|
1598
|
-
|
|
1599
2103
|
},
|
|
1600
2104
|
|
|
1601
2105
|
multiplier: {
|
|
1602
|
-
|
|
1603
2106
|
type: "number",
|
|
1604
|
-
|
|
1605
2107
|
},
|
|
1606
|
-
|
|
1607
2108
|
};
|
|
1608
2109
|
|
|
1609
2110
|
const result = perfectPayload(payload, rules);
|
|
1610
2111
|
|
|
1611
2112
|
console.log(result.validatedPayload.amount);
|
|
1612
2113
|
|
|
1613
|
-
|
|
2114
|
+
// 200***
|
|
1614
2115
|
```
|
|
1615
2116
|
|
|
1616
2117
|
A custom transformer may also change the data type before validation:
|
|
@@ -1629,13 +2130,7 @@ const rules = {
|
|
|
1629
2130
|
};
|
|
1630
2131
|
```
|
|
1631
2132
|
|
|
1632
|
-
The transformed value is validated by the normal validation rules and is
|
|
1633
|
-
|
|
1634
|
-
also the value received by `customValidator`.
|
|
1635
|
-
|
|
1636
|
-
Transformations work inside `objectAttr` and `elementConstraints`, and
|
|
1637
|
-
|
|
1638
|
-
transformed nested/array values are preserved in `validatedPayload`.
|
|
2133
|
+
The transformed value is validated by the normal validation rules and is also the value received by `customValidator`. Transformations work inside `objectAttr` and `elementConstraints`, and transformed nested/array values are preserved in `validatedPayload`.
|
|
1639
2134
|
|
|
1640
2135
|
```js
|
|
1641
2136
|
const rules = {
|
|
@@ -1667,33 +2162,19 @@ const rules = {
|
|
|
1667
2162
|
};
|
|
1668
2163
|
```
|
|
1669
2164
|
|
|
1670
|
-
Missing optional fields are not transformed. An input value of `null` is
|
|
1671
|
-
|
|
1672
|
-
not passed to transformation functions; null handling remains controlled
|
|
1673
|
-
|
|
1674
|
-
by `allowNull`.
|
|
1675
|
-
|
|
1676
|
-
\*\*\*\*Important:\*\*\*\* `transform` is synchronous. A non-function
|
|
1677
|
-
transformer,
|
|
1678
|
-
|
|
1679
|
-
an `async` transformer, a transformer that returns a Promise, or a
|
|
1680
|
-
|
|
1681
|
-
transformer that returns `undefined` is not supported and throws an
|
|
1682
|
-
error.
|
|
1683
|
-
|
|
1684
|
-
Returning `null`, `""`, `0`, or `false` is allowed; the transformed
|
|
1685
|
-
value is
|
|
2165
|
+
Missing optional fields are not transformed. An input value of `null` is not passed to transformation functions; null handling remains controlled by `allowNull`.
|
|
1686
2166
|
|
|
1687
|
-
|
|
1688
|
-
the
|
|
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.
|
|
1689
2168
|
|
|
1690
|
-
transformer propagate to the caller.
|
|
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.
|
|
1691
2170
|
|
|
1692
2171
|
For example, returning `undefined` throws:
|
|
1693
2172
|
|
|
1694
2173
|
```text
|
|
1695
2174
|
|
|
1696
|
-
perfect-payload:- transform must not return undefined for attribute
|
|
2175
|
+
perfect-payload:- transform must not return undefined for attribute
|
|
2176
|
+
|
|
2177
|
+
username
|
|
1697
2178
|
```
|
|
1698
2179
|
|
|
1699
2180
|
### `customValidator`
|
|
@@ -1709,13 +2190,16 @@ customValidator: (value, payload) => {
|
|
|
1709
2190
|
```
|
|
1710
2191
|
|
|
1711
2192
|
- `value` is the field value after transformations have been applied.
|
|
2193
|
+
|
|
1712
2194
|
- `payload` is the current payload/object being validated.
|
|
2195
|
+
|
|
1713
2196
|
- Return `true` to pass.
|
|
2197
|
+
|
|
1714
2198
|
- Any value other than `true` fails validation.
|
|
2199
|
+
|
|
1715
2200
|
- Exceptions thrown by the validator propagate to the caller.
|
|
1716
2201
|
|
|
1717
|
-
For nested validation, `payload` means the current nested object rather
|
|
1718
|
-
than the root request body.
|
|
2202
|
+
For nested validation, `payload` means the current nested object rather than the root request body.
|
|
1719
2203
|
|
|
1720
2204
|
#### Synchronous custom validator
|
|
1721
2205
|
|
|
@@ -1725,7 +2209,9 @@ Use a synchronous validator with `perfectPayload()`:
|
|
|
1725
2209
|
const rules = {
|
|
1726
2210
|
username: {
|
|
1727
2211
|
mandatory: true,
|
|
2212
|
+
|
|
1728
2213
|
type: "string",
|
|
2214
|
+
|
|
1729
2215
|
trim: true,
|
|
1730
2216
|
|
|
1731
2217
|
customValidator: (value) => {
|
|
@@ -1733,6 +2219,7 @@ const rules = {
|
|
|
1733
2219
|
},
|
|
1734
2220
|
|
|
1735
2221
|
customValidatorCode: "RESERVED_USERNAME",
|
|
2222
|
+
|
|
1736
2223
|
customValidatorError: "Username cannot contain admin",
|
|
1737
2224
|
},
|
|
1738
2225
|
};
|
|
@@ -1743,17 +2230,29 @@ const result = perfectPayload({ username: " admin_kiran " }, rules);
|
|
|
1743
2230
|
A failure returns:
|
|
1744
2231
|
|
|
1745
2232
|
```js
|
|
2233
|
+
|
|
1746
2234
|
{
|
|
2235
|
+
|
|
1747
2236
|
statusCode: 400,
|
|
2237
|
+
|
|
1748
2238
|
valid: false,
|
|
2239
|
+
|
|
1749
2240
|
message: "One or more attribute values are invalid",
|
|
2241
|
+
|
|
1750
2242
|
errors: [
|
|
2243
|
+
|
|
1751
2244
|
{
|
|
2245
|
+
|
|
1752
2246
|
path: "username",
|
|
2247
|
+
|
|
1753
2248
|
code: "RESERVED_USERNAME",
|
|
2249
|
+
|
|
1754
2250
|
message: "Username cannot contain admin"
|
|
2251
|
+
|
|
1755
2252
|
}
|
|
2253
|
+
|
|
1756
2254
|
]
|
|
2255
|
+
|
|
1757
2256
|
}
|
|
1758
2257
|
```
|
|
1759
2258
|
|
|
@@ -1773,39 +2272,46 @@ const rules = {
|
|
|
1773
2272
|
},
|
|
1774
2273
|
|
|
1775
2274
|
customValidatorCode: "LIMIT_EXCEEDED",
|
|
2275
|
+
|
|
1776
2276
|
customValidatorError: "Amount cannot exceed limit",
|
|
1777
2277
|
},
|
|
1778
2278
|
};
|
|
1779
2279
|
```
|
|
1780
2280
|
|
|
1781
2281
|
If `customValidatorCode` and `customValidatorError` are omitted, the
|
|
2282
|
+
|
|
1782
2283
|
default error is:
|
|
1783
2284
|
|
|
1784
2285
|
```js
|
|
2286
|
+
|
|
1785
2287
|
{
|
|
2288
|
+
|
|
1786
2289
|
path: "username",
|
|
2290
|
+
|
|
1787
2291
|
code: "CUSTOM_VALIDATION_FAILED",
|
|
2292
|
+
|
|
1788
2293
|
message: "Custom validation failed for attribute username"
|
|
2294
|
+
|
|
1789
2295
|
}
|
|
1790
2296
|
```
|
|
1791
2297
|
|
|
1792
|
-
`customValidator` works recursively inside `objectAttr` and
|
|
1793
|
-
`elementConstraints`. Structured errors preserve the corresponding
|
|
1794
|
-
nested and array paths.
|
|
2298
|
+
`customValidator` works recursively inside `objectAttr` and `elementConstraints`. Structured errors preserve the corresponding nested and array paths.
|
|
1795
2299
|
|
|
1796
|
-
When using `perfectPayload()`, `customValidator` must remain
|
|
1797
|
-
synchronous. A Promise-returning validator throws:
|
|
2300
|
+
When using `perfectPayload()`, `customValidator` must remain synchronous. A Promise-returning validator throws:
|
|
1798
2301
|
|
|
1799
2302
|
```text
|
|
1800
|
-
|
|
2303
|
+
|
|
2304
|
+
perfect-payload:- customValidator must be synchronous for attribute
|
|
2305
|
+
|
|
2306
|
+
username
|
|
1801
2307
|
```
|
|
1802
2308
|
|
|
1803
2309
|
For asynchronous custom validation, use `perfectPayloadAsync()`.
|
|
1804
2310
|
|
|
1805
2311
|
## Asynchronous Validation
|
|
1806
2312
|
|
|
1807
|
-
`perfectPayloadAsync()` supports both synchronous and asynchronous
|
|
1808
|
-
|
|
2313
|
+
`perfectPayloadAsync()` supports both synchronous and asynchronous `customValidator` functions without changing the behavior of
|
|
2314
|
+
|
|
1809
2315
|
`perfectPayload()`.
|
|
1810
2316
|
|
|
1811
2317
|
```js
|
|
@@ -1814,15 +2320,19 @@ import { perfectPayloadAsync } from "perfect-payload";
|
|
|
1814
2320
|
const rules = {
|
|
1815
2321
|
username: {
|
|
1816
2322
|
mandatory: true,
|
|
2323
|
+
|
|
1817
2324
|
type: "string",
|
|
2325
|
+
|
|
1818
2326
|
trim: true,
|
|
1819
2327
|
|
|
1820
2328
|
customValidator: async (value) => {
|
|
1821
2329
|
const available = await checkUsernameAvailability(value);
|
|
2330
|
+
|
|
1822
2331
|
return available;
|
|
1823
2332
|
},
|
|
1824
2333
|
|
|
1825
2334
|
customValidatorCode: "USERNAME_TAKEN",
|
|
2335
|
+
|
|
1826
2336
|
customValidatorError: "Username is already taken",
|
|
1827
2337
|
},
|
|
1828
2338
|
};
|
|
@@ -1831,6 +2341,7 @@ const result = await perfectPayloadAsync(
|
|
|
1831
2341
|
{
|
|
1832
2342
|
username: " kiran ",
|
|
1833
2343
|
},
|
|
2344
|
+
|
|
1834
2345
|
rules,
|
|
1835
2346
|
);
|
|
1836
2347
|
```
|
|
@@ -1838,29 +2349,48 @@ const result = await perfectPayloadAsync(
|
|
|
1838
2349
|
On success, transformations are preserved:
|
|
1839
2350
|
|
|
1840
2351
|
```js
|
|
2352
|
+
|
|
1841
2353
|
{
|
|
2354
|
+
|
|
1842
2355
|
statusCode: 200,
|
|
2356
|
+
|
|
1843
2357
|
valid: true,
|
|
2358
|
+
|
|
1844
2359
|
validatedPayload: {
|
|
2360
|
+
|
|
1845
2361
|
username: "kiran"
|
|
2362
|
+
|
|
1846
2363
|
}
|
|
2364
|
+
|
|
1847
2365
|
}
|
|
1848
2366
|
```
|
|
1849
2367
|
|
|
1850
2368
|
On asynchronous validation failure:
|
|
1851
2369
|
|
|
1852
2370
|
```js
|
|
2371
|
+
|
|
1853
2372
|
{
|
|
2373
|
+
|
|
1854
2374
|
statusCode: 400,
|
|
2375
|
+
|
|
1855
2376
|
valid: false,
|
|
2377
|
+
|
|
1856
2378
|
message: "One or more attribute values are invalid",
|
|
2379
|
+
|
|
1857
2380
|
errors: [
|
|
2381
|
+
|
|
1858
2382
|
{
|
|
2383
|
+
|
|
1859
2384
|
path: "username",
|
|
2385
|
+
|
|
1860
2386
|
code: "USERNAME_TAKEN",
|
|
2387
|
+
|
|
1861
2388
|
message: "Username is already taken"
|
|
2389
|
+
|
|
1862
2390
|
}
|
|
2391
|
+
|
|
1863
2392
|
]
|
|
2393
|
+
|
|
1864
2394
|
}
|
|
1865
2395
|
```
|
|
1866
2396
|
|
|
@@ -1869,20 +2399,27 @@ On asynchronous validation failure:
|
|
|
1869
2399
|
For `perfectPayloadAsync()`:
|
|
1870
2400
|
|
|
1871
2401
|
```text
|
|
2402
|
+
|
|
1872
2403
|
true → pass
|
|
2404
|
+
|
|
1873
2405
|
false → validation failure
|
|
2406
|
+
|
|
1874
2407
|
anything != true → validation failure
|
|
2408
|
+
|
|
1875
2409
|
throw → exception propagates
|
|
2410
|
+
|
|
1876
2411
|
rejected Promise → rejection propagates
|
|
1877
2412
|
```
|
|
1878
2413
|
|
|
1879
2414
|
A normal synchronous validator is also valid when using the asynchronous
|
|
2415
|
+
|
|
1880
2416
|
API:
|
|
1881
2417
|
|
|
1882
2418
|
```js
|
|
1883
2419
|
const rules = {
|
|
1884
2420
|
username: {
|
|
1885
2421
|
type: "string",
|
|
2422
|
+
|
|
1886
2423
|
customValidator: (value) => value !== "admin",
|
|
1887
2424
|
},
|
|
1888
2425
|
};
|
|
@@ -1890,10 +2427,14 @@ const rules = {
|
|
|
1890
2427
|
const result = await perfectPayloadAsync(payload, rules);
|
|
1891
2428
|
```
|
|
1892
2429
|
|
|
1893
|
-
A configured `customValidator` must be a function. Otherwise an error
|
|
2430
|
+
A configured `customValidator` must be a function. Otherwise an error
|
|
2431
|
+
|
|
2432
|
+
is
|
|
2433
|
+
|
|
1894
2434
|
thrown:
|
|
1895
2435
|
|
|
1896
2436
|
```text
|
|
2437
|
+
|
|
1897
2438
|
perfect-payload:- customValidator must be a function for attribute username
|
|
1898
2439
|
```
|
|
1899
2440
|
|
|
@@ -1901,14 +2442,11 @@ perfect-payload:- customValidator must be a function for attribute username
|
|
|
1901
2442
|
|
|
1902
2443
|
`perfectPayloadAsync()` uses two phases:
|
|
1903
2444
|
|
|
1904
|
-
1.
|
|
1905
|
-
2. If phase 1 succeeds, run custom validators with `await`.
|
|
2445
|
+
1. Transform the payload and run normal synchronous validation.
|
|
1906
2446
|
|
|
1907
|
-
If
|
|
1908
|
-
synchronous validation result is returned immediately.
|
|
2447
|
+
2. If phase 1 succeeds, run custom validators with `await`.
|
|
1909
2448
|
|
|
1910
|
-
This means asynchronous validators can assume the payload has already
|
|
1911
|
-
passed its normal synchronous validation rules.
|
|
2449
|
+
If any synchronous validation error exists, phase 2 is skipped and the synchronous validation result is returned immediately. This means asynchronous validators can assume the payload has already passed its normal synchronous validation rules.
|
|
1912
2450
|
|
|
1913
2451
|
### Nested async validation
|
|
1914
2452
|
|
|
@@ -1922,6 +2460,7 @@ const rules = {
|
|
|
1922
2460
|
objectAttr: {
|
|
1923
2461
|
username: {
|
|
1924
2462
|
type: "string",
|
|
2463
|
+
|
|
1925
2464
|
trim: true,
|
|
1926
2465
|
|
|
1927
2466
|
customValidator: async (value) => {
|
|
@@ -1929,6 +2468,7 @@ const rules = {
|
|
|
1929
2468
|
},
|
|
1930
2469
|
|
|
1931
2470
|
customValidatorCode: "USERNAME_TAKEN",
|
|
2471
|
+
|
|
1932
2472
|
customValidatorError: "Username is already taken",
|
|
1933
2473
|
},
|
|
1934
2474
|
},
|
|
@@ -1939,6 +2479,7 @@ const rules = {
|
|
|
1939
2479
|
A failure produces the complete path:
|
|
1940
2480
|
|
|
1941
2481
|
```text
|
|
2482
|
+
|
|
1942
2483
|
profile.username
|
|
1943
2484
|
```
|
|
1944
2485
|
|
|
@@ -1951,6 +2492,7 @@ const rules = {
|
|
|
1951
2492
|
|
|
1952
2493
|
elementConstraints: {
|
|
1953
2494
|
type: "string",
|
|
2495
|
+
|
|
1954
2496
|
trim: true,
|
|
1955
2497
|
|
|
1956
2498
|
customValidator: async (value) => {
|
|
@@ -1958,6 +2500,7 @@ const rules = {
|
|
|
1958
2500
|
},
|
|
1959
2501
|
|
|
1960
2502
|
customValidatorCode: "USERNAME_TAKEN",
|
|
2503
|
+
|
|
1961
2504
|
customValidatorError: "Username is already taken",
|
|
1962
2505
|
},
|
|
1963
2506
|
},
|
|
@@ -1967,31 +2510,42 @@ const rules = {
|
|
|
1967
2510
|
For an invalid second element:
|
|
1968
2511
|
|
|
1969
2512
|
```text
|
|
2513
|
+
|
|
1970
2514
|
usernames[1]
|
|
1971
2515
|
```
|
|
1972
2516
|
|
|
1973
2517
|
Deep combinations of objects and arrays preserve every level of the
|
|
2518
|
+
|
|
1974
2519
|
path:
|
|
1975
2520
|
|
|
1976
2521
|
```text
|
|
2522
|
+
|
|
1977
2523
|
products[1].seller.username
|
|
2524
|
+
|
|
1978
2525
|
profile.teams[1].members[1].username
|
|
1979
2526
|
```
|
|
1980
2527
|
|
|
1981
2528
|
Default async custom-validation messages also use the final indexed
|
|
2529
|
+
|
|
1982
2530
|
path:
|
|
1983
2531
|
|
|
1984
2532
|
```js
|
|
2533
|
+
|
|
1985
2534
|
{
|
|
2535
|
+
|
|
1986
2536
|
path: "users[1].username",
|
|
2537
|
+
|
|
1987
2538
|
code: "CUSTOM_VALIDATION_FAILED",
|
|
2539
|
+
|
|
1988
2540
|
message: "Custom validation failed for attribute users[1].username"
|
|
2541
|
+
|
|
1989
2542
|
}
|
|
1990
2543
|
```
|
|
1991
2544
|
|
|
1992
2545
|
### Transform remains synchronous
|
|
1993
2546
|
|
|
1994
2547
|
`perfectPayloadAsync()` makes custom validation asynchronous; it does
|
|
2548
|
+
|
|
1995
2549
|
not make `transform` asynchronous.
|
|
1996
2550
|
|
|
1997
2551
|
`transform` must still be synchronous:
|
|
@@ -2003,6 +2557,7 @@ transform: (value, payload) => {
|
|
|
2003
2557
|
```
|
|
2004
2558
|
|
|
2005
2559
|
An async transformer or a transformer that returns a Promise is not
|
|
2560
|
+
|
|
2006
2561
|
supported.
|
|
2007
2562
|
|
|
2008
2563
|
## Error Codes
|
|
@@ -2066,9 +2621,7 @@ CUSTOM_VALIDATION_FAILED
|
|
|
2066
2621
|
UNKNOWN_FIELD
|
|
2067
2622
|
```
|
|
2068
2623
|
|
|
2069
|
-
These codes are designed for programmatic handling while `message`
|
|
2070
|
-
|
|
2071
|
-
remains suitable for human-readable API responses.
|
|
2624
|
+
These codes are designed for programmatic handling while `message` remains suitable for human-readable API responses.
|
|
2072
2625
|
|
|
2073
2626
|
For example:
|
|
2074
2627
|
|
|
@@ -2086,7 +2639,7 @@ if (!result.valid) {
|
|
|
2086
2639
|
|
|
2087
2640
|
if (emailError) {
|
|
2088
2641
|
|
|
2089
|
-
|
|
2642
|
+
*****// Handle invalid email*****
|
|
2090
2643
|
|
|
2091
2644
|
}
|
|
2092
2645
|
|
|
@@ -2095,11 +2648,7 @@ if (!result.valid) {
|
|
|
2095
2648
|
|
|
2096
2649
|
## Custom Error Messages
|
|
2097
2650
|
|
|
2098
|
-
Every validation rule can use its corresponding custom error message.
|
|
2099
|
-
|
|
2100
|
-
Custom messages replace the default human-readable `message` while
|
|
2101
|
-
|
|
2102
|
-
keeping the same structured error format:
|
|
2651
|
+
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:
|
|
2103
2652
|
|
|
2104
2653
|
```js
|
|
2105
2654
|
|
|
@@ -2162,35 +2711,21 @@ If `email` is present but invalid:
|
|
|
2162
2711
|
|
|
2163
2712
|
### Supported Custom Error Properties
|
|
2164
2713
|
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
\| `type` \| `typeError` \|
|
|
2182
|
-
|
|
2183
|
-
\| `minLength` \| `minLengthError` \|
|
|
2184
|
-
|
|
2185
|
-
\| `maxLength` \| `maxLengthError` \|
|
|
2186
|
-
|
|
2187
|
-
\| `preventDecimal` \| `preventDecimalError` \|
|
|
2188
|
-
|
|
2189
|
-
\| `min` \| `minError` \|
|
|
2190
|
-
|
|
2191
|
-
\| `max` \| `maxError` \|
|
|
2192
|
-
|
|
2193
|
-
\| `range` \| `rangeError` \|
|
|
2714
|
+
| Validation Rule | Custom Error Property |
|
|
2715
|
+
| -------------------- | ------------------------- |
|
|
2716
|
+
| `mandatory` | `mandatoryError` |
|
|
2717
|
+
| `allowNull` | `allowNullError` |
|
|
2718
|
+
| `allowEmptyObject` | `emptyObjectError` |
|
|
2719
|
+
| `allowEmptyArray` | `emptyArrayError` |
|
|
2720
|
+
| `elementConstraints` | `elementConstraintsError` |
|
|
2721
|
+
| `regex` | `regexError` |
|
|
2722
|
+
| `type` | `typeError` |
|
|
2723
|
+
| `minLength` | `minLengthError` |
|
|
2724
|
+
| `maxLength` | `maxLengthError` |
|
|
2725
|
+
| `preventDecimal` | `preventDecimalError` |
|
|
2726
|
+
| `min` | `minError` |
|
|
2727
|
+
| `max` | `maxError` |
|
|
2728
|
+
| `range` | `rangeError` |
|
|
2194
2729
|
|
|
2195
2730
|
### Example with Multiple Custom Errors
|
|
2196
2731
|
|
|
@@ -2297,9 +2832,7 @@ Example result:
|
|
|
2297
2832
|
|
|
2298
2833
|
### Custom Messages and Error Codes
|
|
2299
2834
|
|
|
2300
|
-
Custom messages only replace the `message`.
|
|
2301
|
-
|
|
2302
|
-
They do not change the validation error `code`.
|
|
2835
|
+
Custom messages only replace the `message`. They do not change the validation error `code`.
|
|
2303
2836
|
|
|
2304
2837
|
For example:
|
|
2305
2838
|
|
|
@@ -2332,25 +2865,29 @@ Still returns:
|
|
|
2332
2865
|
|
|
2333
2866
|
This makes it possible to:
|
|
2334
2867
|
|
|
2335
|
-
|
|
2868
|
+
- show custom messages to API consumers
|
|
2336
2869
|
|
|
2337
|
-
|
|
2870
|
+
- use stable error codes in application logic
|
|
2338
2871
|
|
|
2339
|
-
|
|
2872
|
+
- change user-facing wording without changing programmatic error
|
|
2340
2873
|
|
|
2341
2874
|
handling
|
|
2342
2875
|
|
|
2343
2876
|
## Custom Response Objects
|
|
2344
2877
|
|
|
2345
2878
|
Custom valid and invalid response objects are configured inside the
|
|
2879
|
+
|
|
2346
2880
|
optional third `options` argument.
|
|
2347
2881
|
|
|
2348
2882
|
```js
|
|
2883
|
+
|
|
2349
2884
|
perfectPayload(data, validationRules, options?)
|
|
2885
|
+
|
|
2350
2886
|
await perfectPayloadAsync(data, validationRules, options?)
|
|
2351
2887
|
```
|
|
2352
2888
|
|
|
2353
2889
|
This keeps API-level configuration in one place and avoids positional
|
|
2890
|
+
|
|
2354
2891
|
`undefined` arguments.
|
|
2355
2892
|
|
|
2356
2893
|
### Custom Valid Response
|
|
@@ -2359,7 +2896,9 @@ This keeps API-level configuration in one place and avoids positional
|
|
|
2359
2896
|
const result = perfectPayload(payload, validationRules, {
|
|
2360
2897
|
validPayloadResponse: {
|
|
2361
2898
|
statusCode: 201,
|
|
2899
|
+
|
|
2362
2900
|
valid: true,
|
|
2901
|
+
|
|
2363
2902
|
message: "Payload validated successfully",
|
|
2364
2903
|
},
|
|
2365
2904
|
});
|
|
@@ -2368,15 +2907,25 @@ const result = perfectPayload(payload, validationRules, {
|
|
|
2368
2907
|
When validation succeeds, `validatedPayload` is automatically added:
|
|
2369
2908
|
|
|
2370
2909
|
```js
|
|
2910
|
+
|
|
2371
2911
|
{
|
|
2912
|
+
|
|
2372
2913
|
statusCode: 201,
|
|
2914
|
+
|
|
2373
2915
|
valid: true,
|
|
2916
|
+
|
|
2374
2917
|
message: "Payload validated successfully",
|
|
2918
|
+
|
|
2375
2919
|
validatedPayload: {
|
|
2920
|
+
|
|
2376
2921
|
name: "Kiran",
|
|
2922
|
+
|
|
2377
2923
|
email: "kiran@example.com",
|
|
2924
|
+
|
|
2378
2925
|
age: 29
|
|
2926
|
+
|
|
2379
2927
|
}
|
|
2928
|
+
|
|
2380
2929
|
}
|
|
2381
2930
|
```
|
|
2382
2931
|
|
|
@@ -2386,7 +2935,9 @@ When validation succeeds, `validatedPayload` is automatically added:
|
|
|
2386
2935
|
const result = perfectPayload(payload, validationRules, {
|
|
2387
2936
|
inValidPayloadResponse: {
|
|
2388
2937
|
statusCode: 422,
|
|
2938
|
+
|
|
2389
2939
|
valid: false,
|
|
2940
|
+
|
|
2390
2941
|
message: "Payload validation failed",
|
|
2391
2942
|
},
|
|
2392
2943
|
});
|
|
@@ -2395,17 +2946,29 @@ const result = perfectPayload(payload, validationRules, {
|
|
|
2395
2946
|
When validation fails, `errors` is automatically added:
|
|
2396
2947
|
|
|
2397
2948
|
```js
|
|
2949
|
+
|
|
2398
2950
|
{
|
|
2951
|
+
|
|
2399
2952
|
statusCode: 422,
|
|
2953
|
+
|
|
2400
2954
|
valid: false,
|
|
2955
|
+
|
|
2401
2956
|
message: "Payload validation failed",
|
|
2957
|
+
|
|
2402
2958
|
errors: [
|
|
2959
|
+
|
|
2403
2960
|
{
|
|
2961
|
+
|
|
2404
2962
|
path: "email",
|
|
2963
|
+
|
|
2405
2964
|
code: "INVALID_EMAIL",
|
|
2965
|
+
|
|
2406
2966
|
message: "Invalid email format for attribute email"
|
|
2967
|
+
|
|
2407
2968
|
}
|
|
2969
|
+
|
|
2408
2970
|
]
|
|
2971
|
+
|
|
2409
2972
|
}
|
|
2410
2973
|
```
|
|
2411
2974
|
|
|
@@ -2415,13 +2978,17 @@ When validation fails, `errors` is automatically added:
|
|
|
2415
2978
|
const result = perfectPayload(payload, validationRules, {
|
|
2416
2979
|
validPayloadResponse: {
|
|
2417
2980
|
statusCode: 201,
|
|
2981
|
+
|
|
2418
2982
|
valid: true,
|
|
2983
|
+
|
|
2419
2984
|
message: "CUSTOM_VALID_RESPONSE",
|
|
2420
2985
|
},
|
|
2421
2986
|
|
|
2422
2987
|
inValidPayloadResponse: {
|
|
2423
2988
|
statusCode: 422,
|
|
2989
|
+
|
|
2424
2990
|
valid: false,
|
|
2991
|
+
|
|
2425
2992
|
message: "CUSTOM_INVALID_RESPONSE",
|
|
2426
2993
|
},
|
|
2427
2994
|
});
|
|
@@ -2435,30 +3002,32 @@ const result = perfectPayload(payload, validationRules, {
|
|
|
2435
3002
|
|
|
2436
3003
|
validPayloadResponse: {
|
|
2437
3004
|
statusCode: 201,
|
|
3005
|
+
|
|
2438
3006
|
valid: true,
|
|
2439
3007
|
},
|
|
2440
3008
|
|
|
2441
3009
|
inValidPayloadResponse: {
|
|
2442
3010
|
statusCode: 422,
|
|
3011
|
+
|
|
2443
3012
|
valid: false,
|
|
3013
|
+
|
|
2444
3014
|
message: "Payload validation failed",
|
|
2445
3015
|
},
|
|
2446
3016
|
});
|
|
2447
3017
|
```
|
|
2448
3018
|
|
|
2449
|
-
The response object you provide is preserved while `perfectPayload()`
|
|
2450
|
-
automatically adds `validatedPayload` for successful validation or
|
|
2451
|
-
`errors` for failed validation.
|
|
3019
|
+
The response object you provide is preserved while `perfectPayload()` automatically adds `validatedPayload` for successful validation or `errors` for failed validation.
|
|
2452
3020
|
|
|
2453
3021
|
The same response options are supported by `perfectPayloadAsync()`.
|
|
2454
3022
|
|
|
2455
3023
|
## v1.7 API Migration
|
|
2456
3024
|
|
|
2457
|
-
The current `perfectPayload()` and `perfectPayloadAsync()` APIs use one
|
|
2458
|
-
optional third argument for configuration:
|
|
3025
|
+
The current `perfectPayload()` and `perfectPayloadAsync()` APIs use one optional third argument for configuration:
|
|
2459
3026
|
|
|
2460
3027
|
```js
|
|
3028
|
+
|
|
2461
3029
|
perfectPayload(data, validationRules, options?)
|
|
3030
|
+
|
|
2462
3031
|
perfectPayloadAsync(data, validationRules, options?)
|
|
2463
3032
|
```
|
|
2464
3033
|
|
|
@@ -2469,35 +3038,32 @@ Use:
|
|
|
2469
3038
|
```js
|
|
2470
3039
|
perfectPayload(payload, rules, {
|
|
2471
3040
|
validPayloadResponse: customValidResponse,
|
|
3041
|
+
|
|
2472
3042
|
inValidPayloadResponse: customInvalidResponse,
|
|
2473
3043
|
});
|
|
2474
3044
|
```
|
|
2475
3045
|
|
|
2476
|
-
instead of passing custom response objects as separate positional
|
|
2477
|
-
arguments.
|
|
2478
|
-
|
|
2479
|
-
This also makes it possible to combine response customization with
|
|
2480
|
-
`unknownFields` without placeholder arguments:
|
|
3046
|
+
instead of passing custom response objects as separate positional arguments. This also makes it possible to combine response customization with `unknownFields` without placeholder arguments:
|
|
2481
3047
|
|
|
2482
3048
|
```js
|
|
2483
3049
|
perfectPayload(payload, rules, {
|
|
2484
3050
|
unknownFields: "reject",
|
|
3051
|
+
|
|
2485
3052
|
inValidPayloadResponse: {
|
|
2486
3053
|
statusCode: 422,
|
|
3054
|
+
|
|
2487
3055
|
valid: false,
|
|
3056
|
+
|
|
2488
3057
|
message: "Payload validation failed",
|
|
2489
3058
|
},
|
|
2490
3059
|
});
|
|
2491
3060
|
```
|
|
2492
3061
|
|
|
2493
|
-
`perfectPayloadV1()` is unchanged and retains its legacy signature
|
|
2494
|
-
during its deprecation period.
|
|
3062
|
+
`perfectPayloadV1()` is unchanged and retains its legacy signature during its deprecation period.
|
|
2495
3063
|
|
|
2496
3064
|
## Default Responses
|
|
2497
3065
|
|
|
2498
|
-
If no custom response objects are provided, the default valid response
|
|
2499
|
-
|
|
2500
|
-
is:
|
|
3066
|
+
If no custom response objects are provided, the default valid response is:
|
|
2501
3067
|
|
|
2502
3068
|
```js
|
|
2503
3069
|
|
|
@@ -2509,7 +3075,7 @@ is:
|
|
|
2509
3075
|
|
|
2510
3076
|
validatedPayload: {
|
|
2511
3077
|
|
|
2512
|
-
|
|
3078
|
+
*****// validated fields*****
|
|
2513
3079
|
|
|
2514
3080
|
}
|
|
2515
3081
|
|
|
@@ -2547,13 +3113,7 @@ The default invalid response is:
|
|
|
2547
3113
|
|
|
2548
3114
|
## Nested Objects and Array Field Paths
|
|
2549
3115
|
|
|
2550
|
-
`perfectPayload()` returns the exact location of a validation failure
|
|
2551
|
-
|
|
2552
|
-
through the `path` property.
|
|
2553
|
-
|
|
2554
|
-
This makes validation errors easier to map to API fields, forms, logs,
|
|
2555
|
-
|
|
2556
|
-
and frontend components.
|
|
3116
|
+
`perfectPayload()` returns the exact location of a validation failure through the `path` property. This makes validation errors easier to map to API fields, forms, logs, and frontend components.
|
|
2557
3117
|
|
|
2558
3118
|
### Top-Level Field
|
|
2559
3119
|
|
|
@@ -2626,9 +3186,7 @@ const rules = {
|
|
|
2626
3186
|
const result = perfectPayload(payload, rules);
|
|
2627
3187
|
```
|
|
2628
3188
|
|
|
2629
|
-
Because `latitude` is a string instead of a number, the error contains
|
|
2630
|
-
|
|
2631
|
-
its complete nested path:
|
|
3189
|
+
Because `latitude` is a string instead of a number, the error contains its complete nested path:
|
|
2632
3190
|
|
|
2633
3191
|
```js
|
|
2634
3192
|
|
|
@@ -2658,9 +3216,7 @@ address.location.longitude
|
|
|
2658
3216
|
|
|
2659
3217
|
### Array Elements
|
|
2660
3218
|
|
|
2661
|
-
When `elementConstraints` validation fails, the array index is included
|
|
2662
|
-
|
|
2663
|
-
in the error path.
|
|
3219
|
+
When `elementConstraints` validation fails, the array index is included in the error path.
|
|
2664
3220
|
|
|
2665
3221
|
```js
|
|
2666
3222
|
const payload = {
|
|
@@ -2710,9 +3266,7 @@ marks[1]
|
|
|
2710
3266
|
marks[2]
|
|
2711
3267
|
```
|
|
2712
3268
|
|
|
2713
|
-
Array-level constraints such as `minItems` and `maxItems` report the
|
|
2714
|
-
path of the array itself. For nested arrays, the complete parent path is
|
|
2715
|
-
retained, for example `orders[1].items`.
|
|
3269
|
+
Array-level constraints such as `minItems` and `maxItems` report the path of the array itself. For nested arrays, the complete parent path is retained, for example `orders[1].items`.
|
|
2716
3270
|
|
|
2717
3271
|
### Nested Fields Inside Arrays
|
|
2718
3272
|
|
|
@@ -2729,15 +3283,11 @@ products[1].quantity
|
|
|
2729
3283
|
products[2].price
|
|
2730
3284
|
```
|
|
2731
3285
|
|
|
2732
|
-
This provides enough information for consumers to identify the exact
|
|
2733
|
-
|
|
2734
|
-
field that caused the validation error.
|
|
3286
|
+
This provides enough information for consumers to identify the exact field that caused the validation error.
|
|
2735
3287
|
|
|
2736
3288
|
### Why Structured Paths Are Useful
|
|
2737
3289
|
|
|
2738
|
-
Instead of parsing an error message to determine which field failed,
|
|
2739
|
-
|
|
2740
|
-
applications can directly use:
|
|
3290
|
+
Instead of parsing an error message to determine which field failed, applications can directly use:
|
|
2741
3291
|
|
|
2742
3292
|
```js
|
|
2743
3293
|
error.path;
|
|
@@ -2923,7 +3473,7 @@ sample-2
|
|
|
2923
3473
|
|
|
2924
3474
|
email: {
|
|
2925
3475
|
|
|
2926
|
-
regex:
|
|
3476
|
+
regex: /[^2]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/,
|
|
2927
3477
|
|
|
2928
3478
|
},
|
|
2929
3479
|
|
|
@@ -3130,7 +3680,9 @@ sample-2
|
|
|
3130
3680
|
|
|
3131
3681
|
```js
|
|
3132
3682
|
|
|
3133
|
-
|
|
3683
|
+
****// validatePayload is the middleware that invokes
|
|
3684
|
+
|
|
3685
|
+
perfectPayload()****
|
|
3134
3686
|
|
|
3135
3687
|
router.post(
|
|
3136
3688
|
|
|
@@ -3151,7 +3703,11 @@ import { perfectPayload } from "perfect-payload";
|
|
|
3151
3703
|
export const validatePayload = ({ rule }) => {
|
|
3152
3704
|
return (req, res, next) => {
|
|
3153
3705
|
try {
|
|
3154
|
-
const { statusCode, ...response } = perfectPayload(
|
|
3706
|
+
const { statusCode, ...response } = perfectPayload(
|
|
3707
|
+
req?.body,
|
|
3708
|
+
|
|
3709
|
+
rule,
|
|
3710
|
+
);
|
|
3155
3711
|
|
|
3156
3712
|
if (+statusCode >= 200 && +statusCode <= 299) {
|
|
3157
3713
|
req.validatedBody = response?.validatedPayload;
|
|
@@ -3169,9 +3725,7 @@ export const validatePayload = ({ rule }) => {
|
|
|
3169
3725
|
|
|
3170
3726
|
#### Async ES Modules middleware example
|
|
3171
3727
|
|
|
3172
|
-
When your schema contains an asynchronous `customValidator`, the
|
|
3173
|
-
middleware itself must be `async` and `perfectPayloadAsync()` must be
|
|
3174
|
-
awaited:
|
|
3728
|
+
When your schema contains an asynchronous `customValidator`, the middleware itself must be `async` and `perfectPayloadAsync()` must be awaited:
|
|
3175
3729
|
|
|
3176
3730
|
```js
|
|
3177
3731
|
import { perfectPayloadAsync } from "perfect-payload";
|
|
@@ -3181,17 +3735,20 @@ export const validatePayloadAsync = ({ rule }) => {
|
|
|
3181
3735
|
try {
|
|
3182
3736
|
const { statusCode, ...response } = await perfectPayloadAsync(
|
|
3183
3737
|
req?.body,
|
|
3738
|
+
|
|
3184
3739
|
rule,
|
|
3185
3740
|
);
|
|
3186
3741
|
|
|
3187
3742
|
if (+statusCode >= 200 && +statusCode <= 299) {
|
|
3188
3743
|
req.validatedBody = response?.validatedPayload;
|
|
3744
|
+
|
|
3189
3745
|
next();
|
|
3190
3746
|
} else {
|
|
3191
3747
|
res.status(statusCode).json(response);
|
|
3192
3748
|
}
|
|
3193
3749
|
} catch (error) {
|
|
3194
3750
|
console.error("Error validating payload", error);
|
|
3751
|
+
|
|
3195
3752
|
res.status(500).json({ error: "Internal Server Error" });
|
|
3196
3753
|
}
|
|
3197
3754
|
};
|
|
@@ -3201,10 +3758,15 @@ export const validatePayloadAsync = ({ rule }) => {
|
|
|
3201
3758
|
Route usage:
|
|
3202
3759
|
|
|
3203
3760
|
```js
|
|
3761
|
+
|
|
3204
3762
|
router.post(
|
|
3763
|
+
|
|
3205
3764
|
"/payload-validation",
|
|
3765
|
+
|
|
3206
3766
|
validatePayloadAsync({ rule: <your validation rule json object> }),
|
|
3767
|
+
|
|
3207
3768
|
(req, res) => res.send("OK"),
|
|
3769
|
+
|
|
3208
3770
|
);
|
|
3209
3771
|
```
|
|
3210
3772
|
|
|
@@ -3216,7 +3778,11 @@ function validatePayload({ rule }) {
|
|
|
3216
3778
|
try {
|
|
3217
3779
|
const { perfectPayload } = await import("perfect-payload");
|
|
3218
3780
|
|
|
3219
|
-
const { statusCode, ...response } = perfectPayload(
|
|
3781
|
+
const { statusCode, ...response } = perfectPayload(
|
|
3782
|
+
req?.body,
|
|
3783
|
+
|
|
3784
|
+
rule,
|
|
3785
|
+
);
|
|
3220
3786
|
|
|
3221
3787
|
if (+statusCode >= 200 && +statusCode <= 299) {
|
|
3222
3788
|
req.validatedBody = response?.validatedPayload;
|