perfect-payload 1.5.0-beta.0 → 1.6.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 +791 -350
- package/index.js +172 -6
- package/package.json +11 -1
- package/.babelrc +0 -4
- package/.github/workflows/publish.yml +0 -332
package/README.md
CHANGED
|
@@ -1,22 +1,45 @@
|
|
|
1
1
|
# perfect-payload
|
|
2
2
|
|
|
3
|
-
A lightweight JavaScript payload validation
|
|
4
|
-
and JSON payloads
|
|
5
|
-
|
|
6
|
-
`perfect-payload`
|
|
7
|
-
paths, synchronous custom
|
|
8
|
-
transformation/sanitization, array
|
|
9
|
-
array
|
|
3
|
+
A lightweight JavaScript payload validation and transformation utility
|
|
4
|
+
for API and JSON payloads.
|
|
5
|
+
|
|
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.
|
|
11
|
+
|
|
12
|
+
## Highlights
|
|
13
|
+
|
|
14
|
+
- Lightweight, rule-based payload validation
|
|
15
|
+
- Structured errors with stable machine-readable codes
|
|
16
|
+
- Exact nested paths such as `profile.email` and
|
|
17
|
+
`products[1].quantity`
|
|
18
|
+
- Recursive `objectAttr` and `elementConstraints` validation
|
|
19
|
+
- `minItems` and `maxItems` array constraints
|
|
20
|
+
- Built-in `trim`, `lowercase`, and `uppercase` sanitization
|
|
21
|
+
- Custom synchronous `transform(value, payload)`
|
|
22
|
+
- Custom synchronous validators with `perfectPayload()`
|
|
23
|
+
- Custom synchronous or asynchronous validators with
|
|
24
|
+
`perfectPayloadAsync()`
|
|
25
|
+
- Transformed values returned through `validatedPayload`
|
|
26
|
+
- Original input payload is not mutated
|
|
27
|
+
- Extra payload fields are filtered from `validatedPayload`
|
|
28
|
+
- Legacy `perfectPayloadV1()` retained during the migration period
|
|
10
29
|
|
|
11
30
|
## Quick Links
|
|
12
31
|
|
|
13
32
|
- [Installation](#installation)
|
|
14
33
|
- [Basic Usage](#basic-usage)
|
|
34
|
+
- [Synchronous vs Asynchronous
|
|
35
|
+
Validation](#synchronous-vs-asynchronous-validation)
|
|
15
36
|
- [Validation Rules](#validation-rules)
|
|
16
|
-
- [Array Size and Nested
|
|
37
|
+
- [Array Size and Nested
|
|
38
|
+
Validation](#array-size-and-nested-validation)
|
|
17
39
|
- [Transformations and
|
|
18
40
|
Sanitization](#transformations-and-sanitization)
|
|
19
41
|
- [Custom Validators](#customvalidator)
|
|
42
|
+
- [Asynchronous Validation](#asynchronous-validation)
|
|
20
43
|
- [Error Codes](#error-codes)
|
|
21
44
|
- [Custom Error Messages](#custom-error-messages)
|
|
22
45
|
- [Nested Objects and Array Field
|
|
@@ -79,24 +102,25 @@ console.log(result);
|
|
|
79
102
|
|
|
80
103
|
{
|
|
81
104
|
|
|
82
|
-
|
|
105
|
+
statusCode: 200,
|
|
83
106
|
|
|
84
|
-
|
|
107
|
+
valid: true,
|
|
85
108
|
|
|
86
|
-
|
|
109
|
+
validatedPayload: {
|
|
87
110
|
|
|
88
|
-
|
|
111
|
+
name: "Kiran",
|
|
89
112
|
|
|
90
|
-
|
|
113
|
+
email: "kiran@example.com",
|
|
91
114
|
|
|
92
|
-
|
|
115
|
+
age: 29
|
|
93
116
|
|
|
94
|
-
|
|
117
|
+
}
|
|
95
118
|
|
|
96
119
|
}
|
|
97
120
|
```
|
|
98
121
|
|
|
99
122
|
Note: The validatedPayload contains only the fields
|
|
123
|
+
|
|
100
124
|
defined in the
|
|
101
125
|
|
|
102
126
|
schema, automatically filtering out any extra attributes. You can use it
|
|
@@ -111,25 +135,25 @@ to safely overwrite request.body or assign it to a new request property
|
|
|
111
135
|
|
|
112
136
|
{
|
|
113
137
|
|
|
114
|
-
|
|
138
|
+
statusCode: 400,
|
|
115
139
|
|
|
116
|
-
|
|
140
|
+
valid: false,
|
|
117
141
|
|
|
118
|
-
|
|
142
|
+
message: "One or more attribute values are invalid",
|
|
119
143
|
|
|
120
|
-
|
|
144
|
+
errors: [
|
|
121
145
|
|
|
122
|
-
|
|
146
|
+
{
|
|
123
147
|
|
|
124
|
-
|
|
148
|
+
path: "email",
|
|
125
149
|
|
|
126
|
-
|
|
150
|
+
code: "INVALID_EMAIL",
|
|
127
151
|
|
|
128
|
-
|
|
152
|
+
message: "Invalid email format for attribute email"
|
|
129
153
|
|
|
130
|
-
|
|
154
|
+
}
|
|
131
155
|
|
|
132
|
-
|
|
156
|
+
]
|
|
133
157
|
|
|
134
158
|
}
|
|
135
159
|
```
|
|
@@ -140,11 +164,11 @@ Each error returned by `perfectPayload()` contains:
|
|
|
140
164
|
|
|
141
165
|
{
|
|
142
166
|
|
|
143
|
-
|
|
167
|
+
path: "field.path",
|
|
144
168
|
|
|
145
|
-
|
|
169
|
+
code: "ERROR_CODE",
|
|
146
170
|
|
|
147
|
-
|
|
171
|
+
message: "Human readable validation message"
|
|
148
172
|
|
|
149
173
|
}
|
|
150
174
|
```
|
|
@@ -159,6 +183,55 @@ failure.
|
|
|
159
183
|
|
|
160
184
|
\- Submitted payload values are not included in default error messages.
|
|
161
185
|
|
|
186
|
+
## Synchronous vs Asynchronous Validation
|
|
187
|
+
|
|
188
|
+
For normal synchronous validation, use `perfectPayload()`:
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
import { perfectPayload } from "perfect-payload";
|
|
192
|
+
|
|
193
|
+
const result = perfectPayload(payload, validationRules);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
When any `customValidator` needs to perform asynchronous work, use
|
|
197
|
+
`perfectPayloadAsync()` and `await` the result:
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
import { perfectPayloadAsync } from "perfect-payload";
|
|
201
|
+
|
|
202
|
+
const result = await perfectPayloadAsync(payload, validationRules);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The public APIs are:
|
|
206
|
+
|
|
207
|
+
```text
|
|
208
|
+
perfectPayloadV1() legacy API; deprecated
|
|
209
|
+
perfectPayload() synchronous validation
|
|
210
|
+
perfectPayloadAsync() synchronous + asynchronous customValidator
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`perfectPayload()` remains synchronous and intentionally rejects a
|
|
214
|
+
`customValidator` that returns a Promise. This preserves the existing
|
|
215
|
+
synchronous API contract.
|
|
216
|
+
|
|
217
|
+
`perfectPayloadAsync()` first performs transformations and normal
|
|
218
|
+
synchronous validation. If synchronous validation fails, the result is
|
|
219
|
+
returned immediately and asynchronous validators are not executed. This
|
|
220
|
+
avoids unnecessary asynchronous work for payloads that are already
|
|
221
|
+
invalid.
|
|
222
|
+
|
|
223
|
+
```text
|
|
224
|
+
transformations
|
|
225
|
+
↓
|
|
226
|
+
synchronous validation
|
|
227
|
+
↓
|
|
228
|
+
sync errors? ── yes ──→ return validation errors
|
|
229
|
+
↓ no
|
|
230
|
+
async customValidator
|
|
231
|
+
↓
|
|
232
|
+
return result
|
|
233
|
+
```
|
|
234
|
+
|
|
162
235
|
## Legacy API
|
|
163
236
|
|
|
164
237
|
`perfectPayloadV1()` is still available for backward compatibility.
|
|
@@ -200,6 +273,7 @@ errors: [
|
|
|
200
273
|
```
|
|
201
274
|
|
|
202
275
|
Note: If an inValidPayloadResponse is provided, the
|
|
276
|
+
|
|
203
277
|
system returns
|
|
204
278
|
|
|
205
279
|
it alongside an automatically generated errors property. Do not include
|
|
@@ -211,6 +285,7 @@ object.
|
|
|
211
285
|
## Validation Rules
|
|
212
286
|
|
|
213
287
|
`perfectPayload()` supports validation, nested-schema,
|
|
288
|
+
|
|
214
289
|
custom-validation, and transformation rules.
|
|
215
290
|
|
|
216
291
|
### `mandatory`
|
|
@@ -305,6 +380,7 @@ Default: Not applied when omitted.
|
|
|
305
380
|
const rules = {
|
|
306
381
|
tags: {
|
|
307
382
|
type: "array",
|
|
383
|
+
|
|
308
384
|
minItems: 2,
|
|
309
385
|
},
|
|
310
386
|
};
|
|
@@ -313,14 +389,20 @@ const rules = {
|
|
|
313
389
|
An array with fewer than 2 items returns `MIN_ITEMS`.
|
|
314
390
|
|
|
315
391
|
```js
|
|
392
|
+
|
|
316
393
|
{
|
|
394
|
+
|
|
317
395
|
path: "tags",
|
|
396
|
+
|
|
318
397
|
code: "MIN_ITEMS",
|
|
398
|
+
|
|
319
399
|
message: "Attribute tags must contain at least 2 item(s)"
|
|
400
|
+
|
|
320
401
|
}
|
|
321
402
|
```
|
|
322
403
|
|
|
323
|
-
`minItems` is enforced even when `allowEmptyArray: true` is set. For
|
|
404
|
+
`minItems` is enforced even when `allowEmptyArray: true` is set. For
|
|
405
|
+
example, `minItems: 2` still rejects `[]`.
|
|
324
406
|
|
|
325
407
|
Error code: `MIN_ITEMS`
|
|
326
408
|
|
|
@@ -336,6 +418,7 @@ Default: Not applied when omitted.
|
|
|
336
418
|
const rules = {
|
|
337
419
|
tags: {
|
|
338
420
|
type: "array",
|
|
421
|
+
|
|
339
422
|
maxItems: 5,
|
|
340
423
|
},
|
|
341
424
|
};
|
|
@@ -344,10 +427,15 @@ const rules = {
|
|
|
344
427
|
An array with more than 5 items returns `MAX_ITEMS`.
|
|
345
428
|
|
|
346
429
|
```js
|
|
430
|
+
|
|
347
431
|
{
|
|
432
|
+
|
|
348
433
|
path: "tags",
|
|
434
|
+
|
|
349
435
|
code: "MAX_ITEMS",
|
|
436
|
+
|
|
350
437
|
message: "Attribute tags must contain at most 5 item(s)"
|
|
438
|
+
|
|
351
439
|
}
|
|
352
440
|
```
|
|
353
441
|
|
|
@@ -554,6 +642,7 @@ Error code: `MAX_LENGTH`
|
|
|
554
642
|
Prevents decimal numbers.
|
|
555
643
|
|
|
556
644
|
Default: `false`; both integer and decimal numbers are
|
|
645
|
+
|
|
557
646
|
allowed.
|
|
558
647
|
|
|
559
648
|
Example:
|
|
@@ -664,13 +753,13 @@ Example error:
|
|
|
664
753
|
|
|
665
754
|
{
|
|
666
755
|
|
|
667
|
-
|
|
756
|
+
path: "marks[2]",
|
|
668
757
|
|
|
669
|
-
|
|
758
|
+
code: "OUT_OF_RANGE",
|
|
670
759
|
|
|
671
|
-
|
|
760
|
+
message:
|
|
672
761
|
|
|
673
|
-
|
|
762
|
+
"Attribute marks[2] should have a value between 0 and 100"
|
|
674
763
|
|
|
675
764
|
}
|
|
676
765
|
```
|
|
@@ -739,13 +828,13 @@ Nested errors include the complete field path:
|
|
|
739
828
|
|
|
740
829
|
{
|
|
741
830
|
|
|
742
|
-
|
|
831
|
+
path: "address.location.latitude",
|
|
743
832
|
|
|
744
|
-
|
|
833
|
+
code: "INVALID_TYPE",
|
|
745
834
|
|
|
746
|
-
|
|
835
|
+
message:
|
|
747
836
|
|
|
748
|
-
|
|
837
|
+
"Invalid type for attribute address.location.latitude, required number value"
|
|
749
838
|
|
|
750
839
|
}
|
|
751
840
|
```
|
|
@@ -784,13 +873,13 @@ Example error:
|
|
|
784
873
|
|
|
785
874
|
{
|
|
786
875
|
|
|
787
|
-
|
|
876
|
+
path: "maxSalary",
|
|
788
877
|
|
|
789
|
-
|
|
878
|
+
code: "MIN_VALUE",
|
|
790
879
|
|
|
791
|
-
|
|
880
|
+
message:
|
|
792
881
|
|
|
793
|
-
|
|
882
|
+
"maxSalary must be more than minSalary"
|
|
794
883
|
|
|
795
884
|
}
|
|
796
885
|
```
|
|
@@ -799,7 +888,9 @@ Example error:
|
|
|
799
888
|
|
|
800
889
|
## Array Size and Nested Validation
|
|
801
890
|
|
|
802
|
-
`perfectPayload()` supports array size constraints and recursive
|
|
891
|
+
`perfectPayload()` supports array size constraints and recursive
|
|
892
|
+
validation of arrays and objects at multiple depths. Array indexes and
|
|
893
|
+
nested object keys are preserved in structured error paths.
|
|
803
894
|
|
|
804
895
|
### Array size constraints
|
|
805
896
|
|
|
@@ -809,12 +900,17 @@ Use `minItems` and `maxItems` with `type: "array"`:
|
|
|
809
900
|
const rules = {
|
|
810
901
|
products: {
|
|
811
902
|
type: "array",
|
|
903
|
+
|
|
812
904
|
minItems: 1,
|
|
905
|
+
|
|
813
906
|
maxItems: 3,
|
|
907
|
+
|
|
814
908
|
elementConstraints: {
|
|
815
909
|
type: "object",
|
|
910
|
+
|
|
816
911
|
objectAttr: {
|
|
817
912
|
productId: { mandatory: true, type: "string" },
|
|
913
|
+
|
|
818
914
|
quantity: { mandatory: true, type: "number", min: 1 },
|
|
819
915
|
},
|
|
820
916
|
},
|
|
@@ -825,18 +921,26 @@ const rules = {
|
|
|
825
921
|
If the array is empty, `minItems` reports the array path itself:
|
|
826
922
|
|
|
827
923
|
```js
|
|
924
|
+
|
|
828
925
|
{
|
|
926
|
+
|
|
829
927
|
path: "products",
|
|
928
|
+
|
|
830
929
|
code: "MIN_ITEMS",
|
|
930
|
+
|
|
831
931
|
message: "Attribute products must contain at least 1 item(s)"
|
|
932
|
+
|
|
832
933
|
}
|
|
833
934
|
```
|
|
834
935
|
|
|
835
936
|
### Arrays of objects
|
|
836
937
|
|
|
837
|
-
`elementConstraints` can contain `objectAttr`, allowing every object in
|
|
938
|
+
`elementConstraints` can contain `objectAttr`, allowing every object in
|
|
939
|
+
an array to use a nested schema. An invalid quantity in the second
|
|
940
|
+
product is reported as:
|
|
838
941
|
|
|
839
942
|
```text
|
|
943
|
+
|
|
840
944
|
products[1].quantity
|
|
841
945
|
```
|
|
842
946
|
|
|
@@ -848,21 +952,32 @@ products[1].quantity
|
|
|
848
952
|
const rules = {
|
|
849
953
|
orders: {
|
|
850
954
|
type: "array",
|
|
955
|
+
|
|
851
956
|
minItems: 1,
|
|
957
|
+
|
|
852
958
|
maxItems: 2,
|
|
959
|
+
|
|
853
960
|
elementConstraints: {
|
|
854
961
|
type: "object",
|
|
962
|
+
|
|
855
963
|
objectAttr: {
|
|
856
964
|
orderId: { mandatory: true, type: "string" },
|
|
965
|
+
|
|
857
966
|
items: {
|
|
858
967
|
mandatory: true,
|
|
968
|
+
|
|
859
969
|
type: "array",
|
|
970
|
+
|
|
860
971
|
minItems: 1,
|
|
972
|
+
|
|
861
973
|
maxItems: 2,
|
|
974
|
+
|
|
862
975
|
elementConstraints: {
|
|
863
976
|
type: "object",
|
|
977
|
+
|
|
864
978
|
objectAttr: {
|
|
865
979
|
productId: { mandatory: true, type: "string" },
|
|
980
|
+
|
|
866
981
|
quantity: { mandatory: true, type: "number", min: 1 },
|
|
867
982
|
},
|
|
868
983
|
},
|
|
@@ -873,31 +988,41 @@ const rules = {
|
|
|
873
988
|
};
|
|
874
989
|
```
|
|
875
990
|
|
|
876
|
-
A deep validation failure preserves the complete indexed path, for
|
|
991
|
+
A deep validation failure preserves the complete indexed path, for
|
|
992
|
+
example:
|
|
877
993
|
|
|
878
994
|
```text
|
|
995
|
+
|
|
879
996
|
orders[1].items[2].quantity
|
|
880
997
|
```
|
|
881
998
|
|
|
882
|
-
Array constraints work at nested levels too. A nested array can report
|
|
999
|
+
Array constraints work at nested levels too. A nested array can report
|
|
1000
|
+
paths such as:
|
|
883
1001
|
|
|
884
1002
|
```text
|
|
1003
|
+
|
|
885
1004
|
orders[1].items
|
|
886
1005
|
```
|
|
887
1006
|
|
|
888
1007
|
Nested arrays are supported and every array index is preserved:
|
|
889
1008
|
|
|
890
1009
|
```text
|
|
1010
|
+
|
|
891
1011
|
matrix[1][1]
|
|
1012
|
+
|
|
892
1013
|
matrix[1][1][1]
|
|
893
1014
|
```
|
|
894
1015
|
|
|
895
|
-
Transformations applied inside nested objects or array elements are
|
|
1016
|
+
Transformations applied inside nested objects or array elements are
|
|
1017
|
+
preserved in `validatedPayload`, while the original input remains
|
|
1018
|
+
unchanged.
|
|
896
1019
|
|
|
897
1020
|
### Transformations and Sanitization
|
|
898
1021
|
|
|
899
1022
|
`perfectPayload()` can transform a field before its validation rules
|
|
1023
|
+
|
|
900
1024
|
run. The transformed value is returned in `validatedPayload`, while the
|
|
1025
|
+
|
|
901
1026
|
original input object is not mutated.
|
|
902
1027
|
|
|
903
1028
|
Supported transformation rules:
|
|
@@ -907,53 +1032,81 @@ Rule Purpose
|
|
|
907
1032
|
---
|
|
908
1033
|
|
|
909
1034
|
`trim` Removes leading and trailing whitespace from strings
|
|
1035
|
+
|
|
910
1036
|
`lowercase` Converts strings to lowercase
|
|
1037
|
+
|
|
911
1038
|
`uppercase` Converts strings to uppercase
|
|
1039
|
+
|
|
912
1040
|
`transform` Runs a custom synchronous transformation function
|
|
913
1041
|
|
|
914
1042
|
Transformations always run in this fixed order, regardless of the order
|
|
1043
|
+
|
|
915
1044
|
in which the rule properties are written:
|
|
916
1045
|
|
|
917
1046
|
```text
|
|
1047
|
+
|
|
918
1048
|
trim
|
|
1049
|
+
|
|
919
1050
|
↓
|
|
1051
|
+
|
|
920
1052
|
lowercase
|
|
1053
|
+
|
|
921
1054
|
↓
|
|
1055
|
+
|
|
922
1056
|
uppercase
|
|
1057
|
+
|
|
923
1058
|
↓
|
|
1059
|
+
|
|
924
1060
|
transform(value, payload)
|
|
1061
|
+
|
|
925
1062
|
↓
|
|
1063
|
+
|
|
926
1064
|
validation rules
|
|
1065
|
+
|
|
927
1066
|
↓
|
|
1067
|
+
|
|
928
1068
|
customValidator
|
|
1069
|
+
|
|
929
1070
|
↓
|
|
1071
|
+
|
|
930
1072
|
validatedPayload
|
|
931
1073
|
```
|
|
932
1074
|
|
|
933
1075
|
#### `trim`
|
|
934
1076
|
|
|
935
1077
|
```js
|
|
1078
|
+
|
|
936
1079
|
const payload = {
|
|
1080
|
+
|
|
937
1081
|
name: " Kiran Poojary ",
|
|
1082
|
+
|
|
938
1083
|
};
|
|
939
1084
|
|
|
940
1085
|
const rules = {
|
|
1086
|
+
|
|
941
1087
|
name: {
|
|
1088
|
+
|
|
942
1089
|
type: "string",
|
|
1090
|
+
|
|
943
1091
|
trim: true,
|
|
1092
|
+
|
|
944
1093
|
},
|
|
1094
|
+
|
|
945
1095
|
};
|
|
946
1096
|
|
|
947
1097
|
const result = perfectPayload(payload, rules);
|
|
948
1098
|
|
|
949
1099
|
console.log(result.validatedPayload.name);
|
|
950
|
-
|
|
1100
|
+
|
|
1101
|
+
*// "Kiran Poojary"*
|
|
951
1102
|
|
|
952
1103
|
console.log(payload.name);
|
|
953
|
-
|
|
1104
|
+
|
|
1105
|
+
*// " Kiran Poojary "*
|
|
954
1106
|
```
|
|
955
1107
|
|
|
956
1108
|
`trim` applies only to string values. Non-string values are left
|
|
1109
|
+
|
|
957
1110
|
unchanged.
|
|
958
1111
|
|
|
959
1112
|
#### `lowercase`
|
|
@@ -962,13 +1115,16 @@ unchanged.
|
|
|
962
1115
|
const rules = {
|
|
963
1116
|
email: {
|
|
964
1117
|
trim: true,
|
|
1118
|
+
|
|
965
1119
|
lowercase: true,
|
|
1120
|
+
|
|
966
1121
|
type: "email",
|
|
967
1122
|
},
|
|
968
1123
|
};
|
|
969
1124
|
```
|
|
970
1125
|
|
|
971
1126
|
For `" KIRAN@EXAMPLE.COM "`, the validated value becomes
|
|
1127
|
+
|
|
972
1128
|
`"kiran@example.com"`.
|
|
973
1129
|
|
|
974
1130
|
#### `uppercase`
|
|
@@ -977,6 +1133,7 @@ For `" KIRAN@EXAMPLE.COM "`, the validated value becomes
|
|
|
977
1133
|
const rules = {
|
|
978
1134
|
countryCode: {
|
|
979
1135
|
type: "string",
|
|
1136
|
+
|
|
980
1137
|
uppercase: true,
|
|
981
1138
|
},
|
|
982
1139
|
};
|
|
@@ -985,6 +1142,7 @@ const rules = {
|
|
|
985
1142
|
For `"in"`, the validated value becomes `"IN"`.
|
|
986
1143
|
|
|
987
1144
|
`lowercase: true` and `uppercase: true` cannot be enabled together for
|
|
1145
|
+
|
|
988
1146
|
the same field. Doing so throws a schema configuration error.
|
|
989
1147
|
|
|
990
1148
|
#### `transform`
|
|
@@ -995,6 +1153,7 @@ Use `transform` when the built-in string transformations are not enough.
|
|
|
995
1153
|
const rules = {
|
|
996
1154
|
phone: {
|
|
997
1155
|
type: "string",
|
|
1156
|
+
|
|
998
1157
|
transform: (value) => value.replace(/\s+/g, ""),
|
|
999
1158
|
},
|
|
1000
1159
|
};
|
|
@@ -1011,31 +1170,46 @@ transform: (value, payload) => {
|
|
|
1011
1170
|
```
|
|
1012
1171
|
|
|
1013
1172
|
- `value` is the field value after the built-in transformations have
|
|
1173
|
+
|
|
1014
1174
|
run.
|
|
1175
|
+
|
|
1015
1176
|
- `payload` is the current payload/object being validated.
|
|
1016
1177
|
|
|
1017
1178
|
This makes cross-field transformations possible:
|
|
1018
1179
|
|
|
1019
1180
|
```js
|
|
1181
|
+
|
|
1020
1182
|
const payload = {
|
|
1183
|
+
|
|
1021
1184
|
amount: 100,
|
|
1185
|
+
|
|
1022
1186
|
multiplier: 2,
|
|
1187
|
+
|
|
1023
1188
|
};
|
|
1024
1189
|
|
|
1025
1190
|
const rules = {
|
|
1191
|
+
|
|
1026
1192
|
amount: {
|
|
1193
|
+
|
|
1027
1194
|
transform: (value, payload) => value * payload.multiplier,
|
|
1195
|
+
|
|
1028
1196
|
type: "number",
|
|
1197
|
+
|
|
1029
1198
|
},
|
|
1199
|
+
|
|
1030
1200
|
multiplier: {
|
|
1201
|
+
|
|
1031
1202
|
type: "number",
|
|
1203
|
+
|
|
1032
1204
|
},
|
|
1205
|
+
|
|
1033
1206
|
};
|
|
1034
1207
|
|
|
1035
1208
|
const result = perfectPayload(payload, rules);
|
|
1036
1209
|
|
|
1037
1210
|
console.log(result.validatedPayload.amount);
|
|
1038
|
-
|
|
1211
|
+
|
|
1212
|
+
*// 200*
|
|
1039
1213
|
```
|
|
1040
1214
|
|
|
1041
1215
|
A custom transformer may also change the data type before validation:
|
|
@@ -1044,36 +1218,48 @@ A custom transformer may also change the data type before validation:
|
|
|
1044
1218
|
const rules = {
|
|
1045
1219
|
quantity: {
|
|
1046
1220
|
transform: (value) => Number(value),
|
|
1221
|
+
|
|
1047
1222
|
type: "number",
|
|
1223
|
+
|
|
1048
1224
|
min: 1,
|
|
1225
|
+
|
|
1049
1226
|
max: 100,
|
|
1050
1227
|
},
|
|
1051
1228
|
};
|
|
1052
1229
|
```
|
|
1053
1230
|
|
|
1054
1231
|
The transformed value is validated by the normal validation rules and is
|
|
1232
|
+
|
|
1055
1233
|
also the value received by `customValidator`.
|
|
1056
1234
|
|
|
1057
1235
|
Transformations work inside `objectAttr` and `elementConstraints`, and
|
|
1236
|
+
|
|
1058
1237
|
transformed nested/array values are preserved in `validatedPayload`.
|
|
1059
1238
|
|
|
1060
1239
|
```js
|
|
1061
1240
|
const rules = {
|
|
1062
1241
|
profile: {
|
|
1063
1242
|
type: "object",
|
|
1243
|
+
|
|
1064
1244
|
objectAttr: {
|
|
1065
1245
|
name: {
|
|
1066
1246
|
trim: true,
|
|
1247
|
+
|
|
1067
1248
|
uppercase: true,
|
|
1249
|
+
|
|
1068
1250
|
type: "string",
|
|
1069
1251
|
},
|
|
1070
1252
|
},
|
|
1071
1253
|
},
|
|
1254
|
+
|
|
1072
1255
|
tags: {
|
|
1073
1256
|
type: "array",
|
|
1257
|
+
|
|
1074
1258
|
elementConstraints: {
|
|
1075
1259
|
trim: true,
|
|
1260
|
+
|
|
1076
1261
|
lowercase: true,
|
|
1262
|
+
|
|
1077
1263
|
type: "string",
|
|
1078
1264
|
},
|
|
1079
1265
|
},
|
|
@@ -1081,29 +1267,39 @@ const rules = {
|
|
|
1081
1267
|
```
|
|
1082
1268
|
|
|
1083
1269
|
Missing optional fields are not transformed. An input value of `null` is
|
|
1270
|
+
|
|
1084
1271
|
not passed to transformation functions; null handling remains controlled
|
|
1272
|
+
|
|
1085
1273
|
by `allowNull`.
|
|
1086
1274
|
|
|
1087
|
-
|
|
1275
|
+
\*\*\*\*Important:\*\*\*\* `transform` is synchronous. A non-function
|
|
1276
|
+
transformer,
|
|
1277
|
+
|
|
1088
1278
|
an `async` transformer, a transformer that returns a Promise, or a
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1279
|
+
|
|
1280
|
+
transformer that returns `undefined` is not supported and throws an
|
|
1281
|
+
error.
|
|
1282
|
+
|
|
1283
|
+
Returning `null`, `""`, `0`, or `false` is allowed; the transformed
|
|
1284
|
+
value is
|
|
1285
|
+
|
|
1286
|
+
then processed by the normal validation rules. Exceptions thrown inside
|
|
1287
|
+
the
|
|
1288
|
+
|
|
1092
1289
|
transformer propagate to the caller.
|
|
1093
1290
|
|
|
1094
1291
|
For example, returning `undefined` throws:
|
|
1095
1292
|
|
|
1096
1293
|
```text
|
|
1294
|
+
|
|
1097
1295
|
perfect-payload:- transform must not return undefined for attribute username
|
|
1098
1296
|
```
|
|
1099
1297
|
|
|
1100
1298
|
### `customValidator`
|
|
1101
1299
|
|
|
1102
|
-
|
|
1103
|
-
when the built-in validation rules are not enough.
|
|
1300
|
+
Defines custom validation logic when the built-in rules are not enough.
|
|
1104
1301
|
|
|
1105
|
-
The validator receives
|
|
1106
|
-
being validated:
|
|
1302
|
+
The validator receives:
|
|
1107
1303
|
|
|
1108
1304
|
```js
|
|
1109
1305
|
customValidator: (value, payload) => {
|
|
@@ -1111,100 +1307,302 @@ customValidator: (value, payload) => {
|
|
|
1111
1307
|
};
|
|
1112
1308
|
```
|
|
1113
1309
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1310
|
+
- `value` is the field value after transformations have been applied.
|
|
1311
|
+
- `payload` is the current payload/object being validated.
|
|
1312
|
+
- Return `true` to pass.
|
|
1313
|
+
- Any value other than `true` fails validation.
|
|
1314
|
+
- Exceptions thrown by the validator propagate to the caller.
|
|
1116
1315
|
|
|
1117
|
-
|
|
1316
|
+
For nested validation, `payload` means the current nested object rather
|
|
1317
|
+
than the root request body.
|
|
1318
|
+
|
|
1319
|
+
#### Synchronous custom validator
|
|
1320
|
+
|
|
1321
|
+
Use a synchronous validator with `perfectPayload()`:
|
|
1118
1322
|
|
|
1119
1323
|
```js
|
|
1120
1324
|
const rules = {
|
|
1121
1325
|
username: {
|
|
1122
1326
|
mandatory: true,
|
|
1123
|
-
|
|
1124
1327
|
type: "string",
|
|
1328
|
+
trim: true,
|
|
1125
1329
|
|
|
1126
1330
|
customValidator: (value) => {
|
|
1127
1331
|
return !value.toLowerCase().includes("admin");
|
|
1128
1332
|
},
|
|
1129
1333
|
|
|
1130
1334
|
customValidatorCode: "RESERVED_USERNAME",
|
|
1131
|
-
|
|
1132
1335
|
customValidatorError: "Username cannot contain admin",
|
|
1133
1336
|
},
|
|
1134
1337
|
};
|
|
1338
|
+
|
|
1339
|
+
const result = perfectPayload({ username: " admin_kiran " }, rules);
|
|
1135
1340
|
```
|
|
1136
1341
|
|
|
1137
|
-
|
|
1342
|
+
A failure returns:
|
|
1138
1343
|
|
|
1139
1344
|
```js
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1345
|
+
{
|
|
1346
|
+
statusCode: 400,
|
|
1347
|
+
valid: false,
|
|
1348
|
+
message: "One or more attribute values are invalid",
|
|
1349
|
+
errors: [
|
|
1350
|
+
{
|
|
1351
|
+
path: "username",
|
|
1352
|
+
code: "RESERVED_USERNAME",
|
|
1353
|
+
message: "Username cannot contain admin"
|
|
1354
|
+
}
|
|
1355
|
+
]
|
|
1356
|
+
}
|
|
1143
1357
|
```
|
|
1144
1358
|
|
|
1145
|
-
The
|
|
1359
|
+
The current payload/object can be used for cross-field validation:
|
|
1146
1360
|
|
|
1147
1361
|
```js
|
|
1362
|
+
const rules = {
|
|
1363
|
+
limit: {
|
|
1364
|
+
type: "number",
|
|
1365
|
+
},
|
|
1148
1366
|
|
|
1149
|
-
{
|
|
1367
|
+
amount: {
|
|
1368
|
+
type: "number",
|
|
1150
1369
|
|
|
1151
|
-
|
|
1370
|
+
customValidator: (value, payload) => {
|
|
1371
|
+
return value <= payload.limit;
|
|
1372
|
+
},
|
|
1152
1373
|
|
|
1153
|
-
|
|
1374
|
+
customValidatorCode: "LIMIT_EXCEEDED",
|
|
1375
|
+
customValidatorError: "Amount cannot exceed limit",
|
|
1376
|
+
},
|
|
1377
|
+
};
|
|
1378
|
+
```
|
|
1154
1379
|
|
|
1155
|
-
|
|
1380
|
+
If `customValidatorCode` and `customValidatorError` are omitted, the
|
|
1381
|
+
default error is:
|
|
1156
1382
|
|
|
1383
|
+
```js
|
|
1384
|
+
{
|
|
1385
|
+
path: "username",
|
|
1386
|
+
code: "CUSTOM_VALIDATION_FAILED",
|
|
1387
|
+
message: "Custom validation failed for attribute username"
|
|
1157
1388
|
}
|
|
1158
1389
|
```
|
|
1159
1390
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1391
|
+
`customValidator` works recursively inside `objectAttr` and
|
|
1392
|
+
`elementConstraints`. Structured errors preserve the corresponding
|
|
1393
|
+
nested and array paths.
|
|
1394
|
+
|
|
1395
|
+
When using `perfectPayload()`, `customValidator` must remain
|
|
1396
|
+
synchronous. A Promise-returning validator throws:
|
|
1397
|
+
|
|
1398
|
+
```text
|
|
1399
|
+
perfect-payload:- customValidator must be synchronous for attribute username
|
|
1400
|
+
```
|
|
1401
|
+
|
|
1402
|
+
For asynchronous custom validation, use `perfectPayloadAsync()`.
|
|
1403
|
+
|
|
1404
|
+
## Asynchronous Validation
|
|
1405
|
+
|
|
1406
|
+
`perfectPayloadAsync()` supports both synchronous and asynchronous
|
|
1407
|
+
`customValidator` functions without changing the behavior of
|
|
1408
|
+
`perfectPayload()`.
|
|
1162
1409
|
|
|
1163
1410
|
```js
|
|
1411
|
+
import { perfectPayloadAsync } from "perfect-payload";
|
|
1164
1412
|
|
|
1165
|
-
{
|
|
1413
|
+
const rules = {
|
|
1414
|
+
username: {
|
|
1415
|
+
mandatory: true,
|
|
1416
|
+
type: "string",
|
|
1417
|
+
trim: true,
|
|
1166
1418
|
|
|
1167
|
-
|
|
1419
|
+
customValidator: async (value) => {
|
|
1420
|
+
const available = await checkUsernameAvailability(value);
|
|
1421
|
+
return available;
|
|
1422
|
+
},
|
|
1168
1423
|
|
|
1169
|
-
|
|
1424
|
+
customValidatorCode: "USERNAME_TAKEN",
|
|
1425
|
+
customValidatorError: "Username is already taken",
|
|
1426
|
+
},
|
|
1427
|
+
};
|
|
1170
1428
|
|
|
1171
|
-
|
|
1429
|
+
const result = await perfectPayloadAsync(
|
|
1430
|
+
{
|
|
1431
|
+
username: " kiran ",
|
|
1432
|
+
},
|
|
1433
|
+
rules,
|
|
1434
|
+
);
|
|
1435
|
+
```
|
|
1172
1436
|
|
|
1437
|
+
On success, transformations are preserved:
|
|
1438
|
+
|
|
1439
|
+
```js
|
|
1440
|
+
{
|
|
1441
|
+
statusCode: 200,
|
|
1442
|
+
valid: true,
|
|
1443
|
+
validatedPayload: {
|
|
1444
|
+
username: "kiran"
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
```
|
|
1448
|
+
|
|
1449
|
+
On asynchronous validation failure:
|
|
1450
|
+
|
|
1451
|
+
```js
|
|
1452
|
+
{
|
|
1453
|
+
statusCode: 400,
|
|
1454
|
+
valid: false,
|
|
1455
|
+
message: "One or more attribute values are invalid",
|
|
1456
|
+
errors: [
|
|
1457
|
+
{
|
|
1458
|
+
path: "username",
|
|
1459
|
+
code: "USERNAME_TAKEN",
|
|
1460
|
+
message: "Username is already taken"
|
|
1461
|
+
}
|
|
1462
|
+
]
|
|
1173
1463
|
}
|
|
1174
1464
|
```
|
|
1175
1465
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1466
|
+
### Async validator contract
|
|
1467
|
+
|
|
1468
|
+
For `perfectPayloadAsync()`:
|
|
1469
|
+
|
|
1470
|
+
```text
|
|
1471
|
+
true → pass
|
|
1472
|
+
false → validation failure
|
|
1473
|
+
anything != true → validation failure
|
|
1474
|
+
throw → exception propagates
|
|
1475
|
+
rejected Promise → rejection propagates
|
|
1476
|
+
```
|
|
1477
|
+
|
|
1478
|
+
A normal synchronous validator is also valid when using the asynchronous
|
|
1479
|
+
API:
|
|
1178
1480
|
|
|
1179
1481
|
```js
|
|
1180
1482
|
const rules = {
|
|
1181
|
-
|
|
1182
|
-
type: "
|
|
1483
|
+
username: {
|
|
1484
|
+
type: "string",
|
|
1485
|
+
customValidator: (value) => value !== "admin",
|
|
1183
1486
|
},
|
|
1487
|
+
};
|
|
1184
1488
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1489
|
+
const result = await perfectPayloadAsync(payload, rules);
|
|
1490
|
+
```
|
|
1187
1491
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1492
|
+
A configured `customValidator` must be a function. Otherwise an error is
|
|
1493
|
+
thrown:
|
|
1494
|
+
|
|
1495
|
+
```text
|
|
1496
|
+
perfect-payload:- customValidator must be a function for attribute username
|
|
1497
|
+
```
|
|
1498
|
+
|
|
1499
|
+
### Validation order
|
|
1500
|
+
|
|
1501
|
+
`perfectPayloadAsync()` uses two phases:
|
|
1502
|
+
|
|
1503
|
+
1. Transform the payload and run normal synchronous validation.
|
|
1504
|
+
2. If phase 1 succeeds, run custom validators with `await`.
|
|
1505
|
+
|
|
1506
|
+
If any synchronous validation error exists, phase 2 is skipped and the
|
|
1507
|
+
synchronous validation result is returned immediately.
|
|
1508
|
+
|
|
1509
|
+
This means asynchronous validators can assume the payload has already
|
|
1510
|
+
passed its normal synchronous validation rules.
|
|
1511
|
+
|
|
1512
|
+
### Nested async validation
|
|
1513
|
+
|
|
1514
|
+
Async custom validators work recursively inside `objectAttr`:
|
|
1515
|
+
|
|
1516
|
+
```js
|
|
1517
|
+
const rules = {
|
|
1518
|
+
profile: {
|
|
1519
|
+
type: "object",
|
|
1520
|
+
|
|
1521
|
+
objectAttr: {
|
|
1522
|
+
username: {
|
|
1523
|
+
type: "string",
|
|
1524
|
+
trim: true,
|
|
1525
|
+
|
|
1526
|
+
customValidator: async (value) => {
|
|
1527
|
+
return await isUsernameAvailable(value);
|
|
1528
|
+
},
|
|
1529
|
+
|
|
1530
|
+
customValidatorCode: "USERNAME_TAKEN",
|
|
1531
|
+
customValidatorError: "Username is already taken",
|
|
1532
|
+
},
|
|
1190
1533
|
},
|
|
1534
|
+
},
|
|
1535
|
+
};
|
|
1536
|
+
```
|
|
1191
1537
|
|
|
1192
|
-
|
|
1538
|
+
A failure produces the complete path:
|
|
1193
1539
|
|
|
1194
|
-
|
|
1540
|
+
```text
|
|
1541
|
+
profile.username
|
|
1542
|
+
```
|
|
1543
|
+
|
|
1544
|
+
They also work inside `elementConstraints`:
|
|
1545
|
+
|
|
1546
|
+
```js
|
|
1547
|
+
const rules = {
|
|
1548
|
+
usernames: {
|
|
1549
|
+
type: "array",
|
|
1550
|
+
|
|
1551
|
+
elementConstraints: {
|
|
1552
|
+
type: "string",
|
|
1553
|
+
trim: true,
|
|
1554
|
+
|
|
1555
|
+
customValidator: async (value) => {
|
|
1556
|
+
return await isUsernameAvailable(value);
|
|
1557
|
+
},
|
|
1558
|
+
|
|
1559
|
+
customValidatorCode: "USERNAME_TAKEN",
|
|
1560
|
+
customValidatorError: "Username is already taken",
|
|
1561
|
+
},
|
|
1195
1562
|
},
|
|
1196
1563
|
};
|
|
1197
1564
|
```
|
|
1198
1565
|
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1566
|
+
For an invalid second element:
|
|
1567
|
+
|
|
1568
|
+
```text
|
|
1569
|
+
usernames[1]
|
|
1570
|
+
```
|
|
1571
|
+
|
|
1572
|
+
Deep combinations of objects and arrays preserve every level of the
|
|
1573
|
+
path:
|
|
1574
|
+
|
|
1575
|
+
```text
|
|
1576
|
+
products[1].seller.username
|
|
1577
|
+
profile.teams[1].members[1].username
|
|
1578
|
+
```
|
|
1579
|
+
|
|
1580
|
+
Default async custom-validation messages also use the final indexed
|
|
1581
|
+
path:
|
|
1582
|
+
|
|
1583
|
+
```js
|
|
1584
|
+
{
|
|
1585
|
+
path: "users[1].username",
|
|
1586
|
+
code: "CUSTOM_VALIDATION_FAILED",
|
|
1587
|
+
message: "Custom validation failed for attribute users[1].username"
|
|
1588
|
+
}
|
|
1589
|
+
```
|
|
1590
|
+
|
|
1591
|
+
### Transform remains synchronous
|
|
1202
1592
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
throws an error. Asynchronous validation is not part of this feature.
|
|
1593
|
+
`perfectPayloadAsync()` makes custom validation asynchronous; it does
|
|
1594
|
+
not make `transform` asynchronous.
|
|
1206
1595
|
|
|
1207
|
-
|
|
1596
|
+
`transform` must still be synchronous:
|
|
1597
|
+
|
|
1598
|
+
```js
|
|
1599
|
+
transform: (value, payload) => {
|
|
1600
|
+
return value;
|
|
1601
|
+
};
|
|
1602
|
+
```
|
|
1603
|
+
|
|
1604
|
+
An async transformer or a transformer that returns a Promise is not
|
|
1605
|
+
supported.
|
|
1208
1606
|
|
|
1209
1607
|
## Error Codes
|
|
1210
1608
|
|
|
@@ -1277,17 +1675,17 @@ const result = perfectPayload(payload, validationRules);
|
|
|
1277
1675
|
|
|
1278
1676
|
if (!result.valid) {
|
|
1279
1677
|
|
|
1280
|
-
|
|
1678
|
+
const emailError = result.errors.find(
|
|
1281
1679
|
|
|
1282
|
-
|
|
1680
|
+
(error) => error.code === "INVALID_EMAIL",
|
|
1283
1681
|
|
|
1284
|
-
|
|
1682
|
+
);
|
|
1285
1683
|
|
|
1286
|
-
|
|
1684
|
+
if (emailError) {
|
|
1287
1685
|
|
|
1288
|
-
|
|
1686
|
+
***// Handle invalid email***
|
|
1289
1687
|
|
|
1290
|
-
|
|
1688
|
+
}
|
|
1291
1689
|
|
|
1292
1690
|
}
|
|
1293
1691
|
```
|
|
@@ -1304,11 +1702,11 @@ keeping the same structured error format:
|
|
|
1304
1702
|
|
|
1305
1703
|
{
|
|
1306
1704
|
|
|
1307
|
-
|
|
1705
|
+
path: "email",
|
|
1308
1706
|
|
|
1309
|
-
|
|
1707
|
+
code: "INVALID_EMAIL",
|
|
1310
1708
|
|
|
1311
|
-
|
|
1709
|
+
message: "Email address is invalid"
|
|
1312
1710
|
|
|
1313
1711
|
}
|
|
1314
1712
|
```
|
|
@@ -1335,11 +1733,11 @@ If `email` is missing:
|
|
|
1335
1733
|
|
|
1336
1734
|
{
|
|
1337
1735
|
|
|
1338
|
-
|
|
1736
|
+
path: "email",
|
|
1339
1737
|
|
|
1340
|
-
|
|
1738
|
+
code: "REQUIRED",
|
|
1341
1739
|
|
|
1342
|
-
|
|
1740
|
+
message: "Email is required"
|
|
1343
1741
|
|
|
1344
1742
|
}
|
|
1345
1743
|
```
|
|
@@ -1350,11 +1748,11 @@ If `email` is present but invalid:
|
|
|
1350
1748
|
|
|
1351
1749
|
{
|
|
1352
1750
|
|
|
1353
|
-
|
|
1751
|
+
path: "email",
|
|
1354
1752
|
|
|
1355
|
-
|
|
1753
|
+
code: "INVALID_EMAIL",
|
|
1356
1754
|
|
|
1357
|
-
|
|
1755
|
+
message: "Email address is invalid"
|
|
1358
1756
|
|
|
1359
1757
|
}
|
|
1360
1758
|
```
|
|
@@ -1443,53 +1841,53 @@ Example result:
|
|
|
1443
1841
|
|
|
1444
1842
|
{
|
|
1445
1843
|
|
|
1446
|
-
|
|
1844
|
+
statusCode: 400,
|
|
1447
1845
|
|
|
1448
|
-
|
|
1846
|
+
valid: false,
|
|
1449
1847
|
|
|
1450
|
-
|
|
1848
|
+
message:
|
|
1451
1849
|
|
|
1452
|
-
|
|
1850
|
+
"One or more attribute values are invalid",
|
|
1453
1851
|
|
|
1454
|
-
|
|
1852
|
+
errors: [
|
|
1455
1853
|
|
|
1456
|
-
|
|
1854
|
+
{
|
|
1457
1855
|
|
|
1458
|
-
|
|
1856
|
+
path: "username",
|
|
1459
1857
|
|
|
1460
|
-
|
|
1858
|
+
code: "MIN_LENGTH",
|
|
1461
1859
|
|
|
1462
|
-
|
|
1860
|
+
message:
|
|
1463
1861
|
|
|
1464
|
-
|
|
1862
|
+
"Username must contain at least 3 characters"
|
|
1465
1863
|
|
|
1466
|
-
|
|
1864
|
+
},
|
|
1467
1865
|
|
|
1468
|
-
|
|
1866
|
+
{
|
|
1469
1867
|
|
|
1470
|
-
|
|
1868
|
+
path: "age",
|
|
1471
1869
|
|
|
1472
|
-
|
|
1870
|
+
code: "MIN_VALUE",
|
|
1473
1871
|
|
|
1474
|
-
|
|
1872
|
+
message:
|
|
1475
1873
|
|
|
1476
|
-
|
|
1874
|
+
"Age must be at least 18"
|
|
1477
1875
|
|
|
1478
|
-
|
|
1876
|
+
},
|
|
1479
1877
|
|
|
1480
|
-
|
|
1878
|
+
{
|
|
1481
1879
|
|
|
1482
|
-
|
|
1880
|
+
path: "score",
|
|
1483
1881
|
|
|
1484
|
-
|
|
1882
|
+
code: "OUT_OF_RANGE",
|
|
1485
1883
|
|
|
1486
|
-
|
|
1884
|
+
message:
|
|
1487
1885
|
|
|
1488
|
-
|
|
1886
|
+
"Score must be between 0 and 100"
|
|
1489
1887
|
|
|
1490
|
-
|
|
1888
|
+
}
|
|
1491
1889
|
|
|
1492
|
-
|
|
1890
|
+
]
|
|
1493
1891
|
|
|
1494
1892
|
}
|
|
1495
1893
|
```
|
|
@@ -1520,11 +1918,11 @@ Still returns:
|
|
|
1520
1918
|
|
|
1521
1919
|
{
|
|
1522
1920
|
|
|
1523
|
-
|
|
1921
|
+
path: "age",
|
|
1524
1922
|
|
|
1525
|
-
|
|
1923
|
+
code: "MIN_VALUE",
|
|
1526
1924
|
|
|
1527
|
-
|
|
1925
|
+
message: "You must be 18 or older"
|
|
1528
1926
|
|
|
1529
1927
|
}
|
|
1530
1928
|
```
|
|
@@ -1571,21 +1969,21 @@ When validation succeeds, `validatedPayload` is automatically added:
|
|
|
1571
1969
|
|
|
1572
1970
|
{
|
|
1573
1971
|
|
|
1574
|
-
|
|
1972
|
+
statusCode: 201,
|
|
1575
1973
|
|
|
1576
|
-
|
|
1974
|
+
valid: true,
|
|
1577
1975
|
|
|
1578
|
-
|
|
1976
|
+
message: "Payload validated successfully",
|
|
1579
1977
|
|
|
1580
|
-
|
|
1978
|
+
validatedPayload: {
|
|
1581
1979
|
|
|
1582
|
-
|
|
1980
|
+
name: "Kiran",
|
|
1583
1981
|
|
|
1584
|
-
|
|
1982
|
+
email: "kiran@example.com",
|
|
1585
1983
|
|
|
1586
|
-
|
|
1984
|
+
age: 29
|
|
1587
1985
|
|
|
1588
|
-
|
|
1986
|
+
}
|
|
1589
1987
|
|
|
1590
1988
|
}
|
|
1591
1989
|
```
|
|
@@ -1620,27 +2018,27 @@ When validation fails, `errors` is automatically added:
|
|
|
1620
2018
|
|
|
1621
2019
|
{
|
|
1622
2020
|
|
|
1623
|
-
|
|
2021
|
+
statusCode: 422,
|
|
1624
2022
|
|
|
1625
|
-
|
|
2023
|
+
valid: false,
|
|
1626
2024
|
|
|
1627
|
-
|
|
2025
|
+
message: "Payload validation failed",
|
|
1628
2026
|
|
|
1629
|
-
|
|
2027
|
+
errors: [
|
|
1630
2028
|
|
|
1631
|
-
|
|
2029
|
+
{
|
|
1632
2030
|
|
|
1633
|
-
|
|
2031
|
+
path: "email",
|
|
1634
2032
|
|
|
1635
|
-
|
|
2033
|
+
code: "INVALID_EMAIL",
|
|
1636
2034
|
|
|
1637
|
-
|
|
2035
|
+
message:
|
|
1638
2036
|
|
|
1639
|
-
|
|
2037
|
+
"Invalid email format for attribute email"
|
|
1640
2038
|
|
|
1641
|
-
|
|
2039
|
+
}
|
|
1642
2040
|
|
|
1643
|
-
|
|
2041
|
+
]
|
|
1644
2042
|
|
|
1645
2043
|
}
|
|
1646
2044
|
```
|
|
@@ -1703,15 +2101,15 @@ is:
|
|
|
1703
2101
|
|
|
1704
2102
|
{
|
|
1705
2103
|
|
|
1706
|
-
|
|
2104
|
+
statusCode: 200,
|
|
1707
2105
|
|
|
1708
|
-
|
|
2106
|
+
valid: true,
|
|
1709
2107
|
|
|
1710
|
-
|
|
2108
|
+
validatedPayload: {
|
|
1711
2109
|
|
|
1712
|
-
|
|
2110
|
+
***// validated fields***
|
|
1713
2111
|
|
|
1714
|
-
|
|
2112
|
+
}
|
|
1715
2113
|
|
|
1716
2114
|
}
|
|
1717
2115
|
```
|
|
@@ -1722,25 +2120,25 @@ The default invalid response is:
|
|
|
1722
2120
|
|
|
1723
2121
|
{
|
|
1724
2122
|
|
|
1725
|
-
|
|
2123
|
+
statusCode: 400,
|
|
1726
2124
|
|
|
1727
|
-
|
|
2125
|
+
valid: false,
|
|
1728
2126
|
|
|
1729
|
-
|
|
2127
|
+
message: "One or more attribute values are invalid",
|
|
1730
2128
|
|
|
1731
|
-
|
|
2129
|
+
errors: [
|
|
1732
2130
|
|
|
1733
|
-
|
|
2131
|
+
{
|
|
1734
2132
|
|
|
1735
|
-
|
|
2133
|
+
path: "field",
|
|
1736
2134
|
|
|
1737
|
-
|
|
2135
|
+
code: "ERROR_CODE",
|
|
1738
2136
|
|
|
1739
|
-
|
|
2137
|
+
message: "Validation error message"
|
|
1740
2138
|
|
|
1741
|
-
|
|
2139
|
+
}
|
|
1742
2140
|
|
|
1743
|
-
|
|
2141
|
+
]
|
|
1744
2142
|
|
|
1745
2143
|
}
|
|
1746
2144
|
```
|
|
@@ -1771,11 +2169,11 @@ An error can be returned as:
|
|
|
1771
2169
|
|
|
1772
2170
|
{
|
|
1773
2171
|
|
|
1774
|
-
|
|
2172
|
+
path: "email",
|
|
1775
2173
|
|
|
1776
|
-
|
|
2174
|
+
code: "INVALID_EMAIL",
|
|
1777
2175
|
|
|
1778
|
-
|
|
2176
|
+
message: "Invalid email format for attribute email"
|
|
1779
2177
|
|
|
1780
2178
|
}
|
|
1781
2179
|
```
|
|
@@ -1834,13 +2232,13 @@ its complete nested path:
|
|
|
1834
2232
|
|
|
1835
2233
|
{
|
|
1836
2234
|
|
|
1837
|
-
|
|
2235
|
+
path: "address.location.latitude",
|
|
1838
2236
|
|
|
1839
|
-
|
|
2237
|
+
code: "INVALID_TYPE",
|
|
1840
2238
|
|
|
1841
|
-
|
|
2239
|
+
message:
|
|
1842
2240
|
|
|
1843
|
-
|
|
2241
|
+
"Invalid type for attribute address.location.latitude, required number value"
|
|
1844
2242
|
|
|
1845
2243
|
}
|
|
1846
2244
|
```
|
|
@@ -1888,13 +2286,13 @@ The invalid third element is reported as:
|
|
|
1888
2286
|
|
|
1889
2287
|
{
|
|
1890
2288
|
|
|
1891
|
-
|
|
2289
|
+
path: "marks[2]",
|
|
1892
2290
|
|
|
1893
|
-
|
|
2291
|
+
code: "OUT_OF_RANGE",
|
|
1894
2292
|
|
|
1895
|
-
|
|
2293
|
+
message:
|
|
1896
2294
|
|
|
1897
|
-
|
|
2295
|
+
"Attribute marks[2] should have a value between 0 and 100"
|
|
1898
2296
|
|
|
1899
2297
|
}
|
|
1900
2298
|
```
|
|
@@ -1910,7 +2308,9 @@ marks[1]
|
|
|
1910
2308
|
marks[2]
|
|
1911
2309
|
```
|
|
1912
2310
|
|
|
1913
|
-
Array-level constraints such as `minItems` and `maxItems` report the
|
|
2311
|
+
Array-level constraints such as `minItems` and `maxItems` report the
|
|
2312
|
+
path of the array itself. For nested arrays, the complete parent path is
|
|
2313
|
+
retained, for example `orders[1].items`.
|
|
1914
2314
|
|
|
1915
2315
|
### Nested Fields Inside Arrays
|
|
1916
2316
|
|
|
@@ -1969,11 +2369,11 @@ Result:
|
|
|
1969
2369
|
|
|
1970
2370
|
{
|
|
1971
2371
|
|
|
1972
|
-
|
|
2372
|
+
"email": "Invalid email format for attribute email",
|
|
1973
2373
|
|
|
1974
|
-
|
|
2374
|
+
"address.location.latitude": "Invalid type for attribute address.location.latitude, required number value",
|
|
1975
2375
|
|
|
1976
|
-
|
|
2376
|
+
"marks[2]": "Attribute marks[2] should have a value between 0 and 100"
|
|
1977
2377
|
|
|
1978
2378
|
}
|
|
1979
2379
|
```
|
|
@@ -1988,61 +2388,61 @@ sample-1
|
|
|
1988
2388
|
|
|
1989
2389
|
{
|
|
1990
2390
|
|
|
1991
|
-
|
|
2391
|
+
firstName: {
|
|
1992
2392
|
|
|
1993
|
-
|
|
2393
|
+
mandatory: true,
|
|
1994
2394
|
|
|
1995
|
-
|
|
2395
|
+
allowNull: false,
|
|
1996
2396
|
|
|
1997
|
-
|
|
2397
|
+
type: "string",
|
|
1998
2398
|
|
|
1999
|
-
|
|
2399
|
+
minLength: 3,
|
|
2000
2400
|
|
|
2001
|
-
|
|
2401
|
+
minLengthError: "First name must have minimum 3 characters."
|
|
2002
2402
|
|
|
2003
|
-
|
|
2403
|
+
},
|
|
2004
2404
|
|
|
2005
|
-
|
|
2405
|
+
lastName: {
|
|
2006
2406
|
|
|
2007
|
-
|
|
2407
|
+
mandatory: false,
|
|
2008
2408
|
|
|
2009
|
-
|
|
2409
|
+
allowNull: true,
|
|
2010
2410
|
|
|
2011
|
-
|
|
2411
|
+
type: "string",
|
|
2012
2412
|
|
|
2013
|
-
|
|
2413
|
+
},
|
|
2014
2414
|
|
|
2015
|
-
|
|
2415
|
+
email: {
|
|
2016
2416
|
|
|
2017
|
-
|
|
2417
|
+
mandatory: true,
|
|
2018
2418
|
|
|
2019
|
-
|
|
2419
|
+
allowNull: false,
|
|
2020
2420
|
|
|
2021
|
-
|
|
2421
|
+
type: "email",
|
|
2022
2422
|
|
|
2023
|
-
|
|
2423
|
+
},
|
|
2024
2424
|
|
|
2025
|
-
|
|
2425
|
+
phone: {
|
|
2026
2426
|
|
|
2027
|
-
|
|
2427
|
+
mandatory: true,
|
|
2028
2428
|
|
|
2029
|
-
|
|
2429
|
+
allowNull: false,
|
|
2030
2430
|
|
|
2031
|
-
|
|
2431
|
+
type: "string",
|
|
2032
2432
|
|
|
2033
|
-
|
|
2433
|
+
},
|
|
2034
2434
|
|
|
2035
|
-
|
|
2435
|
+
age: {
|
|
2036
2436
|
|
|
2037
|
-
|
|
2437
|
+
mandatory: false,
|
|
2038
2438
|
|
|
2039
|
-
|
|
2439
|
+
type: "number",
|
|
2040
2440
|
|
|
2041
|
-
|
|
2441
|
+
min: 1,
|
|
2042
2442
|
|
|
2043
|
-
|
|
2443
|
+
max: 120,
|
|
2044
2444
|
|
|
2045
|
-
|
|
2445
|
+
},
|
|
2046
2446
|
|
|
2047
2447
|
};
|
|
2048
2448
|
```
|
|
@@ -2053,271 +2453,271 @@ sample-2
|
|
|
2053
2453
|
|
|
2054
2454
|
{
|
|
2055
2455
|
|
|
2056
|
-
|
|
2456
|
+
id: {
|
|
2057
2457
|
|
|
2058
|
-
|
|
2458
|
+
mandatory: true,
|
|
2059
2459
|
|
|
2060
|
-
|
|
2460
|
+
allowNull: true,
|
|
2061
2461
|
|
|
2062
|
-
|
|
2462
|
+
type: "uuidv4",
|
|
2063
2463
|
|
|
2064
|
-
|
|
2464
|
+
},
|
|
2065
2465
|
|
|
2066
|
-
|
|
2466
|
+
batchId: {
|
|
2067
2467
|
|
|
2068
|
-
|
|
2468
|
+
mandatory: true,
|
|
2069
2469
|
|
|
2070
|
-
|
|
2470
|
+
allowNull: true,
|
|
2071
2471
|
|
|
2072
|
-
|
|
2472
|
+
type: "objectId",
|
|
2073
2473
|
|
|
2074
|
-
|
|
2474
|
+
},
|
|
2075
2475
|
|
|
2076
|
-
|
|
2476
|
+
firstName: {
|
|
2077
2477
|
|
|
2078
|
-
|
|
2478
|
+
mandatory: true,
|
|
2079
2479
|
|
|
2080
|
-
|
|
2480
|
+
type: "string",
|
|
2081
2481
|
|
|
2082
|
-
|
|
2482
|
+
minLength: 3,
|
|
2083
2483
|
|
|
2084
|
-
|
|
2484
|
+
},
|
|
2085
2485
|
|
|
2086
|
-
|
|
2486
|
+
lastName: {
|
|
2087
2487
|
|
|
2088
|
-
|
|
2488
|
+
mandatory: false,
|
|
2089
2489
|
|
|
2090
|
-
|
|
2490
|
+
allowNull: true,
|
|
2091
2491
|
|
|
2092
|
-
|
|
2492
|
+
type: "string",
|
|
2093
2493
|
|
|
2094
|
-
|
|
2494
|
+
},
|
|
2095
2495
|
|
|
2096
|
-
|
|
2496
|
+
age: {
|
|
2097
2497
|
|
|
2098
|
-
|
|
2498
|
+
type: "number",
|
|
2099
2499
|
|
|
2100
|
-
|
|
2500
|
+
min: 0.1,
|
|
2101
2501
|
|
|
2102
|
-
|
|
2502
|
+
max: 120,
|
|
2103
2503
|
|
|
2104
|
-
|
|
2504
|
+
},
|
|
2105
2505
|
|
|
2106
|
-
|
|
2506
|
+
isAdult: {
|
|
2107
2507
|
|
|
2108
|
-
|
|
2508
|
+
type: "boolean",
|
|
2109
2509
|
|
|
2110
|
-
|
|
2510
|
+
},
|
|
2111
2511
|
|
|
2112
|
-
|
|
2512
|
+
totalWins: {
|
|
2113
2513
|
|
|
2114
|
-
|
|
2514
|
+
type: "number",
|
|
2115
2515
|
|
|
2116
|
-
|
|
2516
|
+
min: 0,
|
|
2117
2517
|
|
|
2118
|
-
|
|
2518
|
+
preventDecimal: true,
|
|
2119
2519
|
|
|
2120
|
-
|
|
2520
|
+
},
|
|
2121
2521
|
|
|
2122
|
-
|
|
2522
|
+
email: {
|
|
2123
2523
|
|
|
2124
|
-
|
|
2524
|
+
regex: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\\\\\\\.[a-zA-Z]{2,}$/,
|
|
2125
2525
|
|
|
2126
|
-
|
|
2526
|
+
},
|
|
2127
2527
|
|
|
2128
|
-
|
|
2528
|
+
githubLink: {
|
|
2129
2529
|
|
|
2130
|
-
|
|
2530
|
+
type: "url",
|
|
2131
2531
|
|
|
2132
|
-
|
|
2532
|
+
},
|
|
2133
2533
|
|
|
2134
|
-
|
|
2534
|
+
accountStatus: {
|
|
2135
2535
|
|
|
2136
|
-
|
|
2536
|
+
type: "enum",
|
|
2137
2537
|
|
|
2138
|
-
|
|
2538
|
+
enumValues: ["Active", "Inactive", 200],
|
|
2139
2539
|
|
|
2140
|
-
|
|
2540
|
+
},
|
|
2141
2541
|
|
|
2142
|
-
|
|
2542
|
+
marks: {
|
|
2143
2543
|
|
|
2144
|
-
|
|
2544
|
+
range: "0-100",
|
|
2145
2545
|
|
|
2146
|
-
|
|
2546
|
+
},
|
|
2147
2547
|
|
|
2148
|
-
|
|
2548
|
+
allMarks: {
|
|
2149
2549
|
|
|
2150
|
-
|
|
2550
|
+
type: "array",
|
|
2151
2551
|
|
|
2152
|
-
|
|
2552
|
+
allowEmptyArray: false,
|
|
2153
2553
|
|
|
2154
|
-
|
|
2554
|
+
elementConstraints: {
|
|
2155
2555
|
|
|
2156
|
-
|
|
2556
|
+
type: "number",
|
|
2157
2557
|
|
|
2158
|
-
|
|
2558
|
+
allowNull: false,
|
|
2159
2559
|
|
|
2160
|
-
|
|
2560
|
+
range: "0-100",
|
|
2161
2561
|
|
|
2162
|
-
|
|
2562
|
+
},
|
|
2163
2563
|
|
|
2164
|
-
|
|
2564
|
+
},
|
|
2165
2565
|
|
|
2166
|
-
|
|
2566
|
+
totalScore: {
|
|
2167
2567
|
|
|
2168
|
-
|
|
2568
|
+
type: "number",
|
|
2169
2569
|
|
|
2170
|
-
|
|
2570
|
+
dependency: {
|
|
2171
2571
|
|
|
2172
|
-
|
|
2572
|
+
result: {
|
|
2173
2573
|
|
|
2174
|
-
|
|
2574
|
+
setDependencyRule: (totalScore, result) => {
|
|
2175
2575
|
|
|
2176
|
-
|
|
2576
|
+
return { mandatory: true, allowNull: false, type: "string" };
|
|
2177
2577
|
|
|
2178
|
-
|
|
2578
|
+
},
|
|
2179
2579
|
|
|
2180
|
-
|
|
2580
|
+
},
|
|
2181
2581
|
|
|
2182
|
-
|
|
2582
|
+
},
|
|
2183
2583
|
|
|
2184
|
-
|
|
2584
|
+
},
|
|
2185
2585
|
|
|
2186
|
-
|
|
2586
|
+
result: {
|
|
2187
2587
|
|
|
2188
|
-
|
|
2588
|
+
type: "string",
|
|
2189
2589
|
|
|
2190
|
-
|
|
2590
|
+
dependency: {
|
|
2191
2591
|
|
|
2192
|
-
|
|
2592
|
+
totalScore: {
|
|
2193
2593
|
|
|
2194
|
-
|
|
2594
|
+
setDependencyRule: (result, totalScore) => {
|
|
2195
2595
|
|
|
2196
|
-
|
|
2596
|
+
return { mandatory: true, allowNull: false, type: "number" };
|
|
2197
2597
|
|
|
2198
|
-
|
|
2598
|
+
},
|
|
2199
2599
|
|
|
2200
|
-
|
|
2600
|
+
},
|
|
2201
2601
|
|
|
2202
|
-
|
|
2602
|
+
},
|
|
2203
2603
|
|
|
2204
|
-
|
|
2604
|
+
},
|
|
2205
2605
|
|
|
2206
|
-
|
|
2606
|
+
minSalary: {
|
|
2207
2607
|
|
|
2208
|
-
|
|
2608
|
+
mandatory: true,
|
|
2209
2609
|
|
|
2210
|
-
|
|
2610
|
+
min: 1,
|
|
2211
2611
|
|
|
2212
|
-
|
|
2612
|
+
type: "number",
|
|
2213
2613
|
|
|
2214
|
-
|
|
2614
|
+
dependency: {
|
|
2215
2615
|
|
|
2216
|
-
|
|
2616
|
+
maxSalary: {
|
|
2217
2617
|
|
|
2218
|
-
|
|
2618
|
+
setDependencyRule: (minSalary, maxSalary) => {
|
|
2219
2619
|
|
|
2220
|
-
|
|
2620
|
+
return {
|
|
2221
2621
|
|
|
2222
|
-
|
|
2622
|
+
mandatory: true,
|
|
2223
2623
|
|
|
2224
|
-
|
|
2624
|
+
min: minSalary + 1,
|
|
2225
2625
|
|
|
2226
|
-
|
|
2626
|
+
minError: "maxSalary must be more than minSalary",
|
|
2227
2627
|
|
|
2228
|
-
|
|
2628
|
+
};
|
|
2229
2629
|
|
|
2230
|
-
|
|
2630
|
+
},
|
|
2231
2631
|
|
|
2232
|
-
|
|
2632
|
+
},
|
|
2233
2633
|
|
|
2234
|
-
|
|
2634
|
+
},
|
|
2235
2635
|
|
|
2236
|
-
|
|
2636
|
+
},
|
|
2237
2637
|
|
|
2238
|
-
|
|
2638
|
+
maxSalary: {
|
|
2239
2639
|
|
|
2240
|
-
|
|
2640
|
+
dependency: {
|
|
2241
2641
|
|
|
2242
|
-
|
|
2642
|
+
minSalary: {
|
|
2243
2643
|
|
|
2244
|
-
|
|
2644
|
+
setDependencyRule: (maxSalary, minSalary) => {
|
|
2245
2645
|
|
|
2246
|
-
|
|
2646
|
+
return {
|
|
2247
2647
|
|
|
2248
|
-
|
|
2648
|
+
mandatory: true,
|
|
2249
2649
|
|
|
2250
|
-
|
|
2650
|
+
max: maxSalary - 1,
|
|
2251
2651
|
|
|
2252
|
-
|
|
2652
|
+
maxError: "minSalary must be less than maxSalary",
|
|
2253
2653
|
|
|
2254
|
-
|
|
2654
|
+
};
|
|
2255
2655
|
|
|
2256
|
-
|
|
2656
|
+
},
|
|
2257
2657
|
|
|
2258
|
-
|
|
2658
|
+
},
|
|
2259
2659
|
|
|
2260
|
-
|
|
2660
|
+
},
|
|
2261
2661
|
|
|
2262
|
-
|
|
2662
|
+
},
|
|
2263
2663
|
|
|
2264
|
-
|
|
2664
|
+
address: {
|
|
2265
2665
|
|
|
2266
|
-
|
|
2666
|
+
mandatory: true,
|
|
2267
2667
|
|
|
2268
|
-
|
|
2668
|
+
type: "object",
|
|
2269
2669
|
|
|
2270
|
-
|
|
2670
|
+
allowEmptyObject: false,
|
|
2271
2671
|
|
|
2272
|
-
|
|
2672
|
+
objectAttr: {
|
|
2273
2673
|
|
|
2274
|
-
|
|
2674
|
+
country: { mandatory: true, type: "string" },
|
|
2275
2675
|
|
|
2276
|
-
|
|
2676
|
+
state: {
|
|
2277
2677
|
|
|
2278
|
-
|
|
2678
|
+
mandatory: true,
|
|
2279
2679
|
|
|
2280
|
-
|
|
2680
|
+
type: "string",
|
|
2281
2681
|
|
|
2282
|
-
|
|
2682
|
+
},
|
|
2283
2683
|
|
|
2284
|
-
|
|
2684
|
+
city: {},
|
|
2285
2685
|
|
|
2286
|
-
|
|
2686
|
+
zip: {
|
|
2287
2687
|
|
|
2288
|
-
|
|
2688
|
+
mandatory: true,
|
|
2289
2689
|
|
|
2290
|
-
|
|
2690
|
+
type: "string",
|
|
2291
2691
|
|
|
2292
|
-
|
|
2692
|
+
},
|
|
2293
2693
|
|
|
2294
|
-
|
|
2694
|
+
position: {
|
|
2295
2695
|
|
|
2296
|
-
|
|
2696
|
+
mandatory: true,
|
|
2297
2697
|
|
|
2298
|
-
|
|
2698
|
+
type: "object",
|
|
2299
2699
|
|
|
2300
|
-
|
|
2700
|
+
allowEmptyObject: false,
|
|
2301
2701
|
|
|
2302
|
-
|
|
2702
|
+
objectAttr: {
|
|
2303
2703
|
|
|
2304
|
-
|
|
2704
|
+
lattitude: { mandatory: true, type: "number" },
|
|
2305
2705
|
|
|
2306
|
-
|
|
2706
|
+
longitude: {
|
|
2307
2707
|
|
|
2308
|
-
|
|
2708
|
+
mandatory: true,
|
|
2309
2709
|
|
|
2310
|
-
|
|
2710
|
+
type: "number",
|
|
2311
2711
|
|
|
2312
|
-
|
|
2712
|
+
},
|
|
2313
2713
|
|
|
2314
|
-
|
|
2714
|
+
},
|
|
2315
2715
|
|
|
2316
|
-
|
|
2716
|
+
},
|
|
2317
2717
|
|
|
2318
|
-
|
|
2718
|
+
},
|
|
2319
2719
|
|
|
2320
|
-
|
|
2720
|
+
},
|
|
2321
2721
|
|
|
2322
2722
|
}
|
|
2323
2723
|
```
|
|
@@ -2328,15 +2728,15 @@ sample-2
|
|
|
2328
2728
|
|
|
2329
2729
|
```js
|
|
2330
2730
|
|
|
2331
|
-
|
|
2731
|
+
***// validatePayload is the middleware that invokes perfectPayload()***
|
|
2332
2732
|
|
|
2333
2733
|
router.post(
|
|
2334
2734
|
|
|
2335
|
-
|
|
2735
|
+
"/payload-validation",
|
|
2336
2736
|
|
|
2337
|
-
|
|
2737
|
+
validatePayload({ rule: <your validation rule json object> }),
|
|
2338
2738
|
|
|
2339
|
-
|
|
2739
|
+
(req, res) => res.send("OK")
|
|
2340
2740
|
|
|
2341
2741
|
);
|
|
2342
2742
|
```
|
|
@@ -2365,6 +2765,47 @@ export const validatePayload = ({ rule }) => {
|
|
|
2365
2765
|
};
|
|
2366
2766
|
```
|
|
2367
2767
|
|
|
2768
|
+
#### Async ES Modules middleware example
|
|
2769
|
+
|
|
2770
|
+
When your schema contains an asynchronous `customValidator`, the
|
|
2771
|
+
middleware itself must be `async` and `perfectPayloadAsync()` must be
|
|
2772
|
+
awaited:
|
|
2773
|
+
|
|
2774
|
+
```js
|
|
2775
|
+
import { perfectPayloadAsync } from "perfect-payload";
|
|
2776
|
+
|
|
2777
|
+
export const validatePayloadAsync = ({ rule }) => {
|
|
2778
|
+
return async (req, res, next) => {
|
|
2779
|
+
try {
|
|
2780
|
+
const { statusCode, ...response } = await perfectPayloadAsync(
|
|
2781
|
+
req?.body,
|
|
2782
|
+
rule,
|
|
2783
|
+
);
|
|
2784
|
+
|
|
2785
|
+
if (+statusCode >= 200 && +statusCode <= 299) {
|
|
2786
|
+
req.validatedBody = response?.validatedPayload;
|
|
2787
|
+
next();
|
|
2788
|
+
} else {
|
|
2789
|
+
res.status(statusCode).json(response);
|
|
2790
|
+
}
|
|
2791
|
+
} catch (error) {
|
|
2792
|
+
console.error("Error validating payload", error);
|
|
2793
|
+
res.status(500).json({ error: "Internal Server Error" });
|
|
2794
|
+
}
|
|
2795
|
+
};
|
|
2796
|
+
};
|
|
2797
|
+
```
|
|
2798
|
+
|
|
2799
|
+
Route usage:
|
|
2800
|
+
|
|
2801
|
+
```js
|
|
2802
|
+
router.post(
|
|
2803
|
+
"/payload-validation",
|
|
2804
|
+
validatePayloadAsync({ rule: <your validation rule json object> }),
|
|
2805
|
+
(req, res) => res.send("OK"),
|
|
2806
|
+
);
|
|
2807
|
+
```
|
|
2808
|
+
|
|
2368
2809
|
#### CommonJS middleware example
|
|
2369
2810
|
|
|
2370
2811
|
```js
|