perfect-payload 1.3.0 → 1.4.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 +589 -377
  2. package/index.js +99 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,15 +1,32 @@
1
1
  # perfect-payload
2
2
 
3
3
  A lightweight JavaScript payload validation utility for validating API
4
-
5
4
  and JSON payloads with simple rule-based configuration.
6
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
+
7
25
  ## Installation
8
26
 
9
27
  ```bash
10
28
 
11
29
  npm install perfect-payload
12
-
13
30
  ```
14
31
 
15
32
  ## Basic Usage
@@ -60,25 +77,25 @@ console.log(result);
60
77
 
61
78
  {
62
79
 
63
- statusCode: 200,
80
+   statusCode: 200,
64
81
 
65
- valid: true,
82
+   valid: true,
66
83
 
67
- validatedPayload: {
84
+   validatedPayload: {
68
85
 
69
- name: "Kiran",
86
+     name: "Kiran",
70
87
 
71
- email: "kiran@example.com",
88
+     email: "kiran@example.com",
72
89
 
73
- age: 29
90
+     age: 29
74
91
 
75
- }
92
+   }
76
93
 
77
94
  }
78
-
79
95
  ```
80
96
 
81
- **Note:** The validatedPayload contains only the fields defined in the
97
+ Note: The validatedPayload contains only the fields
98
+ defined in the
82
99
 
83
100
  schema, automatically filtering out any extra attributes. You can use it
84
101
 
@@ -92,28 +109,27 @@ to safely overwrite request.body or assign it to a new request property
92
109
 
93
110
  {
94
111
 
95
- statusCode: 400,
112
+   statusCode: 400,
96
113
 
97
- valid: false,
114
+   valid: false,
98
115
 
99
- message: "One or more attribute values are invalid",
116
+   message: "One or more attribute values are invalid",
100
117
 
101
- errors: [
118
+   errors: [
102
119
 
103
- {
120
+     {
104
121
 
105
- path: "email",
122
+       path: "email",
106
123
 
107
- code: "INVALID_EMAIL",
124
+       code: "INVALID_EMAIL",
108
125
 
109
- message: "Invalid email format for attribute email"
126
+       message: "Invalid email format for attribute email"
110
127
 
111
- }
128
+     }
112
129
 
113
- ]
130
+   ]
114
131
 
115
132
  }
116
-
117
133
  ```
118
134
 
119
135
  Each error returned by `perfectPayload()` contains:
@@ -122,14 +138,13 @@ Each error returned by `perfectPayload()` contains:
122
138
 
123
139
  {
124
140
 
125
- path: "field.path",
141
+   path: "field.path",
126
142
 
127
- code: "ERROR_CODE",
143
+   code: "ERROR_CODE",
128
144
 
129
- message: "Human readable validation message"
145
+   message: "Human readable validation message"
130
146
 
131
147
  }
132
-
133
148
  ```
134
149
 
135
150
  \- `path` identifies the exact field that failed validation.
@@ -152,7 +167,7 @@ import { perfectPayloadV1 } from "perfect-payload";
152
167
 
153
168
  `perfectPayloadV1()` is deprecated and will no longer be supported after
154
169
 
155
- **March 31, 2027**.
170
+ March 31, 2027.
156
171
 
157
172
  Existing applications can continue using it during the migration period,
158
173
 
@@ -182,7 +197,8 @@ errors: [
182
197
  ];
183
198
  ```
184
199
 
185
- **Note:** If an inValidPayloadResponse is provided, the system returns
200
+ Note: If an inValidPayloadResponse is provided, the
201
+ system returns
186
202
 
187
203
  it alongside an automatically generated errors property. Do not include
188
204
 
@@ -192,13 +208,14 @@ object.
192
208
 
193
209
  ## Validation Rules
194
210
 
195
- `perfectPayload()` supports the following validation rules.
211
+ `perfectPayload()` supports validation, nested-schema,
212
+ custom-validation, and transformation rules.
196
213
 
197
214
  ### `mandatory`
198
215
 
199
- 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.
200
217
 
201
- **Default:** `false`, the field is not required.
218
+ Default: `false`, the field is not required.
202
219
 
203
220
  ```js
204
221
  const rules = {
@@ -210,13 +227,13 @@ const rules = {
210
227
 
211
228
  Error code: `REQUIRED`
212
229
 
213
- **---**
230
+ ---
214
231
 
215
232
  ### `allowNull`
216
233
 
217
234
  Controls whether `null` values are accepted.
218
235
 
219
- **Default:** `true` , `null` values are allowed.
236
+ Default: `true`, `null` values are allowed.
220
237
 
221
238
  Example:
222
239
 
@@ -230,13 +247,13 @@ const rules = {
230
247
 
231
248
  Error code: `NULL_NOT_ALLOWED`
232
249
 
233
- **---**
250
+ ---
234
251
 
235
252
  ### `allowEmptyObject`
236
253
 
237
254
  Controls whether an empty object `{}` is accepted.
238
255
 
239
- **Default:** `true`, empty objects are allowed.
256
+ Default: `true`, empty objects are allowed.
240
257
 
241
258
  Example:
242
259
 
@@ -252,13 +269,13 @@ const rules = {
252
269
 
253
270
  Error code: `EMPTY_OBJECT_NOT_ALLOWED`
254
271
 
255
- **---**
272
+ ---
256
273
 
257
274
  ### `allowEmptyArray`
258
275
 
259
276
  Controls whether an empty array `[]` is accepted.
260
277
 
261
- **Default:** `true`, empty arrays are allowed.
278
+ Default: `true`, empty arrays are allowed.
262
279
 
263
280
  Example:
264
281
 
@@ -274,7 +291,7 @@ const rules = {
274
291
 
275
292
  Error code: `EMPTY_ARRAY_NOT_ALLOWED`
276
293
 
277
- **---**
294
+ ---
278
295
 
279
296
  ### `type`
280
297
 
@@ -311,7 +328,6 @@ objectId
311
328
  array
312
329
 
313
330
  object
314
-
315
331
  ```
