perfect-payload 1.2.3 → 1.4.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.
Files changed (3) hide show
  1. package/README.md +928 -137
  2. package/index.js +140 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -3,6 +3,25 @@
3
3
  A lightweight JavaScript payload validation utility for validating API
4
4
  and JSON payloads with simple rule-based configuration.
5
5
 
6
+ `perfect-payload` supports structured validation errors, nested field
7
+ paths, synchronous custom validators, and synchronous payload
8
+ transformation/sanitization while keeping the validation schema simple.
9
+
10
+ ## Quick Links
11
+
12
+ - [Installation](#installation)
13
+ - [Basic Usage](#basic-usage)
14
+ - [Validation Rules](#validation-rules)
15
+ - [Transformations and
16
+ Sanitization](#transformations-and-sanitization)
17
+ - [Custom Validators](#customvalidator)
18
+ - [Error Codes](#error-codes)
19
+ - [Custom Error Messages](#custom-error-messages)
20
+ - [Nested Objects and Array Field
21
+ Paths](#nested-objects-and-array-field-paths)
22
+ - [Examples and Usage](#examples-and-usage)
23
+ - [Legacy API](#legacy-api)
24
+
6
25
  ## Installation
7
26
 
8
27
  ```bash
@@ -19,24 +38,30 @@ import { perfectPayload } from "perfect-payload";
19
38
 
20
39
  const payload = {
21
40
  name: "Kiran",
41
+
22
42
  email: "kiran@example.com",
43
+
23
44
  age: 29,
24
45
  };
25
46
 
26
47
  const validationRules = {
27
48
  name: {
28
49
  mandatory: true,
50
+
29
51
  type: "string",
30
52
  },
31
53
 
32
54
  email: {
33
55
  mandatory: true,
56
+
34
57
  type: "email",
35
58
  },
36
59
 
37
60
  age: {
38
61
  mandatory: true,
62
+
39
63
  type: "number",
64
+
40
65
  min: 18,
41
66
  },
42
67
  };
@@ -51,19 +76,31 @@ console.log(result);
51
76
  ```js
52
77
 
53
78
  {
79
+
54
80
    statusCode: 200,
81
+
55
82
    valid: true,
83
+
56
84
    validatedPayload: {
85
+
57
86
      name: "Kiran",
87
+
58
88
      email: "kiran@example.com",
89
+
59
90
      age: 29
91
+
60
92
    }
93
+
61
94
  }
62
95
  ```
63
96
 
64
- **Note:** The validatedPayload contains only the fields defined in the
97
+ Note: The validatedPayload contains only the fields
98
+ defined in the
99
+
65
100
  schema, automatically filtering out any extra attributes. You can use it
101
+
66
102
  to safely overwrite request.body or assign it to a new request property
103
+
67
104
  (such as validatedBody, sanitisedData or parsedBody).
68
105
 
69
106
  ### Invalid Response
@@ -71,16 +108,27 @@ to safely overwrite request.body or assign it to a new request property
71
108
  ```js
72
109
 
73
110
  {
111
+
74
112
    statusCode: 400,
113
+
75
114
    valid: false,
115
+
76
116
    message: "One or more attribute values are invalid",
117
+
77
118
    errors: [
119
+
78
120
      {
121
+
79
122
        path: "email",
123
+
80
124
        code: "INVALID_EMAIL",
125
+
81
126
        message: "Invalid email format for attribute email"
127
+
82
128
      }
129
+
83
130
    ]
131
+
84
132
  }
85
133
  ```
86
134
 
@@ -89,17 +137,25 @@ Each error returned by `perfectPayload()` contains:
89
137
  ```js
90
138
 
91
139
  {
140
+
92
141
    path: "field.path",
142
+
93
143
    code: "ERROR_CODE",
144
+
94
145
    message: "Human readable validation message"
146
+
95
147
  }
96
148
  ```
97
149
 
98
- - `path` identifies the exact field that failed validation.
99
- - `code` provides a stable machine-readable validation error code.
100
- - `message` provides a human-readable description of the validation
101
- failure.
102
- - Submitted payload values are not included in default error messages.
150
+ \- `path` identifies the exact field that failed validation.
151
+
152
+ \- `code` provides a stable machine-readable validation error code.
153
+
154
+ \- `message` provides a human-readable description of the validation
155
+
156
+ failure.
157
+
158
+ \- Submitted payload values are not included in default error messages.
103
159
 
104
160
  ## Legacy API
105
161
 
@@ -110,9 +166,11 @@ import { perfectPayloadV1 } from "perfect-payload";
110
166
  ```
111
167
 
112
168
  `perfectPayloadV1()` is deprecated and will no longer be supported after
113
- **\*\*March 31, 2027\*\***.
169
+
170
+ March 31, 2027.
114
171
 
115
172
  Existing applications can continue using it during the migration period,
173
+
116
174
  but all new implementations should use:
117
175
 
118
176
  ```js
@@ -131,26 +189,33 @@ while the new `perfectPayload()` API returns structured errors:
131
189
  errors: [
132
190
  {
133
191
  path: "email",
192
+
134
193
  code: "INVALID_EMAIL",
194
+
135
195
  message: "Invalid email format for attribute email",
136
196
  },
137
197
  ];
138
198
  ```
139
199
 
140
- **Note:** If an inValidPayloadResponse is provided, the system returns
200
+ Note: If an inValidPayloadResponse is provided, the
201
+ system returns
202
+
141
203
  it alongside an automatically generated errors property. Do not include
204
+
142
205
  your own errors attribute inside the custom inValidPayloadResponse
206
+
143
207
  object.
144
208
 
145
209
  ## Validation Rules
146
210
 
147
- `perfectPayload()` supports the following validation rules.
211
+ `perfectPayload()` supports validation, nested-schema,
212
+ custom-validation, and transformation rules.
148
213
 
149
214
  ### `mandatory`
150
215
 
151
- Marks a field as required(even empty string also not allowed)
216
+ Marks a field as required. An empty string is also treated as missing.
152
217
 
153
- **Default:** `false`, the field is not required.
218
+ Default: `false`, the field is not required.
154
219
 
155
220
  ```js
156
221
  const rules = {
@@ -168,7 +233,7 @@ Error code: `REQUIRED`
168
233
 
169
234
  Controls whether `null` values are accepted.
170
235
 
171
- **Default:** `true` , `null` values are allowed.
236
+ Default: `true`, `null` values are allowed.
172
237
 
173
238
  Example:
174
239
 
@@ -188,7 +253,7 @@ Error code: `NULL_NOT_ALLOWED`
188
253
 
189
254
  Controls whether an empty object `{}` is accepted.
190
255
 
191
- **Default:** `true`, empty objects are allowed.
256
+ Default: `true`, empty objects are allowed.
192
257
 
193
258
  Example:
194
259
 
@@ -196,6 +261,7 @@ Example:
196
261
  const rules = {
197
262
  address: {
198
263
  type: "object",
264
+
199
265
  allowEmptyObject: false,
200
266
  },
201
267
  };
@@ -209,7 +275,7 @@ Error code: `EMPTY_OBJECT_NOT_ALLOWED`
209
275
 
210
276
  Controls whether an empty array `[]` is accepted.
211
277
 
212
- **Default:** `true`, empty arrays are allowed.
278
+ Default: `true`, empty arrays are allowed.
213
279
 
214
280
  Example:
215
281
 
@@ -217,6 +283,7 @@ Example:
217
283
  const rules = {
218
284
  products: {
219
285
  type: "array",
286
+
220
287
  allowEmptyArray: false,
221
288
  },
222
289
  };
@@ -235,18 +302,31 @@ Supported values:
235
302
  ```text
236
303
 
237
304
  number
305
+
238
306
  string
307
+
239
308
  boolean
309
+
240
310
  email
311
+
241
312
  url
313
+
242
314
  enum
315
+
243
316
  uuid
317
+
244
318
  uuidv1
319
+
245
320
  uuidv3
321
+
246
322
  uuidv4
323
+
247
324
  uuidv5
325
+
248
326
  objectId
327
+
249
328
  array
329
+
250
330
  object
251
331
  ```
252
332
 
@@ -257,9 +337,11 @@ const rules = {
257
337
  age: {
258
338
  type: "number",
259
339
  },
340
+
260
341
  email: {
261
342
  type: "email",
262
343
  },
344
+
263
345
  active: {
264
346
  type: "boolean",
265
347
  },
@@ -280,6 +362,7 @@ Example:
280
362
  const rules = {
281
363
  status: {
282
364
  type: "enum",
365
+
283
366
  enumValues: ["active", "inactive", "blocked", 1, 0],
284
367
  },
285
368
  };
@@ -299,6 +382,7 @@ Example:
299
382
  const rules = {
300
383
  status: {
301
384
  type: "enum",
385
+
302
386
  enumValues: ["active", "inactive", "blocked"],
303
387
  },
304
388
  };
@@ -311,14 +395,23 @@ Possible error codes for types:
311
395
  ```text
312
396
 
313
397
  INVALID_TYPE
398
+
314
399
  INVALID_EMAIL
400
+
315
401
  INVALID_URL
402
+
316
403
  INVALID_ENUM
404
+
317
405
  INVALID_UUID
406
+
318
407
  INVALID_UUID_V1
408
+
319
409
  INVALID_UUID_V3
410
+
320
411
  INVALID_UUID_V4
412
+
321
413
  INVALID_UUID_V5
414
+
322
415
  INVALID_OBJECT_ID
323
416
  ```
324
417
 
@@ -328,7 +421,7 @@ INVALID_OBJECT_ID
328
421
 
329
422
  Validates a value using a regular expression.
330
423
 
331
- **Default:** Not applied when omitted.
424
+ Default: Not applied when omitted.
332
425
 
333
426
  Example:
334
427
 
@@ -336,6 +429,7 @@ Example:
336
429
  const rules = {
337
430
  employeeCode: {
338
431
  type: "string",
432
+
339
433
  regex: /^[A-Z]{3}[0-9]{3}$/,
340
434
  },
341
435
  };
@@ -349,7 +443,7 @@ Error code: `REGEX_MISMATCH`
349
443
 
350
444
  Defines the minimum allowed string length.
351
445
 
352
- **Default:** Not applied when omitted.
446
+ Default: Not applied when omitted.
353
447
 
354
448
  Example:
355
449
 
@@ -357,6 +451,7 @@ Example:
357
451
  const rules = {
358
452
  username: {
359
453
  type: "string",
454
+
360
455
  minLength: 5,
361
456
  },
362
457
  };
@@ -370,7 +465,7 @@ Error code: `MIN_LENGTH`
370
465
 
371
466
  Defines the maximum allowed string length.
372
467
 
373
- **Default:** Not applied when omitted.
468
+ Default: Not applied when omitted.
374
469
 
375
470
  Example:
376
471
 
@@ -378,6 +473,7 @@ Example:
378
473
  const rules = {
379
474
  username: {
380
475
  type: "string",
476
+
381
477
  maxLength: 20,
382
478
  },
383
479
  };
@@ -391,7 +487,8 @@ Error code: `MAX_LENGTH`
391
487
 
392
488
  Prevents decimal numbers.
393
489
 
394
- **Default:** `false` both integer and decimal numbers are allowed.
490
+ Default: `false`; both integer and decimal numbers are
491
+ allowed.
395
492
 
396
493
  Example:
397
494
 
@@ -399,6 +496,7 @@ Example:
399
496
  const rules = {
400
497
  quantity: {
401
498
  type: "number",
499
+
402
500
  preventDecimal: true,
403
501
  },
404
502
  };
@@ -412,7 +510,7 @@ Error code: `DECIMAL_NOT_ALLOWED`
412
510
 
413
511
  Defines the minimum allowed numeric value.
414
512
 
415
- **Default:** Not applied when omitted.
513
+ Default: Not applied when omitted.
416
514
 
417
515
  Example:
418
516
 
@@ -420,6 +518,7 @@ Example:
420
518
  const rules = {
421
519
  age: {
422
520
  type: "number",
521
+
423
522
  min: 18,
424
523
  },
425
524
  };
@@ -433,7 +532,7 @@ Error code: `MIN_VALUE`
433
532
 
434
533
  Defines the maximum allowed numeric value.
435
534
 
436
- **Default:** Not applied when omitted.
535
+ Default: Not applied when omitted.
437
536
 
438
537
  Example:
439
538
 
@@ -441,6 +540,7 @@ Example:
441
540
  const rules = {
442
541
  quantity: {
443
542
  type: "number",
543
+
444
544
  max: 100,
445
545
  },
446
546
  };
@@ -454,7 +554,7 @@ Error code: `MAX_VALUE`
454
554
 
455
555
  Defines the allowed numeric range.
456
556
 
457
- **Default:** Not applied when omitted.
557
+ Default: Not applied when omitted.
458
558
 
459
559
  Example:
460
560
 
@@ -462,6 +562,7 @@ Example:
462
562
  const rules = {
463
563
  marks: {
464
564
  type: "number",
565
+
465
566
  range: "0-100",
466
567
  },
467
568
  };
@@ -481,8 +582,10 @@ Example:
481
582
  const rules = {
482
583
  marks: {
483
584
  type: "array",
585
+
484
586
  elementConstraints: {
485
587
  type: "number",
588
+
486
589
  range: "0-100",
487
590
  },
488
591
  },
@@ -496,13 +599,18 @@ Example error:
496
599
  {
497
600
 
498
601
    path: "marks[2]",
602
+
499
603
    code: "OUT_OF_RANGE",
604
+
500
605
    message:
606
+
501
607
      "Attribute marks[2] should have a value between 0 and 100"
608
+
502
609
  }
503
610
  ```
504
611
 
505
612
  When `elementConstraintsError` is explicitly provided, the error code
613
+
506
614
  is: `INVALID_ARRAY_ELEMENT`
507
615
 
508
616
  Example:
@@ -511,9 +619,11 @@ Example:
511
619
  const rules = {
512
620
  marks: {
513
621
  type: "array",
622
+
514
623
  elementConstraints: {
515
624
  type: "number",
516
625
  },
626
+
517
627
  elementConstraintsError: "Every marks element must be a number",
518
628
  },
519
629
  };
@@ -531,17 +641,22 @@ Example:
531
641
  const rules = {
532
642
  address: {
533
643
  type: "object",
644
+
534
645
  objectAttr: {
535
646
  city: {
536
647
  mandatory: true,
648
+
537
649
  type: "string",
538
650
  },
651
+
539
652
  location: {
540
653
  type: "object",
654
+
541
655
  objectAttr: {
542
656
  latitude: {
543
657
  type: "number",
544
658
  },
659
+
545
660
  longitude: {
546
661
  type: "number",
547
662
  },
@@ -557,10 +672,15 @@ Nested errors include the complete field path:
557
672
  ```js
558
673
 
559
674
  {
675
+
560
676
    path: "address.location.latitude",
677
+
561
678
    code: "INVALID_TYPE",
679
+
562
680
    message:
681
+
563
682
      "Invalid type for attribute address.location.latitude, required number value"
683
+
564
684
  }
565
685
  ```
566
686
 
@@ -576,11 +696,14 @@ Example:
576
696
  const rules = {
577
697
  minSalary: {
578
698
  type: "number",
699
+
579
700
  dependency: {
580
701
  maxSalary: {
581
702
  setDependencyRule: (minSalary, maxSalary) => ({
582
703
  type: "number",
704
+
583
705
  min: minSalary + 1,
706
+
584
707
  minError: "maxSalary must be more than minSalary",
585
708
  }),
586
709
  },
@@ -596,171 +719,540 @@ Example error:
596
719
  {
597
720
 
598
721
    path: "maxSalary",
722
+
599
723
    code: "MIN_VALUE",
724
+
600
725
    message:
726
+
601
727
      "maxSalary must be more than minSalary"
728
+
602
729
  }
603
730
  ```
604
731
 
605
- ## Error Codes
732
+ ---
606
733
 
607
- `perfectPayload()` currently exposes the following machine-readable
608
- validation error codes:
734
+ ### Transformations and Sanitization
609
735
 
610
- ```text
736
+ `perfectPayload()` can transform a field before its validation rules
737
+ run. The transformed value is returned in `validatedPayload`, while the
738
+ original input object is not mutated.
611
739
 
612
- REQUIRED
613
- NULL_NOT_ALLOWED
614
- EMPTY_OBJECT_NOT_ALLOWED
615
- EMPTY_ARRAY_NOT_ALLOWED
616
- INVALID_ARRAY_ELEMENT
617
- REGEX_MISMATCH
618
- INVALID_TYPE
619
- INVALID_EMAIL
620
- INVALID_URL
621
- INVALID_ENUM
622
- INVALID_UUID
623
- INVALID_UUID_V1
624
- INVALID_UUID_V3
625
- INVALID_UUID_V4
626
- INVALID_UUID_V5
627
- INVALID_OBJECT_ID
628
- MIN_LENGTH
629
- MAX_LENGTH
630
- DECIMAL_NOT_ALLOWED
631
- MIN_VALUE
632
- MAX_VALUE
633
- OUT_OF_RANGE
634
- ```
740
+ Supported transformation rules:
635
741
 
636
- These codes are designed for programmatic handling while `message`
637
- remains suitable for human-readable API responses.
742
+ Rule Purpose
638
743
 
639
- For example:
744
+ ---
640
745
 
641
- ```js
642
- const result = perfectPayload(payload, validationRules);
746
+ `trim` Removes leading and trailing whitespace from strings
747
+ `lowercase` Converts strings to lowercase
748
+ `uppercase` Converts strings to uppercase
749
+ `transform` Runs a custom synchronous transformation function
643
750
 
644
- if (!result.valid) {
645
- const emailError = result.errors.find(
646
- (error) => error.code === "INVALID_EMAIL",
647
- );
751
+ Transformations always run in this fixed order, regardless of the order
752
+ in which the rule properties are written:
648
753
 
649
- if (emailError) {
650
- // Handle invalid email
651
- }
652
- }
754
+ ```text
755
+ trim
756
+
757
+ lowercase
758
+
759
+ uppercase
760
+
761
+ transform(value, payload)
762
+
763
+ validation rules
764
+
765
+ customValidator
766
+
767
+ validatedPayload
653
768
  ```
654
769
 
655
- ## Custom Error Messages
770
+ #### `trim`
656
771
 
657
- Every validation rule can use its corresponding custom error message.
772
+ ```js
773
+ const payload = {
774
+ name: " Kiran Poojary ",
775
+ };
658
776
 
659
- Custom messages replace the default human-readable `message` while
660
- keeping the same structured error format:
777
+ const rules = {
778
+ name: {
779
+ type: "string",
780
+ trim: true,
781
+ },
782
+ };
661
783
 
662
- ```js
784
+ const result = perfectPayload(payload, rules);
663
785
 
664
- {
665
-   path: "email",
666
-   code: "INVALID_EMAIL",
667
-   message: "Email address is invalid"
668
- }
786
+ console.log(result.validatedPayload.name);
787
+ // "Kiran Poojary"
788
+
789
+ console.log(payload.name);
790
+ // " Kiran Poojary "
669
791
  ```
670
792
 
671
- Example:
793
+ `trim` applies only to string values. Non-string values are left
794
+ unchanged.
795
+
796
+ #### `lowercase`
672
797
 
673
798
  ```js
674
799
  const rules = {
675
800
  email: {
676
- mandatory: true,
801
+ trim: true,
802
+ lowercase: true,
677
803
  type: "email",
678
- mandatoryError: "Email is required",
679
- typeError: "Email address is invalid",
680
804
  },
681
805
  };
682
806
  ```
683
807
 
684
- If `email` is missing:
808
+ For `" KIRAN@EXAMPLE.COM "`, the validated value becomes
809
+ `"kiran@example.com"`.
685
810
 
686
- ```js
811
+ #### `uppercase`
687
812
 
688
- {
689
-   path: "email",
690
-   code: "REQUIRED",
691
-   message: "Email is required"
692
- }
813
+ ```js
814
+ const rules = {
815
+ countryCode: {
816
+ type: "string",
817
+ uppercase: true,
818
+ },
819
+ };
693
820
  ```
694
821
 
695
- If `email` is present but invalid:
822
+ For `"in"`, the validated value becomes `"IN"`.
696
823
 
697
- ```js
824
+ `lowercase: true` and `uppercase: true` cannot be enabled together for
825
+ the same field. Doing so throws a schema configuration error.
698
826
 
699
- {
700
-   path: "email",
701
-   code: "INVALID_EMAIL",
702
-   message: "Email address is invalid"
703
- }
827
+ #### `transform`
828
+
829
+ Use `transform` when the built-in string transformations are not enough.
830
+
831
+ ```js
832
+ const rules = {
833
+ phone: {
834
+ type: "string",
835
+ transform: (value) => value.replace(/\s+/g, ""),
836
+ },
837
+ };
704
838
  ```
705
839
 
706
- ### Supported Custom Error Properties
840
+ For `"98765 43210"`, the validated value becomes `"9876543210"`.
707
841
 
708
- \| Validation Rule      \| Custom Error Property     \|
842
+ The transformer receives two arguments:
709
843
 
710
- \| -------------------- \| ------------------------- \|
844
+ ```js
845
+ transform: (value, payload) => {
846
+ return value;
847
+ };
848
+ ```
849
+
850
+ - `value` is the field value after the built-in transformations have
851
+ run.
852
+ - `payload` is the current payload/object being validated.
711
853
 
712
- \| `mandatory`          \| `mandatoryError`          \|
854
+ This makes cross-field transformations possible:
713
855
 
714
- \| `allowNull`          \| `allowNullError`          \|
856
+ ```js
857
+ const payload = {
858
+ amount: 100,
859
+ multiplier: 2,
860
+ };
861
+
862
+ const rules = {
863
+ amount: {
864
+ transform: (value, payload) => value * payload.multiplier,
865
+ type: "number",
866
+ },
867
+ multiplier: {
868
+ type: "number",
869
+ },
870
+ };
715
871
 
716
- \| `allowEmptyObject`   \| `emptyObjectError`        \|
872
+ const result = perfectPayload(payload, rules);
717
873
 
718
- \| `allowEmptyArray`    \| `emptyArrayError`         \|
874
+ console.log(result.validatedPayload.amount);
875
+ // 200
876
+ ```
719
877
 
720
- \| `elementConstraints` \| `elementConstraintsError` \|
878
+ A custom transformer may also change the data type before validation:
721
879
 
722
- \| `regex`              \| `regexError`              \|
880
+ ```js
881
+ const rules = {
882
+ quantity: {
883
+ transform: (value) => Number(value),
884
+ type: "number",
885
+ min: 1,
886
+ max: 100,
887
+ },
888
+ };
889
+ ```
723
890
 
724
- \| `type`               \| `typeError`               \|
891
+ The transformed value is validated by the normal validation rules and is
892
+ also the value received by `customValidator`.
725
893
 
726
- \| `minLength`          \| `minLengthError`          \|
894
+ Transformations work inside `objectAttr` and `elementConstraints`, and
895
+ transformed nested/array values are preserved in `validatedPayload`.
727
896
 
728
- \| `maxLength`          \| `maxLengthError`          \|
897
+ ```js
898
+ const rules = {
899
+ profile: {
900
+ type: "object",
901
+ objectAttr: {
902
+ name: {
903
+ trim: true,
904
+ uppercase: true,
905
+ type: "string",
906
+ },
907
+ },
908
+ },
909
+ tags: {
910
+ type: "array",
911
+ elementConstraints: {
912
+ trim: true,
913
+ lowercase: true,
914
+ type: "string",
915
+ },
916
+ },
917
+ };
918
+ ```
729
919
 
730
- \| `preventDecimal`     \| `preventDecimalError`     \|
920
+ Missing optional fields are not transformed. An input value of `null` is
921
+ not passed to transformation functions; null handling remains controlled
922
+ by `allowNull`.
731
923
 
732
- \| `min`                \| `minError`                \|
924
+ **Important:** `transform` is synchronous. A non-function transformer,
925
+ an `async` transformer, or a transformer that returns a Promise is not
926
+ supported and throws an error. Exceptions thrown inside the transformer
927
+ propagate to the caller.
733
928
 
734
- \| `max`                \| `maxError`                \|
929
+ ### `customValidator`
735
930
 
736
- \| `range`              \| `rangeError`              \|
931
+ Allows you to define custom synchronous validation logic for a field
932
+ when the built-in validation rules are not enough.
737
933
 
738
- ### Example with Multiple Custom Errors
934
+ The validator receives the field value and the current payload/object
935
+ being validated:
739
936
 
740
937
  ```js
741
- const payload = {
742
- username: "ab",
743
- age: 15,
744
- score: 120,
938
+ customValidator: (value, payload) => {
939
+ return true;
745
940
  };
941
+ ```
942
+
943
+ The validator must return `true` to pass validation. Any other return
944
+ value causes validation to fail.
945
+
946
+ Example:
746
947
 
948
+ ```js
747
949
  const rules = {
748
950
  username: {
749
951
  mandatory: true,
952
+
750
953
  type: "string",
751
- minLength: 3,
752
- mandatoryError: "Username is required",
753
- typeError: "Username must be a string",
754
- minLengthError: "Username must contain at least 3 characters",
954
+
955
+ customValidator: (value) => {
956
+ return !value.toLowerCase().includes("admin");
957
+ },
958
+
959
+ customValidatorCode: "RESERVED_USERNAME",
960
+
961
+ customValidatorError: "Username cannot contain admin",
755
962
  },
756
- age: {
757
- type: "number",
963
+ };
964
+ ```
965
+
966
+ For this payload:
967
+
968
+ ```js
969
+ const payload = {
970
+ username: "admin_kiran",
971
+ };
972
+ ```
973
+
974
+ The validation error is:
975
+
976
+ ```js
977
+
978
+ {
979
+
980
+   path: "username",
981
+
982
+   code: "RESERVED_USERNAME",
983
+
984
+   message: "Username cannot contain admin"
985
+
986
+ }
987
+ ```
988
+
989
+ If `customValidatorCode` and `customValidatorError` are not provided,
990
+ the default error is:
991
+
992
+ ```js
993
+
994
+ {
995
+
996
+   path: "username",
997
+
998
+   code: "CUSTOM_VALIDATION_FAILED",
999
+
1000
+   message: "Custom validation failed for attribute username"
1001
+
1002
+ }
1003
+ ```
1004
+
1005
+ The current payload/object being validated can be used as the second
1006
+ argument when required:
1007
+
1008
+ ```js
1009
+ const rules = {
1010
+ limit: {
1011
+ type: "number",
1012
+ },
1013
+
1014
+ amount: {
1015
+ type: "number",
1016
+
1017
+ customValidator: (value, payload) => {
1018
+ return value <= payload.limit;
1019
+ },
1020
+
1021
+ customValidatorCode: "LIMIT_EXCEEDED",
1022
+
1023
+ customValidatorError: "Amount cannot exceed limit",
1024
+ },
1025
+ };
1026
+ ```
1027
+
1028
+ `customValidator` also works with nested objects and array
1029
+ `elementConstraints`. The generated structured error automatically
1030
+ contains the corresponding nested or array path.
1031
+
1032
+ Important: `customValidator` is synchronous. An `async`
1033
+ validator or a validator that returns a Promise is not supported and
1034
+ throws an error. Asynchronous validation is not part of this feature.
1035
+
1036
+ Error code when no custom code is provided: `CUSTOM_VALIDATION_FAILED`
1037
+
1038
+ ## Error Codes
1039
+
1040
+ `perfectPayload()` currently exposes the following machine-readable
1041
+
1042
+ validation error codes:
1043
+
1044
+ ```text
1045
+
1046
+ REQUIRED
1047
+
1048
+ NULL_NOT_ALLOWED
1049
+
1050
+ EMPTY_OBJECT_NOT_ALLOWED
1051
+
1052
+ EMPTY_ARRAY_NOT_ALLOWED
1053
+
1054
+ INVALID_ARRAY_ELEMENT
1055
+
1056
+ REGEX_MISMATCH
1057
+
1058
+ INVALID_TYPE
1059
+
1060
+ INVALID_EMAIL
1061
+
1062
+ INVALID_URL
1063
+
1064
+ INVALID_ENUM
1065
+
1066
+ INVALID_UUID
1067
+
1068
+ INVALID_UUID_V1
1069
+
1070
+ INVALID_UUID_V3
1071
+
1072
+ INVALID_UUID_V4
1073
+
1074
+ INVALID_UUID_V5
1075
+
1076
+ INVALID_OBJECT_ID
1077
+
1078
+ MIN_LENGTH
1079
+
1080
+ MAX_LENGTH
1081
+
1082
+ DECIMAL_NOT_ALLOWED
1083
+
1084
+ MIN_VALUE
1085
+
1086
+ MAX_VALUE
1087
+
1088
+ OUT_OF_RANGE
1089
+ ```
1090
+
1091
+ These codes are designed for programmatic handling while `message`
1092
+
1093
+ remains suitable for human-readable API responses.
1094
+
1095
+ For example:
1096
+
1097
+ ```js
1098
+
1099
+ const result = perfectPayload(payload, validationRules);
1100
+
1101
+ if (!result.valid) {
1102
+
1103
+   const emailError = result.errors.find(
1104
+
1105
+     (error) => error.code === "INVALID_EMAIL",
1106
+
1107
+   );
1108
+
1109
+   if (emailError) {
1110
+
1111
+     **// Handle invalid email**
1112
+
1113
+   }
1114
+
1115
+ }
1116
+ ```
1117
+
1118
+ ## Custom Error Messages
1119
+
1120
+ Every validation rule can use its corresponding custom error message.
1121
+
1122
+ Custom messages replace the default human-readable `message` while
1123
+
1124
+ keeping the same structured error format:
1125
+
1126
+ ```js
1127
+
1128
+ {
1129
+
1130
+   path: "email",
1131
+
1132
+   code: "INVALID_EMAIL",
1133
+
1134
+   message: "Email address is invalid"
1135
+
1136
+ }
1137
+ ```
1138
+
1139
+ Example:
1140
+
1141
+ ```js
1142
+ const rules = {
1143
+ email: {
1144
+ mandatory: true,
1145
+
1146
+ type: "email",
1147
+
1148
+ mandatoryError: "Email is required",
1149
+
1150
+ typeError: "Email address is invalid",
1151
+ },
1152
+ };
1153
+ ```
1154
+
1155
+ If `email` is missing:
1156
+
1157
+ ```js
1158
+
1159
+ {
1160
+
1161
+   path: "email",
1162
+
1163
+   code: "REQUIRED",
1164
+
1165
+   message: "Email is required"
1166
+
1167
+ }
1168
+ ```
1169
+
1170
+ If `email` is present but invalid:
1171
+
1172
+ ```js
1173
+
1174
+ {
1175
+
1176
+   path: "email",
1177
+
1178
+   code: "INVALID_EMAIL",
1179
+
1180
+   message: "Email address is invalid"
1181
+
1182
+ }
1183
+ ```
1184
+
1185
+ ### Supported Custom Error Properties
1186
+
1187
+ \| Validation Rule \| Custom Error Property \|
1188
+
1189
+ \| -------------------- \| ------------------------- \|
1190
+
1191
+ \| `mandatory` \| `mandatoryError` \|
1192
+
1193
+ \| `allowNull` \| `allowNullError` \|
1194
+
1195
+ \| `allowEmptyObject` \| `emptyObjectError` \|
1196
+
1197
+ \| `allowEmptyArray` \| `emptyArrayError` \|
1198
+
1199
+ \| `elementConstraints` \| `elementConstraintsError` \|
1200
+
1201
+ \| `regex` \| `regexError` \|
1202
+
1203
+ \| `type` \| `typeError` \|
1204
+
1205
+ \| `minLength` \| `minLengthError` \|
1206
+
1207
+ \| `maxLength` \| `maxLengthError` \|
1208
+
1209
+ \| `preventDecimal` \| `preventDecimalError` \|
1210
+
1211
+ \| `min` \| `minError` \|
1212
+
1213
+ \| `max` \| `maxError` \|
1214
+
1215
+ \| `range` \| `rangeError` \|
1216
+
1217
+ ### Example with Multiple Custom Errors
1218
+
1219
+ ```js
1220
+ const payload = {
1221
+ username: "ab",
1222
+
1223
+ age: 15,
1224
+
1225
+ score: 120,
1226
+ };
1227
+
1228
+ const rules = {
1229
+ username: {
1230
+ mandatory: true,
1231
+
1232
+ type: "string",
1233
+
1234
+ minLength: 3,
1235
+
1236
+ mandatoryError: "Username is required",
1237
+
1238
+ typeError: "Username must be a string",
1239
+
1240
+ minLengthError: "Username must contain at least 3 characters",
1241
+ },
1242
+
1243
+ age: {
1244
+ type: "number",
1245
+
758
1246
  min: 18,
1247
+
759
1248
  minError: "Age must be at least 18",
760
1249
  },
1250
+
761
1251
  score: {
762
1252
  type: "number",
1253
+
763
1254
  range: "0-100",
1255
+
764
1256
  rangeError: "Score must be between 0 and 100",
765
1257
  },
766
1258
  };
@@ -773,30 +1265,55 @@ Example result:
773
1265
  ```js
774
1266
 
775
1267
  {
1268
+
776
1269
    statusCode: 400,
1270
+
777
1271
    valid: false,
1272
+
778
1273
    message:
1274
+
779
1275
      "One or more attribute values are invalid",
1276
+
780
1277
    errors: [
1278
+
781
1279
      {
1280
+
782
1281
        path: "username",
1282
+
783
1283
        code: "MIN_LENGTH",
1284
+
784
1285
        message:
1286
+
785
1287
          "Username must contain at least 3 characters"
1288
+
786
1289
      },
1290
+
787
1291
      {
1292
+
788
1293
        path: "age",
1294
+
789
1295
        code: "MIN_VALUE",
1296
+
790
1297
        message:
1298
+
791
1299
          "Age must be at least 18"
1300
+
792
1301
      },
1302
+
793
1303
      {
1304
+
794
1305
        path: "score",
1306
+
795
1307
        code: "OUT_OF_RANGE",
1308
+
796
1309
        message:
1310
+
797
1311
          "Score must be between 0 and 100"
1312
+
798
1313
      }
1314
+
799
1315
    ]
1316
+
800
1317
  }
801
1318
  ```
802
1319
 
@@ -812,7 +1329,9 @@ For example:
812
1329
  const rules = {
813
1330
  age: {
814
1331
  type: "number",
1332
+
815
1333
  min: 18,
1334
+
816
1335
  minError: "You must be 18 or older",
817
1336
  },
818
1337
  };
@@ -823,22 +1342,30 @@ Still returns:
823
1342
  ```js
824
1343
 
825
1344
  {
1345
+
826
1346
    path: "age",
1347
+
827
1348
    code: "MIN_VALUE",
1349
+
828
1350
    message: "You must be 18 or older"
1351
+
829
1352
  }
830
1353
  ```
831
1354
 
832
1355
  This makes it possible to:
833
1356
 
834
- - show custom messages to API consumers
835
- - use stable error codes in application logic
836
- - change user-facing wording without changing programmatic error
837
- handling
1357
+ \- show custom messages to API consumers
1358
+
1359
+ \- use stable error codes in application logic
1360
+
1361
+ \- change user-facing wording without changing programmatic error
1362
+
1363
+ handling
838
1364
 
839
1365
  ## Custom Response Objects
840
1366
 
841
1367
  `perfectPayload()` allows you to customize both the valid and invalid
1368
+
842
1369
  response objects.
843
1370
 
844
1371
  The third argument is the custom valid response.
@@ -852,7 +1379,9 @@ Example:
852
1379
  ```js
853
1380
  const customValidResponse = {
854
1381
  statusCode: 201,
1382
+
855
1383
  valid: true,
1384
+
856
1385
  message: "Payload validated successfully",
857
1386
  };
858
1387
 
@@ -864,14 +1393,23 @@ When validation succeeds, `validatedPayload` is automatically added:
864
1393
  ```js
865
1394
 
866
1395
  {
1396
+
867
1397
    statusCode: 201,
1398
+
868
1399
    valid: true,
1400
+
869
1401
    message: "Payload validated successfully",
1402
+
870
1403
    validatedPayload: {
1404
+
871
1405
      name: "Kiran",
1406
+
872
1407
      email: "kiran@example.com",
1408
+
873
1409
      age: 29
1410
+
874
1411
    }
1412
+
875
1413
  }
876
1414
  ```
877
1415
 
@@ -882,14 +1420,19 @@ Example:
882
1420
  ```js
883
1421
  const customInvalidResponse = {
884
1422
  statusCode: 422,
1423
+
885
1424
  valid: false,
1425
+
886
1426
  message: "Payload validation failed",
887
1427
  };
888
1428
 
889
1429
  const result = perfectPayload(
890
1430
  payload,
1431
+
891
1432
  validationRules,
1433
+
892
1434
  undefined,
1435
+
893
1436
  customInvalidResponse,
894
1437
  );
895
1438
  ```
@@ -901,16 +1444,27 @@ When validation fails, `errors` is automatically added:
901
1444
  {
902
1445
 
903
1446
    statusCode: 422,
1447
+
904
1448
    valid: false,
1449
+
905
1450
    message: "Payload validation failed",
1451
+
906
1452
    errors: [
1453
+
907
1454
      {
1455
+
908
1456
        path: "email",
1457
+
909
1458
        code: "INVALID_EMAIL",
1459
+
910
1460
        message:
1461
+
911
1462
          "Invalid email format for attribute email"
1463
+
912
1464
      }
1465
+
913
1466
    ]
1467
+
914
1468
  }
915
1469
  ```
916
1470
 
@@ -919,25 +1473,33 @@ When validation fails, `errors` is automatically added:
919
1473
  ```js
920
1474
  const customValidResponse = {
921
1475
  statusCode: 201,
1476
+
922
1477
  valid: true,
1478
+
923
1479
  message: "CUSTOM_VALID_RESPONSE",
924
1480
  };
925
1481
 
926
1482
  const customInvalidResponse = {
927
1483
  statusCode: 422,
1484
+
928
1485
  valid: false,
1486
+
929
1487
  message: "CUSTOM_INVALID_RESPONSE",
930
1488
  };
931
1489
 
932
1490
  const result = perfectPayload(
933
1491
  payload,
1492
+
934
1493
  validationRules,
1494
+
935
1495
  customValidResponse,
1496
+
936
1497
  customInvalidResponse,
937
1498
  );
938
1499
  ```
939
1500
 
940
1501
  The response object you provide is preserved, while `perfectPayload()`
1502
+
941
1503
  automatically adds either:
942
1504
 
943
1505
  ```text
@@ -957,16 +1519,23 @@ for failed validation.
957
1519
  ## Default Responses
958
1520
 
959
1521
  If no custom response objects are provided, the default valid response
1522
+
960
1523
  is:
961
1524
 
962
1525
  ```js
963
1526
 
964
1527
  {
1528
+
965
1529
    statusCode: 200,
1530
+
966
1531
    valid: true,
1532
+
967
1533
    validatedPayload: {
968
-     // validated fields
1534
+
1535
+     **// validated fields**
1536
+
969
1537
    }
1538
+
970
1539
  }
971
1540
  ```
972
1541
 
@@ -975,25 +1544,38 @@ The default invalid response is:
975
1544
  ```js
976
1545
 
977
1546
  {
1547
+
978
1548
    statusCode: 400,
1549
+
979
1550
    valid: false,
1551
+
980
1552
    message: "One or more attribute values are invalid",
1553
+
981
1554
    errors: [
1555
+
982
1556
      {
1557
+
983
1558
        path: "field",
1559
+
984
1560
        code: "ERROR_CODE",
1561
+
985
1562
        message: "Validation error message"
1563
+
986
1564
      }
1565
+
987
1566
    ]
1567
+
988
1568
  }
989
1569
  ```
990
1570
 
991
1571
  ## Nested Objects and Array Field Paths
992
1572
 
993
1573
  `perfectPayload()` returns the exact location of a validation failure
1574
+
994
1575
  through the `path` property.
995
1576
 
996
1577
  This makes validation errors easier to map to API fields, forms, logs,
1578
+
997
1579
  and frontend components.
998
1580
 
999
1581
  ### Top-Level Field
@@ -1011,9 +1593,13 @@ An error can be returned as:
1011
1593
  ```js
1012
1594
 
1013
1595
  {
1596
+
1014
1597
    path: "email",
1598
+
1015
1599
    code: "INVALID_EMAIL",
1600
+
1016
1601
    message: "Invalid email format for attribute email"
1602
+
1017
1603
  }
1018
1604
  ```
1019
1605
 
@@ -1025,8 +1611,10 @@ Use `objectAttr` to validate properties inside an object.
1025
1611
  const payload = {
1026
1612
  address: {
1027
1613
  city: "Bengaluru",
1614
+
1028
1615
  location: {
1029
1616
  latitude: "12.9716",
1617
+
1030
1618
  longitude: 77.5946,
1031
1619
  },
1032
1620
  },
@@ -1035,16 +1623,20 @@ const payload = {
1035
1623
  const rules = {
1036
1624
  address: {
1037
1625
  type: "object",
1626
+
1038
1627
  objectAttr: {
1039
1628
  city: {
1040
1629
  type: "string",
1041
1630
  },
1631
+
1042
1632
  location: {
1043
1633
  type: "object",
1634
+
1044
1635
  objectAttr: {
1045
1636
  latitude: {
1046
1637
  type: "number",
1047
1638
  },
1639
+
1048
1640
  longitude: {
1049
1641
  type: "number",
1050
1642
  },
@@ -1058,15 +1650,21 @@ const result = perfectPayload(payload, rules);
1058
1650
  ```
1059
1651
 
1060
1652
  Because `latitude` is a string instead of a number, the error contains
1653
+
1061
1654
  its complete nested path:
1062
1655
 
1063
1656
  ```js
1064
1657
 
1065
1658
  {
1659
+
1066
1660
    path: "address.location.latitude",
1661
+
1067
1662
    code: "INVALID_TYPE",
1663
+
1068
1664
    message:
1665
+
1069
1666
      "Invalid type for attribute address.location.latitude, required number value"
1667
+
1070
1668
  }
1071
1669
  ```
1072
1670
 
@@ -1075,13 +1673,16 @@ Nested paths use dot notation:
1075
1673
  ```text
1076
1674
 
1077
1675
  address.city
1676
+
1078
1677
  address.location.latitude
1678
+
1079
1679
  address.location.longitude
1080
1680
  ```
1081
1681
 
1082
1682
  ### Array Elements
1083
1683
 
1084
1684
  When `elementConstraints` validation fails, the array index is included
1685
+
1085
1686
  in the error path.
1086
1687
 
1087
1688
  ```js
@@ -1092,8 +1693,10 @@ const payload = {
1092
1693
  const rules = {
1093
1694
  marks: {
1094
1695
  type: "array",
1696
+
1095
1697
  elementConstraints: {
1096
1698
  type: "number",
1699
+
1097
1700
  range: "0-100",
1098
1701
  },
1099
1702
  },
@@ -1107,10 +1710,15 @@ The invalid third element is reported as:
1107
1710
  ```js
1108
1711
 
1109
1712
  {
1713
+
1110
1714
    path: "marks[2]",
1715
+
1111
1716
    code: "OUT_OF_RANGE",
1717
+
1112
1718
    message:
1719
+
1113
1720
      "Attribute marks[2] should have a value between 0 and 100"
1721
+
1114
1722
  }
1115
1723
  ```
1116
1724
 
@@ -1119,7 +1727,9 @@ Array paths use zero-based indexes:
1119
1727
  ```text
1120
1728
 
1121
1729
  marks[0]
1730
+
1122
1731
  marks[1]
1732
+
1123
1733
  marks[2]
1124
1734
  ```
1125
1735
 
@@ -1132,16 +1742,20 @@ For example:
1132
1742
  ```text
1133
1743
 
1134
1744
  products[0].quantity
1745
+
1135
1746
  products[1].quantity
1747
+
1136
1748
  products[2].price
1137
1749
  ```
1138
1750
 
1139
1751
  This provides enough information for consumers to identify the exact
1752
+
1140
1753
  field that caused the validation error.
1141
1754
 
1142
1755
  ### Why Structured Paths Are Useful
1143
1756
 
1144
1757
  Instead of parsing an error message to determine which field failed,
1758
+
1145
1759
  applications can directly use:
1146
1760
 
1147
1761
  ```js
@@ -1177,246 +1791,421 @@ Result:
1177
1791
  {
1178
1792
 
1179
1793
    "email": "Invalid email format for attribute email",
1794
+
1180
1795
    "address.location.latitude": "Invalid type for attribute address.location.latitude, required number value",
1796
+
1181
1797
    "marks[2]": "Attribute marks[2] should have a value between 0 and 100"
1182
1798
 
1183
1799
  }
1184
1800
  ```
1185
1801
 
1186
- ## Examples And Usage
1802
+ ## Examples and Usage
1187
1803
 
1188
1804
  ### Sample Validation Rule
1189
1805
 
1190
1806
  sample-1
1191
1807
 
1192
- ```javascript
1808
+ ```js
1193
1809
 
1194
1810
  {
1195
1811
 
1196
1812
    firstName: {
1813
+
1197
1814
      mandatory: true,
1815
+
1198
1816
      allowNull: false,
1817
+
1199
1818
      type: "string",
1819
+
1200
1820
      minLength: 3,
1201
-     minLengthError:"First name must have minimum 3 characters."
1821
+
1822
+     minLengthError: "First name must have minimum 3 characters."
1823
+
1202
1824
    },
1825
+
1203
1826
    lastName: {
1827
+
1204
1828
      mandatory: false,
1829
+
1205
1830
      allowNull: true,
1831
+
1206
1832
      type: "string",
1833
+
1207
1834
    },
1835
+
1208
1836
    email: {
1837
+
1209
1838
      mandatory: true,
1839
+
1210
1840
      allowNull: false,
1841
+
1211
1842
      type: "email",
1843
+
1212
1844
    },
1845
+
1213
1846
    phone: {
1847
+
1214
1848
      mandatory: true,
1849
+
1215
1850
      allowNull: false,
1851
+
1216
1852
      type: "string",
1853
+
1217
1854
    },
1855
+
1218
1856
    age: {
1857
+
1219
1858
      mandatory: false,
1859
+
1220
1860
      type: "number",
1861
+
1221
1862
      min: 1,
1863
+
1222
1864
      max: 120,
1865
+
1223
1866
    },
1867
+
1224
1868
  };
1225
1869
  ```
1226
1870
 
1227
1871
  sample-2
1228
1872
 
1229
- ```javascript
1873
+ ```js
1230
1874
 
1231
1875
  {
1876
+
1232
1877
    id: {
1878
+
1233
1879
      mandatory: true,
1880
+
1234
1881
      allowNull: true,
1882
+
1235
1883
      type: "uuidv4",
1884
+
1236
1885
    },
1886
+
1237
1887
    batchId: {
1888
+
1238
1889
      mandatory: true,
1890
+
1239
1891
      allowNull: true,
1892
+
1240
1893
      type: "objectId",
1894
+
1241
1895
    },
1896
+
1242
1897
    firstName: {
1898
+
1243
1899
      mandatory: true,
1900
+
1244
1901
      type: "string",
1902
+
1245
1903
      minLength: 3,
1904
+
1246
1905
    },
1906
+
1247
1907
    lastName: {
1908
+
1248
1909
      mandatory: false,
1910
+
1249
1911
      allowNull: true,
1912
+
1250
1913
      type: "string",
1914
+
1251
1915
    },
1916
+
1252
1917
    age: {
1918
+
1253
1919
      type: "number",
1920
+
1254
1921
      min: 0.1,
1922
+
1255
1923
      max: 120,
1924
+
1256
1925
    },
1926
+
1257
1927
    isAdult: {
1928
+
1258
1929
      type: "boolean",
1930
+
1259
1931
    },
1932
+
1260
1933
    totalWins: {
1934
+
1261
1935
      type: "number",
1936
+
1262
1937
      min: 0,
1938
+
1263
1939
      preventDecimal: true,
1940
+
1264
1941
    },
1942
+
1265
1943
    email: {
1266
-     regex: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/,
1944
+
1945
+     regex: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$/,
1946
+
1267
1947
    },
1948
+
1268
1949
    githubLink: {
1950
+
1269
1951
      type: "url",
1952
+
1270
1953
    },
1954
+
1271
1955
    accountStatus: {
1956
+
1272
1957
      type: "enum",
1958
+
1273
1959
      enumValues: ["Active", "Inactive", 200],
1960
+
1274
1961
    },
1962
+
1275
1963
    marks: {
1964
+
1276
1965
      range: "0-100",
1966
+
1277
1967
    },
1968
+
1278
1969
    allMarks: {
1970
+
1279
1971
      type: "array",
1972
+
1280
1973
      allowEmptyArray: false,
1974
+
1281
1975
      elementConstraints: {
1976
+
1282
1977
        type: "number",
1978
+
1283
1979
        allowNull: false,
1980
+
1284
1981
        range: "0-100",
1982
+
1285
1983
      },
1984
+
1286
1985
    },
1986
+
1287
1987
    totalScore: {
1988
+
1288
1989
      type: "number",
1990
+
1289
1991
      dependency: {
1992
+
1290
1993
        result: {
1994
+
1291
1995
          setDependencyRule: (totalScore, result) => {
1996
+
1292
1997
            return { mandatory: true, allowNull: false, type: "string" };
1998
+
1293
1999
          },
2000
+
1294
2001
        },
2002
+
1295
2003
      },
2004
+
1296
2005
    },
2006
+
1297
2007
    result: {
2008
+
1298
2009
      type: "string",
2010
+
1299
2011
      dependency: {
2012
+
1300
2013
        totalScore: {
2014
+
1301
2015
          setDependencyRule: (result, totalScore) => {
2016
+
1302
2017
            return { mandatory: true, allowNull: false, type: "number" };
2018
+
1303
2019
          },
2020
+
1304
2021
        },
2022
+
1305
2023
      },
2024
+
1306
2025
    },
2026
+
1307
2027
    minSalary: {
2028
+
1308
2029
      mandatory: true,
2030
+
1309
2031
      min: 1,
2032
+
1310
2033
      type: "number",
2034
+
1311
2035
      dependency: {
2036
+
1312
2037
        maxSalary: {
2038
+
1313
2039
          setDependencyRule: (minSalary, maxSalary) => {
2040
+
1314
2041
            return {
2042
+
1315
2043
              mandatory: true,
2044
+
1316
2045
              min: minSalary + 1,
2046
+
1317
2047
              minError: "maxSalary must be more than minSalary",
2048
+
1318
2049
            };
2050
+
1319
2051
          },
2052
+
1320
2053
        },
2054
+
1321
2055
      },
2056
+
1322
2057
    },
2058
+
1323
2059
    maxSalary: {
2060
+
1324
2061
      dependency: {
2062
+
1325
2063
        minSalary: {
2064
+
1326
2065
          setDependencyRule: (maxSalary, minSalary) => {
2066
+
1327
2067
            return {
2068
+
1328
2069
              mandatory: true,
2070
+
1329
2071
              max: maxSalary - 1,
2072
+
1330
2073
              maxError: "minSalary must be less than maxSalary",
2074
+
1331
2075
            };
2076
+
1332
2077
          },
2078
+
1333
2079
        },
2080
+
1334
2081
      },
2082
+
1335
2083
    },
2084
+
1336
2085
    address: {
2086
+
1337
2087
      mandatory: true,
2088
+
1338
2089
      type: "object",
2090
+
1339
2091
      allowEmptyObject: false,
2092
+
1340
2093
      objectAttr: {
2094
+
1341
2095
        country: { mandatory: true, type: "string" },
2096
+
1342
2097
        state: {
2098
+
1343
2099
          mandatory: true,
2100
+
1344
2101
          type: "string",
2102
+
1345
2103
        },
2104
+
1346
2105
        city: {},
2106
+
1347
2107
        zip: {
2108
+
1348
2109
          mandatory: true,
2110
+
1349
2111
          type: "string",
2112
+
1350
2113
        },
2114
+
1351
2115
        position: {
2116
+
1352
2117
          mandatory: true,
2118
+
1353
2119
          type: "object",
2120
+
1354
2121
          allowEmptyObject: false,
2122
+
1355
2123
          objectAttr: {
2124
+
1356
2125
            lattitude: { mandatory: true, type: "number" },
2126
+
1357
2127
            longitude: {
2128
+
1358
2129
              mandatory: true,
2130
+
1359
2131
              type: "number",
2132
+
1360
2133
            },
2134
+
1361
2135
          },
2136
+
1362
2137
        },
2138
+
1363
2139
      },
2140
+
1364
2141
    },
2142
+
1365
2143
  }
1366
2144
  ```
1367
2145
 
1368
2146
  ### Usage
1369
2147
 
1370
- #### creating your route with payload validation middleware
2148
+ #### Creating a route with payload validation middleware
1371
2149
 
1372
- ```javascript
2150
+ ```js
1373
2151
 
1374
- //Here validatePayload is your middleware function, where you're invoking perfect payload
2152
+ **// validatePayload is the middleware that invokes perfectPayload()**
1375
2153
 
1376
2154
  router.post(
2155
+
1377
2156
    "/payload-validation",
2157
+
1378
2158
    validatePayload({ rule: <your validation rule json object> }),
2159
+
1379
2160
    (req, res) => res.send("OK")
2161
+
1380
2162
  );
1381
2163
  ```
1382
2164
 
1383
- #### 1 Use perfect-payload in your middleware like below(for MODULE JS)
2165
+ #### ES Modules middleware example
1384
2166
 
1385
- ```javascript
1386
- import { perfectPayloadV1 } from "perfect-payload";
2167
+ ```js
2168
+ import { perfectPayload } from "perfect-payload";
1387
2169
 
1388
2170
  export const validatePayload = ({ rule }) => {
1389
2171
  return (req, res, next) => {
1390
2172
  try {
1391
- const { statusCode, ...response } = perfectPayloadV1(req?.body, rule);
2173
+ const { statusCode, ...response } = perfectPayload(req?.body, rule);
2174
+
1392
2175
  if (+statusCode >= 200 && +statusCode <= 299) {
1393
2176
  req.validatedBody = response?.validatedPayload;
2177
+
1394
2178
  next();
1395
2179
  } else res.status(statusCode).json(response);
1396
2180
  } catch (error) {
1397
2181
  console.error("Error validating payload", error);
2182
+
1398
2183
  res.status(500).json({ error: "Internal Server Error" });
1399
2184
  }
1400
2185
  };
1401
2186
  };
1402
2187
  ```
1403
2188
 
1404
- #### 2 Use perfect-payload in your middleware like below(for COMMON JS)
2189
+ #### CommonJS middleware example
1405
2190
 
1406
- ```javascript
2191
+ ```js
1407
2192
  function validatePayload({ rule }) {
1408
2193
  return async (req, res, next) => {
1409
2194
  try {
1410
- const { perfectPayloadV1 } = await import("perfect-payload");
1411
- const { statusCode, ...response } = perfectPayloadV1(req?.body, rule);
2195
+ const { perfectPayload } = await import("perfect-payload");
2196
+
2197
+ const { statusCode, ...response } = perfectPayload(req?.body, rule);
2198
+
1412
2199
  if (+statusCode >= 200 && +statusCode <= 299) {
1413
2200
  req.validatedBody = response?.validatedPayload;
2201
+
1414
2202
  next();
1415
2203
  } else {
1416
2204
  res.status(statusCode).json(response);
1417
2205
  }
1418
2206
  } catch (error) {
1419
2207
  console.error("Error validating payload", error);
2208
+
1420
2209
  res.status(500).json({ error: "Internal Server Error" });
1421
2210
  }
1422
2211
  };
@@ -1428,5 +2217,7 @@ module.exports = { validatePayload };
1428
2217
  ---
1429
2218
 
1430
2219
  This documentation provides a comprehensive guide to using the data
2220
+
1431
2221
  validation module effectively. Ensure to define your validation rules
2222
+
1432
2223
  clearly to maintain data quality and consistency in your applications.