perfect-payload 1.2.3 → 1.4.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +928 -137
  2. package/index.js +140 -16
  3. package/package.json +1 -1
package/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,72 @@ 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
+ attributeValue = transformedValue;
637
+ }
638
+ // TRANSFORMATIONS END
574
639
  for (const ruleName in attributeRules) {
575
640
  switch (ruleName) {
576
641
  // ==================================================
@@ -674,6 +739,9 @@ function perfectPayloadStructured(
674
739
  if (isArray(attributeValue) && attributeValue.length > 0) {
675
740
  let elementError = null;
676
741
 
742
+ // Clone so original payload array is not mutated
743
+ const transformedArray = [...attributeValue];
744
+
677
745
  for (
678
746
  let elementIndex = 0;
679
747
  elementIndex < attributeValue.length;
@@ -681,7 +749,10 @@ function perfectPayloadStructured(
681
749
  ) {
682
750
  const element = attributeValue[elementIndex];
683
751
 
684
- const { errors = [] } = perfectPayloadStructured(
752
+ const {
753
+ errors = [],
754
+ validatedPayload: elementValidatedPayload,
755
+ } = perfectPayloadStructured(
685
756
  {
686
757
  [attributeName]: element,
687
758
  },
@@ -714,11 +785,18 @@ function perfectPayloadStructured(
714
785
 
715
786
  break;
716
787
  }
788
+
789
+ // Preserve the transformed element
790
+ transformedArray[elementIndex] =
791
+ elementValidatedPayload?.[attributeName];
717
792
  }
718
793
 
719
794
  if (elementError) {
720
795
  rowErrors.push(elementError);
721
796
  addNextError = false;
797
+ } else {
798
+ // All elements passed, so use transformed values
799
+ attributeValue = transformedArray;
722
800
  }
723
801
  }
724
802
  }
@@ -1200,24 +1278,29 @@ function perfectPayloadStructured(
1200
1278
  // ==================================================
1201
1279
  // NESTED OBJECT
1202
1280
  // ==================================================
1203
-
1204
1281
  case "objectAttr":
1205
1282
  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
- );
1283
+ const { errors = [], validatedPayload: nestedValidatedPayload } =
1284
+ perfectPayloadStructured(
1285
+ attributeValue,
1286
+ attributeRules?.[ruleName],
1287
+ {
1288
+ statusCode: 200,
1289
+ valid: true,
1290
+ },
1291
+ {
1292
+ statusCode: 400,
1293
+ valid: false,
1294
+ message: "One or more attribute values are invalid",
1295
+ },
1296
+ attributePath,
1297
+ );
1217
1298
 
1218
- rowErrors = [...rowErrors, ...errors];
1299
+ if (errors.length === 0) {
1300
+ attributeValue = nestedValidatedPayload;
1301
+ }
1219
1302
 
1220
- addNextError = true;
1303
+ rowErrors = [...rowErrors, ...errors];
1221
1304
  }
1222
1305
 
1223
1306
  break;
@@ -1280,6 +1363,47 @@ function perfectPayloadStructured(
1280
1363
 
1281
1364
  break;
1282
1365
 
1366
+ // ==================================================
1367
+ // CUSTOM VALIDATOR
1368
+ // ==================================================
1369
+
1370
+ case "customValidator":
1371
+ if (addNextError && attrExist && attributeValue !== null) {
1372
+ const validator = attributeRules?.[ruleName];
1373
+
1374
+ if (typeof validator !== "function") {
1375
+ throw new Error(
1376
+ `perfect-payload:- customValidator must be a function for attribute ${attributePath}`,
1377
+ );
1378
+ }
1379
+
1380
+ const validationResult = validator(attributeValue, data);
1381
+
1382
+ if (
1383
+ validationResult &&
1384
+ typeof validationResult.then === "function"
1385
+ ) {
1386
+ throw new Error(
1387
+ `perfect-payload:- customValidator must be synchronous for attribute ${attributePath}`,
1388
+ );
1389
+ }
1390
+
1391
+ if (validationResult !== true) {
1392
+ addStructuredError(
1393
+ rowErrors,
1394
+ attributePath,
1395
+ attributeRules?.["customValidatorCode"] ||
1396
+ "CUSTOM_VALIDATION_FAILED",
1397
+ attributeRules?.["customValidatorError"] ||
1398
+ `Custom validation failed for attribute ${attributePath}`,
1399
+ );
1400
+
1401
+ addNextError = false;
1402
+ }
1403
+ }
1404
+
1405
+ break;
1406
+
1283
1407
  default:
1284
1408
  break;
1285
1409
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "perfect-payload",
3
- "version": "1.2.3",
3
+ "version": "1.4.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",