316
332
 
317
333
  Example:
@@ -397,16 +413,15 @@ INVALID_UUID_V4
397
413
  INVALID_UUID_V5
398
414
 
399
415
  INVALID_OBJECT_ID
400
-
401
416
  ```
402
417
 
403
- **---**
418
+ ---
404
419
 
405
420
  ### `regex`
406
421
 
407
422
  Validates a value using a regular expression.
408
423
 
409
- **Default:** Not applied when omitted.
424
+ Default: Not applied when omitted.
410
425
 
411
426
  Example:
412
427
 
@@ -422,13 +437,13 @@ const rules = {
422
437
 
423
438
  Error code: `REGEX_MISMATCH`
424
439
 
425
- **---**
440
+ ---
426
441
 
427
442
  ### `minLength`
428
443
 
429
444
  Defines the minimum allowed string length.
430
445
 
431
- **Default:** Not applied when omitted.
446
+ Default: Not applied when omitted.
432
447
 
433
448
  Example:
434
449
 
@@ -444,13 +459,13 @@ const rules = {
444
459
 
445
460
  Error code: `MIN_LENGTH`
446
461
 
447
- **---**
462
+ ---
448
463
 
449
464
  ### `maxLength`
450
465
 
451
466
  Defines the maximum allowed string length.
452
467
 
453
- **Default:** Not applied when omitted.
468
+ Default: Not applied when omitted.
454
469
 
455
470
  Example:
456
471
 
@@ -466,13 +481,14 @@ const rules = {
466
481
 
467
482
  Error code: `MAX_LENGTH`
468
483
 
469
- **---**
484
+ ---
470
485
 
471
486
  ### `preventDecimal`
472
487
 
473
488
  Prevents decimal numbers.
474
489
 
475
- **Default:** `false` both integer and decimal numbers are allowed.
490
+ Default: `false`; both integer and decimal numbers are
491
+ allowed.
476
492
 
477
493
  Example:
478
494
 
@@ -488,13 +504,13 @@ const rules = {
488
504
 
489
505
  Error code: `DECIMAL_NOT_ALLOWED`
490
506
 
491
- **---**
507
+ ---
492
508
 
493
509
  ### `min`
494
510
 
495
511
  Defines the minimum allowed numeric value.
496
512
 
497
- **Default:** Not applied when omitted.
513
+ Default: Not applied when omitted.
498
514
 
499
515
  Example:
500
516
 
@@ -510,13 +526,13 @@ const rules = {
510
526
 
511
527
  Error code: `MIN_VALUE`
512
528
 
513
- **---**
529
+ ---
514
530
 
515
531
  ### `max`
516
532
 
517
533
  Defines the maximum allowed numeric value.
518
534
 
519
- **Default:** Not applied when omitted.
535
+ Default: Not applied when omitted.
520
536
 
521
537
  Example:
522
538
 
@@ -532,13 +548,13 @@ const rules = {
532
548
 
533
549
  Error code: `MAX_VALUE`
534
550
 
535
- **---**
551
+ ---
536
552
 
537
553
  ### `range`
538
554
 
539
555
  Defines the allowed numeric range.
540
556
 
541
- **Default:** Not applied when omitted.
557
+ Default: Not applied when omitted.
542
558
 
543
559
  Example:
544
560
 
@@ -554,7 +570,7 @@ const rules = {
554
570
 
555
571
  Error code: `OUT_OF_RANGE`
556
572
 
557
- **---**
573
+ ---
558
574
 
559
575
  ### `elementConstraints`
560
576
 
@@ -582,16 +598,15 @@ Example error:
582
598
 
583
599
  {
584
600
 
585
- path: "marks[2]",
601
+   path: "marks[2]",
586
602
 
587
- code: "OUT_OF_RANGE",
603
+   code: "OUT_OF_RANGE",
588
604
 
589
- message:
605
+   message:
590
606
 
591
- "Attribute marks[2] should have a value between 0 and 100"
607
+     "Attribute marks[2] should have a value between 0 and 100"
592
608
 
593
609
  }
594
-
595
610
  ```
596
611
 
597
612
  When `elementConstraintsError` is explicitly provided, the error code
@@ -614,7 +629,7 @@ const rules = {
614
629
  };
615
630
  ```
616
631
 
617
- **---**
632
+ ---
618
633
 
619
634
  ### `objectAttr`
620
635
 
@@ -658,19 +673,18 @@ Nested errors include the complete field path:
658
673
 
659
674
  {
660
675
 
661
- path: "address.location.latitude",
676
+   path: "address.location.latitude",
662
677
 
663
- code: "INVALID_TYPE",
678
+   code: "INVALID_TYPE",
664
679
 
665
- message:
680
+   message:
666
681
 
667
- "Invalid type for attribute address.location.latitude, required number value"
682
+     "Invalid type for attribute address.location.latitude, required number value"
668
683
 
669
684
  }
670
-
671
685
  ```
672
686
 
673
- **---**
687
+ ---
674
688
 
675
689
  ### `dependency`
676
690
 
@@ -704,25 +718,221 @@ Example error:
704
718
 
705
719
  {
706
720
 
707
- path: "maxSalary",
721
+   path: "maxSalary",
708
722
 
709
- code: "MIN_VALUE",
723
+   code: "MIN_VALUE",
710
724
 
711
- message:
725
+   message:
712
726
 
713
- "maxSalary must be more than minSalary"
727
+     "maxSalary must be more than minSalary"
714
728
 
715
729
  }
