nox-validation 1.0.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.
@@ -0,0 +1,867 @@
1
+ const constants = require("./constant");
2
+ const {
3
+ findDisallowedKeys,
4
+ formatLabel,
5
+ getLastChildKey,
6
+ buildNestedStructure,
7
+ getValue,
8
+ setValue,
9
+ isEmpty,
10
+ } = require("./helpers");
11
+
12
+ const typeChecks = {
13
+ date: (val, data) => {
14
+ if (val instanceof Date && !isNaN(val)) return true;
15
+ if (typeof val === "string" && !isNaN(Date.parse(val))) {
16
+ if (data && data?.key && data?.updateValue) {
17
+ data.updateValue(data.key, new Date(val));
18
+ }
19
+ return true;
20
+ }
21
+ return false;
22
+ },
23
+ [constants.types.DATE]: (val, data) => {
24
+ if (val instanceof Date && !isNaN(val)) return true;
25
+ if (typeof val === "string" && !isNaN(Date.parse(val))) {
26
+ if (data && data?.key && data?.updateValue) {
27
+ data.updateValue(data.key, new Date(val));
28
+ }
29
+ return true;
30
+ }
31
+ return false;
32
+ },
33
+ [constants.types.BOOLEAN]: (val, data) => {
34
+ if (typeof val === "boolean") return true;
35
+ if (typeof val === "string") {
36
+ const lowerVal = val.toLowerCase();
37
+ if (lowerVal === "true" || lowerVal === "false") {
38
+ if (data && data?.key && data?.updateValue) {
39
+ data.updateValue(data.key, lowerVal === "true");
40
+ }
41
+ return true;
42
+ }
43
+ }
44
+ return false;
45
+ },
46
+ [constants.types.DATE_TIME]: (val, data) => {
47
+ if (val instanceof Date && !isNaN(val)) return true;
48
+ if (typeof val === "string" && !isNaN(Date.parse(val))) {
49
+ if (data && data?.key && data?.updateValue) {
50
+ data.updateValue(data.key, new Date(val));
51
+ }
52
+ return true;
53
+ }
54
+ return false;
55
+ },
56
+ [constants.types.NUMBER]: (val, data) => {
57
+ if (typeof val === "number" && !isNaN(val)) return true;
58
+ if (typeof val === "string" && !isNaN(parseFloat(val))) {
59
+ if (data && data?.key && data?.updateValue) {
60
+ data.updateValue(data.key, parseFloat(val));
61
+ }
62
+ return true;
63
+ }
64
+ return false;
65
+ },
66
+
67
+ [constants.types.TIME]: (val) =>
68
+ typeof val === "string" &&
69
+ /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/.test(val),
70
+ [constants.types.STRING]: (val) => typeof val === "string",
71
+ [constants.types.OBJECT]: (val) =>
72
+ typeof val === "object" && val !== null && !Array.isArray(val),
73
+ [constants.types.ARRAY]: (val) => Array.isArray(val),
74
+ [constants.types.OBJECT_ID]: (val) =>
75
+ typeof val === "string" && /^[0-9a-fA-F]{24}$/.test(val),
76
+ [constants.types.MIXED]: (val) => (val ? true : false),
77
+ [constants.types.BUFFER]: (val) => val instanceof Buffer,
78
+ [constants.types.ALIAS]: (val) => (val ? true : false),
79
+ };
80
+
81
+ const handleMinMaxValidation = (
82
+ fieldValue,
83
+ ruleValue,
84
+ ruleType,
85
+ field,
86
+ addError,
87
+ currentPath,
88
+ ) => {
89
+ let message = "";
90
+ const fieldLabel = formatLabel(field.display_label);
91
+
92
+ if (ruleType === constants.rulesTypes.MIN) {
93
+ if (
94
+ field.type === constants.types.STRING &&
95
+ typeChecks[constants.types.STRING](fieldValue) &&
96
+ fieldValue?.length < ruleValue[0]
97
+ ) {
98
+ message = constants.rulesMessages.MIN_STRING.replace(
99
+ `{field}`,
100
+ fieldLabel,
101
+ ).replace(`{min}`, ruleValue[0]);
102
+ } else if (
103
+ field.type === constants.types.NUMBER &&
104
+ typeChecks[constants.types.NUMBER](fieldValue) &&
105
+ fieldValue < ruleValue[0]
106
+ ) {
107
+ message = constants.rulesMessages.MIN_NUMBER.replace(
108
+ `{field}`,
109
+ fieldLabel,
110
+ ).replace(`{min}`, ruleValue[0]);
111
+ }
112
+ }
113
+
114
+ if (ruleType === constants.rulesTypes.MAX) {
115
+ if (
116
+ field.type === constants.types.STRING &&
117
+ typeChecks[constants.types.STRING](fieldValue) &&
118
+ fieldValue?.length > ruleValue[0]
119
+ ) {
120
+ message = constants.rulesMessages.MAX_STRING.replace(
121
+ `{field}`,
122
+ fieldLabel,
123
+ ).replace(`{max}`, ruleValue[0]);
124
+ } else if (
125
+ field.type === constants.types.NUMBER &&
126
+ typeChecks[constants.types.NUMBER](fieldValue) &&
127
+ fieldValue > ruleValue[0]
128
+ ) {
129
+ message = constants.rulesMessages.MAX_NUMBER.replace(
130
+ `{field}`,
131
+ fieldLabel,
132
+ ).replace(`{max}`, ruleValue[0]);
133
+ }
134
+ }
135
+
136
+ if (message) {
137
+ addError(
138
+ currentPath,
139
+ { label: fieldLabel, fieldPath: currentPath, description: "", message },
140
+ field,
141
+ );
142
+ }
143
+ };
144
+
145
+ const validateMetaRules = (
146
+ field,
147
+ addError,
148
+ providedValue,
149
+ currentPath,
150
+ updateValue,
151
+ ) => {
152
+ const fieldValue = providedValue;
153
+ const { required = false, nullable = false } = field?.meta ?? {};
154
+
155
+ if (required && isEmpty(fieldValue) && fieldValue !== null) {
156
+ const message = constants.rulesMessages.REQUIRED.replace(
157
+ `{field}`,
158
+ formatLabel(field.display_label),
159
+ );
160
+ addError(
161
+ currentPath,
162
+ {
163
+ label: formatLabel(field.display_label),
164
+ fieldPath: currentPath,
165
+ description: "",
166
+ message,
167
+ },
168
+ field,
169
+ );
170
+ }
171
+
172
+ if (!nullable && fieldValue === null) {
173
+ const message = constants.rulesMessages.NOT_NULLABLE.replace(
174
+ `{field}`,
175
+ formatLabel(field.display_label),
176
+ );
177
+ addError(
178
+ currentPath,
179
+ {
180
+ label: formatLabel(field.display_label),
181
+ fieldPath: currentPath,
182
+ description: "",
183
+ message,
184
+ },
185
+ field,
186
+ );
187
+ }
188
+
189
+ if (
190
+ !isEmpty(fieldValue) &&
191
+ typeChecks[field.type] &&
192
+ !typeChecks[field.type](fieldValue, { key: currentPath, updateValue })
193
+ ) {
194
+ const message = constants.rulesMessages.INVALID_TYPE.replace(
195
+ `{field}`,
196
+ formatLabel(field.display_label),
197
+ ).replace(`{type}`, field?.type);
198
+ addError(
199
+ currentPath,
200
+ {
201
+ label: formatLabel(field.display_label),
202
+ fieldPath: currentPath,
203
+ description: "",
204
+ message,
205
+ },
206
+ field,
207
+ );
208
+ }
209
+ };
210
+
211
+ const handleRegexValidation = (
212
+ fieldValue,
213
+ rule,
214
+ field,
215
+ addError,
216
+ currentPath,
217
+ ) => {
218
+ let message = "";
219
+ const fieldLabel = formatLabel(field.display_label);
220
+ const flags = `${rule.options.case_sensitive ? "" : "i"}${rule.options.multiline ? "m" : ""}${
221
+ rule.options.global ? "g" : ""
222
+ }`;
223
+ const regex = new RegExp(rule.value[0], flags);
224
+
225
+ const isValid = (() => {
226
+ switch (rule.options.type) {
227
+ case constants.regexTypes.MATCH:
228
+ message = constants.rulesMessages.REGEX_MATCH;
229
+ return regex.test(fieldValue);
230
+ case constants.regexTypes.START_WITH:
231
+ message = constants.rulesMessages.REGEX_START_WITH;
232
+ return fieldValue?.startsWith(
233
+ rule.value[0].replace(/^\//, "").replace(/\/$/, ""),
234
+ );
235
+ case constants.regexTypes.ENDS_WITH:
236
+ message = constants.rulesMessages.REGEX_ENDS_WITH;
237
+ return fieldValue?.endsWith(
238
+ rule.value[0].replace(/^\//, "").replace(/\/$/, ""),
239
+ );
240
+ case constants.regexTypes.CONTAINS:
241
+ message = constants.rulesMessages.REGEX_CONTAINS;
242
+ return regex.test(fieldValue);
243
+ case constants.regexTypes.EXACT:
244
+ message = constants.rulesMessages.REGEX_EXACT;
245
+ return (
246
+ fieldValue === rule.value[0].replace(/^\//, "").replace(/\/$/, "")
247
+ );
248
+ case constants.regexTypes.NOT_START_WITH:
249
+ message = constants.rulesMessages.REGEX_NOT_START_WITH;
250
+ return !fieldValue?.startsWith(
251
+ rule.value[0].replace(/^\//, "").replace(/\/$/, ""),
252
+ );
253
+ case constants.regexTypes.NOT_ENDS_WITH:
254
+ message = constants.rulesMessages.REGEX_NOT_ENDS_WITH;
255
+ return !fieldValue?.endsWith(
256
+ rule.value[0].replace(/^\//, "").replace(/\/$/, ""),
257
+ );
258
+ case constants.regexTypes.NOT_CONTAINS:
259
+ message = constants.rulesMessages.REGEX_NOT_CONTAINS;
260
+ return !regex.test(fieldValue);
261
+ default:
262
+ return false;
263
+ }
264
+ })();
265
+
266
+ if (!isValid) {
267
+ message = message
268
+ ?.replace(`{field}`, fieldLabel)
269
+ ?.replace(`{value}`, rule.value[0]);
270
+ addError(
271
+ currentPath,
272
+ { label: fieldLabel, fieldPath: currentPath, description: "", message },
273
+ field,
274
+ );
275
+ }
276
+ };
277
+
278
+ const validateOperatorRule = (
279
+ fieldValue,
280
+ rule,
281
+ field,
282
+ addError,
283
+ currentPath,
284
+ formData,
285
+ ) => {
286
+ const { isFieldCompare = false } = rule?.options ?? {};
287
+ let valid = false;
288
+
289
+ const isNumber = field.type === constants.types.NUMBER;
290
+ const isDate = field.type === constants.types.DATE || field.type === "date";
291
+ const isDateTime = field.type === constants.types.DATE_TIME;
292
+ const isTime = field.type === constants.types.TIME;
293
+ const isArray = field.type === constants.types.ARRAY;
294
+
295
+ const parseTime = (timeStr) => {
296
+ if (!timeStr || typeof timeStr !== "string") return null;
297
+ const [hh, mm, ss] = timeStr.split(":").map(Number);
298
+ return hh * 3600 + mm * 60 + ss;
299
+ };
300
+
301
+ const getComparableValue = (value, forMessage = false) => {
302
+ if (isNumber) return Number(value);
303
+ if (isDate) {
304
+ const date = new Date(value);
305
+ date.setHours(0, 0, 0, 0);
306
+ if (forMessage) {
307
+ return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")}`;
308
+ }
309
+ return Math.floor(date.getTime() / 1000);
310
+ }
311
+ if (isDateTime) {
312
+ const date = new Date(value);
313
+
314
+ if (forMessage) {
315
+ return (
316
+ `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, "0")}-${String(date.getUTCDate()).padStart(2, "0")} ` +
317
+ `${String(date.getUTCHours()).padStart(2, "0")}:${String(date.getUTCMinutes()).padStart(2, "0")}:${String(date.getUTCSeconds()).padStart(2, "0")}`
318
+ );
319
+ }
320
+
321
+ return Math.floor(date.getTime() / 1000);
322
+ }
323
+ if (isTime) {
324
+ if (forMessage) {
325
+ return value;
326
+ }
327
+ return parseTime(value);
328
+ }
329
+ return value;
330
+ };
331
+
332
+ rule.value = rule.value?.map((key) => {
333
+ const value = isFieldCompare
334
+ ? getComparableValue(getValue(formData, key), true)
335
+ : getComparableValue(key, true);
336
+ return value ? value : key;
337
+ });
338
+
339
+ const fieldValueParsed = getComparableValue(fieldValue);
340
+
341
+ const messageValue = Array.isArray(rule.value)
342
+ ? rule.value.join(", ")
343
+ : String(rule.value);
344
+ let message = "";
345
+
346
+ switch (rule.options.operator) {
347
+ case constants.operatorTypes.AND:
348
+ message = constants.rulesMessages.AND?.replace(
349
+ `{field}`,
350
+ formatLabel(field.display_label),
351
+ )?.replace("{value}", messageValue);
352
+ valid = isArray
353
+ ? rule.value.every((val) => fieldValue.includes(val))
354
+ : rule.value.every((val) => val === fieldValueParsed);
355
+ break;
356
+ case constants.operatorTypes.OR:
357
+ message = constants.rulesMessages.OR?.replace(
358
+ `{field}`,
359
+ formatLabel(field.display_label),
360
+ )?.replace("{value}", messageValue);
361
+ valid = isArray
362
+ ? rule.value.some((val) => fieldValue.includes(val))
363
+ : rule.value.some((val) => val === fieldValueParsed);
364
+ break;
365
+ case constants.operatorTypes.EQUAL:
366
+ message = constants.rulesMessages.EQUAL?.replace(
367
+ `{field}`,
368
+ formatLabel(field.display_label),
369
+ )?.replace("{value}", rule.value[0]);
370
+ valid = fieldValueParsed === getComparableValue(rule.value[0]);
371
+ break;
372
+ case constants.operatorTypes.NOT_EQUAL:
373
+ message = constants.rulesMessages.NOT_EQUAL?.replace(
374
+ `{field}`,
375
+ formatLabel(field.display_label),
376
+ )?.replace("{value}", rule.value[0]);
377
+ valid = fieldValueParsed !== getComparableValue(rule.value[0]);
378
+ break;
379
+ case constants.operatorTypes.LESS_THAN:
380
+ message = constants.rulesMessages.LESS_THAN?.replace(
381
+ `{field}`,
382
+ formatLabel(field.display_label),
383
+ )?.replace("{value}", rule.value[0]);
384
+ valid = fieldValueParsed < getComparableValue(rule.value[0]);
385
+ break;
386
+ case constants.operatorTypes.LESS_THAN_EQUAL:
387
+ message = constants.rulesMessages.LESS_THAN_EQUAL?.replace(
388
+ `{field}`,
389
+ formatLabel(field.display_label),
390
+ )?.replace("{value}", rule.value[0]);
391
+ valid = fieldValueParsed <= getComparableValue(rule.value[0]);
392
+ break;
393
+ case constants.operatorTypes.GREATER_THAN:
394
+ message = constants.rulesMessages.GREATER_THAN?.replace(
395
+ `{field}`,
396
+ formatLabel(field.display_label),
397
+ )?.replace("{value}", rule.value[0]);
398
+ valid = fieldValueParsed > getComparableValue(rule.value[0]);
399
+ break;
400
+ case constants.operatorTypes.GREATER_THAN_EQUAL:
401
+ message = constants.rulesMessages.GREATER_THAN_EQUAL?.replace(
402
+ `{field}`,
403
+ formatLabel(field.display_label),
404
+ )?.replace("{value}", rule.value[0]);
405
+ valid = fieldValueParsed >= getComparableValue(rule.value[0]);
406
+ break;
407
+ case constants.operatorTypes.IN:
408
+ message = constants.rulesMessages.IN?.replace(
409
+ `{field}`,
410
+ formatLabel(field.display_label),
411
+ )?.replace("{value}", messageValue);
412
+ valid = rule.value.includes(fieldValue);
413
+ break;
414
+ case constants.operatorTypes.NOT_IN:
415
+ message = constants.rulesMessages.NOT_IN?.replace(
416
+ `{field}`,
417
+ formatLabel(field.display_label),
418
+ )?.replace("{value}", messageValue);
419
+ valid = !rule.value.includes(fieldValue);
420
+ break;
421
+ case constants.operatorTypes.EXISTS:
422
+ message = constants.rulesMessages.EXISTS?.replace(
423
+ `{field}`,
424
+ formatLabel(field.display_label),
425
+ );
426
+ valid = rule.value[0]
427
+ ? fieldValue !== undefined
428
+ : fieldValue === undefined;
429
+ break;
430
+ case constants.operatorTypes.TYPE:
431
+ message = constants.rulesMessages.TYPE?.replace(
432
+ `{field}`,
433
+ formatLabel(field.display_label),
434
+ )?.replace("{value}", rule.value[0]);
435
+ valid = typeChecks[rule.value[0]](fieldValue);
436
+ break;
437
+ case constants.operatorTypes.MOD:
438
+ message = constants.rulesMessages.MOD?.replace(
439
+ `{field}`,
440
+ formatLabel(field.display_label),
441
+ )
442
+ ?.replace("{value[1]}", rule.value[1])
443
+ ?.replace("{value[0]}", rule.value[0]);
444
+ valid = isNumber && fieldValue % rule.value[0] === rule.value[1];
445
+ break;
446
+ case constants.operatorTypes.ALL:
447
+ message = constants.rulesMessages.ALL?.replace(
448
+ `{field}`,
449
+ formatLabel(field.display_label),
450
+ )?.replace("{value}", messageValue);
451
+ valid = isArray && rule.value.every((val) => fieldValue.includes(val));
452
+ break;
453
+ case constants.operatorTypes.SIZE:
454
+ message = constants.rulesMessages.SIZE?.replace(
455
+ `{field}`,
456
+ formatLabel(field.display_label),
457
+ )?.replace("{value}", rule.value[0]);
458
+ valid = isArray && fieldValue.length === rule.value[0];
459
+ break;
460
+ default:
461
+ console.log(`Unknown Operator : ${rule.options.operator}`);
462
+ break;
463
+ }
464
+ if (!valid) {
465
+ addError(
466
+ currentPath,
467
+ {
468
+ label: formatLabel(field.display_label),
469
+ fieldPath: currentPath,
470
+ description: "",
471
+ message,
472
+ },
473
+ field,
474
+ );
475
+ }
476
+ };
477
+
478
+ const generateErrorMessage = (ruleKey, fieldLabel, additionalValues = {}) => {
479
+ let message = constants.rulesMessages[ruleKey]?.replace(
480
+ `{field}`,
481
+ fieldLabel,
482
+ );
483
+ Object.entries(additionalValues).forEach(([key, value]) => {
484
+ message = message.replace(`{${key}}`, value);
485
+ });
486
+ return message;
487
+ };
488
+
489
+ const handleRule = (rule, field, value, addError, currentPath, formData) => {
490
+ const ruleValue = rule?.value;
491
+ const messageValue = Array.isArray(ruleValue)
492
+ ? ruleValue.join(", ")
493
+ : String(ruleValue);
494
+ const fieldLabel = formatLabel(field.display_label);
495
+
496
+ const addValidationError = (ruleKey, extraValues = {}) =>
497
+ addError(
498
+ currentPath,
499
+ {
500
+ label: fieldLabel,
501
+ fieldPath: currentPath,
502
+ description: "",
503
+ message: generateErrorMessage(ruleKey, fieldLabel, extraValues),
504
+ },
505
+ field,
506
+ );
507
+
508
+ switch (rule.case) {
509
+ case constants.rulesTypes.EMPTY:
510
+ if (value) addValidationError("EMPTY");
511
+ break;
512
+ case constants.rulesTypes.NOT_EMPTY:
513
+ if (!value || value.length === 0) addValidationError("NOT_EMPTY");
514
+ break;
515
+ case constants.rulesTypes.ONE_OF:
516
+ if (!ruleValue?.includes(value))
517
+ addValidationError("ONE_OF", { value: messageValue });
518
+ break;
519
+ case constants.rulesTypes.NOT_ONE_OF:
520
+ if (ruleValue?.includes(value))
521
+ addValidationError("NOT_ONE_OF", { value: messageValue });
522
+ break;
523
+ case constants.rulesTypes.NOT_ALLOWED:
524
+ if (ruleValue?.includes(value)) addValidationError("NOT_ALLOWED");
525
+ break;
526
+ case constants.rulesTypes.MIN:
527
+ case constants.rulesTypes.MAX:
528
+ handleMinMaxValidation(
529
+ value,
530
+ ruleValue,
531
+ rule.case,
532
+ field,
533
+ addError,
534
+ currentPath,
535
+ );
536
+ break;
537
+ case constants.rulesTypes.REGEX:
538
+ handleRegexValidation(value, rule, field, addError, currentPath);
539
+ break;
540
+ case constants.rulesTypes.OPERATOR:
541
+ validateOperatorRule(value, rule, field, addError, currentPath, formData);
542
+ break;
543
+ default:
544
+ console.warn(`Unknown Rule: ${rule.case}`, fieldLabel);
545
+ }
546
+ };
547
+
548
+ const validateField = (
549
+ field,
550
+ value,
551
+ fieldPath = "",
552
+ addError,
553
+ formData,
554
+ updateValue,
555
+ ) => {
556
+ const currentPath = fieldPath
557
+ ? `${fieldPath}.${field.key.split(".").pop()}`
558
+ : field.key;
559
+ const fieldLabel = formatLabel(field.display_label);
560
+
561
+ validateMetaRules(field, addError, value, currentPath, updateValue);
562
+
563
+ if (
564
+ field.type === constants.types.OBJECT &&
565
+ field.children &&
566
+ value &&
567
+ typeChecks[constants.types.OBJECT](value)
568
+ ) {
569
+ field.children.forEach((child) =>
570
+ validateField(
571
+ child,
572
+ value[child.key.split(".").pop()],
573
+ currentPath,
574
+ addError,
575
+ updateValue,
576
+ ),
577
+ );
578
+ } else if (field.type === constants.types.ARRAY && Array.isArray(value)) {
579
+ const itemType = field?.array_type || field?.schema_definition?.type;
580
+ if (itemType) {
581
+ value.forEach((item, index) => {
582
+ if (!typeChecks[itemType](item)) {
583
+ addError(`${currentPath}[${index}]`, {
584
+ label: fieldLabel,
585
+ fieldPath: `${currentPath}[${index}]`,
586
+ description: "",
587
+ message: generateErrorMessage("INVALID_TYPE", fieldLabel, {
588
+ type: itemType,
589
+ }),
590
+ });
591
+ }
592
+ });
593
+ }
594
+ if (field.children?.length > 0) {
595
+ value.forEach((item, index) => {
596
+ field.children.forEach((child) =>
597
+ validateField(
598
+ child,
599
+ item[child.key.split(".").pop()],
600
+ `${currentPath}[${index}]`,
601
+ addError,
602
+ updateValue,
603
+ ),
604
+ );
605
+ });
606
+ }
607
+ } else {
608
+ if (!applyValidations(field, value, addError, currentPath, formData)) {
609
+ addError(
610
+ currentPath,
611
+ {
612
+ label: fieldLabel,
613
+ fieldPath: currentPath,
614
+ description: "",
615
+ message: `Invalid value for ${currentPath}`,
616
+ },
617
+ field,
618
+ );
619
+ }
620
+ }
621
+ };
622
+
623
+ const applyValidations = (field, value, addError, currentPath, formData) => {
624
+ if (!field.validations || value === null || value === undefined) return true;
625
+ return !field.validations.some(
626
+ (rule) =>
627
+ handleRule(rule, field, value, addError, currentPath, formData) === false,
628
+ );
629
+ };
630
+
631
+ const schema = {
632
+ formData: { type: constants.types.OBJECT, array_type: null },
633
+ formId: { type: constants.types.OBJECT_ID, array_type: null },
634
+ isSeparatedFields: { type: constants.types.BOOLEAN, array_type: null },
635
+ relations: {
636
+ type: constants.types.ARRAY,
637
+ array_type: constants.types.OBJECT,
638
+ },
639
+ fields: { type: constants.types.ARRAY, array_type: constants.types.OBJECT },
640
+ relationalFields: { type: constants.types.OBJECT, array_type: null },
641
+ abortEarly: { type: constants.types.BOOLEAN, array_type: null },
642
+ byPassKeys: {
643
+ type: constants.types.ARRAY,
644
+ array_type: constants.types.STRING,
645
+ },
646
+ apiVersion: { type: constants.types.STRING, array_type: null },
647
+ };
648
+
649
+ const validate = (data) => {
650
+ const {
651
+ formData,
652
+ isSeparatedFields,
653
+ fields,
654
+ relationalFields,
655
+ relations,
656
+ formId,
657
+ abortEarly,
658
+ byPassKeys,
659
+ apiVersion,
660
+ } = data;
661
+ let result = { status: true, errors: {}, data: formData };
662
+
663
+ const updateValue = (key, value) => {
664
+ setValue(result.data, key, value);
665
+ };
666
+
667
+ const addError = (fieldPath, obj, field) => {
668
+ if (byPassKeys.some((key) => fieldPath.startsWith(key))) return;
669
+ if (!result.errors[fieldPath] && !field?.meta?.hidden) {
670
+ result.errors[fieldPath] = obj;
671
+ result.status = false;
672
+ }
673
+ if (abortEarly && !result.status) return result;
674
+ };
675
+
676
+ // Validate Parameters Starts
677
+
678
+ // Validate Data
679
+ const defaultField = { meta: { hidden: false } };
680
+ if (!data) {
681
+ const message = constants.rulesMessages.REQUIRED.replace(
682
+ `{field}`,
683
+ formatLabel("data"),
684
+ );
685
+ addError(
686
+ "data",
687
+ {
688
+ label: formatLabel("data"),
689
+ fieldPath: "data",
690
+ description: "",
691
+ message,
692
+ },
693
+ defaultField,
694
+ );
695
+ return result;
696
+ }
697
+
698
+ // validate data type
699
+ if (!typeChecks[constants.types.OBJECT](data)) {
700
+ const message = constants.rulesMessages.INVALID_TYPE.replace(
701
+ `{field}`,
702
+ formatLabel("data"),
703
+ ).replace(`{type}`, constants.types.OBJECT);
704
+ addError(
705
+ "data",
706
+ {
707
+ label: formatLabel("data"),
708
+ fieldPath: "data",
709
+ description: "",
710
+ message,
711
+ },
712
+ defaultField,
713
+ );
714
+ return result;
715
+ }
716
+
717
+ // Validate Parameters
718
+ Object.keys(schema).forEach((key) => {
719
+ const expectedType = schema[key].type;
720
+ const fieldValue = data[key];
721
+ // Skip empty values
722
+ if (fieldValue == null || fieldValue == undefined) {
723
+ const message = constants.rulesMessages.REQUIRED.replace(
724
+ `{field}`,
725
+ formatLabel(key),
726
+ );
727
+ addError(
728
+ key,
729
+ {
730
+ label: formatLabel(key),
731
+ fieldPath: key,
732
+ description: "",
733
+ message,
734
+ },
735
+ defaultField,
736
+ );
737
+ return;
738
+ }
739
+
740
+ // Validate field type
741
+ if (!typeChecks[expectedType] || !typeChecks[expectedType](fieldValue)) {
742
+ const message = constants.rulesMessages.INVALID_TYPE.replace(
743
+ `{field}`,
744
+ key,
745
+ ).replace(`{type}`, expectedType);
746
+ addError(
747
+ key,
748
+ {
749
+ label: formatLabel(key),
750
+ fieldPath: key,
751
+ description: "",
752
+ message,
753
+ },
754
+ defaultField,
755
+ );
756
+ return;
757
+ }
758
+
759
+ // Check array items if the field is an array
760
+ if (expectedType === constants.types.ARRAY && typeChecks[expectedType]) {
761
+ fieldValue.forEach((item, index) => {
762
+ // Determine the expected type of array items
763
+ const arrayItemType = schema[key].array_type; // Define item types like "relations[]": "object"
764
+
765
+ if (
766
+ arrayItemType &&
767
+ typeChecks[arrayItemType] &&
768
+ !typeChecks[arrayItemType](item)
769
+ ) {
770
+ const message = constants.rulesMessages.INVALID_TYPE.replace(
771
+ `{field}`,
772
+ `${key}[${index}]`,
773
+ ).replace(`{type}`, arrayItemType);
774
+ addError(
775
+ `${key}[${index}]`,
776
+ {
777
+ label: formatLabel(`${key}[${index}]`),
778
+ fieldPath: `${key}[${index}]`,
779
+ description: "",
780
+ message,
781
+ },
782
+ defaultField,
783
+ );
784
+ }
785
+ });
786
+ }
787
+ });
788
+
789
+ // Validate API Version
790
+ if (!constants.API_VERSIONS.includes(apiVersion)) {
791
+ const message = constants.rulesMessages.IN.replace(
792
+ `{field}`,
793
+ formatLabel("apiVersion"),
794
+ ).replace(`{value}`, constants.API_VERSIONS.join(", "));
795
+ addError(
796
+ "apiVersion",
797
+ {
798
+ label: formatLabel("apiVersion"),
799
+ fieldPath: "apiVersion",
800
+ description: "",
801
+ message,
802
+ },
803
+ defaultField,
804
+ );
805
+ }
806
+
807
+ if (!result.status) {
808
+ return result;
809
+ }
810
+
811
+ // Validate Parameters END
812
+
813
+ // Get Relational Field
814
+ let schemaFields = fields;
815
+ let allFields = isSeparatedFields ? [] : fields;
816
+
817
+ if (!isSeparatedFields) {
818
+ schemaFields = fields.filter(
819
+ (field) => field?.schema_id?.toString() === formId?.toString(),
820
+ );
821
+ }
822
+
823
+ const fieldOptions =
824
+ buildNestedStructure(
825
+ schemaFields || [],
826
+ allFields,
827
+ relations,
828
+ formData,
829
+ relationalFields,
830
+ isSeparatedFields,
831
+ apiVersion,
832
+ ) || [];
833
+
834
+ findDisallowedKeys(formData, fieldOptions).forEach((fieldPath) => {
835
+ if (abortEarly && !result.status) return result;
836
+ const fieldKey = getLastChildKey(fieldPath);
837
+ if (fieldKey && !result.errors[fieldPath]) {
838
+ addError(fieldPath, {
839
+ label: formatLabel(fieldKey),
840
+ fieldPath,
841
+ description: "",
842
+ message: generateErrorMessage(
843
+ "NOT_ALLOWED_FIELD",
844
+ formatLabel(fieldKey),
845
+ {
846
+ field: formatLabel(fieldKey),
847
+ },
848
+ ),
849
+ });
850
+ }
851
+ });
852
+
853
+ fieldOptions.forEach((field) => {
854
+ if (abortEarly && !result.status) return result;
855
+ validateField(
856
+ field,
857
+ formData[field.value],
858
+ "",
859
+ addError,
860
+ formData,
861
+ updateValue,
862
+ );
863
+ });
864
+ return result;
865
+ };
866
+
867
+ module.exports = { validate, validateField ,typeChecks};