perfect-payload 1.8.0-beta.0 → 2.0.0-beta.1

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/dist/index.js ADDED
@@ -0,0 +1,1238 @@
1
+ let perfectPayloadV1DeprecationWarningShown = false;
2
+ function showPerfectPayloadV1DeprecationWarning() {
3
+ if (perfectPayloadV1DeprecationWarningShown)
4
+ return;
5
+ perfectPayloadV1DeprecationWarningShown = true;
6
+ const message = "perfectPayloadV1() is deprecated and will no longer be supported after March 31, 2027. Use perfectPayload() instead.";
7
+ if (typeof process !== "undefined" &&
8
+ typeof process.emitWarning === "function") {
9
+ process.emitWarning(message, {
10
+ type: "DeprecationWarning",
11
+ code: "PERFECT_PAYLOAD_V1_DEPRECATED",
12
+ });
13
+ }
14
+ else if (typeof console !== "undefined" &&
15
+ typeof console.warn === "function") {
16
+ console.warn(`[perfect-payload] DeprecationWarning: ${message}`);
17
+ }
18
+ }
19
+ export function perfectPayloadV1(data = {}, dataValidationRule = {}, validPayloadResponse = {
20
+ statusCode: 200,
21
+ valid: true,
22
+ }, inValidPayloadResponse = {
23
+ statusCode: 400,
24
+ valid: false,
25
+ message: "One or more attribute values are invalid",
26
+ }) {
27
+ showPerfectPayloadV1DeprecationWarning();
28
+ let validatedPayload = {};
29
+ let rowErrors = [];
30
+ for (const attributeName in dataValidationRule) {
31
+ let addNextError = true;
32
+ const attributeRules = dataValidationRule?.[attributeName];
33
+ const attrPath = dataValidationRule?.[attributeName]?.path || null;
34
+ for (const ruleName in attributeRules) {
35
+ const attributePath = `${attrPath ? attrPath + "." : ""}${attributeName}`;
36
+ const attributeValue = data?.[attributeName] ?? null;
37
+ const nullAllowed = dataValidationRule?.[attributeName]?.["allowNull"] ?? true;
38
+ const isMandatoryField = attributeRules?.["mandatory"] ?? false;
39
+ const attrExist = Object.keys(data)?.includes(attributeName); //Mandatory value check
40
+ switch (ruleName) {
41
+ case "mandatory":
42
+ //mandatory value check
43
+ if (isMandatoryField && !attrExist) {
44
+ addNextError = false;
45
+ rowErrors.push(attributeRules?.["mandatoryError"] ||
46
+ `${attributePath} is mandatory`);
47
+ }
48
+ else if (!attrExist) {
49
+ addNextError = false;
50
+ }
51
+ break;
52
+ case "allowNull":
53
+ //allowNull value check
54
+ if (addNextError && attributeValue == null) {
55
+ if (!dataValidationRule?.[attributeName]?.[ruleName]) {
56
+ addNextError = false;
57
+ rowErrors.push(attributeRules?.["allowNullError"] ||
58
+ `value null/'' not valid for attribute ${attributePath}`);
59
+ }
60
+ }
61
+ break;
62
+ case "allowEmptyObject":
63
+ //allowEmptyObject check
64
+ if (addNextError) {
65
+ if (!attributeRules?.["allowEmptyObject"] &&
66
+ Object.keys(attributeValue).length == 0) {
67
+ rowErrors.push(attributeRules?.["emptyObjectError"] ||
68
+ `value {} not valid for attribute ${attributePath}`);
69
+ addNextError = false;
70
+ }
71
+ }
72
+ break;
73
+ case "allowEmptyArray":
74
+ //allowEmptyArray check
75
+ if (addNextError) {
76
+ if (!attributeRules?.["allowEmptyArray"] &&
77
+ isArray(attributeValue) &&
78
+ attributeValue?.length == 0) {
79
+ rowErrors.push(attributeRules?.["emptyArrayError"] ||
80
+ `${attributePath} cannot be an empty array`);
81
+ addNextError = false;
82
+ }
83
+ }
84
+ break;
85
+ case "elementConstraints":
86
+ //check array ele type
87
+ if (addNextError) {
88
+ if (isArray(attributeValue) && attributeValue?.length > 0) {
89
+ let elementError = false;
90
+ for (const element of attributeValue) {
91
+ const { statusCode = 200, errors } = perfectPayloadV1({ [attributeName]: element }, { [attributeName]: attributeRules[ruleName] });
92
+ if (statusCode == 400) {
93
+ elementError = errors?.[0];
94
+ // addNextError = false;
95
+ break;
96
+ }
97
+ }
98
+ if (elementError) {
99
+ rowErrors.push(attributeRules?.["elementConstraintsError"] || elementError);
100
+ addNextError = false;
101
+ }
102
+ }
103
+ }
104
+ break;
105
+ case "regex":
106
+ //custom regex check
107
+ if (addNextError) {
108
+ if (!isPassedRegex(attributeRules[ruleName], attributeValue)) {
109
+ rowErrors.push(attributeRules?.["regexError"] ||
110
+ `${attributePath} failed to pass the regex ${attributeRules[ruleName]}`);
111
+ addNextError = false;
112
+ }
113
+ }
114
+ break;
115
+ case "type":
116
+ //value type check
117
+ if (addNextError) {
118
+ if (attributeValue == null && nullAllowed) {
119
+ addNextError = true;
120
+ }
121
+ else {
122
+ switch (attributeRules[ruleName]) {
123
+ case "number":
124
+ if (!isNumber(attributeValue)) {
125
+ rowErrors.push(attributeRules?.["typeError"] ||
126
+ `Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required ${attributeRules[ruleName]} value`);
127
+ addNextError = false;
128
+ }
129
+ break;
130
+ case "string":
131
+ if (!isString(attributeValue)) {
132
+ rowErrors.push(attributeRules?.["typeError"] ||
133
+ `Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required ${attributeRules[ruleName]} value`);
134
+ addNextError = false;
135
+ }
136
+ break;
137
+ case "boolean":
138
+ if (!isBoolean(attributeValue)) {
139
+ rowErrors.push(attributeRules?.["typeError"] ||
140
+ `Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required ${attributeRules[ruleName]} value`);
141
+ addNextError = false;
142
+ }
143
+ break;
144
+ case "email":
145
+ if (!isValidEmail(attributeValue)) {
146
+ rowErrors.push(attributeRules?.["typeError"] ||
147
+ `Invalid email ID(${attributeValue}) found in attribute ${attributePath}`);
148
+ addNextError = false;
149
+ }
150
+ break;
151
+ case "url":
152
+ if (!isValidUrl(attributeValue)) {
153
+ rowErrors.push(attributeRules?.["typeError"] ||
154
+ `Invalid URL format(${attributeValue}) found in attribute ${attributePath}`);
155
+ addNextError = false;
156
+ }
157
+ break;
158
+ case "enum":
159
+ if (attributeValue == null && nullAllowed) {
160
+ addNextError = true;
161
+ }
162
+ else {
163
+ const allEnumValues = attributeRules["enumValues"];
164
+ if (!allEnumValues?.includes(attributeValue)) {
165
+ rowErrors.push(attributeRules?.["typeError"] ||
166
+ `Invalid value(${attributeValue}) found in attribute ${attributePath}, valid values are ${allEnumValues?.join(", ")}`);
167
+ addNextError = false;
168
+ }
169
+ }
170
+ break;
171
+ case "uuid":
172
+ if (attributeValue == null && nullAllowed) {
173
+ addNextError = true;
174
+ }
175
+ else if (!isUUID(attributeValue)) {
176
+ rowErrors.push(attributeRules?.["typeError"] ||
177
+ `Invalid UUID(${attributeValue}) found in attribute ${attributePath}`);
178
+ addNextError = false;
179
+ }
180
+ break;
181
+ case "uuidv1":
182
+ if (attributeValue == null && nullAllowed) {
183
+ addNextError = true;
184
+ }
185
+ else if (!isUUIDv1(attributeValue)) {
186
+ rowErrors.push(attributeRules?.["typeError"] ||
187
+ `Invalid v1 UUID(${attributeValue}) found in attribute ${attributePath}`);
188
+ addNextError = false;
189
+ }
190
+ break;
191
+ case "uuidv3":
192
+ if (attributeValue == null && nullAllowed) {
193
+ addNextError = true;
194
+ }
195
+ else if (!isUUIDv3(attributeValue)) {
196
+ rowErrors.push(attributeRules?.["typeError"] ||
197
+ `Invalid v3 UUID(${attributeValue}) found in attribute ${attributePath}`);
198
+ addNextError = false;
199
+ }
200
+ break;
201
+ case "uuidv4":
202
+ if (attributeValue == null && nullAllowed) {
203
+ addNextError = true;
204
+ }
205
+ else if (!isUUIDv4(attributeValue)) {
206
+ rowErrors.push(attributeRules?.["typeError"] ||
207
+ `Invalid v4 UUID(${attributeValue}) found in attribute ${attributePath}`);
208
+ addNextError = false;
209
+ }
210
+ break;
211
+ case "uuidv5":
212
+ if (attributeValue == null && nullAllowed) {
213
+ addNextError = true;
214
+ }
215
+ else if (!isUUIDv5(attributeValue)) {
216
+ rowErrors.push(attributeRules?.["typeError"] ||
217
+ `Invalid v5 UUID(${attributeValue}) found in attribute ${attributePath}`);
218
+ addNextError = false;
219
+ }
220
+ break;
221
+ case "objectId":
222
+ if (attributeValue == null && nullAllowed) {
223
+ addNextError = true;
224
+ }
225
+ else {
226
+ if (!isObjectId(attributeValue)) {
227
+ rowErrors.push(attributeRules?.["typeError"] ||
228
+ `Invalid ObjectId(${attributeValue}) found in attribute ${attributePath}`);
229
+ addNextError = false;
230
+ }
231
+ }
232
+ break;
233
+ case "array":
234
+ if (!isArray(attributeValue)) {
235
+ rowErrors.push(attributeRules?.["typeError"] ||
236
+ `Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required ${attributeRules[ruleName]} value`);
237
+ addNextError = false;
238
+ }
239
+ break;
240
+ case "object":
241
+ if (!isObject(attributeValue)) {
242
+ rowErrors.push(attributeRules?.["typeError"] ||
243
+ `Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required ${attributeRules[ruleName]} value`);
244
+ addNextError = false;
245
+ }
246
+ break;
247
+ default:
248
+ break;
249
+ }
250
+ }
251
+ }
252
+ break;
253
+ case "minLength":
254
+ //minimum string length check
255
+ if (addNextError) {
256
+ if (attributeValue == null && nullAllowed) {
257
+ addNextError = true;
258
+ }
259
+ else if (typeof attributeValue == "string") {
260
+ if (attributeValue.length < +attributeRules[ruleName])
261
+ rowErrors.push(attributeRules?.["minLengthError"] ||
262
+ `${attributePath} should be minimum of ${attributeRules[ruleName]} character`);
263
+ addNextError = false;
264
+ }
265
+ else {
266
+ rowErrors.push(`${attributePath} value should be a string type(minLength specified)`);
267
+ addNextError = false;
268
+ }
269
+ }
270
+ break;
271
+ case "maxLength":
272
+ //max string length check
273
+ if (addNextError) {
274
+ if (attributeValue == null && nullAllowed) {
275
+ addNextError = true;
276
+ }
277
+ else if (typeof attributeValue == "string") {
278
+ if (attributeValue.length > +attributeRules[ruleName])
279
+ rowErrors.push(attributeRules?.["maxLengthError"] ||
280
+ `${attributePath} can have maximum of ${attributeRules[ruleName]} character`);
281
+ addNextError = false;
282
+ }
283
+ else {
284
+ rowErrors.push(`${attributePath} value should be a string type(maxLength specified)`);
285
+ addNextError = false;
286
+ }
287
+ }
288
+ break;
289
+ case "preventDecimal":
290
+ //preventFraction
291
+ if (addNextError) {
292
+ if (attributeValue == null && nullAllowed) {
293
+ addNextError = true;
294
+ }
295
+ else if (!isNumber(attributeValue)) {
296
+ rowErrors.push(`Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required number value`);
297
+ addNextError = false;
298
+ }
299
+ else if (+attributeValue % 1 !== 0) {
300
+ rowErrors.push(attributeRules?.["preventDecimalError"] ||
301
+ `Decimal value not allowed in attribute ${attributePath}(${attributeValue})`);
302
+ addNextError = false;
303
+ }
304
+ }
305
+ break;
306
+ case "min":
307
+ //minimum value check
308
+ if (addNextError) {
309
+ if (attributeValue == null && nullAllowed) {
310
+ addNextError = true;
311
+ }
312
+ else if (!isNumber(attributeValue)) {
313
+ rowErrors.push(`Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required number value`);
314
+ addNextError = false;
315
+ }
316
+ else if (+attributeValue < +attributeRules[ruleName]) {
317
+ rowErrors.push(attributeRules?.["minError"] ||
318
+ `minimum value ${attributeRules[ruleName]} is allowed in attribute ${attributePath}, found value ${data[attributeName] ?? "Nil"}`);
319
+ addNextError = false;
320
+ }
321
+ }
322
+ break;
323
+ case "max":
324
+ //maximum value check
325
+ if (addNextError) {
326
+ if (attributeValue == null && nullAllowed) {
327
+ addNextError = true;
328
+ }
329
+ else if (!isNumber(attributeValue)) {
330
+ rowErrors.push(`Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required number value`);
331
+ addNextError = false;
332
+ }
333
+ else if (+attributeValue > +attributeRules[ruleName]) {
334
+ rowErrors.push(attributeRules?.["maxError"] ||
335
+ `maximum value ${attributeRules[ruleName]} is allowed in attribute ${attributePath}, found value ${data[attributeName] ?? "Nil"}`);
336
+ addNextError = false;
337
+ }
338
+ }
339
+ break;
340
+ case "range":
341
+ //value range check
342
+ if (addNextError) {
343
+ if (attributeValue == null && nullAllowed) {
344
+ addNextError = true;
345
+ }
346
+ else if (!isNumber(attributeValue)) {
347
+ rowErrors.push(`Invalid ${typeof attributeValue} value(${attributeValue}) found in attribute ${attributePath}, required number value`);
348
+ addNextError = false;
349
+ }
350
+ else {
351
+ const [min, max] = attributeRules[ruleName].split("-");
352
+ if (!isNumber(attributeValue) ||
353
+ +attributeValue < +min ||
354
+ +attributeValue > +max) {
355
+ rowErrors.push(attributeRules?.["rangeError"] ||
356
+ `Invalid values(${attributeValue}) found in attribute ${attributePath}, value should be between ${min} and ${max} `);
357
+ addNextError = false;
358
+ }
359
+ }
360
+ }
361
+ break;
362
+ case "objectAttr":
363
+ //nested object attributes check
364
+ if (addNextError) {
365
+ const allObjectAttr = Object.keys(attributeRules?.[ruleName]);
366
+ const objectAttrRules = {};
367
+ for (const attr of allObjectAttr) {
368
+ objectAttrRules[attr] = {
369
+ ...attributeRules?.[ruleName]?.[attr],
370
+ path: attributeRules?.[ruleName]?.[attr]?.path
371
+ ? attributeRules?.[ruleName]?.[attr]?.path + `.${attr}`
372
+ : `${attributePath}`,
373
+ };
374
+ }
375
+ const { errors = [] } = perfectPayloadV1(attributeValue, objectAttrRules);
376
+ rowErrors = [...rowErrors, ...errors];
377
+ addNextError = true;
378
+ }
379
+ break;
380
+ case "dependency":
381
+ //dependency check
382
+ if (addNextError) {
383
+ const allDependencyAttr = Object.keys(attributeRules?.[ruleName]);
384
+ for (const attr of allDependencyAttr) {
385
+ if (typeof attributeRules?.[ruleName]?.[attr]?.setDependencyRule ==
386
+ "function") {
387
+ const newRule = attributeRules?.[ruleName]?.[attr]?.setDependencyRule(attributeValue, data?.[attr]);
388
+ let newData = { [attributeName]: attributeValue };
389
+ if (Object.keys(data).includes(attr)) {
390
+ newData = { ...newData, [attr]: data?.[attr] };
391
+ }
392
+ const { errors = [] } = perfectPayloadV1(newData, {
393
+ [attr]: newRule,
394
+ });
395
+ rowErrors = [...rowErrors, ...errors];
396
+ addNextError = true;
397
+ }
398
+ else {
399
+ throw new Error(`perfect-payload:- function setDependencyRule not found in ${attr} dependency `);
400
+ }
401
+ }
402
+ }
403
+ break;
404
+ default:
405
+ break;
406
+ }
407
+ if (attrExist && rowErrors.length == 0)
408
+ validatedPayload[attributeName] = attributeValue;
409
+ }
410
+ }
411
+ if (rowErrors?.length) {
412
+ return { ...inValidPayloadResponse, errors: rowErrors };
413
+ }
414
+ else {
415
+ return { ...validPayloadResponse, validatedPayload };
416
+ }
417
+ }
418
+ export function perfectPayload(data = {}, dataValidationRule = {}, options = {}) {
419
+ const { unknownFields = "strip", prettyErrors = false, validPayloadResponse = {
420
+ statusCode: 200,
421
+ valid: true,
422
+ }, inValidPayloadResponse = {
423
+ statusCode: 400,
424
+ valid: false,
425
+ message: "One or more attribute values are invalid",
426
+ }, } = options ?? {};
427
+ if (!["strip", "allow", "reject"].includes(unknownFields)) {
428
+ throw new Error(`perfect-payload:- unknownFields must be one of strip, allow, reject`);
429
+ }
430
+ if (typeof prettyErrors !== "boolean") {
431
+ throw new Error(`perfect-payload:- prettyErrors must be a boolean`);
432
+ }
433
+ const validationResult = perfectPayloadStructured(data, dataValidationRule, validPayloadResponse, inValidPayloadResponse, "", {
434
+ unknownFields,
435
+ });
436
+ if (prettyErrors &&
437
+ validationResult.valid === false &&
438
+ validationResult.errors.length) {
439
+ return {
440
+ ...validationResult,
441
+ errors: validationResult.errors.map((error) => error.message),
442
+ };
443
+ }
444
+ return validationResult;
445
+ }
446
+ export async function perfectPayloadAsync(data = {}, dataValidationRule = {}, options = {}) {
447
+ const { validPayloadResponse = {
448
+ statusCode: 200,
449
+ valid: true,
450
+ }, inValidPayloadResponse = {
451
+ statusCode: 400,
452
+ valid: false,
453
+ message: "One or more attribute values are invalid",
454
+ }, unknownFields = "strip", prettyErrors = false, } = options ?? {};
455
+ if (!["strip", "allow", "reject"].includes(unknownFields)) {
456
+ throw new Error(`perfect-payload:- unknownFields must be one of strip, allow, reject`);
457
+ }
458
+ if (typeof prettyErrors !== "boolean") {
459
+ throw new Error(`perfect-payload:- prettyErrors must be a boolean`);
460
+ }
461
+ const validationResult = perfectPayloadStructured(data, dataValidationRule, validPayloadResponse, inValidPayloadResponse, "", {
462
+ skipCustomValidator: true,
463
+ unknownFields,
464
+ });
465
+ if (validationResult.valid === false && validationResult.errors.length) {
466
+ if (prettyErrors) {
467
+ return {
468
+ ...validationResult,
469
+ errors: validationResult.errors.map((error) => error.message),
470
+ };
471
+ }
472
+ return validationResult;
473
+ }
474
+ const rowErrors = await runAsyncCustomValidators(validationResult.valid === true ? validationResult.validatedPayload : {}, dataValidationRule);
475
+ if (rowErrors.length > 0) {
476
+ if (prettyErrors) {
477
+ return {
478
+ ...inValidPayloadResponse,
479
+ errors: rowErrors.map((error) => error.message),
480
+ };
481
+ }
482
+ return {
483
+ ...inValidPayloadResponse,
484
+ errors: rowErrors,
485
+ };
486
+ }
487
+ return validationResult;
488
+ }
489
+ function perfectPayloadStructured(data = {}, dataValidationRule = {}, validPayloadResponse = {
490
+ statusCode: 200,
491
+ valid: true,
492
+ }, inValidPayloadResponse = {
493
+ statusCode: 400,
494
+ valid: false,
495
+ message: "One or more attribute values are invalid",
496
+ }, basePath = "", options = {}) {
497
+ let validatedPayload = {};
498
+ let rowErrors = [];
499
+ const skipCustomValidator = options?.skipCustomValidator === true;
500
+ const unknownFields = options?.unknownFields ?? "strip";
501
+ for (const attributeName in dataValidationRule) {
502
+ let addNextError = true;
503
+ const attributeRules = dataValidationRule?.[attributeName];
504
+ const attributePath = getAttributePath(attributeName, attributeRules, basePath);
505
+ let attributeValue = data?.[attributeName] ?? null;
506
+ const nullAllowed = dataValidationRule?.[attributeName]?.["allowNull"] ?? true;
507
+ const isMandatoryField = attributeRules?.["mandatory"] ?? false;
508
+ const attrExist = Object.keys(data ?? {}).includes(attributeName);
509
+ // TRANSFORMATIONS START
510
+ // NO MULTI TRANSFORMATION
511
+ if (attributeRules?.lowercase === true &&
512
+ attributeRules?.uppercase === true) {
513
+ throw new Error(`perfect-payload:- lowercase and uppercase cannot both be enabled for attribute ${attributePath}`);
514
+ }
515
+ // TRIM
516
+ if (attrExist &&
517
+ attributeValue !== null &&
518
+ attributeRules?.trim === true &&
519
+ typeof attributeValue === "string") {
520
+ attributeValue = attributeValue.trim();
521
+ }
522
+ // LOWERCASE
523
+ if (attrExist &&
524
+ attributeValue !== null &&
525
+ attributeRules?.lowercase === true &&
526
+ typeof attributeValue === "string") {
527
+ attributeValue = attributeValue.toLowerCase();
528
+ }
529
+ // UPPERCASE
530
+ if (attrExist &&
531
+ attributeValue !== null &&
532
+ attributeRules?.uppercase === true &&
533
+ typeof attributeValue === "string") {
534
+ attributeValue = attributeValue.toUpperCase();
535
+ }
536
+ // CUSTOM TRANSFORM
537
+ if (attrExist &&
538
+ attributeValue !== null &&
539
+ attributeRules?.transform !== undefined) {
540
+ const transformer = attributeRules.transform;
541
+ if (typeof transformer !== "function") {
542
+ throw new Error(`perfect-payload:- transform must be a function for attribute ${attributePath}`);
543
+ }
544
+ const transformedValue = transformer(attributeValue, data);
545
+ if (transformedValue &&
546
+ typeof transformedValue === "object" &&
547
+ "then" in transformedValue &&
548
+ typeof transformedValue.then === "function") {
549
+ throw new Error(`perfect-payload:- transform must be synchronous for attribute ${attributePath}`);
550
+ }
551
+ if (transformedValue === undefined) {
552
+ throw new Error(`perfect-payload:- transform must not return undefined for attribute ${attributePath}`);
553
+ }
554
+ attributeValue = transformedValue;
555
+ }
556
+ // TRANSFORMATIONS END
557
+ for (const ruleName in attributeRules) {
558
+ switch (ruleName) {
559
+ // ==================================================
560
+ // MANDATORY
561
+ // ==================================================
562
+ case "mandatory":
563
+ if (isMandatoryField && (!attrExist || attributeValue === "")) {
564
+ addNextError = false;
565
+ addStructuredError(rowErrors, attributePath, "REQUIRED", attributeRules?.["mandatoryError"] ||
566
+ `${attributePath} is mandatory`);
567
+ }
568
+ else if (!attrExist) {
569
+ addNextError = false;
570
+ }
571
+ break;
572
+ // ==================================================
573
+ // NULL
574
+ // ==================================================
575
+ case "allowNull":
576
+ if (addNextError && attributeValue === null) {
577
+ if (!attributeRules?.[ruleName]) {
578
+ addNextError = false;
579
+ addStructuredError(rowErrors, attributePath, "NULL_NOT_ALLOWED", attributeRules?.["allowNullError"] ||
580
+ `value null not valid for attribute ${attributePath}`);
581
+ }
582
+ }
583
+ break;
584
+ // ==================================================
585
+ // EMPTY OBJECT
586
+ // ==================================================
587
+ case "allowEmptyObject":
588
+ if (addNextError) {
589
+ if (!attributeRules?.["allowEmptyObject"] &&
590
+ attributeValue != null &&
591
+ Object.keys(attributeValue).length === 0) {
592
+ addStructuredError(rowErrors, attributePath, "EMPTY_OBJECT_NOT_ALLOWED", attributeRules?.["emptyObjectError"] ||
593
+ `value {} not valid for attribute ${attributePath}`);
594
+ addNextError = false;
595
+ }
596
+ }
597
+ break;
598
+ // ==================================================
599
+ // EMPTY ARRAY
600
+ // ==================================================
601
+ case "allowEmptyArray":
602
+ if (addNextError) {
603
+ if (!attributeRules?.["allowEmptyArray"] &&
604
+ isArray(attributeValue) &&
605
+ attributeValue.length === 0) {
606
+ addStructuredError(rowErrors, attributePath, "EMPTY_ARRAY_NOT_ALLOWED", attributeRules?.["emptyArrayError"] ||
607
+ `${attributePath} cannot be an empty array`);
608
+ addNextError = false;
609
+ }
610
+ }
611
+ break;
612
+ // ==================================================
613
+ // ARRAY ELEMENT CONSTRAINTS
614
+ // ==================================================
615
+ case "elementConstraints":
616
+ if (addNextError) {
617
+ if (isArray(attributeValue) && attributeValue.length > 0) {
618
+ const elementConstraints = attributeRules.elementConstraints;
619
+ if (!elementConstraints) {
620
+ break;
621
+ }
622
+ let elementError = null;
623
+ // Clone so original payload array is not mutated
624
+ const transformedArray = [...attributeValue];
625
+ for (let elementIndex = 0; elementIndex < attributeValue.length; elementIndex++) {
626
+ const element = attributeValue[elementIndex];
627
+ const elementResult = perfectPayloadStructured({
628
+ [attributeName]: element,
629
+ }, {
630
+ [attributeName]: elementConstraints,
631
+ }, { statusCode: 200, valid: true }, {
632
+ statusCode: 400,
633
+ valid: false,
634
+ message: "One or more attribute values are invalid",
635
+ }, "", options);
636
+ if (elementResult.valid === false &&
637
+ elementResult.errors.length > 0) {
638
+ const firstElementError = elementResult.errors[0];
639
+ const indexedPath = replaceRootPath(firstElementError.path, attributeName, `${attributePath}[${elementIndex}]`);
640
+ if (attributeRules?.["elementConstraintsError"]) {
641
+ elementError = {
642
+ path: indexedPath,
643
+ code: "INVALID_ARRAY_ELEMENT",
644
+ message: attributeRules["elementConstraintsError"],
645
+ };
646
+ }
647
+ else {
648
+ elementError = {
649
+ ...firstElementError,
650
+ path: indexedPath,
651
+ ...(firstElementError.code === "UNKNOWN_FIELD" && {
652
+ message: `Unknown field ${indexedPath} is not allowed`,
653
+ }),
654
+ };
655
+ }
656
+ break;
657
+ }
658
+ // Preserve the transformed element
659
+ if (elementResult.valid === true) {
660
+ transformedArray[elementIndex] =
661
+ elementResult.validatedPayload[attributeName];
662
+ }
663
+ }
664
+ if (elementError) {
665
+ rowErrors.push(elementError);
666
+ addNextError = false;
667
+ }
668
+ else {
669
+ // All elements passed, so use transformed values
670
+ attributeValue = transformedArray;
671
+ }
672
+ }
673
+ }
674
+ break;
675
+ case "minItems": {
676
+ const minItems = attributeRules.minItems;
677
+ if (addNextError &&
678
+ attrExist &&
679
+ attributeValue !== null &&
680
+ isArray(attributeValue) &&
681
+ minItems !== undefined &&
682
+ attributeValue.length < minItems) {
683
+ addStructuredError(rowErrors, attributePath, "MIN_ITEMS", `Attribute ${attributePath} must contain at least ${minItems} item(s)`);
684
+ addNextError = false;
685
+ }
686
+ break;
687
+ }
688
+ case "maxItems": {
689
+ const maxItems = attributeRules.maxItems;
690
+ if (addNextError &&
691
+ attrExist &&
692
+ attributeValue !== null &&
693
+ isArray(attributeValue) &&
694
+ maxItems !== undefined &&
695
+ attributeValue.length > maxItems) {
696
+ addStructuredError(rowErrors, attributePath, "MAX_ITEMS", `Attribute ${attributePath} must contain at most ${maxItems} item(s)`);
697
+ addNextError = false;
698
+ }
699
+ break;
700
+ }
701
+ // ==================================================
702
+ // REGEX
703
+ // ==================================================
704
+ case "regex":
705
+ if (addNextError) {
706
+ if (!isPassedRegex(attributeRules[ruleName], attributeValue)) {
707
+ addStructuredError(rowErrors, attributePath, "REGEX_MISMATCH", attributeRules?.["regexError"] ||
708
+ `${attributePath} failed to pass the regex ${attributeRules[ruleName]}`);
709
+ addNextError = false;
710
+ }
711
+ }
712
+ break;
713
+ // ==================================================
714
+ // TYPE
715
+ // ==================================================
716
+ case "type":
717
+ if (addNextError) {
718
+ if (attributeValue == null && nullAllowed) {
719
+ addNextError = true;
720
+ }
721
+ else {
722
+ const expectedType = attributeRules[ruleName];
723
+ switch (expectedType) {
724
+ case "number":
725
+ if (!isNumber(attributeValue) ||
726
+ Number.isNaN(attributeValue)) {
727
+ addStructuredError(rowErrors, attributePath, "INVALID_TYPE", attributeRules?.["typeError"] ||
728
+ getInvalidTypeMessage(attributeValue, attributePath, expectedType));
729
+ addNextError = false;
730
+ }
731
+ break;
732
+ case "string":
733
+ if (!isString(attributeValue)) {
734
+ addStructuredError(rowErrors, attributePath, "INVALID_TYPE", attributeRules?.["typeError"] ||
735
+ getInvalidTypeMessage(attributeValue, attributePath, expectedType));
736
+ addNextError = false;
737
+ }
738
+ break;
739
+ case "boolean":
740
+ if (!isBoolean(attributeValue)) {
741
+ addStructuredError(rowErrors, attributePath, "INVALID_TYPE", attributeRules?.["typeError"] ||
742
+ getInvalidTypeMessage(attributeValue, attributePath, expectedType));
743
+ addNextError = false;
744
+ }
745
+ break;
746
+ case "email":
747
+ if (!isValidEmail(attributeValue)) {
748
+ addStructuredError(rowErrors, attributePath, "INVALID_EMAIL", attributeRules?.["typeError"] ||
749
+ `Invalid email format for attribute ${attributePath}`);
750
+ addNextError = false;
751
+ }
752
+ break;
753
+ case "url":
754
+ if (!isValidUrl(attributeValue)) {
755
+ addStructuredError(rowErrors, attributePath, "INVALID_URL", attributeRules?.["typeError"] ||
756
+ `Invalid URL format for attribute ${attributePath}`);
757
+ addNextError = false;
758
+ }
759
+ break;
760
+ case "enum": {
761
+ const allEnumValues = attributeRules["enumValues"];
762
+ if (!allEnumValues?.includes(attributeValue)) {
763
+ addStructuredError(rowErrors, attributePath, "INVALID_ENUM", attributeRules?.["typeError"] ||
764
+ `Invalid value for attribute ${attributePath}, valid values are ${allEnumValues?.join(", ")}`);
765
+ addNextError = false;
766
+ }
767
+ break;
768
+ }
769
+ case "uuid":
770
+ if (!isUUID(attributeValue)) {
771
+ addStructuredError(rowErrors, attributePath, "INVALID_UUID", attributeRules?.["typeError"] ||
772
+ `Invalid UUID for attribute ${attributePath}`);
773
+ addNextError = false;
774
+ }
775
+ break;
776
+ case "uuidv1":
777
+ if (!isUUIDv1(attributeValue)) {
778
+ addStructuredError(rowErrors, attributePath, "INVALID_UUID_V1", attributeRules?.["typeError"] ||
779
+ `Invalid v1 UUID for attribute ${attributePath}`);
780
+ addNextError = false;
781
+ }
782
+ break;
783
+ case "uuidv3":
784
+ if (!isUUIDv3(attributeValue)) {
785
+ addStructuredError(rowErrors, attributePath, "INVALID_UUID_V3", attributeRules?.["typeError"] ||
786
+ `Invalid v3 UUID for attribute ${attributePath}`);
787
+ addNextError = false;
788
+ }
789
+ break;
790
+ case "uuidv4":
791
+ if (!isUUIDv4(attributeValue)) {
792
+ addStructuredError(rowErrors, attributePath, "INVALID_UUID_V4", attributeRules?.["typeError"] ||
793
+ `Invalid v4 UUID for attribute ${attributePath}`);
794
+ addNextError = false;
795
+ }
796
+ break;
797
+ case "uuidv5":
798
+ if (!isUUIDv5(attributeValue)) {
799
+ addStructuredError(rowErrors, attributePath, "INVALID_UUID_V5", attributeRules?.["typeError"] ||
800
+ `Invalid v5 UUID for attribute ${attributePath}`);
801
+ addNextError = false;
802
+ }
803
+ break;
804
+ case "objectId":
805
+ if (!isObjectId(attributeValue)) {
806
+ addStructuredError(rowErrors, attributePath, "INVALID_OBJECT_ID", attributeRules?.["typeError"] ||
807
+ `Invalid ObjectId for attribute ${attributePath}`);
808
+ addNextError = false;
809
+ }
810
+ break;
811
+ case "array":
812
+ if (!isArray(attributeValue)) {
813
+ addStructuredError(rowErrors, attributePath, "INVALID_TYPE", attributeRules?.["typeError"] ||
814
+ getInvalidTypeMessage(attributeValue, attributePath, expectedType));
815
+ addNextError = false;
816
+ }
817
+ break;
818
+ case "object":
819
+ if (!isObject(attributeValue)) {
820
+ addStructuredError(rowErrors, attributePath, "INVALID_TYPE", attributeRules?.["typeError"] ||
821
+ getInvalidTypeMessage(attributeValue, attributePath, expectedType));
822
+ addNextError = false;
823
+ }
824
+ break;
825
+ default:
826
+ break;
827
+ }
828
+ }
829
+ }
830
+ break;
831
+ // ==================================================
832
+ // MIN LENGTH
833
+ // ==================================================
834
+ case "minLength": {
835
+ const minLength = attributeRules.minLength;
836
+ if (addNextError) {
837
+ if (attributeValue == null && nullAllowed) {
838
+ addNextError = true;
839
+ }
840
+ else if (typeof attributeValue === "string") {
841
+ if (minLength !== undefined &&
842
+ attributeValue.length < minLength) {
843
+ addStructuredError(rowErrors, attributePath, "MIN_LENGTH", attributeRules?.["minLengthError"] ||
844
+ `${attributePath} should be minimum of ${minLength} character`);
845
+ addNextError = false;
846
+ }
847
+ }
848
+ else {
849
+ addStructuredError(rowErrors, attributePath, "INVALID_TYPE", `${attributePath} value should be a string type(minLength specified)`);
850
+ addNextError = false;
851
+ }
852
+ }
853
+ break;
854
+ }
855
+ // ==================================================
856
+ // MAX LENGTH
857
+ // ==================================================
858
+ case "maxLength": {
859
+ const maxLength = attributeRules.maxLength;
860
+ if (addNextError) {
861
+ if (attributeValue == null && nullAllowed) {
862
+ addNextError = true;
863
+ }
864
+ else if (typeof attributeValue === "string") {
865
+ if (maxLength !== undefined &&
866
+ attributeValue.length > maxLength) {
867
+ addStructuredError(rowErrors, attributePath, "MAX_LENGTH", attributeRules?.["maxLengthError"] ||
868
+ `${attributePath} can have maximum of ${maxLength} character`);
869
+ addNextError = false;
870
+ }
871
+ }
872
+ else {
873
+ addStructuredError(rowErrors, attributePath, "INVALID_TYPE", `${attributePath} value should be a string type(maxLength specified)`);
874
+ addNextError = false;
875
+ }
876
+ }
877
+ break;
878
+ }
879
+ // ==================================================
880
+ // PREVENT DECIMAL
881
+ // ==================================================
882
+ case "preventDecimal":
883
+ if (addNextError) {
884
+ if (attributeValue == null && nullAllowed) {
885
+ addNextError = true;
886
+ }
887
+ else if (!isNumber(attributeValue)) {
888
+ addStructuredError(rowErrors, attributePath, "INVALID_TYPE", `Invalid type for attribute ${attributePath}, required number value`);
889
+ addNextError = false;
890
+ }
891
+ else if (+attributeValue % 1 !== 0) {
892
+ addStructuredError(rowErrors, attributePath, "DECIMAL_NOT_ALLOWED", attributeRules?.["preventDecimalError"] ||
893
+ `Decimal value not allowed in attribute ${attributePath}`);
894
+ addNextError = false;
895
+ }
896
+ }
897
+ break;
898
+ // ==================================================
899
+ // MIN
900
+ // ==================================================
901
+ case "min": {
902
+ const min = attributeRules.min;
903
+ if (addNextError) {
904
+ if (attributeValue == null && nullAllowed) {
905
+ addNextError = true;
906
+ }
907
+ else if (!isNumber(attributeValue)) {
908
+ addStructuredError(rowErrors, attributePath, "INVALID_TYPE", `Invalid type for attribute ${attributePath}, required number value`);
909
+ addNextError = false;
910
+ }
911
+ else if (min !== undefined && +attributeValue < min) {
912
+ addStructuredError(rowErrors, attributePath, "MIN_VALUE", attributeRules?.["minError"] ||
913
+ `Minimum value ${min} is allowed in attribute ${attributePath}`);
914
+ addNextError = false;
915
+ }
916
+ }
917
+ break;
918
+ }
919
+ // ==================================================
920
+ // MAX
921
+ // ==================================================
922
+ case "max": {
923
+ const max = attributeRules.max;
924
+ if (addNextError) {
925
+ if (attributeValue == null && nullAllowed) {
926
+ addNextError = true;
927
+ }
928
+ else if (!isNumber(attributeValue)) {
929
+ addStructuredError(rowErrors, attributePath, "INVALID_TYPE", `Invalid type for attribute ${attributePath}, required number value`);
930
+ addNextError = false;
931
+ }
932
+ else if (max !== undefined && +attributeValue > max) {
933
+ addStructuredError(rowErrors, attributePath, "MAX_VALUE", attributeRules?.["maxError"] ||
934
+ `Maximum value ${max} is allowed in attribute ${attributePath}`);
935
+ addNextError = false;
936
+ }
937
+ }
938
+ break;
939
+ }
940
+ // ==================================================
941
+ // RANGE
942
+ // ==================================================
943
+ case "range": {
944
+ const range = attributeRules.range;
945
+ if (addNextError) {
946
+ if (attributeValue == null && nullAllowed) {
947
+ addNextError = true;
948
+ }
949
+ else if (!isNumber(attributeValue)) {
950
+ addStructuredError(rowErrors, attributePath, "INVALID_TYPE", `Invalid type for attribute ${attributePath}, required number value`);
951
+ addNextError = false;
952
+ }
953
+ else if (range !== undefined) {
954
+ const [min, max] = range.split("-");
955
+ if (+attributeValue < +min || +attributeValue > +max) {
956
+ addStructuredError(rowErrors, attributePath, "OUT_OF_RANGE", attributeRules?.["rangeError"] ||
957
+ `Attribute ${attributePath} should have a value between ${min} and ${max}`);
958
+ addNextError = false;
959
+ }
960
+ }
961
+ }
962
+ break;
963
+ }
964
+ // ==================================================
965
+ // NESTED OBJECT
966
+ // ==================================================
967
+ case "objectAttr": {
968
+ const objectAttr = attributeRules.objectAttr;
969
+ if (addNextError &&
970
+ attributeValue !== null &&
971
+ typeof attributeValue === "object" &&
972
+ !Array.isArray(attributeValue) &&
973
+ objectAttr !== undefined) {
974
+ const nestedResult = perfectPayloadStructured(attributeValue, objectAttr, { statusCode: 200, valid: true }, {
975
+ statusCode: 400,
976
+ valid: false,
977
+ message: "One or more attribute values are invalid",
978
+ }, attributePath, options);
979
+ if (nestedResult.valid === true) {
980
+ attributeValue = nestedResult.validatedPayload;
981
+ }
982
+ else {
983
+ rowErrors = [...rowErrors, ...nestedResult.errors];
984
+ }
985
+ }
986
+ break;
987
+ }
988
+ // ==================================================
989
+ // DEPENDENCY
990
+ // ==================================================
991
+ case "dependency": {
992
+ const dependency = attributeRules.dependency;
993
+ if (addNextError && dependency !== undefined) {
994
+ const allDependencyAttr = Object.keys(dependency);
995
+ for (const attr of allDependencyAttr) {
996
+ const dependencyRule = dependency[attr];
997
+ if (typeof dependencyRule?.setDependencyRule === "function") {
998
+ const newRule = dependencyRule.setDependencyRule(attributeValue, data?.[attr]);
999
+ const newData = {
1000
+ [attributeName]: attributeValue,
1001
+ };
1002
+ if (Object.keys(data ?? {}).includes(attr)) {
1003
+ newData[attr] = data?.[attr];
1004
+ }
1005
+ const dependencyResult = perfectPayloadStructured(newData, {
1006
+ [attr]: newRule,
1007
+ }, {
1008
+ statusCode: 200,
1009
+ valid: true,
1010
+ }, {
1011
+ statusCode: 400,
1012
+ valid: false,
1013
+ message: "One or more attribute values are invalid",
1014
+ }, basePath);
1015
+ if (dependencyResult.valid === false) {
1016
+ rowErrors = [...rowErrors, ...dependencyResult.errors];
1017
+ }
1018
+ addNextError = true;
1019
+ }
1020
+ else {
1021
+ throw new Error(`perfect-payload:- function setDependencyRule not found in ${attr} dependency `);
1022
+ }
1023
+ }
1024
+ }
1025
+ break;
1026
+ }
1027
+ // ==================================================
1028
+ // CUSTOM VALIDATOR
1029
+ // ==================================================
1030
+ case "customValidator":
1031
+ if (!skipCustomValidator &&
1032
+ addNextError &&
1033
+ attrExist &&
1034
+ attributeValue !== null) {
1035
+ const validator = attributeRules?.[ruleName];
1036
+ if (typeof validator !== "function") {
1037
+ throw new Error(`perfect-payload:- customValidator must be a function for attribute ${attributePath}`);
1038
+ }
1039
+ const validationResult = validator(attributeValue, data);
1040
+ if (validationResult &&
1041
+ typeof validationResult === "object" &&
1042
+ "then" in validationResult &&
1043
+ typeof validationResult.then === "function") {
1044
+ throw new Error(`perfect-payload:- customValidator must be synchronous for attribute ${attributePath}`);
1045
+ }
1046
+ if (validationResult !== true) {
1047
+ addStructuredError(rowErrors, attributePath, attributeRules?.["customValidatorCode"] ||
1048
+ "CUSTOM_VALIDATION_FAILED", attributeRules?.["customValidatorError"] ||
1049
+ `Custom validation failed for attribute ${attributePath}`);
1050
+ addNextError = false;
1051
+ }
1052
+ }
1053
+ break;
1054
+ default:
1055
+ break;
1056
+ }
1057
+ }
1058
+ if (attrExist &&
1059
+ !rowErrors.some((error) => error.path === attributePath ||
1060
+ error.path.startsWith(`${attributePath}.`) ||
1061
+ error.path.startsWith(`${attributePath}[`))) {
1062
+ validatedPayload[attributeName] = attributeValue;
1063
+ }
1064
+ }
1065
+ // UNKNOWN FIELD HANDLING
1066
+ if (unknownFields === "allow") {
1067
+ for (const attributeName in data) {
1068
+ if (Object.prototype.hasOwnProperty.call(data, attributeName) &&
1069
+ !Object.prototype.hasOwnProperty.call(dataValidationRule, attributeName)) {
1070
+ validatedPayload[attributeName] = data[attributeName];
1071
+ }
1072
+ }
1073
+ }
1074
+ if (unknownFields === "reject") {
1075
+ for (const attributeName in data) {
1076
+ if (Object.prototype.hasOwnProperty.call(data, attributeName) &&
1077
+ !Object.prototype.hasOwnProperty.call(dataValidationRule, attributeName)) {
1078
+ const path = basePath ? `${basePath}.${attributeName}` : attributeName;
1079
+ rowErrors.push({
1080
+ path,
1081
+ code: "UNKNOWN_FIELD",
1082
+ message: `Unknown field ${path} is not allowed`,
1083
+ });
1084
+ }
1085
+ }
1086
+ }
1087
+ // INVALID RESPONSE
1088
+ if (rowErrors.length > 0) {
1089
+ return {
1090
+ ...inValidPayloadResponse,
1091
+ errors: rowErrors,
1092
+ };
1093
+ }
1094
+ // VALID RESPONSE
1095
+ return {
1096
+ ...validPayloadResponse,
1097
+ validatedPayload: validatedPayload,
1098
+ };
1099
+ }
1100
+ function addStructuredError(errors, path, code, message) {
1101
+ errors.push({
1102
+ path,
1103
+ code,
1104
+ message,
1105
+ });
1106
+ }
1107
+ function getAttributePath(attributeName, attributeRules, basePath) {
1108
+ const customPath = attributeRules?.path;
1109
+ if (customPath) {
1110
+ return `${customPath}.${attributeName}`;
1111
+ }
1112
+ if (basePath) {
1113
+ return `${basePath}.${attributeName}`;
1114
+ }
1115
+ return attributeName;
1116
+ }
1117
+ function replaceRootPath(currentPath, currentRoot, replacementRoot) {
1118
+ if (currentPath === currentRoot) {
1119
+ return replacementRoot;
1120
+ }
1121
+ if (currentPath.startsWith(`${currentRoot}.`)) {
1122
+ return `${replacementRoot}${currentPath.slice(currentRoot.length)}`;
1123
+ }
1124
+ if (currentPath.startsWith(`${currentRoot}[`)) {
1125
+ return `${replacementRoot}${currentPath.slice(currentRoot.length)}`;
1126
+ }
1127
+ return replacementRoot;
1128
+ }
1129
+ function getInvalidTypeMessage(value, attributePath, expectedType) {
1130
+ return `Invalid type for attribute ${attributePath}, required ${expectedType} value`;
1131
+ // return `Invalid ${typeof value} value(${value}) found in attribute ${attributePath}, required ${expectedType} value`;
1132
+ }
1133
+ function isNumber(value) {
1134
+ return typeof value == "number";
1135
+ }
1136
+ function isString(value) {
1137
+ return typeof value == "string";
1138
+ }
1139
+ function isBoolean(value) {
1140
+ return typeof value == "boolean";
1141
+ }
1142
+ function isArray(value) {
1143
+ return Array.isArray(value);
1144
+ }
1145
+ function isObject(value) {
1146
+ return typeof value == "object";
1147
+ }
1148
+ function isValidEmail(email) {
1149
+ const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
1150
+ return emailRegex.test(String(email));
1151
+ }
1152
+ function isValidUrl(url) {
1153
+ const urlRegex = /^(https?:\/\/)([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})(:[0-9]+)?(\/[^\s]*)?(\?[^\s]*)?$/;
1154
+ return urlRegex.test(String(url));
1155
+ }
1156
+ function isUUID(uuid) {
1157
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1158
+ return uuidRegex.test(String(uuid));
1159
+ }
1160
+ function isUUIDv1(uuid) {
1161
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-1[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1162
+ return uuidRegex.test(String(uuid));
1163
+ }
1164
+ function isUUIDv3(uuid) {
1165
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1166
+ return uuidRegex.test(String(uuid));
1167
+ }
1168
+ function isUUIDv4(uuid) {
1169
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1170
+ return uuidRegex.test(String(uuid));
1171
+ }
1172
+ function isUUIDv5(uuid) {
1173
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1174
+ return uuidRegex.test(String(uuid));
1175
+ }
1176
+ function isObjectId(id) {
1177
+ return /^[0-9a-fA-F]{24}$/.test(String(id));
1178
+ }
1179
+ function isPassedRegex(reExpression, value) {
1180
+ return reExpression?.test(String(value)) ?? false;
1181
+ }
1182
+ async function runAsyncCustomValidators(data = {}, dataValidationRule = {}, basePath = "") {
1183
+ const errors = [];
1184
+ for (const attributeName in dataValidationRule) {
1185
+ const attributeRules = dataValidationRule?.[attributeName];
1186
+ const attrExist = Object.keys(data ?? {}).includes(attributeName);
1187
+ if (!attrExist) {
1188
+ continue;
1189
+ }
1190
+ const attributeValue = data?.[attributeName];
1191
+ const attributePath = getAttributePath(attributeName, attributeRules, basePath);
1192
+ // customValidator
1193
+ if (Object.prototype.hasOwnProperty.call(attributeRules ?? {}, "customValidator")) {
1194
+ if (typeof attributeRules.customValidator !== "function") {
1195
+ throw new Error(`perfect-payload:- customValidator must be a function for attribute ${attributePath}`);
1196
+ }
1197
+ if (attributeValue !== null) {
1198
+ const validationResponse = await attributeRules.customValidator(attributeValue, data);
1199
+ if (validationResponse !== true) {
1200
+ addStructuredError(errors, attributePath, attributeRules?.customValidatorCode || "CUSTOM_VALIDATION_FAILED", attributeRules?.customValidatorError ||
1201
+ `Custom validation failed for attribute ${attributePath}`);
1202
+ }
1203
+ }
1204
+ }
1205
+ // nested objectAttr
1206
+ if (attributeValue !== null &&
1207
+ typeof attributeValue === "object" &&
1208
+ !Array.isArray(attributeValue) &&
1209
+ attributeRules?.objectAttr) {
1210
+ const nestedErrors = await runAsyncCustomValidators(attributeValue, attributeRules.objectAttr, attributePath);
1211
+ errors.push(...nestedErrors);
1212
+ }
1213
+ // nested elementConstraints
1214
+ if (Array.isArray(attributeValue) && attributeRules?.elementConstraints) {
1215
+ for (let elementIndex = 0; elementIndex < attributeValue.length; elementIndex++) {
1216
+ const element = attributeValue[elementIndex];
1217
+ const elementErrors = await runAsyncCustomValidators({
1218
+ [attributeName]: element,
1219
+ }, {
1220
+ [attributeName]: attributeRules.elementConstraints,
1221
+ });
1222
+ for (const elementError of elementErrors) {
1223
+ const indexedPath = replaceRootPath(elementError.path, attributeName, `${attributePath}[${elementIndex}]`);
1224
+ errors.push({
1225
+ ...elementError,
1226
+ path: indexedPath,
1227
+ message: elementError.code === "CUSTOM_VALIDATION_FAILED" &&
1228
+ elementError.message ===
1229
+ `Custom validation failed for attribute ${elementError.path}`
1230
+ ? `Custom validation failed for attribute ${indexedPath}`
1231
+ : elementError.message,
1232
+ });
1233
+ }
1234
+ }
1235
+ }
1236
+ }
1237
+ return errors;
1238
+ }