perfect-payload 1.4.0-beta.0 → 1.5.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 +184 -5
- package/index.js +49 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,14 +4,16 @@ A lightweight JavaScript payload validation utility for validating API
|
|
|
4
4
|
and JSON payloads with simple rule-based configuration.
|
|
5
5
|
|
|
6
6
|
`perfect-payload` supports structured validation errors, nested field
|
|
7
|
-
paths, synchronous custom validators,
|
|
8
|
-
transformation/sanitization
|
|
7
|
+
paths, synchronous custom validators, synchronous payload
|
|
8
|
+
transformation/sanitization, array size constraints, and deeply nested
|
|
9
|
+
array/object validation while keeping the validation schema simple.
|
|
9
10
|
|
|
10
11
|
## Quick Links
|
|
11
12
|
|
|
12
13
|
- [Installation](#installation)
|
|
13
14
|
- [Basic Usage](#basic-usage)
|
|
14
15
|
- [Validation Rules](#validation-rules)
|
|
16
|
+
- [Array Size and Nested Validation](#array-size-and-nested-validation)
|
|
15
17
|
- [Transformations and
|
|
16
18
|
Sanitization](#transformations-and-sanitization)
|
|
17
19
|
- [Custom Validators](#customvalidator)
|
|
@@ -293,6 +295,68 @@ Error code: `EMPTY_ARRAY_NOT_ALLOWED`
|
|
|
293
295
|
|
|
294
296
|
---
|
|
295
297
|
|
|
298
|
+
### `minItems`
|
|
299
|
+
|
|
300
|
+
Defines the minimum number of items required in an array.
|
|
301
|
+
|
|
302
|
+
Default: Not applied when omitted.
|
|
303
|
+
|
|
304
|
+
```js
|
|
305
|
+
const rules = {
|
|
306
|
+
tags: {
|
|
307
|
+
type: "array",
|
|
308
|
+
minItems: 2,
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
An array with fewer than 2 items returns `MIN_ITEMS`.
|
|
314
|
+
|
|
315
|
+
```js
|
|
316
|
+
{
|
|
317
|
+
path: "tags",
|
|
318
|
+
code: "MIN_ITEMS",
|
|
319
|
+
message: "Attribute tags must contain at least 2 item(s)"
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`minItems` is enforced even when `allowEmptyArray: true` is set. For example, `minItems: 2` still rejects `[]`.
|
|
324
|
+
|
|
325
|
+
Error code: `MIN_ITEMS`
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
### `maxItems`
|
|
330
|
+
|
|
331
|
+
Defines the maximum number of items allowed in an array.
|
|
332
|
+
|
|
333
|
+
Default: Not applied when omitted.
|
|
334
|
+
|
|
335
|
+
```js
|
|
336
|
+
const rules = {
|
|
337
|
+
tags: {
|
|
338
|
+
type: "array",
|
|
339
|
+
maxItems: 5,
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
An array with more than 5 items returns `MAX_ITEMS`.
|
|
345
|
+
|
|
346
|
+
```js
|
|
347
|
+
{
|
|
348
|
+
path: "tags",
|
|
349
|
+
code: "MAX_ITEMS",
|
|
350
|
+
message: "Attribute tags must contain at most 5 item(s)"
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
`minItems` and `maxItems` can be used together.
|
|
355
|
+
|
|
356
|
+
Error code: `MAX_ITEMS`
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
296
360
|
### `type`
|
|
297
361
|
|
|
298
362
|
Validates the expected data type.
|
|
@@ -415,6 +479,8 @@ INVALID_UUID_V5
|
|
|
415
479
|
INVALID_OBJECT_ID
|
|
416
480
|
```
|
|
417
481
|
|
|
482
|
+
For `type: "number"`, `NaN` is rejected as `INVALID_TYPE`.
|
|
483
|
+
|
|
418
484
|
---
|
|
419
485
|
|
|
420
486
|
### `regex`
|
|
@@ -731,6 +797,103 @@ Example error:
|
|
|
731
797
|
|
|
732
798
|
---
|
|
733
799
|
|
|
800
|
+
## Array Size and Nested Validation
|
|
801
|
+
|
|
802
|
+
`perfectPayload()` supports array size constraints and recursive validation of arrays and objects at multiple depths. Array indexes and nested object keys are preserved in structured error paths.
|
|
803
|
+
|
|
804
|
+
### Array size constraints
|
|
805
|
+
|
|
806
|
+
Use `minItems` and `maxItems` with `type: "array"`:
|
|
807
|
+
|
|
808
|
+
```js
|
|
809
|
+
const rules = {
|
|
810
|
+
products: {
|
|
811
|
+
type: "array",
|
|
812
|
+
minItems: 1,
|
|
813
|
+
maxItems: 3,
|
|
814
|
+
elementConstraints: {
|
|
815
|
+
type: "object",
|
|
816
|
+
objectAttr: {
|
|
817
|
+
productId: { mandatory: true, type: "string" },
|
|
818
|
+
quantity: { mandatory: true, type: "number", min: 1 },
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
},
|
|
822
|
+
};
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
If the array is empty, `minItems` reports the array path itself:
|
|
826
|
+
|
|
827
|
+
```js
|
|
828
|
+
{
|
|
829
|
+
path: "products",
|
|
830
|
+
code: "MIN_ITEMS",
|
|
831
|
+
message: "Attribute products must contain at least 1 item(s)"
|
|
832
|
+
}
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
### Arrays of objects
|
|
836
|
+
|
|
837
|
+
`elementConstraints` can contain `objectAttr`, allowing every object in an array to use a nested schema. An invalid quantity in the second product is reported as:
|
|
838
|
+
|
|
839
|
+
```text
|
|
840
|
+
products[1].quantity
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
### Deeply nested arrays and objects
|
|
844
|
+
|
|
845
|
+
`objectAttr` and `elementConstraints` can be combined recursively:
|
|
846
|
+
|
|
847
|
+
```js
|
|
848
|
+
const rules = {
|
|
849
|
+
orders: {
|
|
850
|
+
type: "array",
|
|
851
|
+
minItems: 1,
|
|
852
|
+
maxItems: 2,
|
|
853
|
+
elementConstraints: {
|
|
854
|
+
type: "object",
|
|
855
|
+
objectAttr: {
|
|
856
|
+
orderId: { mandatory: true, type: "string" },
|
|
857
|
+
items: {
|
|
858
|
+
mandatory: true,
|
|
859
|
+
type: "array",
|
|
860
|
+
minItems: 1,
|
|
861
|
+
maxItems: 2,
|
|
862
|
+
elementConstraints: {
|
|
863
|
+
type: "object",
|
|
864
|
+
objectAttr: {
|
|
865
|
+
productId: { mandatory: true, type: "string" },
|
|
866
|
+
quantity: { mandatory: true, type: "number", min: 1 },
|
|
867
|
+
},
|
|
868
|
+
},
|
|
869
|
+
},
|
|
870
|
+
},
|
|
871
|
+
},
|
|
872
|
+
},
|
|
873
|
+
};
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
A deep validation failure preserves the complete indexed path, for example:
|
|
877
|
+
|
|
878
|
+
```text
|
|
879
|
+
orders[1].items[2].quantity
|
|
880
|
+
```
|
|
881
|
+
|
|
882
|
+
Array constraints work at nested levels too. A nested array can report paths such as:
|
|
883
|
+
|
|
884
|
+
```text
|
|
885
|
+
orders[1].items
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
Nested arrays are supported and every array index is preserved:
|
|
889
|
+
|
|
890
|
+
```text
|
|
891
|
+
matrix[1][1]
|
|
892
|
+
matrix[1][1][1]
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
Transformations applied inside nested objects or array elements are preserved in `validatedPayload`, while the original input remains unchanged.
|
|
896
|
+
|
|
734
897
|
### Transformations and Sanitization
|
|
735
898
|
|
|
736
899
|
`perfectPayload()` can transform a field before its validation rules
|
|
@@ -922,9 +1085,17 @@ not passed to transformation functions; null handling remains controlled
|
|
|
922
1085
|
by `allowNull`.
|
|
923
1086
|
|
|
924
1087
|
**Important:** `transform` is synchronous. A non-function transformer,
|
|
925
|
-
an `async` transformer,
|
|
926
|
-
supported and throws an error.
|
|
927
|
-
|
|
1088
|
+
an `async` transformer, a transformer that returns a Promise, or a
|
|
1089
|
+
transformer that returns `undefined` is not supported and throws an error.
|
|
1090
|
+
Returning `null`, `""`, `0`, or `false` is allowed; the transformed value is
|
|
1091
|
+
then processed by the normal validation rules. Exceptions thrown inside the
|
|
1092
|
+
transformer propagate to the caller.
|
|
1093
|
+
|
|
1094
|
+
For example, returning `undefined` throws:
|
|
1095
|
+
|
|
1096
|
+
```text
|
|
1097
|
+
perfect-payload:- transform must not return undefined for attribute username
|
|
1098
|
+
```
|
|
928
1099
|
|
|
929
1100
|
### `customValidator`
|
|
930
1101
|
|
|
@@ -1051,6 +1222,10 @@ EMPTY_OBJECT_NOT_ALLOWED
|
|
|
1051
1222
|
|
|
1052
1223
|
EMPTY_ARRAY_NOT_ALLOWED
|
|
1053
1224
|
|
|
1225
|
+
MIN_ITEMS
|
|
1226
|
+
|
|
1227
|
+
MAX_ITEMS
|
|
1228
|
+
|
|
1054
1229
|
INVALID_ARRAY_ELEMENT
|
|
1055
1230
|
|
|
1056
1231
|
REGEX_MISMATCH
|
|
@@ -1086,6 +1261,8 @@ MIN_VALUE
|
|
|
1086
1261
|
MAX_VALUE
|
|
1087
1262
|
|
|
1088
1263
|
OUT_OF_RANGE
|
|
1264
|
+
|
|
1265
|
+
CUSTOM_VALIDATION_FAILED
|
|
1089
1266
|
```
|
|
1090
1267
|
|
|
1091
1268
|
These codes are designed for programmatic handling while `message`
|
|
@@ -1733,6 +1910,8 @@ marks[1]
|
|
|
1733
1910
|
marks[2]
|
|
1734
1911
|
```
|
|
1735
1912
|
|
|
1913
|
+
Array-level constraints such as `minItems` and `maxItems` report the path of the array itself. For nested arrays, the complete parent path is retained, for example `orders[1].items`.
|
|
1914
|
+
|
|
1736
1915
|
### Nested Fields Inside Arrays
|
|
1737
1916
|
|
|
1738
1917
|
Paths can also identify fields inside array elements.
|
package/index.js
CHANGED
|
@@ -633,6 +633,12 @@ function perfectPayloadStructured(
|
|
|
633
633
|
);
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
+
if (transformedValue === undefined) {
|
|
637
|
+
throw new Error(
|
|
638
|
+
`perfect-payload:- transform must not return undefined for attribute ${attributePath}`,
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
|
|
636
642
|
attributeValue = transformedValue;
|
|
637
643
|
}
|
|
638
644
|
// TRANSFORMATIONS END
|
|
@@ -803,6 +809,45 @@ function perfectPayloadStructured(
|
|
|
803
809
|
|
|
804
810
|
break;
|
|
805
811
|
|
|
812
|
+
case "minItems":
|
|
813
|
+
if (
|
|
814
|
+
addNextError &&
|
|
815
|
+
attrExist &&
|
|
816
|
+
attributeValue !== null &&
|
|
817
|
+
isArray(attributeValue) &&
|
|
818
|
+
attributeValue.length < attributeRules[ruleName]
|
|
819
|
+
) {
|
|
820
|
+
addStructuredError(
|
|
821
|
+
rowErrors,
|
|
822
|
+
attributePath,
|
|
823
|
+
"MIN_ITEMS",
|
|
824
|
+
`Attribute ${attributePath} must contain at least ${attributeRules[ruleName]} item(s)`,
|
|
825
|
+
);
|
|
826
|
+
|
|
827
|
+
addNextError = false;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
break;
|
|
831
|
+
|
|
832
|
+
case "maxItems":
|
|
833
|
+
if (
|
|
834
|
+
addNextError &&
|
|
835
|
+
attrExist &&
|
|
836
|
+
attributeValue !== null &&
|
|
837
|
+
isArray(attributeValue) &&
|
|
838
|
+
attributeValue.length > attributeRules[ruleName]
|
|
839
|
+
) {
|
|
840
|
+
addStructuredError(
|
|
841
|
+
rowErrors,
|
|
842
|
+
attributePath,
|
|
843
|
+
"MAX_ITEMS",
|
|
844
|
+
`Attribute ${attributePath} must contain at most ${attributeRules[ruleName]} item(s)`,
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
addNextError = false;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
break;
|
|
806
851
|
// ==================================================
|
|
807
852
|
// REGEX
|
|
808
853
|
// ==================================================
|
|
@@ -837,7 +882,10 @@ function perfectPayloadStructured(
|
|
|
837
882
|
|
|
838
883
|
switch (expectedType) {
|
|
839
884
|
case "number":
|
|
840
|
-
if (
|
|
885
|
+
if (
|
|
886
|
+
!isNumber(attributeValue) ||
|
|
887
|
+
Number.isNaN(attributeValue)
|
|
888
|
+
) {
|
|
841
889
|
addStructuredError(
|
|
842
890
|
rowErrors,
|
|
843
891
|
attributePath,
|
|
@@ -854,7 +902,6 @@ function perfectPayloadStructured(
|
|
|
854
902
|
}
|
|
855
903
|
|
|
856
904
|
break;
|
|
857
|
-
|
|
858
905
|
case "string":
|
|
859
906
|
if (!isString(attributeValue)) {
|
|
860
907
|
addStructuredError(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "perfect-payload",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0-beta.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Lightweight JSON payload validation library with structured errors, nested validation, field paths, and customizable validation rules.",
|
|
6
6
|
"main": "index.js",
|