perfect-payload 1.3.0-beta.2 → 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.
Files changed (3) hide show
  1. package/README.md +767 -376
  2. package/index.js +148 -18
  3. package/package.json +1 -1
package/index.js CHANGED
@@ -562,7 +562,7 @@ function perfectPayloadStructured(
562
562
  basePath,
563
563
  );
564
564
 
565
- const attributeValue = data?.[attributeName] ?? null;
565
+ let attributeValue = data?.[attributeName] ?? null;
566
566
 
567
567
  const nullAllowed =
568
568
  dataValidationRule?.[attributeName]?.["allowNull"] ?? true;
@@ -570,7 +570,78 @@ function perfectPayloadStructured(
570
570
  const isMandatoryField = attributeRules?.["mandatory"] ?? false;
571
571
 
572
572
  const attrExist = Object.keys(data ?? {}).includes(attributeName);
573
+ // TRANSFORMATIONS START
574
+ // NO MULTI TRANSFORMATION
575
+ if (
576
+ attributeRules?.lowercase === true &&
577
+ attributeRules?.uppercase === true
578
+ ) {
579
+ throw new Error(
580
+ `perfect-payload:- lowercase and uppercase cannot both be enabled for attribute ${attributePath}`,
581
+ );
582
+ }
583
+ // TRIM
584
+ if (
585
+ attrExist &&
586
+ attributeValue !== null &&
587
+ attributeRules?.trim === true &&
588
+ typeof attributeValue === "string"
589
+ ) {
590
+ attributeValue = attributeValue.trim();
591
+ }
592
+ // LOWERCASE
593
+ if (
594
+ attrExist &&
595
+ attributeValue !== null &&
596
+ attributeRules?.lowercase === true &&
597
+ typeof attributeValue === "string"
598
+ ) {
599
+ attributeValue = attributeValue.toLowerCase();
600
+ }
601
+
602
+ // UPPERCASE
603
+
604
+ if (
605
+ attrExist &&
606
+ attributeValue !== null &&
607
+ attributeRules?.uppercase === true &&
608
+ typeof attributeValue === "string"
609
+ ) {
610
+ attributeValue = attributeValue.toUpperCase();
611
+ }
573
612
 
613
+ // CUSTOM TRANSFORM
614
+
615
+ if (
616
+ attrExist &&
617
+ attributeValue !== null &&
618
+ attributeRules?.transform !== undefined
619
+ ) {
620
+ const transformer = attributeRules.transform;
621
+
622
+ if (typeof transformer !== "function") {
623
+ throw new Error(
624
+ `perfect-payload:- transform must be a function for attribute ${attributePath}`,
625
+ );
626
+ }
627
+
628
+ const transformedValue = transformer(attributeValue, data);
629
+
630
+ if (transformedValue && typeof transformedValue.then === "function") {
631
+ throw new Error(
632
+ `perfect-payload:- transform must be synchronous for attribute ${attributePath}`,
633
+ );
634
+ }
635
+
636
+ if (transformedValue === undefined) {
637
+ throw new Error(
638
+ `perfect-payload:- transform must not return undefined for attribute ${attributePath}`,
639
+ );
640
+ }
641
+
642
+ attributeValue = transformedValue;
643
+ }
644
+ // TRANSFORMATIONS END
574
645
  for (const ruleName in attributeRules) {
575
646
  switch (ruleName) {
576
647
  // ==================================================
@@ -674,6 +745,9 @@ function perfectPayloadStructured(
674
745
  if (isArray(attributeValue) && attributeValue.length > 0) {
675
746
  let elementError = null;
676
747
 
748
+ // Clone so original payload array is not mutated
749
+ const transformedArray = [...attributeValue];
750
+
677
751
  for (
678
752
  let elementIndex = 0;
679
753
  elementIndex < attributeValue.length;
@@ -681,7 +755,10 @@ function perfectPayloadStructured(
681
755
  ) {
682
756
  const element = attributeValue[elementIndex];
683
757
 
684
- const { errors = [] } = perfectPayloadStructured(
758
+ const {
759
+ errors = [],
760
+ validatedPayload: elementValidatedPayload,
761
+ } = perfectPayloadStructured(
685
762
  {
686
763
  [attributeName]: element,
687
764
  },
@@ -714,17 +791,63 @@ function perfectPayloadStructured(
714
791
 
715
792
  break;
716
793
  }
794
+
795
+ // Preserve the transformed element
796
+ transformedArray[elementIndex] =
797
+ elementValidatedPayload?.[attributeName];
717
798
  }
718
799
 
719
800
  if (elementError) {
720
801
  rowErrors.push(elementError);
721
802
  addNextError = false;
803
+ } else {
804
+ // All elements passed, so use transformed values
805
+ attributeValue = transformedArray;
722
806
  }
723
807
  }
724
808
  }
725
809
 
726
810
  break;
727
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;
728
851
  // ==================================================
729
852
  // REGEX
730
853
  // ==================================================
@@ -759,7 +882,10 @@ function perfectPayloadStructured(
759
882
 
760
883
  switch (expectedType) {
761
884
  case "number":
762
- if (!isNumber(attributeValue)) {
885
+ if (
886
+ !isNumber(attributeValue) ||
887
+ Number.isNaN(attributeValue)
888
+ ) {
763
889
  addStructuredError(
764
890
  rowErrors,
765
891
  attributePath,
@@ -776,7 +902,6 @@ function perfectPayloadStructured(
776
902
  }
777
903
 
778
904
  break;
779
-
780
905
  case "string":
781
906
  if (!isString(attributeValue)) {
782
907
  addStructuredError(
@@ -1200,24 +1325,29 @@ function perfectPayloadStructured(
1200
1325
  // ==================================================
1201
1326
  // NESTED OBJECT
1202
1327
  // ==================================================
1203
-
1204
1328
  case "objectAttr":
1205
1329
  if (addNextError) {
1206
- const { errors = [] } = perfectPayloadStructured(
1207
- attributeValue,
1208
- attributeRules[ruleName],
1209
- { statusCode: 200, valid: true },
1210
- {
1211
- statusCode: 400,
1212
- valid: false,
1213
- message: "One or more attribute values are invalid",
1214
- },
1215
- attributePath,
1216
- );
1330
+ const { errors = [], validatedPayload: nestedValidatedPayload } =
1331
+ perfectPayloadStructured(
1332
+ attributeValue,
1333
+ attributeRules?.[ruleName],
1334
+ {
1335
+ statusCode: 200,
1336
+ valid: true,
1337
+ },
1338
+ {
1339
+ statusCode: 400,
1340
+ valid: false,
1341
+ message: "One or more attribute values are invalid",
1342
+ },
1343
+ attributePath,
1344
+ );
1217
1345
 
1218
- rowErrors = [...rowErrors, ...errors];
1346
+ if (errors.length === 0) {
1347
+ attributeValue = nestedValidatedPayload;
1348
+ }
1219
1349
 
1220
- addNextError = true;
1350
+ rowErrors = [...rowErrors, ...errors];
1221
1351
  }
1222
1352
 
1223
1353
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perfect-payload",
3
- "version": "1.3.0-beta.2",
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",