730
+ ```
731
+
732
+ ---
733
+
734
+ ### Transformations and Sanitization
735
+
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.
739
+
740
+ Supported transformation rules:
741
+
742
+ Rule Purpose
743
+
744
+ ---
745
+
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
750
+
751
+ Transformations always run in this fixed order, regardless of the order
752
+ in which the rule properties are written:
753
+
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
768
+ ```
769
+
770
+ #### `trim`
771
+
772
+ ```js
773
+ const payload = {
774
+ name: " Kiran Poojary ",
775
+ };
776
+
777
+ const rules = {
778
+ name: {
779
+ type: "string",
780
+ trim: true,
781
+ },
782
+ };
783
+
784
+ const result = perfectPayload(payload, rules);
785
+
786
+ console.log(result.validatedPayload.name);
787
+ // "Kiran Poojary"
788
+
789
+ console.log(payload.name);
790
+ // " Kiran Poojary "
791
+ ```
792
+
793
+ `trim` applies only to string values. Non-string values are left
794
+ unchanged.
795
+
796
+ #### `lowercase`
797
+
798
+ ```js
799
+ const rules = {
800
+ email: {
801
+ trim: true,
802
+ lowercase: true,
803
+ type: "email",
804
+ },
805
+ };
806
+ ```
807
+
808
+ For `" KIRAN@EXAMPLE.COM "`, the validated value becomes
809
+ `"kiran@example.com"`.
810
+
811
+ #### `uppercase`
812
+
813
+ ```js
814
+ const rules = {
815
+ countryCode: {
816
+ type: "string",
817
+ uppercase: true,
818
+ },
819
+ };
820
+ ```
821
+
822
+ For `"in"`, the validated value becomes `"IN"`.
823
+
824
+ `lowercase: true` and `uppercase: true` cannot be enabled together for
825
+ the same field. Doing so throws a schema configuration error.
826
+
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
+ };
838
+ ```
839
+
840
+ For `"98765 43210"`, the validated value becomes `"9876543210"`.
841
+
842
+ The transformer receives two arguments:
843
+
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.
853
+
854
+ This makes cross-field transformations possible:
855
+
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
+ };
871
+
872
+ const result = perfectPayload(payload, rules);
873
+
874
+ console.log(result.validatedPayload.amount);
875
+ // 200
876
+ ```
877
+
878
+ A custom transformer may also change the data type before validation:
879
+
880
+ ```js
881
+ const rules = {
882
+ quantity: {
883
+ transform: (value) => Number(value),
884
+ type: "number",
885
+ min: 1,
886
+ max: 100,
887
+ },
888
+ };
889
+ ```
716
890
 
891
+ The transformed value is validated by the normal validation rules and is
892
+ also the value received by `customValidator`.
893
+
894
+ Transformations work inside `objectAttr` and `elementConstraints`, and
895
+ transformed nested/array values are preserved in `validatedPayload`.
896
+
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
+ };
717
918
  ```
718
919
 
719
- **---**
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`.
923
+
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.
720
928
 
721
929
  ### `customValidator`
722
930
 
723
- Allows you to define custom synchronous validation logic for a field when the built-in validation rules are not enough.
931
+ Allows you to define custom synchronous validation logic for a field
932
+ when the built-in validation rules are not enough.
724
933
 
725
- The validator receives the field value and the complete payload:
934
+ The validator receives the field value and the current payload/object
935
+ being validated:
726
936
 
727
937
  ```js
728
938
  customValidator: (value, payload) => {
@@ -730,7 +940,8 @@ customValidator: (value, payload) => {
730
940
  };
731
941
  ```
732
942
 
733
- The validator must return `true` to pass validation. Any other return value causes validation to fail.
943
+ The validator must return `true` to pass validation. Any other return
944
+ value causes validation to fail.
734
945
 
735
946
  Example:
736
947
 
@@ -738,11 +949,15 @@ Example:
738
949
  const rules = {
739
950
  username: {
740
951
  mandatory: true,
952
+
741
953
  type: "string",
954
+
742
955
  customValidator: (value) => {
743
956
  return !value.toLowerCase().includes("admin");
744
957
  },
958
+
745
959
  customValidatorCode: "RESERVED_USERNAME",
960
+
746
961
  customValidatorError: "Username cannot contain admin",
747
962
  },
748
963
  };
