perfect-payload 1.8.0-beta.0 → 2.0.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/index.js DELETED
@@ -1,1861 +0,0 @@
1
- let perfectPayloadV1DeprecationWarningShown = false;
2
-
3
- function showPerfectPayloadV1DeprecationWarning() {
4
- if (perfectPayloadV1DeprecationWarningShown) return;
5
-
6
- perfectPayloadV1DeprecationWarningShown = true;
7
-
8
- const message =
9
- "perfectPayloadV1() is deprecated and will no longer be supported after March 31, 2027. Use perfectPayload() instead.";
10
-
11
- if (
12
- typeof process !== "undefined" &&
13
- typeof process.emitWarning === "function"
14
- ) {
15
- process.emitWarning(message, {
16
- type: "DeprecationWarning",
17
- code: "PERFECT_PAYLOAD_V1_DEPRECATED",
18
- });
19
- } else if (
20
- typeof console !== "undefined" &&
21
- typeof console.warn === "function"
22
- ) {
23
- console.warn(`[perfect-payload] DeprecationWarning: ${message}`);
24
- }
25
- }
26
-
27
- export function perfectPayloadV1(
28
- data = {},
29
- dataValidationRule = {},
30
- validPayloadResponse = { statusCode: 200, valid: true },
31
- inValidPayloadResponse = {
32
- statusCode: 400,
33
- valid: false,
34
- message: "One or more attribute values are invalid",
35
- },
36
- ) {
37
- showPerfectPayloadV1DeprecationWarning();
38
- let validatedPayload = {};
39
- let rowErrors = [];
40
- for (const attributeName in dataValidationRule) {
41
- let addNextError = true;
42
- const attributeRules = dataValidationRule?.[attributeName];
43
- const attrPath = dataValidationRule?.[attributeName]?.path || null;
44
- for (const ruleName in attributeRules) {
45
- const attributePath = `${attrPath ? attrPath + "." : ""}${attributeName}`;
46
- const attributeValue = data?.[attributeName] ?? null;
47
- const nullAllowed =
48
- dataValidationRule?.[attributeName]?.["allowNull"] ?? true;
49
- const isMandatoryField = attributeRules?.["mandatory"] ?? false;
50
- const attrExist = Object.keys(data)?.includes(attributeName); //Mandatory value check
51
-
52
- switch (ruleName) {
53
- case "mandatory":
54
- //mandatory value check
55
- if (isMandatoryField && !attrExist) {
56
- addNextError = false;
57
- rowErrors.push(
58
- attributeRules?.["mandatoryError"] ||
59
- `${attributePath} is mandatory`,
60
- );
61
- } else if (!attrExist) {
62
- addNextError = false;
63
- }
64
- break;
65
- case "allowNull":
66
- //allowNull value check
67
- if (addNextError && attributeValue == null) {
68
- if (!dataValidationRule?.[attributeName]?.[ruleName]) {
69
- addNextError = false;
70
- rowErrors.push(
71
- attributeRules?.["allowNullError"] ||
72
- `value null/'' not valid for attribute ${attributePath}`,
73
- );
74
- }
75
- }
76
- break;
77
- case "allowEmptyObject":
78
- //allowEmptyObject check
79
- if (addNextError) {
80
- if (
81
- !attributeRules?.["allowEmptyObject"] &&
82
- Object.keys(attributeValue).length == 0
83
- ) {
84
- rowErrors.push(
85
- attributeRules?.["emptyObjectError"] ||
86
- `value {} not valid for attribute ${attributePath}`,
87
- );
88
- addNextError = false;
89
- }
90
- }
91
-
92
- break;
93
- case "allowEmptyArray":
94
- //allowEmptyArray check
95
- if (addNextError) {
96
- if (
97
- !attributeRules?.["allowEmptyArray"] &&
98
- isArray(attributeValue) &&
99
- attributeValue?.length == 0
100
- ) {
101
- rowErrors.push(
102
- attributeRules?.["emptyArrayError"] ||
103
- `${attributePath} cannot be an empty array`,
104
- );
105
- addNextError = false;
106
- }
107
- }
108
-
109
- break;
110
- case "elementConstraints":
111
- //check array ele type
112
- if (addNextError) {
113
- if (isArray(attributeValue) && attributeValue?.length > 0) {
114
- let elementError = false;
115
- for (const element of attributeValue) {
116
- const { statusCode = 200, errors } = perfectPayloadV1(
117
- { [attributeName]: element },
118
- { [attributeName]: attributeRules[ruleName] },
119
- );
120
- if (statusCode == 400) {
121
- elementError = errors?.[0];
122
- // addNextError = false;
123
- break;
124
- }
125
- }
126
- if (elementError) {
127
- rowErrors.push(
128
- attributeRules?.["elementConstraintsError"] || elementError,
129
- );
130
- addNextError = false;
131
- }
132
- }
133
- }
134
-
135
- break;
136
- case "regex":
137
- //custom regex check
138
- if (addNextError) {
139
- if (!isPassedRegex(attributeRules[ruleName], attributeValue)) {
140
- rowErrors.push(
141
- attributeRules?.["regexError"] ||
142
- `${attributePath} failed to pass the regex ${attributeRules[ruleName]}`,
143
- );
144
- addNextError = false;
145
- }
146
- }
147
- break;
148
- case "type":
149
- //value type check
150
- if (addNextError) {
151
- if (attributeValue == null && nullAllowed) {
152
- addNextError = true;
153
- } else {
154
- switch (attributeRules[ruleName]) {
155
- case "number":
156
- if (!isNumber(attributeValue)) {
157
- rowErrors.push(
158
- attributeRules?.["typeError"] ||
159
- `Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required ${
160
- attributeRules[ruleName]
161
- } value`,
162
- );
163
- addNextError = false;
164
- }
165
- break;
166
-
167
- case "string":
168
- if (!isString(attributeValue)) {
169
- rowErrors.push(
170
- attributeRules?.["typeError"] ||
171
- `Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required ${
172
- attributeRules[ruleName]
173
- } value`,
174
- );
175
- addNextError = false;
176
- }
177
- break;
178
- case "boolean":
179
- if (!isBoolean(attributeValue)) {
180
- rowErrors.push(
181
- attributeRules?.["typeError"] ||
182
- `Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required ${
183
- attributeRules[ruleName]
184
- } value`,
185
- );
186
- addNextError = false;
187
- }
188
- break;
189
- case "email":
190
- if (!isValidEmail(attributeValue)) {
191
- rowErrors.push(
192
- attributeRules?.["typeError"] ||
193
- `Invalid email ID(${attributeValue}) found in attribute ${attributePath}`,
194
- );
195
- addNextError = false;
196
- }
197
- break;
198
-
199
- case "url":
200
- if (!isValidUrl(attributeValue)) {
201
- rowErrors.push(
202
- attributeRules?.["typeError"] ||
203
- `Invalid URL format(${attributeValue}) found in attribute ${attributePath}`,
204
- );
205
- addNextError = false;
206
- }
207
- break;
208
- case "enum":
209
- if (attributeValue == null && nullAllowed) {
210
- addNextError = true;
211
- } else {
212
- const allEnumValues = attributeRules["enumValues"];
213
- if (!allEnumValues?.includes(attributeValue)) {
214
- rowErrors.push(
215
- attributeRules?.["typeError"] ||
216
- `Invalid value(${attributeValue}) found in attribute ${attributePath}, valid values are ${allEnumValues?.join(
217
- ", ",
218
- )}`,
219
- );
220
- addNextError = false;
221
- }
222
- }
223
- break;
224
- case "uuid":
225
- if (attributeValue == null && nullAllowed) {
226
- addNextError = true;
227
- } else if (!isUUID(attributeValue)) {
228
- rowErrors.push(
229
- attributeRules?.["typeError"] ||
230
- `Invalid UUID(${attributeValue}) found in attribute ${attributePath}`,
231
- );
232
- addNextError = false;
233
- }
234
- break;
235
- case "uuidv1":
236
- if (attributeValue == null && nullAllowed) {
237
- addNextError = true;
238
- } else if (!isUUIDv1(attributeValue)) {
239
- rowErrors.push(
240
- attributeRules?.["typeError"] ||
241
- `Invalid v1 UUID(${attributeValue}) found in attribute ${attributePath}`,
242
- );
243
- addNextError = false;
244
- }
245
- break;
246
- case "uuidv3":
247
- if (attributeValue == null && nullAllowed) {
248
- addNextError = true;
249
- } else if (!isUUIDv3(attributeValue)) {
250
- rowErrors.push(
251
- attributeRules?.["typeError"] ||
252
- `Invalid v3 UUID(${attributeValue}) found in attribute ${attributePath}`,
253
- );
254
- addNextError = false;
255
- }
256
- break;
257
- case "uuidv4":
258
- if (attributeValue == null && nullAllowed) {
259
- addNextError = true;
260
- } else if (!isUUIDv4(attributeValue)) {
261
- rowErrors.push(
262
- attributeRules?.["typeError"] ||
263
- `Invalid v4 UUID(${attributeValue}) found in attribute ${attributePath}`,
264
- );
265
- addNextError = false;
266
- }
267
- break;
268
- case "uuidv5":
269
- if (attributeValue == null && nullAllowed) {
270
- addNextError = true;
271
- } else if (!isUUIDv5(attributeValue)) {
272
- rowErrors.push(
273
- attributeRules?.["typeError"] ||
274
- `Invalid v5 UUID(${attributeValue}) found in attribute ${attributePath}`,
275
- );
276
- addNextError = false;
277
- }
278
- break;
279
- case "objectId":
280
- if (attributeValue == null && nullAllowed) {
281
- addNextError = true;
282
- } else {
283
- if (!isObjectId(attributeValue)) {
284
- rowErrors.push(
285
- attributeRules?.["typeError"] ||
286
- `Invalid ObjectId(${attributeValue}) found in attribute ${attributePath}`,
287
- );
288
- addNextError = false;
289
- }
290
- }
291
- break;
292
-
293
- case "array":
294
- if (!isArray(attributeValue)) {
295
- rowErrors.push(
296
- attributeRules?.["typeError"] ||
297
- `Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required ${
298
- attributeRules[ruleName]
299
- } value`,
300
- );
301
- addNextError = false;
302
- }
303
- break;
304
-
305
- case "object":
306
- if (!isObject(attributeValue)) {
307
- rowErrors.push(
308
- attributeRules?.["typeError"] ||
309
- `Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required ${
310
- attributeRules[ruleName]
311
- } value`,
312
- );
313
- addNextError = false;
314
- }
315
- break;
316
-
317
- default:
318
- break;
319
- }
320
- }
321
- }
322
-
323
- break;
324
- case "minLength":
325
- //minimum string length check
326
- if (addNextError) {
327
- if (attributeValue == null && nullAllowed) {
328
- addNextError = true;
329
- } else if (typeof attributeValue == "string") {
330
- if (attributeValue.length < +attributeRules[ruleName])
331
- rowErrors.push(
332
- attributeRules?.["minLengthError"] ||
333
- `${attributePath} should be minimum of ${attributeRules[ruleName]} character`,
334
- );
335
- addNextError = false;
336
- } else {
337
- rowErrors.push(
338
- `${attributePath} value should be a string type(minLength specified)`,
339
- );
340
- addNextError = false;
341
- }
342
- }
343
- break;
344
- case "maxLength":
345
- //max string length check
346
- if (addNextError) {
347
- if (attributeValue == null && nullAllowed) {
348
- addNextError = true;
349
- } else if (typeof attributeValue == "string") {
350
- if (attributeValue.length > +attributeRules[ruleName])
351
- rowErrors.push(
352
- attributeRules?.["maxLengthError"] ||
353
- `${attributePath} can have maximum of ${attributeRules[ruleName]} character`,
354
- );
355
- addNextError = false;
356
- } else {
357
- rowErrors.push(
358
- `${attributePath} value should be a string type(maxLength specified)`,
359
- );
360
- addNextError = false;
361
- }
362
- }
363
- break;
364
- case "preventDecimal":
365
- //preventFraction
366
- if (addNextError) {
367
- if (attributeValue == null && nullAllowed) {
368
- addNextError = true;
369
- } else if (!isNumber(attributeValue)) {
370
- rowErrors.push(
371
- `Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required number value`,
372
- );
373
- addNextError = false;
374
- } else if (+attributeValue % 1 !== 0) {
375
- rowErrors.push(
376
- attributeRules?.["preventDecimalError"] ||
377
- `Decimal value not allowed in attribute ${attributePath}(${attributeValue})`,
378
- );
379
- addNextError = false;
380
- }
381
- }
382
- break;
383
- case "min":
384
- //minimum value check
385
- if (addNextError) {
386
- if (attributeValue == null && nullAllowed) {
387
- addNextError = true;
388
- } else if (!isNumber(attributeValue)) {
389
- rowErrors.push(
390
- `Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required number value`,
391
- );
392
- addNextError = false;
393
- } else if (+attributeValue < +attributeRules[ruleName]) {
394
- rowErrors.push(
395
- attributeRules?.["minError"] ||
396
- `minimum value ${
397
- attributeRules[ruleName]
398
- } is allowed in attribute ${attributePath}, found value ${
399
- data[attributeName] ?? "Nil"
400
- }`,
401
- );
402
- addNextError = false;
403
- }
404
- }
405
- break;
406
- case "max":
407
- //maximum value check
408
- if (addNextError) {
409
- if (attributeValue == null && nullAllowed) {
410
- addNextError = true;
411
- } else if (!isNumber(attributeValue)) {
412
- rowErrors.push(
413
- `Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required number value`,
414
- );
415
- addNextError = false;
416
- } else if (+attributeValue > +attributeRules[ruleName]) {
417
- rowErrors.push(
418
- attributeRules?.["maxError"] ||
419
- `maximum value ${
420
- attributeRules[ruleName]
421
- } is allowed in attribute ${attributePath}, found value ${
422
- data[attributeName] ?? "Nil"
423
- }`,
424
- );
425
- addNextError = false;
426
- }
427
- }
428
- break;
429
- case "range":
430
- //value range check
431
- if (addNextError) {
432
- if (attributeValue == null && nullAllowed) {
433
- addNextError = true;
434
- } else if (!isNumber(attributeValue)) {
435
- rowErrors.push(
436
- `Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required number value`,
437
- );
438
- addNextError = false;
439
- } else {
440
- const [min, max] = attributeRules[ruleName].split("-");
441
- if (
442
- !isNumber(attributeValue) ||
443
- +attributeValue < +min ||
444
- +attributeValue > +max
445
- ) {
446
- rowErrors.push(
447
- attributeRules?.["rangeError"] ||
448
- `Invalid values(${attributeValue}) found in attribute ${attributePath}, value should be between ${min} and ${max} `,
449
- );
450
- addNextError = false;
451
- }
452
- }
453
- }
454
- break;
455
- case "objectAttr":
456
- //nested object attributes check
457
- if (addNextError) {
458
- const allObjectAttr = Object.keys(attributeRules?.[ruleName]);
459
- const objectAttrRules = {};
460
- for (const attr of allObjectAttr) {
461
- objectAttrRules[attr] = {
462
- ...attributeRules?.[ruleName]?.[attr],
463
- path: attributeRules?.[ruleName]?.[attr]?.path
464
- ? attributeRules?.[ruleName]?.[attr]?.path + `.${attr}`
465
- : `${attributePath}`,
466
- };
467
- }
468
-
469
- const { errors = [] } = perfectPayloadV1(
470
- attributeValue,
471
- objectAttrRules,
472
- );
473
- rowErrors = [...rowErrors, ...errors];
474
- addNextError = true;
475
- }
476
- break;
477
- case "dependency":
478
- //dependency check
479
- if (addNextError) {
480
- const allDependencyAttr = Object.keys(attributeRules?.[ruleName]);
481
- for (const attr of allDependencyAttr) {
482
- if (
483
- typeof attributeRules?.[ruleName]?.[attr]?.setDependencyRule ==
484
- "function"
485
- ) {
486
- const newRule = attributeRules?.[ruleName]?.[
487
- attr
488
- ]?.setDependencyRule(attributeValue, data?.[attr]);
489
- let newData = { [attributeName]: attributeValue };
490
- if (Object.keys(data).includes(attr)) {
491
- newData = { ...newData, [attr]: data?.[attr] };
492
- }
493
- const { errors = [] } = perfectPayloadV1(newData, {
494
- [attr]: newRule,
495
- });
496
- rowErrors = [...rowErrors, ...errors];
497
- addNextError = true;
498
- } else {
499
- throw new Error(
500
- `perfect-payload:- function setDependencyRule not found in ${attr} dependency `,
501
- );
502
- }
503
- }
504
- }
505
- break;
506
- default:
507
- break;
508
- }
509
-
510
- if (attrExist && rowErrors.length == 0)
511
- validatedPayload[attributeName] = attributeValue;
512
- }
513
- }
514
-
515
- if (rowErrors?.length) {
516
- return { ...inValidPayloadResponse, errors: rowErrors };
517
- } else {
518
- return { ...validPayloadResponse, validatedPayload };
519
- }
520
- }
521
-
522
- export function perfectPayload(
523
- data = {},
524
- dataValidationRule = {},
525
- options = {},
526
- ) {
527
- const {
528
- unknownFields = "strip",
529
- prettyErrors = false,
530
- validPayloadResponse = {
531
- statusCode: 200,
532
- valid: true,
533
- },
534
-
535
- inValidPayloadResponse = {
536
- statusCode: 400,
537
- valid: false,
538
- message: "One or more attribute values are invalid",
539
- },
540
- } = options ?? {};
541
-
542
- if (!["strip", "allow", "reject"].includes(unknownFields)) {
543
- throw new Error(
544
- `perfect-payload:- unknownFields must be one of strip, allow, reject`,
545
- );
546
- }
547
- if (typeof prettyErrors !== "boolean") {
548
- throw new Error(`perfect-payload:- prettyErrors must be a boolean`);
549
- }
550
- const validationResult = perfectPayloadStructured(
551
- data,
552
- dataValidationRule,
553
- validPayloadResponse,
554
- inValidPayloadResponse,
555
- "",
556
- {
557
- unknownFields,
558
- },
559
- );
560
-
561
- if (prettyErrors && validationResult?.errors?.length) {
562
- return {
563
- ...validationResult,
564
- errors: validationResult.errors.map((error) => error.message),
565
- };
566
- }
567
-
568
- return validationResult;
569
- }
570
-
571
- export async function perfectPayloadAsync(
572
- data = {},
573
- dataValidationRule = {},
574
- options = {},
575
- ) {
576
- const {
577
- validPayloadResponse = {
578
- statusCode: 200,
579
- valid: true,
580
- },
581
-
582
- inValidPayloadResponse = {
583
- statusCode: 400,
584
- valid: false,
585
- message: "One or more attribute values are invalid",
586
- },
587
- unknownFields = "strip",
588
- prettyErrors = false,
589
- } = options ?? {};
590
-
591
- if (!["strip", "allow", "reject"].includes(unknownFields)) {
592
- throw new Error(
593
- `perfect-payload:- unknownFields must be one of strip, allow, reject`,
594
- );
595
- }
596
- if (typeof prettyErrors !== "boolean") {
597
- throw new Error(`perfect-payload:- prettyErrors must be a boolean`);
598
- }
599
- const validationResult = perfectPayloadStructured(
600
- data,
601
- dataValidationRule,
602
- validPayloadResponse,
603
- inValidPayloadResponse,
604
- "",
605
- {
606
- skipCustomValidator: true,
607
- unknownFields,
608
- },
609
- );
610
-
611
- if (validationResult?.errors?.length) {
612
- if (prettyErrors) {
613
- return {
614
- ...validationResult,
615
- errors: validationResult.errors.map((error) => error.message),
616
- };
617
- }
618
-
619
- return validationResult;
620
- }
621
-
622
- const rowErrors = await runAsyncCustomValidators(
623
- validationResult?.validatedPayload ?? {},
624
- dataValidationRule,
625
- );
626
-
627
- if (rowErrors.length > 0) {
628
- return {
629
- ...inValidPayloadResponse,
630
- errors: prettyErrors
631
- ? rowErrors.map((error) => error.message)
632
- : rowErrors,
633
- };
634
- }
635
-
636
- return validationResult;
637
- }
638
-
639
- function perfectPayloadStructured(
640
- data = {},
641
- dataValidationRule = {},
642
- validPayloadResponse = { statusCode: 200, valid: true },
643
- inValidPayloadResponse = {
644
- statusCode: 400,
645
- valid: false,
646
- message: "One or more attribute values are invalid",
647
- },
648
- basePath = "",
649
- options = {},
650
- ) {
651
- let validatedPayload = {};
652
- let rowErrors = [];
653
- const skipCustomValidator = options?.skipCustomValidator === true;
654
- const unknownFields = options?.unknownFields ?? "strip";
655
-
656
- for (const attributeName in dataValidationRule) {
657
- let addNextError = true;
658
-
659
- const attributeRules = dataValidationRule?.[attributeName];
660
-
661
- const attributePath = getAttributePath(
662
- attributeName,
663
- attributeRules,
664
- basePath,
665
- );
666
-
667
- let attributeValue = data?.[attributeName] ?? null;
668
-
669
- const nullAllowed =
670
- dataValidationRule?.[attributeName]?.["allowNull"] ?? true;
671
-
672
- const isMandatoryField = attributeRules?.["mandatory"] ?? false;
673
-
674
- const attrExist = Object.keys(data ?? {}).includes(attributeName);
675
- // TRANSFORMATIONS START
676
- // NO MULTI TRANSFORMATION
677
- if (
678
- attributeRules?.lowercase === true &&
679
- attributeRules?.uppercase === true
680
- ) {
681
- throw new Error(
682
- `perfect-payload:- lowercase and uppercase cannot both be enabled for attribute ${attributePath}`,
683
- );
684
- }
685
- // TRIM
686
- if (
687
- attrExist &&
688
- attributeValue !== null &&
689
- attributeRules?.trim === true &&
690
- typeof attributeValue === "string"
691
- ) {
692
- attributeValue = attributeValue.trim();
693
- }
694
- // LOWERCASE
695
- if (
696
- attrExist &&
697
- attributeValue !== null &&
698
- attributeRules?.lowercase === true &&
699
- typeof attributeValue === "string"
700
- ) {
701
- attributeValue = attributeValue.toLowerCase();
702
- }
703
-
704
- // UPPERCASE
705
-
706
- if (
707
- attrExist &&
708
- attributeValue !== null &&
709
- attributeRules?.uppercase === true &&
710
- typeof attributeValue === "string"
711
- ) {
712
- attributeValue = attributeValue.toUpperCase();
713
- }
714
-
715
- // CUSTOM TRANSFORM
716
-
717
- if (
718
- attrExist &&
719
- attributeValue !== null &&
720
- attributeRules?.transform !== undefined
721
- ) {
722
- const transformer = attributeRules.transform;
723
-
724
- if (typeof transformer !== "function") {
725
- throw new Error(
726
- `perfect-payload:- transform must be a function for attribute ${attributePath}`,
727
- );
728
- }
729
-
730
- const transformedValue = transformer(attributeValue, data);
731
-
732
- if (transformedValue && typeof transformedValue.then === "function") {
733
- throw new Error(
734
- `perfect-payload:- transform must be synchronous for attribute ${attributePath}`,
735
- );
736
- }
737
-
738
- if (transformedValue === undefined) {
739
- throw new Error(
740
- `perfect-payload:- transform must not return undefined for attribute ${attributePath}`,
741
- );
742
- }
743
-
744
- attributeValue = transformedValue;
745
- }
746
- // TRANSFORMATIONS END
747
- for (const ruleName in attributeRules) {
748
- switch (ruleName) {
749
- // ==================================================
750
- // MANDATORY
751
- // ==================================================
752
-
753
- case "mandatory":
754
- if (isMandatoryField && (!attrExist || attributeValue === "")) {
755
- addNextError = false;
756
-
757
- addStructuredError(
758
- rowErrors,
759
- attributePath,
760
- "REQUIRED",
761
- attributeRules?.["mandatoryError"] ||
762
- `${attributePath} is mandatory`,
763
- );
764
- } else if (!attrExist) {
765
- addNextError = false;
766
- }
767
-
768
- break;
769
-
770
- // ==================================================
771
- // NULL
772
- // ==================================================
773
-
774
- case "allowNull":
775
- if (addNextError && attributeValue === null) {
776
- if (!attributeRules?.[ruleName]) {
777
- addNextError = false;
778
-
779
- addStructuredError(
780
- rowErrors,
781
- attributePath,
782
- "NULL_NOT_ALLOWED",
783
- attributeRules?.["allowNullError"] ||
784
- `value null not valid for attribute ${attributePath}`,
785
- );
786
- }
787
- }
788
-
789
- break;
790
-
791
- // ==================================================
792
- // EMPTY OBJECT
793
- // ==================================================
794
-
795
- case "allowEmptyObject":
796
- if (addNextError) {
797
- if (
798
- !attributeRules?.["allowEmptyObject"] &&
799
- attributeValue != null &&
800
- Object.keys(attributeValue).length === 0
801
- ) {
802
- addStructuredError(
803
- rowErrors,
804
- attributePath,
805
- "EMPTY_OBJECT_NOT_ALLOWED",
806
- attributeRules?.["emptyObjectError"] ||
807
- `value {} not valid for attribute ${attributePath}`,
808
- );
809
-
810
- addNextError = false;
811
- }
812
- }
813
-
814
- break;
815
-
816
- // ==================================================
817
- // EMPTY ARRAY
818
- // ==================================================
819
-
820
- case "allowEmptyArray":
821
- if (addNextError) {
822
- if (
823
- !attributeRules?.["allowEmptyArray"] &&
824
- isArray(attributeValue) &&
825
- attributeValue.length === 0
826
- ) {
827
- addStructuredError(
828
- rowErrors,
829
- attributePath,
830
- "EMPTY_ARRAY_NOT_ALLOWED",
831
- attributeRules?.["emptyArrayError"] ||
832
- `${attributePath} cannot be an empty array`,
833
- );
834
-
835
- addNextError = false;
836
- }
837
- }
838
-
839
- break;
840
-
841
- // ==================================================
842
- // ARRAY ELEMENT CONSTRAINTS
843
- // ==================================================
844
-
845
- case "elementConstraints":
846
- if (addNextError) {
847
- if (isArray(attributeValue) && attributeValue.length > 0) {
848
- let elementError = null;
849
-
850
- // Clone so original payload array is not mutated
851
- const transformedArray = [...attributeValue];
852
-
853
- for (
854
- let elementIndex = 0;
855
- elementIndex < attributeValue.length;
856
- elementIndex++
857
- ) {
858
- const element = attributeValue[elementIndex];
859
-
860
- const {
861
- errors = [],
862
- validatedPayload: elementValidatedPayload,
863
- } = perfectPayloadStructured(
864
- {
865
- [attributeName]: element,
866
- },
867
- {
868
- [attributeName]: attributeRules[ruleName],
869
- },
870
- { statusCode: 200, valid: true },
871
- {
872
- statusCode: 400,
873
- valid: false,
874
- message: "One or more attribute values are invalid",
875
- },
876
- "",
877
- options,
878
- );
879
-
880
- if (errors.length > 0) {
881
- const firstElementError = errors[0];
882
-
883
- const indexedPath = replaceRootPath(
884
- firstElementError.path,
885
- attributeName,
886
- `${attributePath}[${elementIndex}]`,
887
- );
888
-
889
- if (attributeRules?.["elementConstraintsError"]) {
890
- elementError = {
891
- path: indexedPath,
892
- code: "INVALID_ARRAY_ELEMENT",
893
- message: attributeRules["elementConstraintsError"],
894
- };
895
- } else {
896
- elementError = {
897
- ...firstElementError,
898
- path: indexedPath,
899
- ...(firstElementError.code === "UNKNOWN_FIELD" && {
900
- message: `Unknown field ${indexedPath} is not allowed`,
901
- }),
902
- };
903
- }
904
-
905
- break;
906
- }
907
-
908
- // Preserve the transformed element
909
- transformedArray[elementIndex] =
910
- elementValidatedPayload?.[attributeName];
911
- }
912
-
913
- if (elementError) {
914
- rowErrors.push(elementError);
915
- addNextError = false;
916
- } else {
917
- // All elements passed, so use transformed values
918
- attributeValue = transformedArray;
919
- }
920
- }
921
- }
922
-
923
- break;
924
- case "minItems":
925
- if (
926
- addNextError &&
927
- attrExist &&
928
- attributeValue !== null &&
929
- isArray(attributeValue) &&
930
- attributeValue.length < attributeRules[ruleName]
931
- ) {
932
- addStructuredError(
933
- rowErrors,
934
- attributePath,
935
- "MIN_ITEMS",
936
- `Attribute ${attributePath} must contain at least ${attributeRules[ruleName]} item(s)`,
937
- );
938
-
939
- addNextError = false;
940
- }
941
-
942
- break;
943
-
944
- case "maxItems":
945
- if (
946
- addNextError &&
947
- attrExist &&
948
- attributeValue !== null &&
949
- isArray(attributeValue) &&
950
- attributeValue.length > attributeRules[ruleName]
951
- ) {
952
- addStructuredError(
953
- rowErrors,
954
- attributePath,
955
- "MAX_ITEMS",
956
- `Attribute ${attributePath} must contain at most ${attributeRules[ruleName]} item(s)`,
957
- );
958
-
959
- addNextError = false;
960
- }
961
-
962
- break;
963
- // ==================================================
964
- // REGEX
965
- // ==================================================
966
-
967
- case "regex":
968
- if (addNextError) {
969
- if (!isPassedRegex(attributeRules[ruleName], attributeValue)) {
970
- addStructuredError(
971
- rowErrors,
972
- attributePath,
973
- "REGEX_MISMATCH",
974
- attributeRules?.["regexError"] ||
975
- `${attributePath} failed to pass the regex ${attributeRules[ruleName]}`,
976
- );
977
-
978
- addNextError = false;
979
- }
980
- }
981
-
982
- break;
983
-
984
- // ==================================================
985
- // TYPE
986
- // ==================================================
987
-
988
- case "type":
989
- if (addNextError) {
990
- if (attributeValue == null && nullAllowed) {
991
- addNextError = true;
992
- } else {
993
- const expectedType = attributeRules[ruleName];
994
-
995
- switch (expectedType) {
996
- case "number":
997
- if (
998
- !isNumber(attributeValue) ||
999
- Number.isNaN(attributeValue)
1000
- ) {
1001
- addStructuredError(
1002
- rowErrors,
1003
- attributePath,
1004
- "INVALID_TYPE",
1005
- attributeRules?.["typeError"] ||
1006
- getInvalidTypeMessage(
1007
- attributeValue,
1008
- attributePath,
1009
- expectedType,
1010
- ),
1011
- );
1012
-
1013
- addNextError = false;
1014
- }
1015
-
1016
- break;
1017
- case "string":
1018
- if (!isString(attributeValue)) {
1019
- addStructuredError(
1020
- rowErrors,
1021
- attributePath,
1022
- "INVALID_TYPE",
1023
- attributeRules?.["typeError"] ||
1024
- getInvalidTypeMessage(
1025
- attributeValue,
1026
- attributePath,
1027
- expectedType,
1028
- ),
1029
- );
1030
-
1031
- addNextError = false;
1032
- }
1033
-
1034
- break;
1035
-
1036
- case "boolean":
1037
- if (!isBoolean(attributeValue)) {
1038
- addStructuredError(
1039
- rowErrors,
1040
- attributePath,
1041
- "INVALID_TYPE",
1042
- attributeRules?.["typeError"] ||
1043
- getInvalidTypeMessage(
1044
- attributeValue,
1045
- attributePath,
1046
- expectedType,
1047
- ),
1048
- );
1049
-
1050
- addNextError = false;
1051
- }
1052
-
1053
- break;
1054
-
1055
- case "email":
1056
- if (!isValidEmail(attributeValue)) {
1057
- addStructuredError(
1058
- rowErrors,
1059
- attributePath,
1060
- "INVALID_EMAIL",
1061
- attributeRules?.["typeError"] ||
1062
- `Invalid email format for attribute ${attributePath}`,
1063
- );
1064
-
1065
- addNextError = false;
1066
- }
1067
-
1068
- break;
1069
- case "url":
1070
- if (!isValidUrl(attributeValue)) {
1071
- addStructuredError(
1072
- rowErrors,
1073
- attributePath,
1074
- "INVALID_URL",
1075
- attributeRules?.["typeError"] ||
1076
- `Invalid URL format for attribute ${attributePath}`,
1077
- );
1078
-
1079
- addNextError = false;
1080
- }
1081
-
1082
- break;
1083
-
1084
- case "enum": {
1085
- const allEnumValues = attributeRules["enumValues"];
1086
-
1087
- if (!allEnumValues?.includes(attributeValue)) {
1088
- addStructuredError(
1089
- rowErrors,
1090
- attributePath,
1091
- "INVALID_ENUM",
1092
- attributeRules?.["typeError"] ||
1093
- `Invalid value for attribute ${attributePath}, valid values are ${allEnumValues?.join(
1094
- ", ",
1095
- )}`,
1096
- );
1097
-
1098
- addNextError = false;
1099
- }
1100
-
1101
- break;
1102
- }
1103
-
1104
- case "uuid":
1105
- if (!isUUID(attributeValue)) {
1106
- addStructuredError(
1107
- rowErrors,
1108
- attributePath,
1109
- "INVALID_UUID",
1110
- attributeRules?.["typeError"] ||
1111
- `Invalid UUID for attribute ${attributePath}`,
1112
- );
1113
-
1114
- addNextError = false;
1115
- }
1116
-
1117
- break;
1118
-
1119
- case "uuidv1":
1120
- if (!isUUIDv1(attributeValue)) {
1121
- addStructuredError(
1122
- rowErrors,
1123
- attributePath,
1124
- "INVALID_UUID_V1",
1125
- attributeRules?.["typeError"] ||
1126
- `Invalid v1 UUID for attribute ${attributePath}`,
1127
- );
1128
-
1129
- addNextError = false;
1130
- }
1131
-
1132
- break;
1133
-
1134
- case "uuidv3":
1135
- if (!isUUIDv3(attributeValue)) {
1136
- addStructuredError(
1137
- rowErrors,
1138
- attributePath,
1139
- "INVALID_UUID_V3",
1140
- attributeRules?.["typeError"] ||
1141
- `Invalid v3 UUID for attribute ${attributePath}`,
1142
- );
1143
-
1144
- addNextError = false;
1145
- }
1146
-
1147
- break;
1148
-
1149
- case "uuidv4":
1150
- if (!isUUIDv4(attributeValue)) {
1151
- addStructuredError(
1152
- rowErrors,
1153
- attributePath,
1154
- "INVALID_UUID_V4",
1155
- attributeRules?.["typeError"] ||
1156
- `Invalid v4 UUID for attribute ${attributePath}`,
1157
- );
1158
-
1159
- addNextError = false;
1160
- }
1161
-
1162
- break;
1163
-
1164
- case "uuidv5":
1165
- if (!isUUIDv5(attributeValue)) {
1166
- addStructuredError(
1167
- rowErrors,
1168
- attributePath,
1169
- "INVALID_UUID_V5",
1170
- attributeRules?.["typeError"] ||
1171
- `Invalid v5 UUID for attribute ${attributePath}`,
1172
- );
1173
-
1174
- addNextError = false;
1175
- }
1176
-
1177
- break;
1178
-
1179
- case "objectId":
1180
- if (!isObjectId(attributeValue)) {
1181
- addStructuredError(
1182
- rowErrors,
1183
- attributePath,
1184
- "INVALID_OBJECT_ID",
1185
- attributeRules?.["typeError"] ||
1186
- `Invalid ObjectId for attribute ${attributePath}`,
1187
- );
1188
-
1189
- addNextError = false;
1190
- }
1191
-
1192
- break;
1193
-
1194
- case "array":
1195
- if (!isArray(attributeValue)) {
1196
- addStructuredError(
1197
- rowErrors,
1198
- attributePath,
1199
- "INVALID_TYPE",
1200
- attributeRules?.["typeError"] ||
1201
- getInvalidTypeMessage(
1202
- attributeValue,
1203
- attributePath,
1204
- expectedType,
1205
- ),
1206
- );
1207
-
1208
- addNextError = false;
1209
- }
1210
-
1211
- break;
1212
-
1213
- case "object":
1214
- if (!isObject(attributeValue)) {
1215
- addStructuredError(
1216
- rowErrors,
1217
- attributePath,
1218
- "INVALID_TYPE",
1219
- attributeRules?.["typeError"] ||
1220
- getInvalidTypeMessage(
1221
- attributeValue,
1222
- attributePath,
1223
- expectedType,
1224
- ),
1225
- );
1226
-
1227
- addNextError = false;
1228
- }
1229
-
1230
- break;
1231
-
1232
- default:
1233
- break;
1234
- }
1235
- }
1236
- }
1237
-
1238
- break;
1239
-
1240
- // ==================================================
1241
- // MIN LENGTH
1242
- // ==================================================
1243
-
1244
- case "minLength":
1245
- if (addNextError) {
1246
- if (attributeValue == null && nullAllowed) {
1247
- addNextError = true;
1248
- } else if (typeof attributeValue === "string") {
1249
- if (attributeValue.length < +attributeRules[ruleName]) {
1250
- addStructuredError(
1251
- rowErrors,
1252
- attributePath,
1253
- "MIN_LENGTH",
1254
- attributeRules?.["minLengthError"] ||
1255
- `${attributePath} should be minimum of ${attributeRules[ruleName]} character`,
1256
- );
1257
-
1258
- addNextError = false;
1259
- }
1260
- } else {
1261
- addStructuredError(
1262
- rowErrors,
1263
- attributePath,
1264
- "INVALID_TYPE",
1265
- `${attributePath} value should be a string type(minLength specified)`,
1266
- );
1267
-
1268
- addNextError = false;
1269
- }
1270
- }
1271
-
1272
- break;
1273
-
1274
- // ==================================================
1275
- // MAX LENGTH
1276
- // ==================================================
1277
-
1278
- case "maxLength":
1279
- if (addNextError) {
1280
- if (attributeValue == null && nullAllowed) {
1281
- addNextError = true;
1282
- } else if (typeof attributeValue === "string") {
1283
- if (attributeValue.length > +attributeRules[ruleName]) {
1284
- addStructuredError(
1285
- rowErrors,
1286
- attributePath,
1287
- "MAX_LENGTH",
1288
- attributeRules?.["maxLengthError"] ||
1289
- `${attributePath} can have maximum of ${attributeRules[ruleName]} character`,
1290
- );
1291
-
1292
- addNextError = false;
1293
- }
1294
- } else {
1295
- addStructuredError(
1296
- rowErrors,
1297
- attributePath,
1298
- "INVALID_TYPE",
1299
- `${attributePath} value should be a string type(maxLength specified)`,
1300
- );
1301
-
1302
- addNextError = false;
1303
- }
1304
- }
1305
-
1306
- break;
1307
-
1308
- // ==================================================
1309
- // PREVENT DECIMAL
1310
- // ==================================================
1311
-
1312
- case "preventDecimal":
1313
- if (addNextError) {
1314
- if (attributeValue == null && nullAllowed) {
1315
- addNextError = true;
1316
- } else if (!isNumber(attributeValue)) {
1317
- addStructuredError(
1318
- rowErrors,
1319
- attributePath,
1320
- "INVALID_TYPE",
1321
- `Invalid type for attribute ${attributePath}, required number value`,
1322
- );
1323
-
1324
- addNextError = false;
1325
- } else if (+attributeValue % 1 !== 0) {
1326
- addStructuredError(
1327
- rowErrors,
1328
- attributePath,
1329
- "DECIMAL_NOT_ALLOWED",
1330
- attributeRules?.["preventDecimalError"] ||
1331
- `Decimal value not allowed in attribute ${attributePath}`,
1332
- );
1333
-
1334
- addNextError = false;
1335
- }
1336
- }
1337
-
1338
- break;
1339
-
1340
- // ==================================================
1341
- // MIN
1342
- // ==================================================
1343
-
1344
- case "min":
1345
- if (addNextError) {
1346
- if (attributeValue == null && nullAllowed) {
1347
- addNextError = true;
1348
- } else if (!isNumber(attributeValue)) {
1349
- addStructuredError(
1350
- rowErrors,
1351
- attributePath,
1352
- "INVALID_TYPE",
1353
- `Invalid type for attribute ${attributePath}, required number value`,
1354
- );
1355
-
1356
- addNextError = false;
1357
- } else if (+attributeValue < +attributeRules[ruleName]) {
1358
- addStructuredError(
1359
- rowErrors,
1360
- attributePath,
1361
- "MIN_VALUE",
1362
- attributeRules?.["minError"] ||
1363
- `Minimum value ${attributeRules[ruleName]} is allowed in attribute ${attributePath}`,
1364
- );
1365
-
1366
- addNextError = false;
1367
- }
1368
- }
1369
-
1370
- break;
1371
- // ==================================================
1372
- // MAX
1373
- // ==================================================
1374
- case "max":
1375
- if (addNextError) {
1376
- if (attributeValue == null && nullAllowed) {
1377
- addNextError = true;
1378
- } else if (!isNumber(attributeValue)) {
1379
- addStructuredError(
1380
- rowErrors,
1381
- attributePath,
1382
- "INVALID_TYPE",
1383
- `Invalid type for attribute ${attributePath}, required number value`,
1384
- );
1385
-
1386
- addNextError = false;
1387
- } else if (+attributeValue > +attributeRules[ruleName]) {
1388
- addStructuredError(
1389
- rowErrors,
1390
- attributePath,
1391
- "MAX_VALUE",
1392
- attributeRules?.["maxError"] ||
1393
- `Maximum value ${attributeRules[ruleName]} is allowed in attribute ${attributePath}`,
1394
- );
1395
-
1396
- addNextError = false;
1397
- }
1398
- }
1399
-
1400
- break;
1401
-
1402
- // ==================================================
1403
- // RANGE
1404
- // ==================================================
1405
-
1406
- case "range":
1407
- if (addNextError) {
1408
- if (attributeValue == null && nullAllowed) {
1409
- addNextError = true;
1410
- } else if (!isNumber(attributeValue)) {
1411
- addStructuredError(
1412
- rowErrors,
1413
- attributePath,
1414
- "INVALID_TYPE",
1415
- `Invalid type for attribute ${attributePath}, required number value`,
1416
- );
1417
-
1418
- addNextError = false;
1419
- } else {
1420
- const [min, max] = attributeRules[ruleName].split("-");
1421
-
1422
- if (+attributeValue < +min || +attributeValue > +max) {
1423
- addStructuredError(
1424
- rowErrors,
1425
- attributePath,
1426
- "OUT_OF_RANGE",
1427
- attributeRules?.["rangeError"] ||
1428
- `Attribute ${attributePath} should have a value between ${min} and ${max}`,
1429
- );
1430
-
1431
- addNextError = false;
1432
- }
1433
- }
1434
- }
1435
-
1436
- break;
1437
- // ==================================================
1438
- // NESTED OBJECT
1439
- // ==================================================
1440
- case "objectAttr":
1441
- if (addNextError) {
1442
- const { errors = [], validatedPayload: nestedValidatedPayload } =
1443
- perfectPayloadStructured(
1444
- attributeValue,
1445
- attributeRules?.[ruleName],
1446
- { statusCode: 200, valid: true },
1447
- {
1448
- statusCode: 400,
1449
- valid: false,
1450
- message: "One or more attribute values are invalid",
1451
- },
1452
- attributePath,
1453
- options,
1454
- );
1455
-
1456
- if (errors.length === 0) {
1457
- attributeValue = nestedValidatedPayload;
1458
- }
1459
-
1460
- rowErrors = [...rowErrors, ...errors];
1461
- }
1462
-
1463
- break;
1464
-
1465
- // ==================================================
1466
- // DEPENDENCY
1467
- // ==================================================
1468
-
1469
- case "dependency":
1470
- if (addNextError) {
1471
- const allDependencyAttr = Object.keys(attributeRules?.[ruleName]);
1472
-
1473
- for (const attr of allDependencyAttr) {
1474
- const dependencyRule = attributeRules?.[ruleName]?.[attr];
1475
-
1476
- if (typeof dependencyRule?.setDependencyRule === "function") {
1477
- const newRule = dependencyRule.setDependencyRule(
1478
- attributeValue,
1479
- data?.[attr],
1480
- );
1481
-
1482
- let newData = {
1483
- [attributeName]: attributeValue,
1484
- };
1485
-
1486
- if (Object.keys(data ?? {}).includes(attr)) {
1487
- newData = {
1488
- ...newData,
1489
- [attr]: data?.[attr],
1490
- };
1491
- }
1492
-
1493
- const { errors = [] } = perfectPayloadStructured(
1494
- newData,
1495
- {
1496
- [attr]: newRule,
1497
- },
1498
- {
1499
- statusCode: 200,
1500
- valid: true,
1501
- },
1502
- {
1503
- statusCode: 400,
1504
- valid: false,
1505
- message: "One or more attribute values are invalid",
1506
- },
1507
- basePath,
1508
- );
1509
-
1510
- rowErrors = [...rowErrors, ...errors];
1511
-
1512
- addNextError = true;
1513
- } else {
1514
- throw new Error(
1515
- `perfect-payload:- function setDependencyRule not found in ${attr} dependency `,
1516
- );
1517
- }
1518
- }
1519
- }
1520
-
1521
- break;
1522
-
1523
- // ==================================================
1524
- // CUSTOM VALIDATOR
1525
- // ==================================================
1526
-
1527
- case "customValidator":
1528
- if (
1529
- !skipCustomValidator &&
1530
- addNextError &&
1531
- attrExist &&
1532
- attributeValue !== null
1533
- ) {
1534
- const validator = attributeRules?.[ruleName];
1535
-
1536
- if (typeof validator !== "function") {
1537
- throw new Error(
1538
- `perfect-payload:- customValidator must be a function for attribute ${attributePath}`,
1539
- );
1540
- }
1541
-
1542
- const validationResult = validator(attributeValue, data);
1543
-
1544
- if (
1545
- validationResult &&
1546
- typeof validationResult.then === "function"
1547
- ) {
1548
- throw new Error(
1549
- `perfect-payload:- customValidator must be synchronous for attribute ${attributePath}`,
1550
- );
1551
- }
1552
-
1553
- if (validationResult !== true) {
1554
- addStructuredError(
1555
- rowErrors,
1556
- attributePath,
1557
- attributeRules?.["customValidatorCode"] ||
1558
- "CUSTOM_VALIDATION_FAILED",
1559
- attributeRules?.["customValidatorError"] ||
1560
- `Custom validation failed for attribute ${attributePath}`,
1561
- );
1562
-
1563
- addNextError = false;
1564
- }
1565
- }
1566
-
1567
- break;
1568
-
1569
- default:
1570
- break;
1571
- }
1572
- }
1573
-
1574
- if (
1575
- attrExist &&
1576
- !rowErrors.some(
1577
- (error) =>
1578
- error.path === attributePath ||
1579
- error.path.startsWith(`${attributePath}.`) ||
1580
- error.path.startsWith(`${attributePath}[`),
1581
- )
1582
- ) {
1583
- validatedPayload[attributeName] = attributeValue;
1584
- }
1585
- }
1586
-
1587
- // UNKNOWN FIELD HANDLING
1588
- if (unknownFields === "allow") {
1589
- for (const attributeName in data) {
1590
- if (
1591
- Object.prototype.hasOwnProperty.call(data, attributeName) &&
1592
- !Object.prototype.hasOwnProperty.call(dataValidationRule, attributeName)
1593
- ) {
1594
- validatedPayload[attributeName] = data[attributeName];
1595
- }
1596
- }
1597
- }
1598
-
1599
- if (unknownFields === "reject") {
1600
- for (const attributeName in data) {
1601
- if (
1602
- Object.prototype.hasOwnProperty.call(data, attributeName) &&
1603
- !Object.prototype.hasOwnProperty.call(dataValidationRule, attributeName)
1604
- ) {
1605
- const path = basePath ? `${basePath}.${attributeName}` : attributeName;
1606
-
1607
- rowErrors.push({
1608
- path,
1609
- code: "UNKNOWN_FIELD",
1610
- message: `Unknown field ${path} is not allowed`,
1611
- });
1612
- }
1613
- }
1614
- }
1615
-
1616
- // INVALID RESPONSE
1617
- if (rowErrors.length > 0) {
1618
- return {
1619
- ...inValidPayloadResponse,
1620
- errors: rowErrors,
1621
- };
1622
- }
1623
-
1624
- // VALID RESPONSE
1625
- return {
1626
- ...validPayloadResponse,
1627
- validatedPayload,
1628
- };
1629
- }
1630
-
1631
- function addStructuredError(errors, path, code, message) {
1632
- errors.push({
1633
- path,
1634
- code,
1635
- message,
1636
- });
1637
- }
1638
-
1639
- function getAttributePath(attributeName, attributeRules, basePath) {
1640
- const customPath = attributeRules?.path;
1641
-
1642
- if (customPath) {
1643
- return `${customPath}.${attributeName}`;
1644
- }
1645
-
1646
- if (basePath) {
1647
- return `${basePath}.${attributeName}`;
1648
- }
1649
-
1650
- return attributeName;
1651
- }
1652
-
1653
- function replaceRootPath(currentPath, currentRoot, replacementRoot) {
1654
- if (currentPath === currentRoot) {
1655
- return replacementRoot;
1656
- }
1657
-
1658
- if (currentPath.startsWith(`${currentRoot}.`)) {
1659
- return `${replacementRoot}${currentPath.slice(currentRoot.length)}`;
1660
- }
1661
-
1662
- if (currentPath.startsWith(`${currentRoot}[`)) {
1663
- return `${replacementRoot}${currentPath.slice(currentRoot.length)}`;
1664
- }
1665
-
1666
- return replacementRoot;
1667
- }
1668
-
1669
- function getInvalidTypeMessage(value, attributePath, expectedType) {
1670
- return `Invalid type for attribute ${attributePath}, required ${expectedType} value`;
1671
- // return `Invalid ${typeof value} value(${value}) found in attribute ${attributePath}, required ${expectedType} value`;
1672
- }
1673
-
1674
- function isNumber(value) {
1675
- return typeof value == "number";
1676
- }
1677
-
1678
- function isString(value) {
1679
- return typeof value == "string";
1680
- }
1681
-
1682
- function isBoolean(value) {
1683
- return typeof value == "boolean";
1684
- }
1685
-
1686
- function isArray(value) {
1687
- return Array.isArray(value);
1688
- }
1689
-
1690
- function isObject(value) {
1691
- return typeof value == "object";
1692
- }
1693
-
1694
- function isValidEmail(email) {
1695
- const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
1696
- return emailRegex.test(email);
1697
- }
1698
-
1699
- function isValidUrl(url) {
1700
- const urlRegex =
1701
- /^(https?:\/\/)([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})(:[0-9]+)?(\/[^\s]*)?(\?[^\s]*)?$/;
1702
- return urlRegex.test(url);
1703
- }
1704
-
1705
- function isUUID(uuid) {
1706
- const uuidRegex =
1707
- /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1708
-
1709
- return uuidRegex.test(uuid);
1710
- }
1711
-
1712
- function isUUIDv1(uuid) {
1713
- const uuidRegex =
1714
- /^[0-9a-f]{8}-[0-9a-f]{4}-1[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1715
-
1716
- return uuidRegex.test(uuid);
1717
- }
1718
-
1719
- function isUUIDv3(uuid) {
1720
- const uuidRegex =
1721
- /^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1722
-
1723
- return uuidRegex.test(uuid);
1724
- }
1725
-
1726
- function isUUIDv4(uuid) {
1727
- const uuidRegex =
1728
- /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1729
-
1730
- return uuidRegex.test(uuid);
1731
- }
1732
-
1733
- function isUUIDv5(uuid) {
1734
- const uuidRegex =
1735
- /^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1736
-
1737
- return uuidRegex.test(uuid);
1738
- }
1739
-
1740
- function isObjectId(id) {
1741
- return /^[0-9a-fA-F]{24}$/.test(id);
1742
- }
1743
-
1744
- function isPassedRegex(reExpression, value) {
1745
- return reExpression.test(value);
1746
- }
1747
-
1748
- async function runAsyncCustomValidators(
1749
- data = {},
1750
- dataValidationRule = {},
1751
- basePath = "",
1752
- ) {
1753
- const errors = [];
1754
-
1755
- for (const attributeName in dataValidationRule) {
1756
- const attributeRules = dataValidationRule?.[attributeName];
1757
-
1758
- const attrExist = Object.keys(data ?? {}).includes(attributeName);
1759
-
1760
- if (!attrExist) {
1761
- continue;
1762
- }
1763
-
1764
- const attributeValue = data?.[attributeName];
1765
-
1766
- const attributePath = getAttributePath(
1767
- attributeName,
1768
- attributeRules,
1769
- basePath,
1770
- );
1771
-
1772
- // customValidator
1773
- if (
1774
- Object.prototype.hasOwnProperty.call(
1775
- attributeRules ?? {},
1776
- "customValidator",
1777
- )
1778
- ) {
1779
- if (typeof attributeRules.customValidator !== "function") {
1780
- throw new Error(
1781
- `perfect-payload:- customValidator must be a function for attribute ${attributePath}`,
1782
- );
1783
- }
1784
-
1785
- if (attributeValue !== null) {
1786
- const validationResponse = await attributeRules.customValidator(
1787
- attributeValue,
1788
- data,
1789
- );
1790
-
1791
- if (validationResponse !== true) {
1792
- addStructuredError(
1793
- errors,
1794
- attributePath,
1795
- attributeRules?.customValidatorCode || "CUSTOM_VALIDATION_FAILED",
1796
- attributeRules?.customValidatorError ||
1797
- `Custom validation failed for attribute ${attributePath}`,
1798
- );
1799
- }
1800
- }
1801
- }
1802
-
1803
- // nested objectAttr
1804
- if (
1805
- attributeValue !== null &&
1806
- typeof attributeValue === "object" &&
1807
- !Array.isArray(attributeValue) &&
1808
- attributeRules?.objectAttr
1809
- ) {
1810
- const nestedErrors = await runAsyncCustomValidators(
1811
- attributeValue,
1812
- attributeRules.objectAttr,
1813
- attributePath,
1814
- );
1815
-
1816
- errors.push(...nestedErrors);
1817
- }
1818
-
1819
- // nested elementConstraints
1820
- if (Array.isArray(attributeValue) && attributeRules?.elementConstraints) {
1821
- for (
1822
- let elementIndex = 0;
1823
- elementIndex < attributeValue.length;
1824
- elementIndex++
1825
- ) {
1826
- const element = attributeValue[elementIndex];
1827
-
1828
- const elementErrors = await runAsyncCustomValidators(
1829
- {
1830
- [attributeName]: element,
1831
- },
1832
- {
1833
- [attributeName]: attributeRules.elementConstraints,
1834
- },
1835
- );
1836
-
1837
- for (const elementError of elementErrors) {
1838
- const indexedPath = replaceRootPath(
1839
- elementError.path,
1840
- attributeName,
1841
- `${attributePath}[${elementIndex}]`,
1842
- );
1843
-
1844
- errors.push({
1845
- ...elementError,
1846
- path: indexedPath,
1847
-
1848
- message:
1849
- elementError.code === "CUSTOM_VALIDATION_FAILED" &&
1850
- elementError.message ===
1851
- `Custom validation failed for attribute ${elementError.path}`
1852
- ? `Custom validation failed for attribute ${indexedPath}`
1853
- : elementError.message,
1854
- });
1855
- }
1856
- }
1857
- }
1858
- }
1859
-
1860
- return errors;
1861
- }