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 CHANGED
@@ -1,31 +1,39 @@
1
1
  # perfect-payload
2
2
 
3
- A lightweight JavaScript payload validation and transformation utility
4
- for API and JSON payloads.
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, synchronous
8
- transformation/sanitization, array constraints, and deeply nested
9
- object/array validation while keeping schemas simple and the package
10
- lightweight.
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` array constraints
20
- - Built-in `trim`, `lowercase`, and `uppercase` sanitization
21
+ - Array constraints with `minItems` and `maxItems`
22
+ - Built-in `trim`, `lowercase`, and `uppercase` transformations
21
23
  - Custom synchronous `transform(value, payload)`
22
- - Custom synchronous validators with `perfectPayload()`
23
- - Custom synchronous or asynchronous validators with
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
- - Clean three-argument API with validation options in one object
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.7.0
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
- v1.7.0 introduces two API-level improvements:
75
+ Express is **not** installed as a dependency of `perfect-payload`.
56
76
 
57
- 1. `unknownFields` gives explicit control over fields that are not
58
- declared in the validation schema: `"strip"`, `"allow"`, or
59
- `"reject"`.
60
- 2. `perfectPayload()` and `perfectPayloadAsync()` now use a clean
61
- three-argument API where custom response objects and other API
62
- options live inside one `options` object.
77
+ ### Import
63
78
 
64
79
  ```js
65
- const result = perfectPayload(payload, rules, {
66
- unknownFields: "reject",
67
- inValidPayloadResponse: {
68
- statusCode: 422,
69
- valid: false,
70
- message: "Payload validation failed",
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
- The default `unknownFields` mode is `"strip"`, preserving the previous
76
- validated-payload filtering behavior when no option is supplied.
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
- \- `path` identifies the exact field that failed validation.
678
+ - `path` identifies the exact field that failed validation.
203
679
 
204
- \- `code` provides a stable machine-readable validation error code.
680
+ - `code` provides a stable machine-readable validation error code.
205
681
 
206
- \- `message` provides a human-readable description of the validation
682
+ - `message` provides a human-readable description of the validation
207
683
 
208
684
  failure.
209
685
 
210
- \- Submitted payload values are not included in default error messages.
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
- Argument Required Description
227
-
228
- ---
229
-
230
- `data` No Payload/object to
231
- validate. Defaults to
232
- `{}`.
233
-
234
- `validationRules` No Validation schema.
235
- Defaults to `{}`.
236
-
237
- `options` No API-level configuration
238
- such as unknown-field
239
- handling and custom
240
- response objects.
241
-
242
- ---
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
- Value Behavior
331
-
332
- ---
333
-
334
- `"strip"` Removes unknown fields from
335
- `validatedPayload`. This is the
336
- default and preserves the existing
337
- behavior.
338
-
339
- `"allow"` Preserves unknown fields in
340
- `validatedPayload`.
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
- `"reject"` Rejects unknown fields with
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(payload, validationRules, options);
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
- perfectPayloadAsync(data, rules, options?) synchronous + asynchronous customValidator
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 after
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: /^[A-Z]{3}[0-9]{3}$/,
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
- `trim` Removes leading and trailing whitespace from strings
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
- `uppercase` Converts strings to uppercase
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
- *// "Kiran Poojary"*
2020
+ // "Kiran Poojary"
1503
2021
 
1504
2022
  console.log(payload.name);
1505
2023
 
1506
- *// " Kiran Poojary "*
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(/\s+/g, ""),
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
- *// 200*
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
- then processed by the normal validation rules. Exceptions thrown inside
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 username
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
- perfect-payload:- customValidator must be synchronous for attribute username
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
- `customValidator` functions without changing the behavior of
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 is
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. Transform the payload and run normal synchronous validation.
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 any synchronous validation error exists, phase 2 is skipped and the
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
- ***// Handle invalid email***
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
- \| Validation Rule \| Custom Error Property \|
2166
-
2167
- \| -------------------- \| ------------------------- \|
2168
-
2169
- \| `mandatory` \| `mandatoryError` \|
2170
-
2171
- \| `allowNull` \| `allowNullError` \|
2172
-
2173
- \| `allowEmptyObject` \| `emptyObjectError` \|
2174
-
2175
- \| `allowEmptyArray` \| `emptyArrayError` \|
2176
-
2177
- \| `elementConstraints` \| `elementConstraintsError` \|
2178
-
2179
- \| `regex` \| `regexError` \|
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
- \- show custom messages to API consumers
2868
+ - show custom messages to API consumers
2336
2869
 
2337
- \- use stable error codes in application logic
2870
+ - use stable error codes in application logic
2338
2871
 
2339
- \- change user-facing wording without changing programmatic error
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
- ***// validated fields***
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: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\\\\\.[a-zA-Z]{2,}$/,
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
- ***// validatePayload is the middleware that invokes perfectPayload()***
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(req?.body, rule);
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(req?.body, rule);
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;