@@ -759,44 +974,64 @@ const payload = {
759
974
  The validation error is:
760
975
 
761
976
  ```js
977
+
762
978
  {
763
- path: "username",
764
- code: "RESERVED_USERNAME",
765
- message: "Username cannot contain admin"
979
+
980
+   path: "username",
981
+
982
+   code: "RESERVED_USERNAME",
983
+
984
+   message: "Username cannot contain admin"
985
+
766
986
  }
767
987
  ```
768
988
 
769
- If `customValidatorCode` and `customValidatorError` are not provided, the default error is:
989
+ If `customValidatorCode` and `customValidatorError` are not provided,
990
+ the default error is:
770
991
 
771
992
  ```js
993
+
772
994
  {
773
- path: "username",
774
- code: "CUSTOM_VALIDATION_FAILED",
775
- message: "Custom validation failed for attribute username"
995
+
996
+   path: "username",
997
+
998
+   code: "CUSTOM_VALIDATION_FAILED",
999
+
1000
+   message: "Custom validation failed for attribute username"
1001
+
776
1002
  }
777
1003
  ```
778
1004
 
779
- The complete payload can be used as the second argument when required:
1005
+ The current payload/object being validated can be used as the second
1006
+ argument when required:
780
1007
 
781
1008
  ```js
782
1009
  const rules = {
783
1010
  limit: {
784
1011
  type: "number",
785
1012
  },
1013
+
786
1014
  amount: {
787
1015
  type: "number",
1016
+
788
1017
  customValidator: (value, payload) => {
789
1018
  return value <= payload.limit;
790
1019
  },
1020
+
791
1021
  customValidatorCode: "LIMIT_EXCEEDED",
1022
+
792
1023
  customValidatorError: "Amount cannot exceed limit",
793
1024
  },
794
1025
  };
795
1026
  ```
796
1027
 
797
- `customValidator` also works with nested objects and array `elementConstraints`. The generated structured error automatically contains the corresponding nested or array path.
1028
+ `customValidator` also works with nested objects and array
1029
+ `elementConstraints`. The generated structured error automatically
1030
+ contains the corresponding nested or array path.
798
1031
 
799
- **Important:** `customValidator` is synchronous. An `async` validator or a validator that returns a Promise is not supported and throws an error. Asynchronous validation is not part of this feature.
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.
800
1035
 
801
1036
  Error code when no custom code is provided: `CUSTOM_VALIDATION_FAILED`
802
1037
 
@@ -851,7 +1086,6 @@ MIN_VALUE
851
1086
  MAX_VALUE
852
1087
 
853
1088
  OUT_OF_RANGE
854
-
855
1089
  ```
856
1090
 
857
1091
  These codes are designed for programmatic handling while `message`
@@ -866,20 +1100,19 @@ const result = perfectPayload(payload, validationRules);
866
1100
 
867
1101
  if (!result.valid) {
868
1102
 
869
- const emailError = result.errors.find(
1103
+   const emailError = result.errors.find(
870
1104
 
871
- (error) => error.code === "INVALID_EMAIL",
1105
+     (error) => error.code === "INVALID_EMAIL",
872
1106
 
873
- );
1107
+   );
874
1108
 
875
- if (emailError) {
1109
+   if (emailError) {
876
1110
 
877
- *// Handle invalid email*
1111
+     **// Handle invalid email**
878
1112
 
879
- }
1113
+   }
880
1114
 
881
1115
  }
882
-
883
1116
  ```
884
1117
 
885
1118
  ## Custom Error Messages
@@ -894,14 +1127,13 @@ keeping the same structured error format:
894
1127
 
895
1128
  {
896
1129
 
897
- path: "email",
1130
+   path: "email",
898
1131
 
899
- code: "INVALID_EMAIL",
1132
+   code: "INVALID_EMAIL",
900
1133
 
901
- message: "Email address is invalid"
1134
+   message: "Email address is invalid"
902
1135
 
903
1136
  }
904
-
905
1137
  ```
906
1138
 
907
1139
  Example:
@@ -926,14 +1158,13 @@ If `email` is missing:
926
1158
 
927
1159
  {
928
1160
 
929
- path: "email",
1161
+   path: "email",
930
1162
 
931
- code: "REQUIRED",
1163
+   code: "REQUIRED",
932
1164
 
933
- message: "Email is required"
1165
+   message: "Email is required"
934
1166
 
935
1167
  }
936
-
937
1168
  ```
938
1169
 
939
1170
  If `email` is present but invalid:
@@ -942,14 +1173,13 @@ If `email` is present but invalid:
942
1173
 
943
1174
  {
944
1175
 
945
- path: "email",
1176
+   path: "email",
946
1177
 
947
- code: "INVALID_EMAIL",
1178
+   code: "INVALID_EMAIL",
948
1179
 
949
- message: "Email address is invalid"
1180
+   message: "Email address is invalid"
950
1181
 
951
1182
  }
952
-
953
1183
  ```
954
1184
 
955
1185
  ### Supported Custom Error Properties
@@ -1036,56 +1266,55 @@ Example result:
1036
1266
 
1037
1267
  {
1038
1268
 
1039
- statusCode: 400,
1269
+   statusCode: 400,
1040
1270
 
1041
- valid: false,
1271
+   valid: false,
1042
1272
 
1043
- message:
1273
+   message:
1044
1274
 
1045
- "One or more attribute values are invalid",
1275
+     "One or more attribute values are invalid",
1046
1276
 
1047
- errors: [
1277
+   errors: [
1048
1278
 
1049
- {
1279
+     {
1050
1280
 
1051
- path: "username",
1281
+       path: "username",
1052
1282
 
1053
- code: "MIN_LENGTH",
1283
+       code: "MIN_LENGTH",
1054
1284
 
1055
- message:
1285
+       message:
1056
1286
 
1057
- "Username must contain at least 3 characters"
1287
+         "Username must contain at least 3 characters"
1058
1288
 
1059
- },
1289
+     },
1060
1290
 
1061
- {
1291
+     {
1062
1292
 
1063
- path: "age",
1293
+       path: "age",
1064
1294
 
1065
- code: "MIN_VALUE",
1295
+       code: "MIN_VALUE",
1066
1296
 
1067
- message:
1297
+       message:
1068
1298
 
1069
- "Age must be at least 18"
1299
+         "Age must be at least 18"
1070
1300
 
1071
- },
1301
+     },
1072
1302
 
1073
- {
1303
+     {
1074
1304
 
1075
- path: "score",
1305
+       path: "score",
1076
1306
 
1077
- code: "OUT_OF_RANGE",
1307
+       code: "OUT_OF_RANGE",
1078
1308
 
1079
- message:
1309
+       message:
1080
1310
 
1081
- "Score must be between 0 and 100"
1311
+         "Score must be between 0 and 100"
1082
1312
 
1083
- }
1313
+     }
1084
1314
 
1085
- ]
1315
+   ]
1086
1316
 
1087
1317
  }
1088
-
1089
1318
  ```
1090
1319
 
1091
1320
  ### Custom Messages and Error Codes
@@ -1114,14 +1343,13 @@ Still returns:
1114
1343
 
1115
1344
  {
1116
1345
 
1117
- path: "age",
1346
+   path: "age",
1118
1347
 
1119
- code: "MIN_VALUE",
1348
+   code: "MIN_VALUE",
1120
1349
 
1121
- message: "You must be 18 or older"
1350
+   message: "You must be 18 or older"
1122
1351
 
1123
1352
  }
1124
-
1125
1353
  ```
1126
1354
 
1127
1355
  This makes it possible to:
@@ -1166,24 +1394,23 @@ When validation succeeds, `validatedPayload` is automatically added:
1166
1394
 
1167
1395
  {
1168
1396
 
1169
- statusCode: 201,
1397
+   statusCode: 201,
1170
1398
 
1171
- valid: true,
1399
+   valid: true,
1172
1400
 
1173
- message: "Payload validated successfully",
1401
+   message: "Payload validated successfully",
1174
1402
 
1175
- validatedPayload: {
1403
+   validatedPayload: {
1176
1404
 
1177
- name: "Kiran",
1405
+     name: "Kiran",
1178
1406
 
1179
- email: "kiran@example.com",
1407
+     email: "kiran@example.com",
1180
1408
 
1181
- age: 29
1409
+     age: 29
1182
1410
 
1183
- }
1411
+   }
1184
1412
 
1185
1413
  }
1186
-
1187
1414
  ```
1188
1415
 
1189
1416
  ### Custom Invalid Response
@@ -1216,30 +1443,29 @@ When validation fails, `errors` is automatically added:
1216
1443
 
1217
1444
  {
1218
1445
 
1219
- statusCode: 422,
1446
+   statusCode: 422,
1220
1447
 
1221
- valid: false,
1448
+   valid: false,
1222
1449
 
1223
- message: "Payload validation failed",
1450
+   message: "Payload validation failed",
1224
1451
 
1225
- errors: [
1452
+   errors: [
1226
1453
 
1227
- {
1454
+     {
1228
1455
 
1229
- path: "email",
1456
+       path: "email",
1230
1457
 
1231
- code: "INVALID_EMAIL",
1458
+       code: "INVALID_EMAIL",
1232
1459
 
1233
- message:
1460
+       message:
1234
1461
 
1235
- "Invalid email format for attribute email"
1462
+         "Invalid email format for attribute email"
1236
1463
 
1237
- }
1464
+     }
1238
1465
 
1239
- ]
1466
+   ]
1240
1467
 
1241
1468
  }
1242
-
1243
1469
  ```
1244
1470
 
1245
1471
  ### Custom Valid and Invalid Responses Together
@@ -1279,7 +1505,6 @@ automatically adds either:
1279
1505
  ```text
1280
1506
 
1281
1507
  validatedPayload
1282
-
1283
1508
  ```
1284
1509
 
1285
1510
  for successful validation, or:
@@ -1287,7 +1512,6 @@ for successful validation, or:
1287
1512
  ```text
1288
1513
 
1289
1514
  errors
1290
-
1291
1515
  ```
1292
1516
 
1293
1517
  for failed validation.
@@ -1302,18 +1526,17 @@ is:
1302
1526
 
1303
1527
  {
1304
1528
 
1305
- statusCode: 200,
1529
+   statusCode: 200,
1306
1530
 
1307
- valid: true,
1531
+   valid: true,
1308
1532
 
1309
- validatedPayload: {
1533
+   validatedPayload: {
1310
1534
 
1311
- *// validated fields*
1535
+     **// validated fields**
1312
1536
 
1313
- }
1537
+   }
1314
1538
 
1315
1539
  }
1316
-
1317
1540
  ```
1318
1541
 
1319
1542
  The default invalid response is:
@@ -1322,28 +1545,27 @@ The default invalid response is:
1322
1545
 
1323
1546
  {
1324
1547
 
1325
- statusCode: 400,
1548
+   statusCode: 400,
1326
1549
 
1327
- valid: false,
1550
+   valid: false,
1328
1551
 
1329
- message: "One or more attribute values are invalid",
1552
+   message: "One or more attribute values are invalid",
1330
1553
 
1331
- errors: [
1554
+   errors: [
1332
1555
 
1333
- {
1556
+     {
1334
1557
 
1335
- path: "field",
1558
+       path: "field",
1336
1559
 
1337
- code: "ERROR_CODE",
1560
+       code: "ERROR_CODE",
1338
1561
 
1339
- message: "Validation error message"
1562
+       message: "Validation error message"
1340
1563
 
1341
- }
1564
+     }
1342
1565
 
1343
- ]
1566
+   ]
1344
1567
 
1345
1568
  }
1346
-
1347
1569
  ```
1348
1570
 
1349
1571
  ## Nested Objects and Array Field Paths
@@ -1372,14 +1594,13 @@ An error can be returned as:
1372
1594
 
1373
1595
  {
1374
1596
 
1375
- path: "email",
1597
+   path: "email",
1376
1598
 
1377
- code: "INVALID_EMAIL",
1599
+   code: "INVALID_EMAIL",
1378
1600
 
1379
- message: "Invalid email format for attribute email"
1601
+   message: "Invalid email format for attribute email"
1380
1602
 
1381
1603
  }
1382
-
1383
1604
  ```
1384
1605
 
1385
1606
  ### Nested Object
@@ -1436,16 +1657,15 @@ its complete nested path:
1436
1657
 
1437
1658
  {
1438
1659
 
1439
- path: "address.location.latitude",
1660
+   path: "address.location.latitude",
1440
1661
 
1441
- code: "INVALID_TYPE",
1662
+   code: "INVALID_TYPE",
1442
1663
 
1443
- message:
1664
+   message:
1444
1665
 
1445
- "Invalid type for attribute address.location.latitude, required number value"
1666
+     "Invalid type for attribute address.location.latitude, required number value"
1446
1667
 
1447
1668
  }
1448
-
1449
1669
  ```
1450
1670
 
1451
1671
  Nested paths use dot notation:
@@ -1457,7 +1677,6 @@ address.city
1457
1677
  address.location.latitude
1458
1678
 
1459
1679
  address.location.longitude
1460
-
1461
1680
  ```
1462
1681
 
1463
1682
  ### Array Elements
@@ -1492,16 +1711,15 @@ The invalid third element is reported as:
1492
1711
 
1493
1712
  {
1494
1713
 
1495
- path: "marks[2]",
1714
+   path: "marks[2]",
1496
1715
 
1497
- code: "OUT_OF_RANGE",
1716
+   code: "OUT_OF_RANGE",
1498
1717
 
1499
- message:
1718
+   message:
1500
1719
 
1501
- "Attribute marks[2] should have a value between 0 and 100"
1720
+     "Attribute marks[2] should have a value between 0 and 100"
1502
1721
 
1503
1722
  }
1504
-
1505
1723
  ```
1506
1724
 
1507
1725
  Array paths use zero-based indexes:
@@ -1513,7 +1731,6 @@ marks[0]
1513
1731
  marks[1]
1514
1732
 
1515
1733
  marks[2]
1516
-
1517
1734
  ```
1518
1735
 
1519
1736
  ### Nested Fields Inside Arrays
@@ -1529,7 +1746,6 @@ products[0].quantity
1529
1746
  products[1].quantity
1530
1747
 
1531
1748
  products[2].price
1532
-
1533
1749
  ```
1534
1750
 
1535
1751
  This provides enough information for consumers to identify the exact
@@ -1574,391 +1790,387 @@ Result:
1574
1790
 
1575
1791
  {
1576
1792
 
1577
- "email": "Invalid email format for attribute email",
1793
+   "email": "Invalid email format for attribute email",
1578
1794
 
1579
- "address.location.latitude": "Invalid type for attribute address.location.latitude, required number value",
1795
+   "address.location.latitude": "Invalid type for attribute address.location.latitude, required number value",
1580
1796
 
1581
- "marks[2]": "Attribute marks[2] should have a value between 0 and 100"
1797
+   "marks[2]": "Attribute marks[2] should have a value between 0 and 100"
1582
1798
 
1583
1799
  }
1584
-
1585
1800
  ```
1586
1801
 
1587
- ## Examples And Usage
1802
+ ## Examples and Usage
1588
1803
 
1589
1804
  ### Sample Validation Rule
1590
1805
 
1591
1806
  sample-1
1592
1807
 
1593
- ```javascript
1808
+ ```js
1594
1809
 
1595
1810
  {
1596
1811
 
1597
- firstName: {
1812
+   firstName: {
1598
1813
 
1599
- mandatory: true,
1814
+     mandatory: true,
1600
1815
 
1601
- allowNull: false,
1816
+     allowNull: false,
1602
1817
 
1603
- type: "string",
1818
+     type: "string",
1604
1819
 
1605
- minLength: 3,
1820
+     minLength: 3,
1606
1821
 
1607
- minLengthError:"First name must have minimum 3 characters."
1822
+     minLengthError: "First name must have minimum 3 characters."
1608
1823
 
1609
- },
1824
+   },
1610
1825
 
1611
- lastName: {
1826
+   lastName: {
1612
1827
 
1613
- mandatory: false,
1828
+     mandatory: false,
1614
1829
 
1615
- allowNull: true,
1830
+     allowNull: true,
1616
1831
 
1617
- type: "string",
1832
+     type: "string",
1618
1833
 
1619
- },
1834
+   },
1620
1835
 
1621
- email: {
1836
+   email: {
1622
1837
 
1623
- mandatory: true,
1838
+     mandatory: true,
1624
1839
 
1625
- allowNull: false,
1840
+     allowNull: false,
1626
1841
 
1627
- type: "email",
1842
+     type: "email",
1628
1843
 
1629
- },
1844
+   },
1630
1845
 
1631
- phone: {
1846
+   phone: {
1632
1847
 
1633
- mandatory: true,
1848
+     mandatory: true,
1634
1849
 
1635
- allowNull: false,
1850
+     allowNull: false,
1636
1851
 
1637
- type: "string",
1852
+     type: "string",
1638
1853
 
1639
- },
1854
+   },
1640
1855
 
1641
- age: {
1856
+   age: {
1642
1857
 
1643
- mandatory: false,
1858
+     mandatory: false,
1644
1859
 
1645
- type: "number",
1860
+     type: "number",
1646
1861
 
1647
- min: 1,
1862
+     min: 1,
1648
1863
 
1649
- max: 120,
1864
+     max: 120,
1650
1865
 
1651
- },
1866
+   },
1652
1867
 
1653
1868
  };
1654
-
1655
1869
  ```
1656
1870
 
1657
1871
  sample-2
1658
1872
 
1659
- ```javascript
1873
+ ```js
1660
1874
 
1661
1875
  {
1662
1876
 
1663
- id: {
1877
+   id: {
1664
1878
 
1665
- mandatory: true,
1879
+     mandatory: true,
1666
1880
 
1667
- allowNull: true,
1881
+     allowNull: true,
1668
1882
 
1669
- type: "uuidv4",
1883
+     type: "uuidv4",
1670
1884
 
1671
- },
1885
+   },
1672
1886
 
1673
- batchId: {
1887
+   batchId: {
1674
1888
 
1675
- mandatory: true,
1889
+     mandatory: true,
1676
1890
 
1677
- allowNull: true,
1891
+     allowNull: true,
1678
1892
 
1679
- type: "objectId",
1893
+     type: "objectId",
1680
1894
 
1681
- },
1895
+   },
1682
1896
 
1683
- firstName: {
1897
+   firstName: {
1684
1898
 
1685
- mandatory: true,
1899
+     mandatory: true,
1686
1900
 
1687
- type: "string",
1901
+     type: "string",
1688
1902
 
1689
- minLength: 3,
1903
+     minLength: 3,
1690
1904
 
1691
- },
1905
+   },
1692
1906
 
1693
- lastName: {
1907
+   lastName: {
1694
1908
 
1695
- mandatory: false,
1909
+     mandatory: false,
1696
1910
 
1697
- allowNull: true,
1911
+     allowNull: true,
1698
1912
 
1699
- type: "string",
1913
+     type: "string",
1700
1914
 
1701
- },
1915
+   },
1702
1916
 
1703
- age: {
1917
+   age: {
1704
1918
 
1705
- type: "number",
1919
+     type: "number",
1706
1920
 
1707
- min: 0.1,
1921
+     min: 0.1,
1708
1922
 
1709
- max: 120,
1923
+     max: 120,
1710
1924
 
1711
- },
1925
+   },
1712
1926
 
1713
- isAdult: {
1927
+   isAdult: {
1714
1928
 
1715
- type: "boolean",
1929
+     type: "boolean",
1716
1930
 
1717
- },
1931
+   },
1718
1932
 
1719
- totalWins: {
1933
+   totalWins: {
1720
1934
 
1721
- type: "number",
1935
+     type: "number",
1722
1936
 
1723
- min: 0,
1937
+     min: 0,
1724
1938
 
1725
- preventDecimal: true,
1939
+     preventDecimal: true,
1726
1940
 
1727
- },
1941
+   },
1728
1942
 
1729
- email: {
1943
+   email: {
1730
1944
 
1731
- regex: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$/,
1945
+     regex: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\.[a-zA-Z]{2,}$/,
1732
1946
 
1733
- },
1947
+   },
1734
1948
 
1735
- githubLink: {
1949
+   githubLink: {
1736
1950
 
1737
- type: "url",
1951
+     type: "url",
1738
1952
 
1739
- },
1953
+   },
1740
1954
 
1741
- accountStatus: {
1955
+   accountStatus: {
1742
1956
 
1743
- type: "enum",
1957
+     type: "enum",
1744
1958
 
1745
- enumValues: ["Active", "Inactive", 200],
1959
+     enumValues: ["Active", "Inactive", 200],
1746
1960
 
1747
- },
1961
+   },
1748
1962
 
1749
- marks: {
1963
+   marks: {
1750
1964
 
1751
- range: "0-100",
1965
+     range: "0-100",
1752
1966
 
1753
- },
1967
+   },
1754
1968
 
1755
- allMarks: {
1969
+   allMarks: {
1756
1970
 
1757
- type: "array",
1971
+     type: "array",
1758
1972
 
1759
- allowEmptyArray: false,
1973
+     allowEmptyArray: false,
1760
1974
 
1761
- elementConstraints: {
1975
+     elementConstraints: {
1762
1976
 
1763
- type: "number",
1977
+       type: "number",
1764
1978
 
1765
- allowNull: false,
1979
+       allowNull: false,
1766
1980
 
1767
- range: "0-100",
1981
+       range: "0-100",
1768
1982
 
1769
- },
1983
+     },
1770
1984
 
1771
- },
1985
+   },
1772
1986
 
1773
- totalScore: {
1987
+   totalScore: {
1774
1988
 
1775
- type: "number",
1989
+     type: "number",
1776
1990
 
1777
- dependency: {
1991
+     dependency: {
1778
1992
 
1779
- result: {
1993
+       result: {
1780
1994
 
1781
- setDependencyRule: (totalScore, result) => {
1995
+         setDependencyRule: (totalScore, result) => {
1782
1996
 
1783
- return { mandatory: true, allowNull: false, type: "string" };
1997
+           return { mandatory: true, allowNull: false, type: "string" };
1784
1998
 
1785
- },
1999
+         },
1786
2000
 
1787
- },
2001
+       },
1788
2002
 
1789
- },
2003
+     },
1790
2004
 
1791
- },
2005
+   },
1792
2006
 
1793
- result: {
2007
+   result: {
1794
2008
 
1795
- type: "string",
2009
+     type: "string",
1796
2010
 
1797
- dependency: {
2011
+     dependency: {
1798
2012
 
1799
- totalScore: {
2013
+       totalScore: {
1800
2014
 
1801
- setDependencyRule: (result, totalScore) => {
2015
+         setDependencyRule: (result, totalScore) => {
1802
2016
 
1803
- return { mandatory: true, allowNull: false, type: "number" };
2017
+           return { mandatory: true, allowNull: false, type: "number" };
1804
2018
 
1805
- },
2019
+         },
1806
2020
 
1807
- },
2021
+       },
1808
2022
 
1809
- },
2023
+     },
1810
2024
 
1811
- },
2025
+   },
1812
2026
 
1813
- minSalary: {
2027
+   minSalary: {
1814
2028
 
1815
- mandatory: true,
2029
+     mandatory: true,
1816
2030
 
1817
- min: 1,
2031
+     min: 1,
1818
2032
 
1819
- type: "number",
2033
+     type: "number",
1820
2034
 
1821
- dependency: {
2035
+     dependency: {
1822
2036
 
1823
- maxSalary: {
2037
+       maxSalary: {
1824
2038
 
1825
- setDependencyRule: (minSalary, maxSalary) => {
2039
+         setDependencyRule: (minSalary, maxSalary) => {
1826
2040
 
1827
- return {
2041
+           return {
1828
2042
 
1829
- mandatory: true,
2043
+             mandatory: true,
1830
2044
 
1831
- min: minSalary + 1,
2045
+             min: minSalary + 1,
1832
2046
 
1833
- minError: "maxSalary must be more than minSalary",
2047
+             minError: "maxSalary must be more than minSalary",
1834
2048
 
1835
- };
2049
+           };
1836
2050
 
1837
- },
2051
+         },
1838
2052
 
1839
- },
2053
+       },
1840
2054
 
1841
- },
2055
+     },
1842
2056
 
1843
- },
2057
+   },
1844
2058
 
1845
- maxSalary: {
2059
+   maxSalary: {
1846
2060
 
1847
- dependency: {
2061
+     dependency: {
1848
2062
 
1849
- minSalary: {
2063
+       minSalary: {
1850
2064
 
1851
- setDependencyRule: (maxSalary, minSalary) => {
2065
+         setDependencyRule: (maxSalary, minSalary) => {
1852
2066
 
1853
- return {
2067
+           return {
1854
2068
 
1855
- mandatory: true,
2069
+             mandatory: true,
1856
2070
 
1857
- max: maxSalary - 1,
2071
+             max: maxSalary - 1,
1858
2072
 
1859
- maxError: "minSalary must be less than maxSalary",
2073
+             maxError: "minSalary must be less than maxSalary",
1860
2074
 
1861
- };
2075
+           };
1862
2076
 
1863
- },
2077
+         },
1864
2078
 
1865
- },
2079
+       },
1866
2080
 
1867
- },
2081
+     },
1868
2082
 
1869
- },
2083
+   },
1870
2084
 
1871
- address: {
2085
+   address: {
1872
2086
 
1873
- mandatory: true,
2087
+     mandatory: true,
1874
2088
 
1875
- type: "object",
2089
+     type: "object",
1876
2090
 
1877
- allowEmptyObject: false,
2091
+     allowEmptyObject: false,
1878
2092
 
1879
- objectAttr: {
2093
+     objectAttr: {
1880
2094
 
1881
- country: { mandatory: true, type: "string" },
2095
+       country: { mandatory: true, type: "string" },
1882
2096
 
1883
- state: {
2097
+       state: {
1884
2098
 
1885
- mandatory: true,
2099
+         mandatory: true,
1886
2100
 
1887
- type: "string",
2101
+         type: "string",
1888
2102
 
1889
- },
2103
+       },
1890
2104
 
1891
- city: {},
2105
+       city: {},
1892
2106
 
1893
- zip: {
2107
+       zip: {
1894
2108
 
1895
- mandatory: true,
2109
+         mandatory: true,
1896
2110
 
1897
- type: "string",
2111
+         type: "string",
1898
2112
 
1899
- },
2113
+       },
1900
2114
 
1901
- position: {
2115
+       position: {
1902
2116
 
1903
- mandatory: true,
2117
+         mandatory: true,
1904
2118
 
1905
- type: "object",
2119
+         type: "object",
1906
2120
 
1907
- allowEmptyObject: false,
2121
+         allowEmptyObject: false,
1908
2122
 
1909
- objectAttr: {
2123
+         objectAttr: {
1910
2124
 
1911
- lattitude: { mandatory: true, type: "number" },
2125
+           lattitude: { mandatory: true, type: "number" },
1912
2126
 
1913
- longitude: {
2127
+           longitude: {
1914
2128
 
1915
- mandatory: true,
2129
+             mandatory: true,
1916
2130
 
1917
- type: "number",
2131
+             type: "number",
1918
2132
 
1919
- },
2133
+           },
1920
2134
 
1921
- },
2135
+         },
1922
2136
 
1923
- },
2137
+       },
1924
2138
 
1925
- },
2139
+     },
1926
2140
 
1927
- },
2141
+   },
1928
2142
 
1929
2143
  }
1930
-
1931
2144
  ```
1932
2145
 
1933
2146
  ### Usage
1934
2147
 
1935
- #### creating your route with payload validation middleware
2148
+ #### Creating a route with payload validation middleware
1936
2149
 
1937
- ```javascript
2150
+ ```js
1938
2151
 
1939
- *//Here validatePayload is your middleware function, where you're invoking perfect payload*
2152
+ **// validatePayload is the middleware that invokes perfectPayload()**
1940
2153
 
1941
2154
  router.post(
1942
2155
 
1943
- "/payload-validation",
2156
+   "/payload-validation",
1944
2157
 
1945
- validatePayload({ rule: <your validation rule json object> }),
2158
+   validatePayload({ rule: <your validation rule json object> }),
1946
2159
 
1947
- (req, res) => res.send("OK")
2160
+   (req, res) => res.send("OK")
1948
2161
 
1949
2162
  );
1950
-
1951
2163
  ```
1952
2164
 
1953
- #### 1 Use perfect-payload in your middleware like below(for MODULE JS)
2165
+ #### ES Modules middleware example
1954
2166
 
1955
- ```javascript
1956
- import { perfectPayloadV1 } from "perfect-payload";
2167
+ ```js
2168
+ import { perfectPayload } from "perfect-payload";
1957
2169
 
1958
2170
  export const validatePayload = ({ rule }) => {
1959
2171
  return (req, res, next) => {
1960
2172
  try {
1961
- const { statusCode, ...response } = perfectPayloadV1(req?.body, rule);
2173
+ const { statusCode, ...response } = perfectPayload(req?.body, rule);
1962
2174
 
1963
2175
  if (+statusCode >= 200 && +statusCode <= 299) {
1964
2176
  req.validatedBody = response?.validatedPayload;
@@ -1974,15 +2186,15 @@ export const validatePayload = ({ rule }) => {
1974
2186
  };
1975
2187
  ```
1976
2188
 
1977
- #### 2 Use perfect-payload in your middleware like below(for COMMON JS)
2189
+ #### CommonJS middleware example
1978
2190
 
1979
- ```javascript
2191
+ ```js
1980
2192
  function validatePayload({ rule }) {
1981
2193
  return async (req, res, next) => {
1982
2194
  try {
1983
- const { perfectPayloadV1 } = await import("perfect-payload");
2195
+ const { perfectPayload } = await import("perfect-payload");
1984
2196
 
1985
- const { statusCode, ...response } = perfectPayloadV1(req?.body, rule);
2197
+ const { statusCode, ...response } = perfectPayload(req?.body, rule);
1986
2198
 
1987
2199
  if (+statusCode >= 200 && +statusCode <= 299) {
1988
2200
  req.validatedBody = response?.validatedPayload;
@@ -2002,7 +2214,7 @@ function validatePayload({ rule }) {
2002
2214
  module.exports = { validatePayload };
2003
2215
  ```
2004
2216
 
2005
- **---**
2217
+ ---
2006
2218
 
2007
2219
  This documentation provides a comprehensive guide to using the data
2008
2220