kanun 0.1.7 → 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.
package/dist/index.cjs CHANGED
@@ -26,11 +26,50 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  }) : target, mod));
27
27
 
28
28
  //#endregion
29
+ let node_async_hooks = require("node:async_hooks");
29
30
  let dayjs_plugin_customParseFormat_js = require("dayjs/plugin/customParseFormat.js");
30
31
  dayjs_plugin_customParseFormat_js = __toESM(dayjs_plugin_customParseFormat_js);
31
32
  let dayjs = require("dayjs");
32
33
  dayjs = __toESM(dayjs);
33
34
 
35
+ //#region src/Context.ts
36
+ const validatorContextStorage = new node_async_hooks.AsyncLocalStorage();
37
+ /**
38
+ * Get the current validator context.
39
+ *
40
+ * @returns
41
+ */
42
+ function getValidatorContext() {
43
+ return validatorContextStorage.getStore() ?? {};
44
+ }
45
+ /**
46
+ * Use a context for the current validator instance and all of its children.
47
+ *
48
+ * @param context
49
+ * @returns
50
+ */
51
+ function useValidatorContext(context = {}) {
52
+ const nextContext = {
53
+ ...getValidatorContext(),
54
+ ...context
55
+ };
56
+ validatorContextStorage.enterWith(nextContext);
57
+ return nextContext;
58
+ }
59
+ /**
60
+ * Run a function with a given validator context.
61
+ *
62
+ * @param context The context to use during the execution of the callback
63
+ * @param callback The function to execute with the given context
64
+ */
65
+ function runWithValidatorContext(context, callback) {
66
+ return validatorContextStorage.run({
67
+ ...getValidatorContext(),
68
+ ...context
69
+ }, callback);
70
+ }
71
+
72
+ //#endregion
34
73
  //#region src/Rules/baseRule.ts
35
74
  var BaseRule = class {};
36
75
 
@@ -266,6 +305,61 @@ var locales_default = {
266
305
  ar: ar_default
267
306
  };
268
307
 
308
+ //#endregion
309
+ //#region src/utilities/helpers.ts
310
+ /**
311
+ * Pluralizes a word based on the count.
312
+ *
313
+ * @param word
314
+ * @param count
315
+ * @returns
316
+ */
317
+ const plural = (word, count) => {
318
+ return count === 1 ? word : `${word}s`;
319
+ };
320
+ /**
321
+ * Determine if a value is an object. Arrays and null are not considered objects.
322
+ *
323
+ * @param value
324
+ * @returns
325
+ */
326
+ function isObject(value) {
327
+ return typeof value === "object" && value !== null && !Array.isArray(value);
328
+ }
329
+ /**
330
+ * Deeply merge two objects.
331
+ * The source object will overwrite the target object when there is a conflict.
332
+ * Arrays and non-object values will be overwritten, not merged.
333
+ *
334
+ * @param target
335
+ * @param source
336
+ * @returns
337
+ */
338
+ function mergeDeep(target, source) {
339
+ const output = Object.assign({}, target);
340
+ if (!isObject(target) || !isObject(source)) return output;
341
+ for (const key in source) if (isObject(source[key])) if (!target[key]) Object.assign(output, { [key]: source[key] });
342
+ else output[key] = mergeDeep(target[key], source[key]);
343
+ else Object.assign(output, { [key]: source[key] });
344
+ return output;
345
+ }
346
+ /**
347
+ * Deeply find a value in an object using a dot-notated path.
348
+ * Returns undefined if the path does not exist.
349
+ *
350
+ * @param obj
351
+ * @param path
352
+ * @returns
353
+ */
354
+ function deepFindMessage(obj, path) {
355
+ const paths = path.split(".");
356
+ for (const segment of paths) {
357
+ if (typeof obj[segment] === "undefined") return;
358
+ obj = obj[segment];
359
+ }
360
+ return obj;
361
+ }
362
+
269
363
  //#endregion
270
364
  //#region src/Lang.ts
271
365
  var Lang = class {
@@ -286,6 +380,10 @@ var Lang = class {
286
380
  */
287
381
  static translations = {};
288
382
  /**
383
+ * Store translations contributed by plugins.
384
+ */
385
+ static translationExtensions = {};
386
+ /**
289
387
  * Stores the messages that are already loaded
290
388
  */
291
389
  static messages = {};
@@ -316,6 +414,16 @@ var Lang = class {
316
414
  static setTranslationObject(translations) {
317
415
  this.translations = translations;
318
416
  this.existingLangs = Array.from(new Set([...this.existingLangs, ...Object.keys(translations)]));
417
+ this.resetLoadedMessages();
418
+ this.setDefaultLang(this.defaultLang);
419
+ }
420
+ /**
421
+ * Merge additional translations into the global catalog.
422
+ */
423
+ static extendTranslationObject(translations) {
424
+ this.translationExtensions = mergeDeep(this.translationExtensions, translations);
425
+ this.existingLangs = Array.from(new Set([...this.existingLangs, ...Object.keys(translations)]));
426
+ this.resetLoadedMessages();
319
427
  this.setDefaultLang(this.defaultLang);
320
428
  }
321
429
  /**
@@ -336,6 +444,7 @@ var Lang = class {
336
444
  this.fallbackLang = lang;
337
445
  this.fallbackMessages = locales_default.en;
338
446
  if (Object.prototype.hasOwnProperty.call(locales_default, lang)) this.fallbackMessages = mergeDeep(this.fallbackMessages, locales_default[lang]);
447
+ if (Object.prototype.hasOwnProperty.call(this.translationExtensions, lang)) this.fallbackMessages = mergeDeep(this.fallbackMessages, this.translationExtensions[lang]);
339
448
  if (Object.prototype.hasOwnProperty.call(this.translations, lang)) this.fallbackMessages = mergeDeep(this.fallbackMessages, this.translations[lang]);
340
449
  }
341
450
  /**
@@ -356,8 +465,13 @@ var Lang = class {
356
465
  if (this.messages[lang]) return;
357
466
  if (Object.prototype.hasOwnProperty.call(locales_default, lang)) this.messages[lang] = mergeDeep(this.fallbackMessages, locales_default[lang]);
358
467
  else this.messages[lang] = mergeDeep({}, this.fallbackMessages);
468
+ if (Object.prototype.hasOwnProperty.call(this.translationExtensions, lang)) this.messages[lang] = mergeDeep(this.messages[lang], this.translationExtensions[lang]);
359
469
  if (Object.prototype.hasOwnProperty.call(this.translations, lang)) this.messages[lang] = mergeDeep(this.messages[lang], this.translations[lang]);
360
470
  }
471
+ static resetLoadedMessages() {
472
+ this.messages = {};
473
+ this.fallbackMessages = locales_default.en;
474
+ }
361
475
  };
362
476
 
363
477
  //#endregion
@@ -405,7 +519,7 @@ var IRuleContract = class {
405
519
  * Get the translated error message based on the specified path
406
520
  */
407
521
  trans(path, params = {}) {
408
- let message = deepFind(Lang.get(this.lang), path) || "";
522
+ let message = deepFindMessage(Lang.get(this.lang), path) || "";
409
523
  if (!message) return message;
410
524
  for (const key in params) message = message.replace(`:${key}`, params[key]);
411
525
  return message;
@@ -413,1473 +527,1506 @@ var IRuleContract = class {
413
527
  };
414
528
 
415
529
  //#endregion
416
- //#region src/utilities/general.ts
417
- const implicitRues = [
418
- "accepted",
419
- "accepted_if",
420
- "declined",
421
- "declined_if",
422
- "filled",
423
- "present",
424
- "required",
425
- "required_if",
426
- "required_unless",
427
- "required_with",
428
- "required_with_all",
429
- "required_without",
430
- "required_without_all"
431
- ];
432
- /**
433
- * Get the size of a value based on its type
434
- */
435
- function getSize(value, hasNumericRule = false) {
436
- if (typeof value === "number" || isNaN(value) === false && hasNumericRule === true) return Number(value);
437
- else if (typeof value === "string" || Array.isArray(value)) return value.length;
438
- else if (typeof value === "object" && value !== null) return Object.keys(value).length;
439
- return -1;
440
- }
441
- /**
442
- * Check if two values are of the same type
443
- */
444
- function sameType(value, otherValue) {
445
- return (Array.isArray(value) ? "array" : value === null ? null : typeof value) === (Array.isArray(otherValue) ? "array" : otherValue === null ? null : typeof otherValue);
446
- }
447
- /**
448
- * Check if Value is an Ineteger
449
- */
450
- function isInteger(value) {
451
- return value !== null && isNaN(value) === false && value % 1 === 0;
452
- }
453
- /**
454
- * Check if the value can be considered as rule
455
- */
456
- function isRule(value) {
457
- return typeof value === "string" || typeof value === "function" || value instanceof IRuleContract || value instanceof BaseRule;
458
- }
459
- /**
460
- * Check if the array contain any potentiel valid rule
461
- */
462
- function isArrayOfRules(values) {
463
- for (let i = 0; i < values.length; i++) if (isRule(values[i])) return true;
464
- return false;
465
- }
466
- /**
467
- * Check if the rule is related to size
468
- */
469
- function isSizeRule(rule) {
470
- return [
471
- "size",
472
- "between",
473
- "min",
474
- "max",
475
- "gt",
476
- "lt",
477
- "gte",
478
- "lte"
479
- ].indexOf(rule) !== -1;
480
- }
481
- /**
482
- * Check if rule implies that the field is required
483
- */
484
- function isImplicitRule(rule) {
485
- if (rule instanceof IRuleContract && rule.__isImplicitRule === true) return true;
486
- if (typeof rule === "string") return implicitRues.indexOf(rule) !== -1;
487
- return false;
488
- }
489
- /**
490
- * Add a new implicit rule
491
- */
492
- function addImplicitRule(rule) {
493
- implicitRues.push(rule);
494
- }
495
- /**
496
- * Returns the numeric rules
497
- */
498
- function getNumericRules() {
499
- return ["numeric", "integer"];
500
- }
501
- /**
502
- * Check if the rule is numeric
503
- */
504
- function isNumericRule(rule) {
505
- return getNumericRules().indexOf(rule) !== -1;
506
- }
507
- /**
508
- * Determine if a comparison passes between the given values.
509
- */
510
- function compare(first, second, operator, strict = false) {
511
- switch (operator) {
512
- case "<": return first < second;
513
- case ">": return first > second;
514
- case "<=": return first <= second;
515
- case ">=": return first >= second;
516
- case "=":
517
- if (strict === true) return first === second;
518
- return first == second;
519
- default: throw "Invalid operator parameter";
520
- }
521
- }
522
- /**
523
- * Convert the given values to boolean if they are string "true" / "false".
524
- */
525
- function convertValuesToBoolean(values) {
526
- return values.map((value) => {
527
- if (value === "true") return true;
528
- else if (value === "false") return false;
529
- return value;
530
- });
531
- }
532
- /**
533
- * Convert the given values to numbers if they are numbers in a string "1", "2"
534
- */
535
- function convertValuesToNumber(values) {
536
- return values.map((value) => {
537
- if (!isNaN(Number(value))) return Number(value);
538
- return value;
539
- });
540
- }
530
+ //#region src/utilities/date.ts
541
531
  /**
542
- * Convert the given values to null if they have null values in a string "null", "NULL"
532
+ * Convert value to date instance
543
533
  */
544
- function convertValuesToNull(values) {
545
- return values.map((value) => {
546
- if (value.toLowerCase() === "null") return null;
547
- return value;
548
- });
534
+ function toDate(value) {
535
+ const date = Date.parse(value);
536
+ return !isNaN(date) ? new Date(date) : null;
549
537
  }
550
538
 
551
539
  //#endregion
552
- //#region src/utilities/object.ts
553
- /**
554
- * Get value at path of object. If the resolved value is undifined, the returned result will be undefined
555
- *
556
- * @param obj
557
- * @param path
558
- * @returns
559
- */
560
- function deepFind(obj, path) {
561
- const paths = path.split(".");
562
- for (let i = 0; i < paths.length; i++) {
563
- if (typeof obj[paths[i]] === "undefined") return;
564
- obj = obj[paths[i]];
565
- }
566
- return obj;
567
- }
568
- /**
569
- * Set value at path of object.
570
- *
571
- * @param target
572
- * @param path
573
- * @param value
574
- */
575
- function deepSet(target, path, value) {
576
- const paths = typeof path === "string" ? path.split(".") : path;
577
- const segment = paths.shift();
578
- if (segment === "*") {
579
- target = Array.isArray(target) ? target : [];
580
- if (paths.length > 0) target.forEach((inner) => deepSet(inner, [...paths], value));
581
- else for (let i = 0; i < target.length; i++) target[i] = value;
582
- } else if (paths.length > 0 && typeof segment === "string") {
583
- if (typeof target[segment] !== "object" || target[segment] === null) target[segment] = {};
584
- deepSet(target[segment], paths, value);
585
- } else {
586
- if (typeof target !== "object" || target === null) target = {};
587
- target[segment] = value;
588
- }
589
- }
590
- /**
591
- * Flatten a multi-dimensional associative array with dots.
592
- *
593
- * @param obj
594
- * @param ignoreRulesArray
595
- * @param withBaseObjectType
596
- * @returns
597
- */
598
- function dotify(obj, ignoreRulesArray = false, withBaseObjectType = false) {
599
- const res = {};
600
- (function recurse(obj, current = "") {
601
- for (const key in obj) {
602
- const value = obj[key];
603
- const newKey = current ? `${current}.${key}` : key;
604
- if (value && typeof value === "object" && !isRule(value) && !(value instanceof Date)) if (ignoreRulesArray === true && Array.isArray(value) && isArrayOfRules(value)) res[newKey] = value;
605
- else {
606
- if (withBaseObjectType) res[newKey] = Array.isArray(value) ? "array" : "object";
607
- recurse(value, newKey);
608
- }
609
- else res[newKey] = value;
610
- }
611
- })(obj);
612
- return res;
613
- }
614
- /**
615
- * Check if the value is an object
616
- *
617
- * @param value
618
- * @returns
619
- */
620
- function isObject(value) {
621
- return value && typeof value === "object" && !Array.isArray(value);
622
- }
623
- /**
624
- * Deeply merge nested objects
625
- *
626
- * @param target
627
- * @param source
628
- * @returns
629
- */
630
- function mergeDeep(target, source) {
631
- const output = Object.assign({}, target);
632
- if (!isObject(target) || !isObject(source)) return output;
633
- for (const key in source) if (isObject(source[key])) if (!target[key]) Object.assign(output, { [key]: source[key] });
634
- else output[key] = mergeDeep(target[key], source[key]);
635
- else Object.assign(output, { [key]: source[key] });
636
- return output;
637
- }
638
- /**
639
- * Check if objects are deep equal
640
- *
641
- * @param firstParam
642
- * @param secondParam
643
- * @returns
644
- */
645
- function deepEqual(firstParam, secondParam) {
646
- const first = dotify(firstParam, false, true);
647
- const second = dotify(secondParam, false, true);
648
- if (Object.keys(first).length !== Object.keys(second).length) return false;
649
- return Object.entries(first).every(([key, value]) => second[key] === value);
650
- }
540
+ //#region src/validators/replaceAttributes.ts
541
+ const replaceAttributes = {
542
+ replaceAcceptedIf: function({ data, message, parameters, getDisplayableAttribute }) {
543
+ const [other] = parameters;
544
+ const result = deepFind(data, other);
545
+ const values = {
546
+ ":other": getDisplayableAttribute(other),
547
+ ":value": result
548
+ };
549
+ return message.replace(/:other|:value/gi, (matched) => values[matched]);
550
+ },
551
+ replaceAfter: function(payload) {
552
+ return this.replaceBefore(payload);
553
+ },
554
+ replaceAfterOrEqual: function(payload) {
555
+ return this.replaceBefore(payload);
556
+ },
557
+ replaceBefore: function({ message, parameters, getDisplayableAttribute }) {
558
+ if (!toDate(parameters[0])) return message.replace(":date", getDisplayableAttribute(parameters[0]));
559
+ return message.replace(":date", parameters[0]);
560
+ },
561
+ replaceBeforeOrEqual: function(payload) {
562
+ return this.replaceBefore(payload);
563
+ },
564
+ replaceBetween: function({ message, parameters }) {
565
+ const values = {
566
+ ":min": parameters[0],
567
+ ":max": parameters[1]
568
+ };
569
+ return message.replace(/:min|:max/gi, (matched) => values[matched]);
570
+ },
571
+ replaceDateEquals: function(payload) {
572
+ return this.replaceBefore(payload);
573
+ },
574
+ replaceDatetime: function({ message, parameters }) {
575
+ return message.replace(":format", parameters[0]);
576
+ },
577
+ replaceDeclinedIf: function({ message, parameters, data, getDisplayableAttribute }) {
578
+ const [other] = parameters;
579
+ const result = deepFind(data, other);
580
+ const values = {
581
+ ":other": getDisplayableAttribute(other),
582
+ ":value": result
583
+ };
584
+ return message.replace(/:other|:value/gi, (matched) => values[matched]);
585
+ },
586
+ replaceDifferent: function(payload) {
587
+ return this.replaceSame(payload);
588
+ },
589
+ replaceDigits: function({ message, parameters }) {
590
+ return message.replace(":digits", parameters[0]);
591
+ },
592
+ replaceDigitsBetween: function(payload) {
593
+ return this.replaceBetween(payload);
594
+ },
595
+ replaceEndsWith: function({ message, parameters }) {
596
+ return message.replace(":values", parameters.join(", "));
597
+ },
598
+ replaceExists: function({ message, parameters, data }) {
599
+ return message.replace(":value", data[parameters[1]]);
600
+ },
601
+ replaceIn: function({ message, parameters }) {
602
+ return message.replace(":values", parameters.join(", "));
603
+ },
604
+ replaceIncludes: function({ message, parameters }) {
605
+ return message.replace(":values", parameters.join(", "));
606
+ },
607
+ replaceStartsWith: function({ message, parameters }) {
608
+ return message.replace(":values", parameters.join(", "));
609
+ },
610
+ replaceMin: function({ message, parameters }) {
611
+ return message.replace(":min", parameters[0]);
612
+ },
613
+ replaceMax: function({ message, parameters }) {
614
+ return message.replace(":max", parameters[0]);
615
+ },
616
+ replaceNotIncludes: function({ message, parameters }) {
617
+ return message.replace(":values", parameters.join(", "));
618
+ },
619
+ replaceRequiredWith: function({ message, parameters, getDisplayableAttribute }) {
620
+ return message.replace(":values", parameters.map((attribute) => getDisplayableAttribute(attribute)).join(", "));
621
+ },
622
+ replaceRequiredWithAll: function(payload) {
623
+ return this.replaceRequiredWith(payload);
624
+ },
625
+ replaceRequiredWithout: function(payload) {
626
+ return this.replaceRequiredWith(payload);
627
+ },
628
+ replaceRequiredWithoutAll: function(payload) {
629
+ return this.replaceRequiredWith(payload);
630
+ },
631
+ replaceGt: function({ message, parameters, data, hasNumericRule }) {
632
+ const [value] = parameters;
633
+ const result = deepFind(data, value);
634
+ if (typeof result === "undefined") return message.replace(":value", value);
635
+ return message.replace(":value", getSize(result, hasNumericRule).toString());
636
+ },
637
+ replaceLt: function(payload) {
638
+ return this.replaceGt(payload);
639
+ },
640
+ replaceGte: function(payload) {
641
+ return this.replaceGt(payload);
642
+ },
643
+ replaceLte: function(payload) {
644
+ return this.replaceGt(payload);
645
+ },
646
+ replaceRequiredIf: function({ message, parameters, data, getDisplayableAttribute }) {
647
+ const [other] = parameters;
648
+ const result = deepFind(data, other);
649
+ const values = {
650
+ ":other": getDisplayableAttribute(other),
651
+ ":value": result
652
+ };
653
+ return message.replace(/:other|:value/gi, (matched) => values[matched]);
654
+ },
655
+ replaceRequiredUnless: function({ message, parameters, getDisplayableAttribute }) {
656
+ const [other] = parameters;
657
+ const values = {
658
+ ":other": getDisplayableAttribute(other),
659
+ ":values": parameters.slice(1).join(", ")
660
+ };
661
+ return message.replace(/:other|:values/gi, (matched) => values[matched]);
662
+ },
663
+ replaceSame: function({ message, parameters, getDisplayableAttribute }) {
664
+ return message.replace(":other", getDisplayableAttribute(parameters[0]));
665
+ },
666
+ replaceSize: function({ message, parameters }) {
667
+ return message.replace(":size", parameters[0]);
668
+ },
669
+ replaceUnique: function({ message, parameters, data }) {
670
+ return message.replace(":value", data[parameters[1]]);
671
+ }
672
+ };
651
673
 
652
674
  //#endregion
653
- //#region src/utilities/formatMessages.ts
654
- /**
655
- * Get the message type based on the value. The message type is used essentially for size rules
656
- */
657
- function getMesageType(value, hasNumericRule = false) {
658
- if (typeof value === "number" || typeof value === "undefined" || isNaN(value) === false && hasNumericRule === true) return "number";
659
- if (Array.isArray(value)) return "array";
660
- return typeof value;
661
- }
662
- /**
663
- * Get the custom message for a rule if exists
664
- */
665
- function getCustomMessage(attributes, rule, customMessages, messageType, lang) {
666
- const [attribute, primaryAttribute] = attributes;
667
- const translatedMessages = dotify(Lang.get(lang)["custom"] || {});
668
- const keys = getKeyCombinations(`${attribute}.${rule}`);
669
- let allKeys = keys;
670
- if (primaryAttribute) {
671
- allKeys = [];
672
- const primaryAttributeKeys = getKeyCombinations(`${primaryAttribute}.${rule}`);
673
- for (let i = 0; i < keys.length; i++) {
674
- allKeys.push(keys[i]);
675
- if (keys[i] !== primaryAttributeKeys[i]) allKeys.push(primaryAttributeKeys[i]);
676
- }
675
+ //#region src/Rules/closureValidationRule.ts
676
+ var ClosureValidationRule = class extends IRuleContract {
677
+ /**
678
+ * The callback that validates the attribute
679
+ */
680
+ callback;
681
+ /**
682
+ * Indicates if the validation callback failed.
683
+ */
684
+ failed = false;
685
+ constructor(callback) {
686
+ super();
687
+ this.callback = callback;
677
688
  }
678
- if (isSizeRule(rule)) {
679
- allKeys.pop();
680
- allKeys.push(`${rule}.${messageType}`);
681
- allKeys.push(rule);
689
+ /**
690
+ * Determine if the validation rule passes.
691
+ */
692
+ passes(value, attribute) {
693
+ this.failed = false;
694
+ const result = this.callback(value, (message) => {
695
+ this.failed = true;
696
+ this.message = message;
697
+ }, attribute);
698
+ if (result instanceof Promise) return result.then(() => !this.failed);
699
+ return !this.failed;
682
700
  }
683
- let key = "";
684
- let message = "";
685
- for (let i = 0; i < allKeys.length; i++) {
686
- key = allKeys[i];
687
- if (Object.prototype.hasOwnProperty.call(customMessages, key)) return customMessages[key];
688
- message = translatedMessages[key];
689
- if (typeof message === "string") return message;
701
+ };
702
+
703
+ //#endregion
704
+ //#region src/validators/validationData.ts
705
+ const validationData = {
706
+ initializeAndGatherData: function(attribute, masterData) {
707
+ const data = dotify(this.initializeAttributeOnData(attribute, masterData));
708
+ return {
709
+ ...data,
710
+ ...this.extractValuesFromWildCards(masterData, data, attribute)
711
+ };
712
+ },
713
+ initializeAttributeOnData: function(attribute, masterData) {
714
+ const explicitPath = this.getLeadingExplicitAttributePath(attribute);
715
+ const data = this.extractDataFromPath(explicitPath, JSON.parse(JSON.stringify(masterData)));
716
+ if (attribute.indexOf("*") === -1 || attribute.indexOf("*") === attribute.length - 1) return data;
717
+ deepSet(data, attribute, null);
718
+ return data;
719
+ },
720
+ extractValuesFromWildCards(masterData, data, attribute) {
721
+ const keys = [];
722
+ const pattern = new RegExp("^" + attribute.replace(/\*/g, "[^\\.]*"));
723
+ let result = null;
724
+ for (const key in data) {
725
+ result = key.match(pattern);
726
+ if (result) keys.push(result[0]);
727
+ }
728
+ data = {};
729
+ keys.forEach((key) => data[key] = deepFind(masterData, key));
730
+ return data;
731
+ },
732
+ getLeadingExplicitAttributePath: function(attribute) {
733
+ return attribute.split("*")[0].replace(/\.$/, "");
734
+ },
735
+ extractDataFromPath(path, masterData) {
736
+ const results = {};
737
+ const value = deepFind(masterData, path);
738
+ if (value !== void 0) deepSet(results, path, value);
739
+ return results;
690
740
  }
691
- return null;
692
- }
693
- /**
694
- * Get the validation message for an attribute and rule.
695
- */
696
- function getMessage(attributes, rule, value, customMessages, hasNumericRule, lang) {
697
- const inlineMessage = getCustomMessage(attributes, rule, customMessages, getMesageType(value, hasNumericRule), lang);
698
- if (inlineMessage) return inlineMessage;
699
- const validationMessages = Lang.get(lang);
700
- if (isSizeRule(rule) === true) return validationMessages[rule][getMesageType(value, hasNumericRule)];
701
- return validationMessages[rule] || "";
702
- }
703
- /**
704
- * Convert a string to snake case.
705
- */
706
- function toSnakeCase(string) {
707
- return string.split(/ |\B(?=[A-Z])/).map((word) => word.toLowerCase()).join("_");
708
- }
709
- /**
710
- * Get the formatted name of the attribute
711
- */
712
- function getFormattedAttribute(attribute) {
713
- return toSnakeCase(getPrimaryKeyFromPath(attribute)).replace(/_/g, " ").trim();
714
- }
715
- /**
716
- * Get the combinations of keys from a main key. For example if the main key is 'user.info.name',
717
- * the combination will be [user.info.name, info.name, name]
718
- */
719
- function getKeyCombinations(key) {
720
- const combinations = [key];
721
- const splittedKey = key.split(".");
722
- while (splittedKey.length > 1) {
723
- splittedKey.shift();
724
- combinations.push(splittedKey.join("."));
741
+ };
742
+
743
+ //#endregion
744
+ //#region src/validators/validationRuleParser.ts
745
+ const validationRuleParser = {
746
+ explodeRules: function(rules, data = {}) {
747
+ const implicitAttributes = {};
748
+ for (const key in rules) if (key.indexOf("*") !== -1) {
749
+ rules = this.explodeWildCardRules(rules, key, data, implicitAttributes);
750
+ delete rules[key];
751
+ } else if (Object.prototype.hasOwnProperty.call(rules, key) && Array.isArray(rules)) rules[Number(key)] = this.explodeExplicitRules(rules[Number(key)]);
752
+ else if (Object.prototype.hasOwnProperty.call(rules, key) && !Array.isArray(rules)) rules[key] = this.explodeExplicitRules(rules[key]);
753
+ return {
754
+ rules,
755
+ implicitAttributes
756
+ };
757
+ },
758
+ explodeWildCardRules: function(results, attribute, masterData, implicitAttributes) {
759
+ const pattern = new RegExp("^" + attribute.replace(/\*/g, "[^.]*") + "$");
760
+ const data = validationData.initializeAndGatherData(attribute, masterData);
761
+ const rule = results[attribute];
762
+ for (const key in data) if (key.slice(0, attribute.length) === attribute || key.match(pattern) !== null) {
763
+ if (Array.isArray(implicitAttributes[attribute])) implicitAttributes[attribute].push(key);
764
+ else implicitAttributes[attribute] = [key];
765
+ results = this.mergeRulesForAttribute(results, key, rule);
766
+ }
767
+ return results;
768
+ },
769
+ mergeRulesForAttribute(results, attribute, rules) {
770
+ const merge = this.explodeRules([rules]).rules[0];
771
+ results[attribute] = [...results[attribute] ? this.explodeExplicitRules(results[attribute]) : [], ...merge];
772
+ return results;
773
+ },
774
+ explodeExplicitRules: function(rules) {
775
+ if (typeof rules === "string") return rules.split("|");
776
+ if (!Array.isArray(rules)) return [this.prepareRule(rules)];
777
+ return rules.map((rule) => this.prepareRule(rule));
778
+ },
779
+ prepareRule(rule) {
780
+ if (rule instanceof IRuleContract) return rule;
781
+ if (typeof rule === "function") return new ClosureValidationRule(rule);
782
+ return rule.toString();
783
+ },
784
+ parse(rule) {
785
+ if (rule instanceof IRuleContract) return [rule, []];
786
+ return this.parseStringRule(rule);
787
+ },
788
+ parseStringRule: function(rule) {
789
+ let parameters = [];
790
+ let parameter;
791
+ if (rule.indexOf(":") !== -1) {
792
+ [rule, parameter] = rule.split(/:(.+)/);
793
+ parameters = parameter.split(",");
794
+ }
795
+ return [rule, parameters];
796
+ },
797
+ getRule: function(attribute, searchRules, availableRules) {
798
+ if (!availableRules[attribute]) return [];
799
+ searchRules = Array.isArray(searchRules) ? searchRules : [searchRules];
800
+ for (let i = 0; i < availableRules[attribute].length; i++) {
801
+ const [rule, parameters] = this.parse(availableRules[attribute][i]);
802
+ if (searchRules.indexOf(rule) !== -1) return [rule, parameters];
803
+ }
804
+ return [];
805
+ },
806
+ hasRule: function(attribute, searchRules, availableRules) {
807
+ return this.getRule(attribute, searchRules, availableRules).length > 0;
725
808
  }
726
- return combinations;
727
- }
728
- /**
729
- * The purpose of this method if to get the primary key associated with a path
730
- * For example the primary key for path 'user.info.email' will be 'email'
731
- */
732
- function getPrimaryKeyFromPath(path) {
733
- const splittedPath = path.split(".");
734
- if (splittedPath.length <= 1) return path;
735
- const key = splittedPath.pop();
736
- if (isNaN(parseInt(key)) === false) return getPrimaryKeyFromPath(splittedPath.join("."));
737
- return key;
738
- }
809
+ };
739
810
 
740
811
  //#endregion
741
- //#region src/validators/errorBag.ts
742
- var ErrorBag = class ErrorBag {
812
+ //#region src/validators/validateAttributes.ts
813
+ var validateAttributes = class {
743
814
  /**
744
- * All of the registered messages.
815
+ * Stores the data object
745
816
  */
746
- errors = {};
817
+ data;
747
818
  /**
748
- * All Messages
819
+ * Stores the rules object
749
820
  */
750
- messages = {};
821
+ rules;
751
822
  /**
752
- * Stores the first error message
823
+ * Stores validator context that plugins can consume.
753
824
  */
754
- firstMessage = "";
825
+ context;
826
+ constructor(data = {}, rules = {}, context = {}) {
827
+ this.data = data;
828
+ this.rules = rules;
829
+ this.context = context;
830
+ }
755
831
  /**
756
- * Specify whether error types should be returned or no
832
+ * Validate that an attribute was "accepted".
833
+ *
834
+ * This validation rule implies the attribute is "required".
757
835
  */
758
- withErrorTypes = false;
759
- constructor(errors = {}, messages = {}, firstMessage = "", withErrorTypes = false) {
760
- this.errors = errors;
761
- this.messages = messages;
762
- this.firstMessage = firstMessage;
763
- this.withErrorTypes = withErrorTypes;
836
+ validateAccepted(value) {
837
+ return this.validateRequired(value) && [
838
+ "yes",
839
+ "on",
840
+ "1",
841
+ 1,
842
+ true,
843
+ "true"
844
+ ].indexOf(value) !== -1;
764
845
  }
765
846
  /**
766
- * Set withErrorTypes attribute to true
847
+ * Validate that an attribute was "accepted" when another attribute has a given value.
767
848
  */
768
- addErrorTypes() {
769
- this.withErrorTypes = true;
770
- return this;
849
+ validateAcceptedIf(value, parameters) {
850
+ this.requireParameterCount(2, parameters, "accepted_if");
851
+ const other = deepFind(this.data, parameters[0]);
852
+ if (!other) return true;
853
+ if (parameters.slice(1).indexOf(other) !== -1) return this.validateAccepted(value);
854
+ return true;
771
855
  }
772
856
  /**
773
- * Add new recodrs to the errors and messages objects
857
+ * Validate the date is after a given date.
774
858
  */
775
- add(key, error) {
776
- if (Array.isArray(this.errors[key]) && Array.isArray(this.messages[key])) {
777
- this.errors[key].push(error);
778
- this.messages[key].push(error.message);
779
- } else {
780
- this.errors[key] = [error];
781
- this.messages[key] = [error.message];
782
- }
783
- this.firstMessage = this.firstMessage || error.message;
859
+ validateAfter(value, parameters) {
860
+ this.requireParameterCount(1, parameters, "after");
861
+ return this.compareDates(value, parameters[0], ">", "after");
784
862
  }
785
863
  /**
786
- * Get the first error related to a specific key
864
+ * Validate the date is after or equal a given date.
787
865
  */
788
- first(key = null) {
789
- if (!key) return this.firstMessage;
790
- if (this.has(key)) return this.messages[key][0];
791
- return "";
866
+ validateAfterOrEqual(value, parameters) {
867
+ this.requireParameterCount(1, parameters, "after_or_equal");
868
+ return this.compareDates(value, parameters[0], ">=", "after_or_equal");
792
869
  }
793
870
  /**
794
- * Get the error messages keys
871
+ * Validate that an attribute contains only alphabetic characters.
795
872
  */
796
- keys() {
797
- return Object.keys(this.messages);
873
+ validateAlpha(value) {
874
+ return typeof value === "string" && /^[a-zA-Z]+$/.test(value);
798
875
  }
799
876
  /**
800
- * Get all the messages related to a specific key
877
+ * Validate that an attribute contains only alpha-numeric characters, dashes, and underscores.
801
878
  */
802
- get(key, withErrorTypes = this.withErrorTypes) {
803
- if (!this.has(key)) return [];
804
- if (withErrorTypes) return this.errors[key];
805
- return this.messages[key];
879
+ validateAlphaDash(value) {
880
+ if (typeof value != "string" && typeof value != "number") return false;
881
+ return /^(?=.*[a-zA-Z0-9])[a-zA-Z0-9-_]+$/.test(value.toString());
806
882
  }
807
883
  /**
808
- * Check if key exists in messages
884
+ * Validate that an attribute contains only alpha-numeric characters.
809
885
  */
810
- has(key) {
811
- return this.messages[key] && this.messages[key].length > 0 ? true : false;
886
+ validateAlphaNum(value) {
887
+ if (typeof value != "string" && typeof value != "number") return false;
888
+ return /^[a-zA-Z0-9]+$/.test(value.toString());
812
889
  }
813
890
  /**
814
- * Get all error messages
891
+ * Validate that an attribute is an array
815
892
  */
816
- all(allMessages = true, withErrorTypes = this.withErrorTypes) {
817
- const messages = withErrorTypes ? { ...this.errors } : { ...this.messages };
818
- if (!allMessages) Object.keys(messages).map((attribute) => messages[attribute] = messages[attribute][0]);
819
- return messages;
893
+ validateArray(value) {
894
+ return Array.isArray(value);
895
+ }
896
+ /**
897
+ * Validate that an attribute is an array and that his values are unique
898
+ */
899
+ validateArrayUnique(value) {
900
+ if (!Array.isArray(value)) return false;
901
+ return new Set(value).size === value.length;
820
902
  }
821
903
  /**
822
- * Remove error messages
904
+ * Always returns true - this method will be used in conbination with other rules and will be used to stop validation of first failure
823
905
  */
824
- clear(keys) {
825
- if (keys.length === 0) {
826
- this.errors = {};
827
- this.messages = {};
828
- this.firstMessage = "";
829
- return this;
830
- }
831
- keys.forEach((key) => {
832
- if (Object.prototype.hasOwnProperty.call(this.messages, key)) {
833
- delete this.messages[key];
834
- delete this.errors[key];
835
- }
836
- });
837
- this.firstMessage = "";
838
- if (this.keys().length > 0) this.firstMessage = this.messages[Object.keys(this.messages)[0]][0];
839
- return this;
906
+ validateBail() {
907
+ return true;
840
908
  }
841
909
  /**
842
- * Clone ErrorBag Instance
910
+ * Validate the date is before a given date.
843
911
  */
844
- clone() {
845
- return new ErrorBag({ ...this.errors }, { ...this.messages }, this.firstMessage, this.withErrorTypes);
912
+ validateBefore(value, parameters) {
913
+ this.requireParameterCount(1, parameters, "before");
914
+ return this.compareDates(value, parameters[0], "<", "before");
846
915
  }
847
- };
848
-
849
- //#endregion
850
- //#region src/Rules/password.ts
851
- var Password$1 = class Password$1 extends IRuleContract {
852
916
  /**
853
- * The validator performing the validation.
854
- */
855
- validator;
856
- /**
857
- * The minimum size of the password.
917
+ * Validate the date is before or equal a given date.
858
918
  */
859
- minLength = 8;
919
+ validateBeforeOrEqual(value, parameters) {
920
+ this.requireParameterCount(1, parameters, "before_or_equal");
921
+ return this.compareDates(value, parameters[0], "<=", "before_or_equal");
922
+ }
860
923
  /**
861
- * The min amount of lower case letters required in the password
924
+ * Validate the size of an attribute is between a set of values
862
925
  */
863
- minLowerCase = 0;
926
+ validateBetween(value, parameters, attribute) {
927
+ if (typeof value !== "string" && typeof value !== "number" && typeof value !== "object") throw "Validation rule between requires the field under validation to be a number, string, array, or object.";
928
+ this.requireParameterCount(2, parameters, "between");
929
+ let [min, max] = parameters;
930
+ if (isNaN(min) || isNaN(max)) throw "Validation rule between requires both parameters to be numbers.";
931
+ min = Number(min);
932
+ max = Number(max);
933
+ if (min >= max) throw "Validation rule between requires that the first parameter be greater than the second one.";
934
+ const size = getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules));
935
+ return size >= min && size <= max;
936
+ }
864
937
  /**
865
- * The min amount of uppercase letters required in the password
938
+ * Validate that an attribute is boolean
866
939
  */
867
- minUpperCase = 0;
940
+ validateBoolean(value, parameters, attribute) {
941
+ if (validationRuleParser.hasRule(attribute, "strict", this.rules)) return typeof value === "boolean";
942
+ return [
943
+ true,
944
+ false,
945
+ 0,
946
+ 1,
947
+ "0",
948
+ "1"
949
+ ].indexOf(value) !== -1;
950
+ }
868
951
  /**
869
- * The min amount of letters required in the password
952
+ * Validate that an attribute has matching confirmation.
870
953
  */
871
- minLetters = 0;
954
+ validateConfirmed(value, parameters, attribute) {
955
+ return this.validateSame(value, [`${attribute}_confirmation`]) || this.validateSame(value, [`${attribute}Confirmation`]);
956
+ }
872
957
  /**
873
- * The min amount of letters required in the password
958
+ * Validate that an attribute is a valid date.
874
959
  */
875
- minNumbers = 0;
960
+ validateDate(value) {
961
+ return toDate(value) ? true : false;
962
+ }
876
963
  /**
877
- * The min amount of symbols required in the password
964
+ * Validate that an attribute is equal to another date.
878
965
  */
879
- minSymbols = 0;
966
+ validateDateEquals(value, paramters) {
967
+ this.requireParameterCount(1, paramters, "date_equals");
968
+ return this.compareDates(value, paramters[0], "=", "date_equals");
969
+ }
880
970
  /**
881
- * Additional validation rules that should be merged into the default rules during validation.
971
+ * Validate that an attribute was "declined".
972
+ *
973
+ * This validation rule implies the attribute is "required".
882
974
  */
883
- customRules = [];
975
+ validateDeclined(value) {
976
+ return this.validateRequired(value) && [
977
+ "no",
978
+ "off",
979
+ "0",
980
+ 0,
981
+ false,
982
+ "false"
983
+ ].indexOf(value) !== -1;
984
+ }
884
985
  /**
885
- * The callback that will generate the "default" version of the password rule.
986
+ * Validate that an attribute was "declined" when another attribute has a given value.
886
987
  */
887
- static defaultCallback;
988
+ validateDeclinedIf(value, parameters) {
989
+ this.requireParameterCount(2, parameters, "declined_if");
990
+ const other = deepFind(this.data, parameters[0]);
991
+ if (!other) return true;
992
+ if (parameters.slice(1).indexOf(other) !== -1) return this.validateDeclined(value);
993
+ return true;
994
+ }
888
995
  /**
889
- * Create a new instance of the password class
996
+ * Validate that an attribute is different from another attribute.
890
997
  */
891
- static create() {
892
- return new Password$1();
998
+ validateDifferent(value, parameters) {
999
+ this.requireParameterCount(1, parameters, "different");
1000
+ const other = deepFind(this.data, parameters[0]);
1001
+ if (!sameType(value, other)) return true;
1002
+ if (value !== null && typeof value === "object") return !deepEqual(value, other);
1003
+ return value !== other;
893
1004
  }
894
1005
  /**
895
- * Set the minimum length of the password
1006
+ * Validate that an attribute has a given number of digits.
896
1007
  */
897
- min(min) {
898
- this.minLength = min;
899
- return this;
1008
+ validateDigits(value, parameters) {
1009
+ this.requireParameterCount(1, parameters, "digits");
1010
+ if (isInteger(parameters[0]) === false) throw "Validation rule digits requires the parameter to be an integer.";
1011
+ if (parameters[0] <= 0) throw "Validation rule digits requires the parameter to be an integer greater than 0.";
1012
+ if (typeof value !== "string" && typeof value !== "number") return false;
1013
+ value = value.toString();
1014
+ return /^\d+$/.test(value) && value.length === parseInt(parameters[0]);
900
1015
  }
901
1016
  /**
902
- * Set the min amount of letters required in the password
1017
+ * Validate that an attribute is between a given number of digits.
903
1018
  */
904
- letters(letters = 1) {
905
- this.minLetters = letters;
906
- return this;
1019
+ validateDigitsBetween(value, parameters) {
1020
+ this.requireParameterCount(2, parameters, "digits_between");
1021
+ let [min, max] = parameters;
1022
+ if (isInteger(min) === false || isInteger(max) === false) throw "Validation rule digits_between requires both parameters to be integers.";
1023
+ min = parseInt(min);
1024
+ max = parseInt(max);
1025
+ if (min <= 0 || max <= 0) throw "Validation rule digits_between requires the parameters to be an integer greater than 0.";
1026
+ if (min >= max) throw "Validation rule digits_between requires the max param to be greater than the min param.";
1027
+ if (typeof value !== "string" && typeof value !== "number") return false;
1028
+ value = value.toString();
1029
+ const valueLength = value.length;
1030
+ return /^\d+$/.test(value) && valueLength >= min && valueLength <= max;
907
1031
  }
908
1032
  /**
909
- * Set the min amount of upper and lower case letters required in the password
1033
+ * Validate that an attribute is a valid email address.
910
1034
  */
911
- mixedCase(upperCase = 1, lowerCase = 1) {
912
- this.minUpperCase = upperCase;
913
- this.minLowerCase = lowerCase;
914
- return this;
1035
+ validateEmail(value) {
1036
+ if (typeof value !== "string") return false;
1037
+ /**
1038
+ * Max allowed length for a top-level-domain is 24 characters.
1039
+ * reference to list of top-level-domains: https://data.iana.org/TLD/tlds-alpha-by-domain.txt
1040
+ */
1041
+ return value.toLowerCase().match(/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,24})+$/) !== null;
915
1042
  }
916
1043
  /**
917
- * Set the min amount of numbers required in the password
1044
+ * Validate the attribute ends with a given substring.
918
1045
  */
919
- numbers(numbers = 1) {
920
- this.minNumbers = numbers;
921
- return this;
1046
+ validateEndsWith(value, parameters) {
1047
+ this.requireParameterCount(1, parameters, "ends_with");
1048
+ if (typeof value !== "string") throw "The field under validation must be a string";
1049
+ const valueLength = value.length;
1050
+ for (let i = 0; i < parameters.length; i++) if (typeof parameters[i] === "string" && value.indexOf(parameters[i], valueLength - parameters[i].length) !== -1) return true;
1051
+ return false;
922
1052
  }
923
1053
  /**
924
- * Set the min amount of symbols required in the password
1054
+ * Validate that two attributes match.
925
1055
  */
926
- symbols(symbols = 1) {
927
- this.minSymbols = symbols;
928
- return this;
1056
+ validateSame(value, paramaters) {
1057
+ this.requireParameterCount(1, paramaters, "same");
1058
+ const other = deepFind(this.data, paramaters[0]);
1059
+ if (!sameType(value, other)) return false;
1060
+ if (value !== null && typeof value === "object") return deepEqual(value, other);
1061
+ return value === other;
929
1062
  }
930
1063
  /**
931
- * Determine if the validation rule passes.
1064
+ * Validate the size of an attribute.
932
1065
  */
933
- passes(value, attribute) {
934
- const validator = new BaseValidator(this.data, { [attribute]: [
935
- "string",
936
- `min:${this.minLength}`,
937
- ...this.customRules
938
- ] }, this.validator.customMessages, this.validator.customAttributes).setLang(this.lang);
939
- if (!validator.validate()) {
940
- const errors = validator.errors().addErrorTypes().get(attribute);
941
- for (const key in errors) this.validator.errors().add(attribute, errors[key]);
942
- }
943
- if (typeof value !== "string") value = "";
944
- let pattern;
945
- const formattedAttribute = this.validator.getDisplayableAttribute(attribute);
946
- if (this.minLowerCase) {
947
- pattern = this.minLowerCase === 1 ? /\p{Ll}/u : new RegExp(`(.*\\p{Ll}){${this.minLowerCase}}.*`, "u");
948
- if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
949
- error_type: "min_lower_case",
950
- message: this.trans(`password.${this.minLowerCase === 1 ? "lower_case" : "lower_cases"}`, {
951
- attribute: formattedAttribute,
952
- amount: this.minLowerCase
953
- })
954
- });
955
- }
956
- if (this.minUpperCase) {
957
- pattern = this.minUpperCase === 1 ? /\p{Lu}/u : new RegExp(`(.*\\p{Lu}){${this.minUpperCase}}.*`, "u");
958
- if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
959
- error_type: "min_upper_case",
960
- message: this.trans(`password.${this.minUpperCase === 1 ? "upper_case" : "upper_cases"}`, {
961
- attribute: formattedAttribute,
962
- amount: this.minUpperCase
963
- })
964
- });
965
- }
966
- if (this.minLetters) {
967
- pattern = this.minLetters === 1 ? /\p{L}/u : new RegExp(`(.*\\p{L}){${this.minLetters}}.*`, "u");
968
- if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
969
- error_type: "min_letters",
970
- message: this.trans(`password.${this.minLetters === 1 ? "letter" : "letters"}`, {
971
- attribute: formattedAttribute,
972
- amount: this.minLetters
973
- })
974
- });
975
- }
976
- if (this.minNumbers) {
977
- pattern = this.minNumbers === 1 ? /\p{N}/u : new RegExp(`(.*\\p{N}){${this.minNumbers}}.*`, "u");
978
- if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
979
- error_type: "min_numbers",
980
- message: this.trans(`password.${this.minNumbers === 1 ? "number" : "numbers"}`, {
981
- attribute: formattedAttribute,
982
- amount: this.minNumbers
983
- })
984
- });
985
- }
986
- if (this.minSymbols) {
987
- pattern = this.minSymbols === 1 ? /\p{Z}|\p{S}|\p{P}/u : new RegExp(`(.*(\\p{Z}|\\p{S}|\\p{P})){${this.minSymbols}}.*`, "u");
988
- if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
989
- error_type: "min_symbols",
990
- message: this.trans(`password.${this.minSymbols === 1 ? "symbol" : "symbols"}`, {
991
- attribute: formattedAttribute,
992
- amount: this.minSymbols
993
- })
994
- });
995
- }
996
- if (this.validator.errors().has(attribute)) return false;
997
- return true;
1066
+ validateSize(value, parameters, attribute) {
1067
+ this.requireParameterCount(1, parameters, "size");
1068
+ return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) === Number(parameters[0]);
998
1069
  }
999
1070
  /**
1000
- * Specify additional validation rules that should be merged with the default rules during validation.
1071
+ * Validate Optinial attributes. Always return true, just lets us put sometimes in rule.
1001
1072
  */
1002
- rules(rules) {
1003
- this.customRules = rules;
1004
- return this;
1073
+ validateSometimes() {
1074
+ return true;
1005
1075
  }
1006
1076
  /**
1007
- * Get all the validation error messages related to the password
1077
+ * Validate the attribute starts with a given substring.
1008
1078
  */
1009
- getMessage() {
1010
- return {};
1079
+ validateStartsWith(value, parameters) {
1080
+ this.requireParameterCount(1, parameters, "starts_with");
1081
+ if (typeof value !== "string") throw "The field under validation must be a string";
1082
+ for (let i = 0; i < parameters.length; i++) if (typeof parameters[i] === "string" && value.substr(0, parameters[i].length) === parameters[i]) return true;
1083
+ return false;
1011
1084
  }
1012
1085
  /**
1013
- * Set the validator instance used to validate the password
1086
+ * Validate that a required attribute exists
1014
1087
  */
1015
- setValidator(validator) {
1016
- this.validator = validator;
1017
- return this;
1088
+ validateRequired(value) {
1089
+ if (value === null || typeof value === "undefined") return false;
1090
+ else if (typeof value === "string" && value.trim() === "") return false;
1091
+ else if (Array.isArray(value) && value.length < 1) return false;
1092
+ else if (typeof value === "object" && Object.keys(value).length < 1) return false;
1093
+ return true;
1018
1094
  }
1019
1095
  /**
1020
- * Set the default callback to be used for determining a password's default rules.
1096
+ * Validate that an attribute exists when another atteribute has a given value
1021
1097
  */
1022
- static setDefault(callback = null) {
1023
- if (callback instanceof Password$1) {
1024
- this.defaultCallback = callback;
1025
- return;
1026
- }
1027
- if (typeof callback !== "function") throw "The given callback should be callable";
1028
- this.defaultCallback = callback;
1098
+ validateRequiredIf(value, parameters) {
1099
+ this.requireParameterCount(2, parameters, "required_if");
1100
+ const other = deepFind(this.data, parameters[0]);
1101
+ if (typeof other === "undefined") return true;
1102
+ if (this.parseDependentRuleParameters(other, parameters).indexOf(other) !== -1) return this.validateRequired(value);
1103
+ return true;
1029
1104
  }
1030
1105
  /**
1031
- * Get the default configuration of the password rule.
1106
+ * Validate that an attribute exists when another attribute does not have a given value.
1032
1107
  */
1033
- static default() {
1034
- const password = typeof this.defaultCallback === "function" ? this.defaultCallback() : this.defaultCallback;
1035
- return password instanceof IRuleContract ? password : Password$1.create().min(8);
1108
+ validateRequiredUnless(value, parameters) {
1109
+ this.requireParameterCount(2, parameters, "required_unless");
1110
+ let other = deepFind(this.data, parameters[0]);
1111
+ other = typeof other === "undefined" ? null : other;
1112
+ if (this.parseDependentRuleParameters(other, parameters).indexOf(other) === -1) return this.validateRequired(value);
1113
+ return true;
1036
1114
  }
1037
- };
1038
-
1039
- //#endregion
1040
- //#region src/utilities/build.ts
1041
- function buildValidationMethodName(rule) {
1042
- if (!rule) return rule;
1043
- return rule.split("_").map((rule) => `${rule[0].toUpperCase()}${rule.slice(1)}`).join("");
1044
- }
1045
-
1046
- //#endregion
1047
- //#region src/payloads/replaceAttributePayload.ts
1048
- var replaceAttributePayload = class {
1049
1115
  /**
1050
- * Stores the data object
1116
+ * Validate that an attribute exists when any other attribute exists.
1051
1117
  */
1052
- data;
1118
+ validateRequiredWith(value, parameters) {
1119
+ if (!this.allFailingRequired(parameters)) return this.validateRequired(value);
1120
+ return true;
1121
+ }
1053
1122
  /**
1054
- * The message in which attributes will be replaced
1123
+ * Validate that an attribute exists when all other attributes exist.
1055
1124
  */
1056
- message;
1125
+ validateRequiredWithAll(value, parameters) {
1126
+ if (!this.anyFailingRequired(parameters)) return this.validateRequired(value);
1127
+ return true;
1128
+ }
1057
1129
  /**
1058
- * Parameters that will be used to replace the attributes
1130
+ * Validate that an attribute exists when another attribute does not.
1059
1131
  */
1060
- parameters;
1132
+ validateRequiredWithout(value, parameters) {
1133
+ if (this.anyFailingRequired(parameters)) return this.validateRequired(value);
1134
+ return true;
1135
+ }
1061
1136
  /**
1062
- * Flag that identifies wether the numeric rule exists or not
1137
+ * Validate that an attribute exists when all other attributes do not.
1063
1138
  */
1064
- hasNumericRule;
1139
+ validateRequiredWithoutAll(value, parameters) {
1140
+ if (this.allFailingRequired(parameters)) return this.validateRequired(value);
1141
+ return true;
1142
+ }
1065
1143
  /**
1066
- * The function that will be used to format attributes
1144
+ * Determine if any of the given attributes fail the required test.
1067
1145
  */
1068
- getDisplayableAttribute;
1069
- constructor(data, message, parameters, hasNumericRule, getDisplayableAttribute) {
1070
- this.data = data;
1071
- this.message = message;
1072
- this.parameters = parameters;
1073
- this.hasNumericRule = hasNumericRule;
1074
- this.getDisplayableAttribute = getDisplayableAttribute;
1075
- }
1076
- };
1077
-
1078
- //#endregion
1079
- //#region src/utilities/date.ts
1080
- /**
1081
- * Convert value to date instance
1082
- */
1083
- function toDate(value) {
1084
- const date = Date.parse(value);
1085
- return !isNaN(date) ? new Date(date) : null;
1086
- }
1087
-
1088
- //#endregion
1089
- //#region src/validators/replaceAttributes.ts
1090
- const replaceAttributes = {
1091
- replaceAcceptedIf: function({ data, message, parameters, getDisplayableAttribute }) {
1092
- const [other] = parameters;
1093
- const result = deepFind(data, other);
1094
- const values = {
1095
- ":other": getDisplayableAttribute(other),
1096
- ":value": result
1097
- };
1098
- return message.replace(/:other|:value/gi, (matched) => values[matched]);
1099
- },
1100
- replaceAfter: function(payload) {
1101
- return this.replaceBefore(payload);
1102
- },
1103
- replaceAfterOrEqual: function(payload) {
1104
- return this.replaceBefore(payload);
1105
- },
1106
- replaceBefore: function({ message, parameters, getDisplayableAttribute }) {
1107
- if (!toDate(parameters[0])) return message.replace(":date", getDisplayableAttribute(parameters[0]));
1108
- return message.replace(":date", parameters[0]);
1109
- },
1110
- replaceBeforeOrEqual: function(payload) {
1111
- return this.replaceBefore(payload);
1112
- },
1113
- replaceBetween: function({ message, parameters }) {
1114
- const values = {
1115
- ":min": parameters[0],
1116
- ":max": parameters[1]
1117
- };
1118
- return message.replace(/:min|:max/gi, (matched) => values[matched]);
1119
- },
1120
- replaceDateEquals: function(payload) {
1121
- return this.replaceBefore(payload);
1122
- },
1123
- replaceDatetime: function({ message, parameters }) {
1124
- return message.replace(":format", parameters[0]);
1125
- },
1126
- replaceDeclinedIf: function({ message, parameters, data, getDisplayableAttribute }) {
1127
- const [other] = parameters;
1128
- const result = deepFind(data, other);
1129
- const values = {
1130
- ":other": getDisplayableAttribute(other),
1131
- ":value": result
1132
- };
1133
- return message.replace(/:other|:value/gi, (matched) => values[matched]);
1134
- },
1135
- replaceDifferent: function(payload) {
1136
- return this.replaceSame(payload);
1137
- },
1138
- replaceDigits: function({ message, parameters }) {
1139
- return message.replace(":digits", parameters[0]);
1140
- },
1141
- replaceDigitsBetween: function(payload) {
1142
- return this.replaceBetween(payload);
1143
- },
1144
- replaceEndsWith: function({ message, parameters }) {
1145
- return message.replace(":values", parameters.join(", "));
1146
- },
1147
- replaceExists: function({ message, parameters, data }) {
1148
- return message.replace(":value", data[parameters[1]]);
1149
- },
1150
- replaceIn: function({ message, parameters }) {
1151
- return message.replace(":values", parameters.join(", "));
1152
- },
1153
- replaceIncludes: function({ message, parameters }) {
1154
- return message.replace(":values", parameters.join(", "));
1155
- },
1156
- replaceStartsWith: function({ message, parameters }) {
1157
- return message.replace(":values", parameters.join(", "));
1158
- },
1159
- replaceMin: function({ message, parameters }) {
1160
- return message.replace(":min", parameters[0]);
1161
- },
1162
- replaceMax: function({ message, parameters }) {
1163
- return message.replace(":max", parameters[0]);
1164
- },
1165
- replaceNotIncludes: function({ message, parameters }) {
1166
- return message.replace(":values", parameters.join(", "));
1167
- },
1168
- replaceRequiredWith: function({ message, parameters, getDisplayableAttribute }) {
1169
- return message.replace(":values", parameters.map((attribute) => getDisplayableAttribute(attribute)).join(", "));
1170
- },
1171
- replaceRequiredWithAll: function(payload) {
1172
- return this.replaceRequiredWith(payload);
1173
- },
1174
- replaceRequiredWithout: function(payload) {
1175
- return this.replaceRequiredWith(payload);
1176
- },
1177
- replaceRequiredWithoutAll: function(payload) {
1178
- return this.replaceRequiredWith(payload);
1179
- },
1180
- replaceGt: function({ message, parameters, data, hasNumericRule }) {
1181
- const [value] = parameters;
1182
- const result = deepFind(data, value);
1183
- if (typeof result === "undefined") return message.replace(":value", value);
1184
- return message.replace(":value", getSize(result, hasNumericRule).toString());
1185
- },
1186
- replaceLt: function(payload) {
1187
- return this.replaceGt(payload);
1188
- },
1189
- replaceGte: function(payload) {
1190
- return this.replaceGt(payload);
1191
- },
1192
- replaceLte: function(payload) {
1193
- return this.replaceGt(payload);
1194
- },
1195
- replaceRequiredIf: function({ message, parameters, data, getDisplayableAttribute }) {
1196
- const [other] = parameters;
1197
- const result = deepFind(data, other);
1198
- const values = {
1199
- ":other": getDisplayableAttribute(other),
1200
- ":value": result
1201
- };
1202
- return message.replace(/:other|:value/gi, (matched) => values[matched]);
1203
- },
1204
- replaceRequiredUnless: function({ message, parameters, getDisplayableAttribute }) {
1205
- const [other] = parameters;
1206
- const values = {
1207
- ":other": getDisplayableAttribute(other),
1208
- ":values": parameters.slice(1).join(", ")
1209
- };
1210
- return message.replace(/:other|:values/gi, (matched) => values[matched]);
1211
- },
1212
- replaceSame: function({ message, parameters, getDisplayableAttribute }) {
1213
- return message.replace(":other", getDisplayableAttribute(parameters[0]));
1214
- },
1215
- replaceSize: function({ message, parameters }) {
1216
- return message.replace(":size", parameters[0]);
1217
- },
1218
- replaceUnique: function({ message, parameters, data }) {
1219
- return message.replace(":value", data[parameters[1]]);
1220
- }
1221
- };
1222
-
1223
- //#endregion
1224
- //#region src/Rules/closureValidationRule.ts
1225
- var ClosureValidationRule = class extends IRuleContract {
1146
+ anyFailingRequired(attributes) {
1147
+ for (let i = 0; i < attributes.length; i++) if (!this.validateRequired(deepFind(this.data, attributes[i]))) return true;
1148
+ return false;
1149
+ }
1226
1150
  /**
1227
- * The callback that validates the attribute
1151
+ * Determine if all of the given attributes fail the required test.
1228
1152
  */
1229
- callback;
1153
+ allFailingRequired(attributes) {
1154
+ for (let i = 0; i < attributes.length; i++) if (this.validateRequired(deepFind(this.data, attributes[i]))) return false;
1155
+ return true;
1156
+ }
1230
1157
  /**
1231
- * Indicates if the validation callback failed.
1158
+ * Validate that an attribute is a string.
1232
1159
  */
1233
- failed = false;
1234
- constructor(callback) {
1235
- super();
1236
- this.callback = callback;
1160
+ validateString(value) {
1161
+ return typeof value === "string";
1237
1162
  }
1238
1163
  /**
1239
- * Determine if the validation rule passes.
1164
+ * Validate the size of an attribute is less than a maximum value.
1240
1165
  */
1241
- passes(value, attribute) {
1242
- this.failed = false;
1243
- const result = this.callback(value, (message) => {
1244
- this.failed = true;
1245
- this.message = message;
1246
- }, attribute);
1247
- if (result instanceof Promise) return result.then(() => !this.failed);
1248
- return !this.failed;
1166
+ validateMax(value, parameters, attribute) {
1167
+ this.requireParameterCount(1, parameters, "max");
1168
+ if (isNaN(parameters[0])) throw "Validation rule max requires parameter to be a number.";
1169
+ return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) <= Number(parameters[0]);
1249
1170
  }
1250
- };
1251
-
1252
- //#endregion
1253
- //#region src/validators/validationData.ts
1254
- const validationData = {
1255
- initializeAndGatherData: function(attribute, masterData) {
1256
- const data = dotify(this.initializeAttributeOnData(attribute, masterData));
1257
- return {
1258
- ...data,
1259
- ...this.extractValuesFromWildCards(masterData, data, attribute)
1260
- };
1261
- },
1262
- initializeAttributeOnData: function(attribute, masterData) {
1263
- const explicitPath = this.getLeadingExplicitAttributePath(attribute);
1264
- const data = this.extractDataFromPath(explicitPath, JSON.parse(JSON.stringify(masterData)));
1265
- if (attribute.indexOf("*") === -1 || attribute.indexOf("*") === attribute.length - 1) return data;
1266
- deepSet(data, attribute, null);
1267
- return data;
1268
- },
1269
- extractValuesFromWildCards(masterData, data, attribute) {
1270
- const keys = [];
1271
- const pattern = new RegExp("^" + attribute.replace(/\*/g, "[^\\.]*"));
1272
- let result = null;
1273
- for (const key in data) {
1274
- result = key.match(pattern);
1275
- if (result) keys.push(result[0]);
1276
- }
1277
- data = {};
1278
- keys.forEach((key) => data[key] = deepFind(masterData, key));
1279
- return data;
1280
- },
1281
- getLeadingExplicitAttributePath: function(attribute) {
1282
- return attribute.split("*")[0].replace(/\.$/, "");
1283
- },
1284
- extractDataFromPath(path, masterData) {
1285
- const results = {};
1286
- const value = deepFind(masterData, path);
1287
- if (value !== void 0) deepSet(results, path, value);
1288
- return results;
1171
+ /**
1172
+ * Validate the size of an attribute is greater than a minimum value.
1173
+ */
1174
+ validateMin(value, parameters, attribute) {
1175
+ this.requireParameterCount(1, parameters, "min");
1176
+ if (isNaN(parameters[0])) throw "Validation rule min requires parameter to be a number.";
1177
+ return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) >= Number(parameters[0]);
1289
1178
  }
1290
- };
1291
-
1292
- //#endregion
1293
- //#region src/validators/validationRuleParser.ts
1294
- const validationRuleParser = {
1295
- explodeRules: function(rules, data = {}) {
1296
- const implicitAttributes = {};
1297
- for (const key in rules) if (key.indexOf("*") !== -1) {
1298
- rules = this.explodeWildCardRules(rules, key, data, implicitAttributes);
1299
- delete rules[key];
1300
- } else if (Object.prototype.hasOwnProperty.call(rules, key) && Array.isArray(rules)) rules[Number(key)] = this.explodeExplicitRules(rules[Number(key)]);
1301
- else if (Object.prototype.hasOwnProperty.call(rules, key) && !Array.isArray(rules)) rules[key] = this.explodeExplicitRules(rules[key]);
1302
- return {
1303
- rules,
1304
- implicitAttributes
1305
- };
1306
- },
1307
- explodeWildCardRules: function(results, attribute, masterData, implicitAttributes) {
1308
- const pattern = new RegExp("^" + attribute.replace(/\*/g, "[^.]*") + "$");
1309
- const data = validationData.initializeAndGatherData(attribute, masterData);
1310
- const rule = results[attribute];
1311
- for (const key in data) if (key.slice(0, attribute.length) === attribute || key.match(pattern) !== null) {
1312
- if (Array.isArray(implicitAttributes[attribute])) implicitAttributes[attribute].push(key);
1313
- else implicitAttributes[attribute] = [key];
1314
- results = this.mergeRulesForAttribute(results, key, rule);
1315
- }
1316
- return results;
1317
- },
1318
- mergeRulesForAttribute(results, attribute, rules) {
1319
- const merge = this.explodeRules([rules]).rules[0];
1320
- results[attribute] = [...results[attribute] ? this.explodeExplicitRules(results[attribute]) : [], ...merge];
1321
- return results;
1322
- },
1323
- explodeExplicitRules: function(rules) {
1324
- if (typeof rules === "string") return rules.split("|");
1325
- if (!Array.isArray(rules)) return [this.prepareRule(rules)];
1326
- return rules.map((rule) => this.prepareRule(rule));
1327
- },
1328
- prepareRule(rule) {
1329
- if (rule instanceof IRuleContract) return rule;
1330
- if (typeof rule === "function") return new ClosureValidationRule(rule);
1331
- return rule.toString();
1332
- },
1333
- parse(rule) {
1334
- if (rule instanceof IRuleContract) return [rule, []];
1335
- return this.parseStringRule(rule);
1336
- },
1337
- parseStringRule: function(rule) {
1338
- let parameters = [];
1339
- let parameter;
1340
- if (rule.indexOf(":") !== -1) {
1341
- [rule, parameter] = rule.split(/:(.+)/);
1342
- parameters = parameter.split(",");
1343
- }
1344
- return [rule, parameters];
1345
- },
1346
- getRule: function(attribute, searchRules, availableRules) {
1347
- if (!availableRules[attribute]) return [];
1348
- searchRules = Array.isArray(searchRules) ? searchRules : [searchRules];
1349
- for (let i = 0; i < availableRules[attribute].length; i++) {
1350
- const [rule, parameters] = this.parse(availableRules[attribute][i]);
1351
- if (searchRules.indexOf(rule) !== -1) return [rule, parameters];
1352
- }
1353
- return [];
1354
- },
1355
- hasRule: function(attribute, searchRules, availableRules) {
1356
- return this.getRule(attribute, searchRules, availableRules).length > 0;
1179
+ /**
1180
+ * Validate that an attribute is numeric.
1181
+ */
1182
+ validateNumeric(value, parameters, attribute) {
1183
+ if (validationRuleParser.hasRule(attribute, "strict", this.rules) && typeof value !== "number") return false;
1184
+ return value !== null && isNaN(value) === false;
1357
1185
  }
1358
- };
1359
-
1360
- //#endregion
1361
- //#region src/validators/validateAttributes.ts
1362
- var validateAttributes = class {
1363
1186
  /**
1364
- * Stores the data object
1187
+ * Validate that an attribute is an object
1365
1188
  */
1366
- data;
1189
+ validateObject(value) {
1190
+ return isObject(value);
1191
+ }
1367
1192
  /**
1368
- * Stores the rules object
1193
+ * Validate that an attribute exists even if not filled.
1369
1194
  */
1370
- rules;
1371
- constructor(data = {}, rules = {}) {
1372
- this.data = data;
1373
- this.rules = rules;
1195
+ validatePresent(value, parameters, attribute) {
1196
+ return typeof deepFind(this.data, attribute) !== "undefined";
1374
1197
  }
1375
1198
  /**
1376
- * Validate that an attribute was "accepted".
1377
- *
1378
- * This validation rule implies the attribute is "required".
1199
+ * Validate that an attribute is an integer.
1379
1200
  */
1380
- validateAccepted(value) {
1381
- return this.validateRequired(value) && [
1382
- "yes",
1383
- "on",
1384
- "1",
1385
- 1,
1386
- true,
1387
- "true"
1388
- ].indexOf(value) !== -1;
1201
+ validateInteger(value, parameters, attribute) {
1202
+ if (validationRuleParser.hasRule(attribute, "strict", this.rules) && typeof value !== "number") return false;
1203
+ return isInteger(value);
1389
1204
  }
1390
1205
  /**
1391
- * Validate that an attribute was "accepted" when another attribute has a given value.
1206
+ * Validate that the attribute is a valid JSON string
1392
1207
  */
1393
- validateAcceptedIf(value, parameters) {
1394
- this.requireParameterCount(2, parameters, "accepted_if");
1395
- const other = deepFind(this.data, parameters[0]);
1396
- if (!other) return true;
1397
- if (parameters.slice(1).indexOf(other) !== -1) return this.validateAccepted(value);
1208
+ validateJson(value) {
1209
+ if (!value || typeof value !== "string") return false;
1210
+ try {
1211
+ JSON.parse(value);
1212
+ } catch {
1213
+ return false;
1214
+ }
1398
1215
  return true;
1399
1216
  }
1400
1217
  /**
1401
- * Validate the date is after a given date.
1218
+ * Validate that an attribute is greater than another attribute.
1402
1219
  */
1403
- validateAfter(value, parameters) {
1404
- this.requireParameterCount(1, parameters, "after");
1405
- return this.compareDates(value, parameters[0], ">", "after");
1220
+ validateGt(value, parameters, attribute) {
1221
+ this.requireParameterCount(1, parameters, "gt");
1222
+ if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
1223
+ const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
1224
+ if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) > compartedToValue;
1225
+ if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
1226
+ return getSize(value) > getSize(compartedToValue);
1406
1227
  }
1407
1228
  /**
1408
- * Validate the date is after or equal a given date.
1229
+ * Validate that an attribute is greater than or equal another attribute.
1409
1230
  */
1410
- validateAfterOrEqual(value, parameters) {
1411
- this.requireParameterCount(1, parameters, "after_or_equal");
1412
- return this.compareDates(value, parameters[0], ">=", "after_or_equal");
1231
+ validateGte(value, parameters, attribute) {
1232
+ this.requireParameterCount(1, parameters, "gte");
1233
+ if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
1234
+ const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
1235
+ if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) >= compartedToValue;
1236
+ if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
1237
+ return getSize(value) >= getSize(compartedToValue);
1413
1238
  }
1414
1239
  /**
1415
- * Validate that an attribute contains only alphabetic characters.
1240
+ * Validate that an attribute is less than another attribute.
1416
1241
  */
1417
- validateAlpha(value) {
1418
- return typeof value === "string" && /^[a-zA-Z]+$/.test(value);
1242
+ validateLt(value, parameters, attribute) {
1243
+ this.requireParameterCount(1, parameters, "lt");
1244
+ if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
1245
+ const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
1246
+ if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) < compartedToValue;
1247
+ if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
1248
+ return getSize(value) < getSize(compartedToValue);
1249
+ }
1250
+ /**
1251
+ * Validate that an attribute is less than or equal another attribute.
1252
+ */
1253
+ validateLte(value, parameters, attribute) {
1254
+ this.requireParameterCount(1, parameters, "lte");
1255
+ if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
1256
+ const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
1257
+ if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) <= compartedToValue;
1258
+ if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
1259
+ return getSize(value) <= getSize(compartedToValue);
1260
+ }
1261
+ /**
1262
+ * Validate an attribute is contained within a list of values.
1263
+ */
1264
+ validateIn(value, parameters) {
1265
+ this.requireParameterCount(1, parameters, "in");
1266
+ if (Array.isArray(value)) {
1267
+ for (let index = 0; index < value.length; index++) if (typeof value[index] !== "number" && typeof value[index] !== "string") return false;
1268
+ return value.filter((element) => parameters.indexOf(element.toString()) === -1).length === 0;
1269
+ }
1270
+ if (typeof value !== "number" && typeof value !== "string") return false;
1271
+ return parameters.indexOf(value.toString()) !== -1;
1419
1272
  }
1420
1273
  /**
1421
- * Validate that an attribute contains only alpha-numeric characters, dashes, and underscores.
1274
+ * "Indicate" validation should pass if value is null
1275
+ *
1276
+ * Always returns true, just lets us put "nullable" in rules.
1422
1277
  */
1423
- validateAlphaDash(value) {
1424
- if (typeof value != "string" && typeof value != "number") return false;
1425
- return /^(?=.*[a-zA-Z0-9])[a-zA-Z0-9-_]+$/.test(value.toString());
1278
+ validateNullable() {
1279
+ return true;
1426
1280
  }
1427
1281
  /**
1428
- * Validate that an attribute contains only alpha-numeric characters.
1282
+ * Validate an attribute is not contained within a list of values.
1429
1283
  */
1430
- validateAlphaNum(value) {
1431
- if (typeof value != "string" && typeof value != "number") return false;
1432
- return /^[a-zA-Z0-9]+$/.test(value.toString());
1284
+ validateNotIn(value, parameters) {
1285
+ this.requireParameterCount(1, parameters, "not_in");
1286
+ const valuesToCheck = [];
1287
+ if (Array.isArray(value)) {
1288
+ for (let index = 0; index < value.length; index++) if (typeof value[index] === "number" || typeof value[index] === "string") valuesToCheck.push(value[index]);
1289
+ if (valuesToCheck.length === 0) return true;
1290
+ return valuesToCheck.filter((element) => parameters.indexOf(element.toString()) !== -1).length === 0;
1291
+ }
1292
+ if (typeof value !== "number" && typeof value !== "string") return true;
1293
+ return parameters.indexOf(value.toString()) === -1;
1433
1294
  }
1434
1295
  /**
1435
- * Validate that an attribute is an array
1296
+ * Always returns true - this method will be used in conbination with other rules
1436
1297
  */
1437
- validateArray(value) {
1438
- return Array.isArray(value);
1298
+ validateStrict() {
1299
+ return true;
1439
1300
  }
1440
1301
  /**
1441
- * Validate that an attribute is an array and that his values are unique
1302
+ * Validate that an attribute is a valid URL.
1442
1303
  */
1443
- validateArrayUnique(value) {
1444
- if (!Array.isArray(value)) return false;
1445
- return new Set(value).size === value.length;
1304
+ validateUrl(value) {
1305
+ if (typeof value !== "string") return false;
1306
+ return new RegExp("^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|localhost|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$", "i").test(value);
1446
1307
  }
1447
1308
  /**
1448
- * Always returns true - this method will be used in conbination with other rules and will be used to stop validation of first failure
1309
+ * Determine if a comparison passes between the given values.
1449
1310
  */
1450
- validateBail() {
1451
- return true;
1311
+ compareDates(value, parameter, operator, rule) {
1312
+ value = toDate(value);
1313
+ if (!value) throw `Validation rule ${rule} requires the field under valation to be a date.`;
1314
+ const compartedToValue = toDate(deepFind(this.data, parameter) || parameter);
1315
+ if (!compartedToValue) throw `Validation rule ${rule} requires the parameter to be a date.`;
1316
+ return compare(value.getTime(), compartedToValue.getTime(), operator);
1452
1317
  }
1453
1318
  /**
1454
- * Validate the date is before a given date.
1319
+ * Require a certain number of parameters to be present
1455
1320
  */
1456
- validateBefore(value, parameters) {
1457
- this.requireParameterCount(1, parameters, "before");
1458
- return this.compareDates(value, parameters[0], "<", "before");
1321
+ requireParameterCount(count, parameters, rule) {
1322
+ if (parameters.length < count) throw `Validation rule ${rule} requires at least ${count} parameters.`;
1459
1323
  }
1460
1324
  /**
1461
- * Validate the date is before or equal a given date.
1325
+ * Prepare the values for validation
1462
1326
  */
1463
- validateBeforeOrEqual(value, parameters) {
1464
- this.requireParameterCount(1, parameters, "before_or_equal");
1465
- return this.compareDates(value, parameters[0], "<=", "before_or_equal");
1327
+ parseDependentRuleParameters(other, parameters) {
1328
+ let values = parameters.slice(1);
1329
+ if (other === null) values = convertValuesToNull(values);
1330
+ if (typeof other === "number") values = convertValuesToNumber(values);
1331
+ if (typeof other === "boolean") values = convertValuesToBoolean(values);
1332
+ return values;
1466
1333
  }
1467
- /**
1468
- * Validate the size of an attribute is between a set of values
1469
- */
1470
- validateBetween(value, parameters, attribute) {
1471
- if (typeof value !== "string" && typeof value !== "number" && typeof value !== "object") throw "Validation rule between requires the field under validation to be a number, string, array, or object.";
1472
- this.requireParameterCount(2, parameters, "between");
1473
- let [min, max] = parameters;
1474
- if (isNaN(min) || isNaN(max)) throw "Validation rule between requires both parameters to be numbers.";
1475
- min = Number(min);
1476
- max = Number(max);
1477
- if (min >= max) throw "Validation rule between requires that the first parameter be greater than the second one.";
1478
- const size = getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules));
1479
- return size >= min && size <= max;
1334
+ };
1335
+
1336
+ //#endregion
1337
+ //#region src/Rules/registerRule.ts
1338
+ function register(rule, validate, replaceMessage) {
1339
+ const method = buildValidationMethodName(rule);
1340
+ if (new validateAttributes()[`validate${method}`]) return false;
1341
+ validateAttributes.prototype[`validate${method}`] = validate;
1342
+ if (typeof replaceMessage === "function") replaceAttributes[`replace${method}`] = ({ message, parameters, data, getDisplayableAttribute }) => replaceMessage(message, parameters, data, getDisplayableAttribute);
1343
+ return true;
1344
+ }
1345
+ function registerImplicit(rule, validate, replaceMessage) {
1346
+ if (register(rule, validate, replaceMessage) === true) addImplicitRule(rule);
1347
+ }
1348
+
1349
+ //#endregion
1350
+ //#region src/Plugin.ts
1351
+ const valueInspectors = [];
1352
+ const api = {
1353
+ registerRule: register,
1354
+ registerImplicitRule: registerImplicit,
1355
+ registerValueInspector,
1356
+ extendTranslations: (translations) => {
1357
+ Lang.extendTranslationObject(translations);
1358
+ }
1359
+ };
1360
+ function definePlugin(plugin) {
1361
+ return plugin;
1362
+ }
1363
+ function usePlugin(plugin) {
1364
+ plugin.install(api);
1365
+ }
1366
+ function registerValueInspector(inspector) {
1367
+ const existing = valueInspectors.findIndex((candidate) => candidate.type === inspector.type);
1368
+ if (existing >= 0) {
1369
+ valueInspectors.splice(existing, 1, inspector);
1370
+ return;
1371
+ }
1372
+ valueInspectors.push(inspector);
1373
+ }
1374
+ function getValidationValueInspector(value) {
1375
+ return valueInspectors.find((inspector) => inspector.matches(value));
1376
+ }
1377
+ function getValidationMessageType(value, hasNumericRule = false) {
1378
+ if (typeof value === "number" || typeof value === "undefined" || isNaN(value) === false && hasNumericRule === true) return "number";
1379
+ const inspector = getValidationValueInspector(value);
1380
+ if (inspector) return inspector.type;
1381
+ if (Array.isArray(value)) return "array";
1382
+ return typeof value;
1383
+ }
1384
+ function getValidationSize(value, hasNumericRule = false) {
1385
+ if (typeof value === "number" || isNaN(value) === false && hasNumericRule === true) return Number(value);
1386
+ const inspector = getValidationValueInspector(value);
1387
+ if (inspector?.size) return inspector.size(value);
1388
+ if (typeof value === "string" || Array.isArray(value)) return value.length;
1389
+ if (typeof value === "object" && value !== null) return Object.keys(value).length;
1390
+ return -1;
1391
+ }
1392
+
1393
+ //#endregion
1394
+ //#region src/utilities/general.ts
1395
+ const implicitRues = [
1396
+ "accepted",
1397
+ "accepted_if",
1398
+ "declined",
1399
+ "declined_if",
1400
+ "filled",
1401
+ "present",
1402
+ "required",
1403
+ "required_if",
1404
+ "required_unless",
1405
+ "required_with",
1406
+ "required_with_all",
1407
+ "required_without",
1408
+ "required_without_all"
1409
+ ];
1410
+ /**
1411
+ * Get the size of a value based on its type
1412
+ */
1413
+ function getSize(value, hasNumericRule = false) {
1414
+ return getValidationSize(value, hasNumericRule);
1415
+ }
1416
+ /**
1417
+ * Check if two values are of the same type
1418
+ */
1419
+ function sameType(value, otherValue) {
1420
+ return (Array.isArray(value) ? "array" : value === null ? null : typeof value) === (Array.isArray(otherValue) ? "array" : otherValue === null ? null : typeof otherValue);
1421
+ }
1422
+ /**
1423
+ * Check if Value is an Ineteger
1424
+ */
1425
+ function isInteger(value) {
1426
+ return value !== null && isNaN(value) === false && value % 1 === 0;
1427
+ }
1428
+ /**
1429
+ * Check if the value can be considered as rule
1430
+ */
1431
+ function isRule(value) {
1432
+ return typeof value === "string" || typeof value === "function" || value instanceof IRuleContract || value instanceof BaseRule;
1433
+ }
1434
+ /**
1435
+ * Check if the array contain any potentiel valid rule
1436
+ */
1437
+ function isArrayOfRules(values) {
1438
+ for (let i = 0; i < values.length; i++) if (isRule(values[i])) return true;
1439
+ return false;
1440
+ }
1441
+ /**
1442
+ * Check if the rule is related to size
1443
+ */
1444
+ function isSizeRule(rule) {
1445
+ return [
1446
+ "size",
1447
+ "between",
1448
+ "min",
1449
+ "max",
1450
+ "gt",
1451
+ "lt",
1452
+ "gte",
1453
+ "lte"
1454
+ ].indexOf(rule) !== -1;
1455
+ }
1456
+ /**
1457
+ * Check if rule implies that the field is required
1458
+ */
1459
+ function isImplicitRule(rule) {
1460
+ if (rule instanceof IRuleContract && rule.__isImplicitRule === true) return true;
1461
+ if (typeof rule === "string") return implicitRues.indexOf(rule) !== -1;
1462
+ return false;
1463
+ }
1464
+ /**
1465
+ * Add a new implicit rule
1466
+ */
1467
+ function addImplicitRule(rule) {
1468
+ implicitRues.push(rule);
1469
+ }
1470
+ /**
1471
+ * Returns the numeric rules
1472
+ */
1473
+ function getNumericRules() {
1474
+ return ["numeric", "integer"];
1475
+ }
1476
+ /**
1477
+ * Check if the rule is numeric
1478
+ */
1479
+ function isNumericRule(rule) {
1480
+ return getNumericRules().indexOf(rule) !== -1;
1481
+ }
1482
+ /**
1483
+ * Determine if a comparison passes between the given values.
1484
+ */
1485
+ function compare(first, second, operator, strict = false) {
1486
+ switch (operator) {
1487
+ case "<": return first < second;
1488
+ case ">": return first > second;
1489
+ case "<=": return first <= second;
1490
+ case ">=": return first >= second;
1491
+ case "=":
1492
+ if (strict === true) return first === second;
1493
+ return first == second;
1494
+ default: throw "Invalid operator parameter";
1495
+ }
1496
+ }
1497
+ /**
1498
+ * Convert the given values to boolean if they are string "true" / "false".
1499
+ */
1500
+ function convertValuesToBoolean(values) {
1501
+ return values.map((value) => {
1502
+ if (value === "true") return true;
1503
+ else if (value === "false") return false;
1504
+ return value;
1505
+ });
1506
+ }
1507
+ /**
1508
+ * Convert the given values to numbers if they are numbers in a string "1", "2"
1509
+ */
1510
+ function convertValuesToNumber(values) {
1511
+ return values.map((value) => {
1512
+ if (!isNaN(Number(value))) return Number(value);
1513
+ return value;
1514
+ });
1515
+ }
1516
+ /**
1517
+ * Convert the given values to null if they have null values in a string "null", "NULL"
1518
+ */
1519
+ function convertValuesToNull(values) {
1520
+ return values.map((value) => {
1521
+ if (value.toLowerCase() === "null") return null;
1522
+ return value;
1523
+ });
1524
+ }
1525
+
1526
+ //#endregion
1527
+ //#region src/utilities/object.ts
1528
+ /**
1529
+ * Get value at path of object. If the resolved value is undifined, the returned result will be undefined
1530
+ *
1531
+ * @param obj
1532
+ * @param path
1533
+ * @returns
1534
+ */
1535
+ function deepFind(obj, path) {
1536
+ const paths = path.split(".");
1537
+ for (let i = 0; i < paths.length; i++) {
1538
+ if (typeof obj[paths[i]] === "undefined") return;
1539
+ obj = obj[paths[i]];
1480
1540
  }
1481
- /**
1482
- * Validate that an attribute is boolean
1483
- */
1484
- validateBoolean(value, parameters, attribute) {
1485
- if (validationRuleParser.hasRule(attribute, "strict", this.rules)) return typeof value === "boolean";
1486
- return [
1487
- true,
1488
- false,
1489
- 0,
1490
- 1,
1491
- "0",
1492
- "1"
1493
- ].indexOf(value) !== -1;
1541
+ return obj;
1542
+ }
1543
+ /**
1544
+ * Set value at path of object.
1545
+ *
1546
+ * @param target
1547
+ * @param path
1548
+ * @param value
1549
+ */
1550
+ function deepSet(target, path, value) {
1551
+ const paths = typeof path === "string" ? path.split(".") : path;
1552
+ const segment = paths.shift();
1553
+ if (segment === "*") {
1554
+ target = Array.isArray(target) ? target : [];
1555
+ if (paths.length > 0) target.forEach((inner) => deepSet(inner, [...paths], value));
1556
+ else for (let i = 0; i < target.length; i++) target[i] = value;
1557
+ } else if (paths.length > 0 && typeof segment === "string") {
1558
+ if (typeof target[segment] !== "object" || target[segment] === null) target[segment] = {};
1559
+ deepSet(target[segment], paths, value);
1560
+ } else {
1561
+ if (typeof target !== "object" || target === null) target = {};
1562
+ target[segment] = value;
1494
1563
  }
1495
- /**
1496
- * Validate that an attribute has matching confirmation.
1497
- */
1498
- validateConfirmed(value, parameters, attribute) {
1499
- return this.validateSame(value, [`${attribute}_confirmation`]) || this.validateSame(value, [`${attribute}Confirmation`]);
1564
+ }
1565
+ /**
1566
+ * Flatten a multi-dimensional associative array with dots.
1567
+ *
1568
+ * @param obj
1569
+ * @param ignoreRulesArray
1570
+ * @param withBaseObjectType
1571
+ * @returns
1572
+ */
1573
+ function dotify(obj, ignoreRulesArray = false, withBaseObjectType = false) {
1574
+ const res = {};
1575
+ (function recurse(obj, current = "") {
1576
+ for (const key in obj) {
1577
+ const value = obj[key];
1578
+ const newKey = current ? `${current}.${key}` : key;
1579
+ if (value && typeof value === "object" && !isRule(value) && !(value instanceof Date)) if (ignoreRulesArray === true && Array.isArray(value) && isArrayOfRules(value)) res[newKey] = value;
1580
+ else {
1581
+ if (withBaseObjectType) res[newKey] = Array.isArray(value) ? "array" : "object";
1582
+ recurse(value, newKey);
1583
+ }
1584
+ else res[newKey] = value;
1585
+ }
1586
+ })(obj);
1587
+ return res;
1588
+ }
1589
+ /**
1590
+ * Check if objects are deep equal
1591
+ *
1592
+ * @param firstParam
1593
+ * @param secondParam
1594
+ * @returns
1595
+ */
1596
+ function deepEqual(firstParam, secondParam) {
1597
+ const first = dotify(firstParam, false, true);
1598
+ const second = dotify(secondParam, false, true);
1599
+ if (Object.keys(first).length !== Object.keys(second).length) return false;
1600
+ return Object.entries(first).every(([key, value]) => second[key] === value);
1601
+ }
1602
+
1603
+ //#endregion
1604
+ //#region src/utilities/formatMessages.ts
1605
+ /**
1606
+ * Get the message type based on the value. The message type is used essentially for size rules
1607
+ */
1608
+ function getMesageType(value, hasNumericRule = false) {
1609
+ return getValidationMessageType(value, hasNumericRule);
1610
+ }
1611
+ /**
1612
+ * Get the custom message for a rule if exists
1613
+ */
1614
+ function getCustomMessage(attributes, rule, customMessages, messageType, lang) {
1615
+ const [attribute, primaryAttribute] = attributes;
1616
+ const translatedMessages = dotify(Lang.get(lang)["custom"] || {});
1617
+ const keys = getKeyCombinations(`${attribute}.${rule}`);
1618
+ let allKeys = keys;
1619
+ if (primaryAttribute) {
1620
+ allKeys = [];
1621
+ const primaryAttributeKeys = getKeyCombinations(`${primaryAttribute}.${rule}`);
1622
+ for (let i = 0; i < keys.length; i++) {
1623
+ allKeys.push(keys[i]);
1624
+ if (keys[i] !== primaryAttributeKeys[i]) allKeys.push(primaryAttributeKeys[i]);
1625
+ }
1500
1626
  }
1501
- /**
1502
- * Validate that an attribute is a valid date.
1503
- */
1504
- validateDate(value) {
1505
- return toDate(value) ? true : false;
1627
+ if (isSizeRule(rule)) {
1628
+ allKeys.pop();
1629
+ allKeys.push(`${rule}.${messageType}`);
1630
+ allKeys.push(rule);
1506
1631
  }
1507
- /**
1508
- * Validate that an attribute is equal to another date.
1509
- */
1510
- validateDateEquals(value, paramters) {
1511
- this.requireParameterCount(1, paramters, "date_equals");
1512
- return this.compareDates(value, paramters[0], "=", "date_equals");
1632
+ let key = "";
1633
+ let message = "";
1634
+ for (let i = 0; i < allKeys.length; i++) {
1635
+ key = allKeys[i];
1636
+ if (Object.prototype.hasOwnProperty.call(customMessages, key)) return customMessages[key];
1637
+ message = translatedMessages[key];
1638
+ if (typeof message === "string") return message;
1639
+ }
1640
+ return null;
1641
+ }
1642
+ /**
1643
+ * Get the validation message for an attribute and rule.
1644
+ */
1645
+ function getMessage(attributes, rule, value, customMessages, hasNumericRule, lang) {
1646
+ const inlineMessage = getCustomMessage(attributes, rule, customMessages, getMesageType(value, hasNumericRule), lang);
1647
+ if (inlineMessage) return inlineMessage;
1648
+ const validationMessages = Lang.get(lang);
1649
+ if (isSizeRule(rule) === true) return validationMessages[rule][getMesageType(value, hasNumericRule)];
1650
+ return validationMessages[rule] || "";
1651
+ }
1652
+ /**
1653
+ * Convert a string to snake case.
1654
+ */
1655
+ function toSnakeCase(string) {
1656
+ return string.split(/ |\B(?=[A-Z])/).map((word) => word.toLowerCase()).join("_");
1657
+ }
1658
+ /**
1659
+ * Get the formatted name of the attribute
1660
+ */
1661
+ function getFormattedAttribute(attribute) {
1662
+ return toSnakeCase(getPrimaryKeyFromPath(attribute)).replace(/_/g, " ").trim();
1663
+ }
1664
+ /**
1665
+ * Get the combinations of keys from a main key. For example if the main key is 'user.info.name',
1666
+ * the combination will be [user.info.name, info.name, name]
1667
+ */
1668
+ function getKeyCombinations(key) {
1669
+ const combinations = [key];
1670
+ const splittedKey = key.split(".");
1671
+ while (splittedKey.length > 1) {
1672
+ splittedKey.shift();
1673
+ combinations.push(splittedKey.join("."));
1513
1674
  }
1675
+ return combinations;
1676
+ }
1677
+ /**
1678
+ * The purpose of this method if to get the primary key associated with a path
1679
+ * For example the primary key for path 'user.info.email' will be 'email'
1680
+ */
1681
+ function getPrimaryKeyFromPath(path) {
1682
+ const splittedPath = path.split(".");
1683
+ if (splittedPath.length <= 1) return path;
1684
+ const key = splittedPath.pop();
1685
+ if (isNaN(parseInt(key)) === false) return getPrimaryKeyFromPath(splittedPath.join("."));
1686
+ return key;
1687
+ }
1688
+
1689
+ //#endregion
1690
+ //#region src/validators/errorBag.ts
1691
+ var ErrorBag = class ErrorBag {
1514
1692
  /**
1515
- * Validate that an attribute was "declined".
1516
- *
1517
- * This validation rule implies the attribute is "required".
1693
+ * All of the registered messages.
1518
1694
  */
1519
- validateDeclined(value) {
1520
- return this.validateRequired(value) && [
1521
- "no",
1522
- "off",
1523
- "0",
1524
- 0,
1525
- false,
1526
- "false"
1527
- ].indexOf(value) !== -1;
1528
- }
1695
+ errors = {};
1529
1696
  /**
1530
- * Validate that an attribute was "declined" when another attribute has a given value.
1697
+ * All Messages
1531
1698
  */
1532
- validateDeclinedIf(value, parameters) {
1533
- this.requireParameterCount(2, parameters, "declined_if");
1534
- const other = deepFind(this.data, parameters[0]);
1535
- if (!other) return true;
1536
- if (parameters.slice(1).indexOf(other) !== -1) return this.validateDeclined(value);
1537
- return true;
1538
- }
1699
+ messages = {};
1539
1700
  /**
1540
- * Validate that an attribute is different from another attribute.
1701
+ * Stores the first error message
1541
1702
  */
1542
- validateDifferent(value, parameters) {
1543
- this.requireParameterCount(1, parameters, "different");
1544
- const other = deepFind(this.data, parameters[0]);
1545
- if (!sameType(value, other)) return true;
1546
- if (value !== null && typeof value === "object") return !deepEqual(value, other);
1547
- return value !== other;
1548
- }
1703
+ firstMessage = "";
1549
1704
  /**
1550
- * Validate that an attribute has a given number of digits.
1705
+ * Specify whether error types should be returned or no
1551
1706
  */
1552
- validateDigits(value, parameters) {
1553
- this.requireParameterCount(1, parameters, "digits");
1554
- if (isInteger(parameters[0]) === false) throw "Validation rule digits requires the parameter to be an integer.";
1555
- if (parameters[0] <= 0) throw "Validation rule digits requires the parameter to be an integer greater than 0.";
1556
- if (typeof value !== "string" && typeof value !== "number") return false;
1557
- value = value.toString();
1558
- return /^\d+$/.test(value) && value.length === parseInt(parameters[0]);
1707
+ withErrorTypes = false;
1708
+ constructor(errors = {}, messages = {}, firstMessage = "", withErrorTypes = false) {
1709
+ this.errors = errors;
1710
+ this.messages = messages;
1711
+ this.firstMessage = firstMessage;
1712
+ this.withErrorTypes = withErrorTypes;
1559
1713
  }
1560
1714
  /**
1561
- * Validate that an attribute is between a given number of digits.
1715
+ * Set withErrorTypes attribute to true
1562
1716
  */
1563
- validateDigitsBetween(value, parameters) {
1564
- this.requireParameterCount(2, parameters, "digits_between");
1565
- let [min, max] = parameters;
1566
- if (isInteger(min) === false || isInteger(max) === false) throw "Validation rule digits_between requires both parameters to be integers.";
1567
- min = parseInt(min);
1568
- max = parseInt(max);
1569
- if (min <= 0 || max <= 0) throw "Validation rule digits_between requires the parameters to be an integer greater than 0.";
1570
- if (min >= max) throw "Validation rule digits_between requires the max param to be greater than the min param.";
1571
- if (typeof value !== "string" && typeof value !== "number") return false;
1572
- value = value.toString();
1573
- const valueLength = value.length;
1574
- return /^\d+$/.test(value) && valueLength >= min && valueLength <= max;
1717
+ addErrorTypes() {
1718
+ this.withErrorTypes = true;
1719
+ return this;
1575
1720
  }
1576
1721
  /**
1577
- * Validate that an attribute is a valid email address.
1722
+ * Add new recodrs to the errors and messages objects
1578
1723
  */
1579
- validateEmail(value) {
1580
- if (typeof value !== "string") return false;
1581
- /**
1582
- * Max allowed length for a top-level-domain is 24 characters.
1583
- * reference to list of top-level-domains: https://data.iana.org/TLD/tlds-alpha-by-domain.txt
1584
- */
1585
- return value.toLowerCase().match(/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,24})+$/) !== null;
1724
+ add(key, error) {
1725
+ if (Array.isArray(this.errors[key]) && Array.isArray(this.messages[key])) {
1726
+ this.errors[key].push(error);
1727
+ this.messages[key].push(error.message);
1728
+ } else {
1729
+ this.errors[key] = [error];
1730
+ this.messages[key] = [error.message];
1731
+ }
1732
+ this.firstMessage = this.firstMessage || error.message;
1586
1733
  }
1587
1734
  /**
1588
- * Validate the attribute ends with a given substring.
1735
+ * Get the first error related to a specific key
1589
1736
  */
1590
- validateEndsWith(value, parameters) {
1591
- this.requireParameterCount(1, parameters, "ends_with");
1592
- if (typeof value !== "string") throw "The field under validation must be a string";
1593
- const valueLength = value.length;
1594
- for (let i = 0; i < parameters.length; i++) if (typeof parameters[i] === "string" && value.indexOf(parameters[i], valueLength - parameters[i].length) !== -1) return true;
1595
- return false;
1737
+ first(key = null) {
1738
+ if (!key) return this.firstMessage;
1739
+ if (this.has(key)) return this.messages[key][0];
1740
+ return "";
1596
1741
  }
1597
1742
  /**
1598
- * Validate that two attributes match.
1743
+ * Get the error messages keys
1599
1744
  */
1600
- validateSame(value, paramaters) {
1601
- this.requireParameterCount(1, paramaters, "same");
1602
- const other = deepFind(this.data, paramaters[0]);
1603
- if (!sameType(value, other)) return false;
1604
- if (value !== null && typeof value === "object") return deepEqual(value, other);
1605
- return value === other;
1745
+ keys() {
1746
+ return Object.keys(this.messages);
1606
1747
  }
1607
1748
  /**
1608
- * Validate the size of an attribute.
1749
+ * Get all the messages related to a specific key
1609
1750
  */
1610
- validateSize(value, parameters, attribute) {
1611
- this.requireParameterCount(1, parameters, "size");
1612
- return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) === Number(parameters[0]);
1751
+ get(key, withErrorTypes = this.withErrorTypes) {
1752
+ if (!this.has(key)) return [];
1753
+ if (withErrorTypes) return this.errors[key];
1754
+ return this.messages[key];
1613
1755
  }
1614
1756
  /**
1615
- * Validate Optinial attributes. Always return true, just lets us put sometimes in rule.
1757
+ * Check if key exists in messages
1616
1758
  */
1617
- validateSometimes() {
1618
- return true;
1759
+ has(key) {
1760
+ return this.messages[key] && this.messages[key].length > 0 ? true : false;
1619
1761
  }
1620
1762
  /**
1621
- * Validate the attribute starts with a given substring.
1763
+ * Get all error messages
1622
1764
  */
1623
- validateStartsWith(value, parameters) {
1624
- this.requireParameterCount(1, parameters, "starts_with");
1625
- if (typeof value !== "string") throw "The field under validation must be a string";
1626
- for (let i = 0; i < parameters.length; i++) if (typeof parameters[i] === "string" && value.substr(0, parameters[i].length) === parameters[i]) return true;
1627
- return false;
1765
+ all(allMessages = true, withErrorTypes = this.withErrorTypes) {
1766
+ const messages = withErrorTypes ? { ...this.errors } : { ...this.messages };
1767
+ if (!allMessages) Object.keys(messages).map((attribute) => messages[attribute] = messages[attribute][0]);
1768
+ return messages;
1628
1769
  }
1629
1770
  /**
1630
- * Validate that a required attribute exists
1771
+ * Remove error messages
1631
1772
  */
1632
- validateRequired(value) {
1633
- if (value === null || typeof value === "undefined") return false;
1634
- else if (typeof value === "string" && value.trim() === "") return false;
1635
- else if (Array.isArray(value) && value.length < 1) return false;
1636
- else if (typeof value === "object" && Object.keys(value).length < 1) return false;
1637
- return true;
1773
+ clear(keys) {
1774
+ if (keys.length === 0) {
1775
+ this.errors = {};
1776
+ this.messages = {};
1777
+ this.firstMessage = "";
1778
+ return this;
1779
+ }
1780
+ keys.forEach((key) => {
1781
+ if (Object.prototype.hasOwnProperty.call(this.messages, key)) {
1782
+ delete this.messages[key];
1783
+ delete this.errors[key];
1784
+ }
1785
+ });
1786
+ this.firstMessage = "";
1787
+ if (this.keys().length > 0) this.firstMessage = this.messages[Object.keys(this.messages)[0]][0];
1788
+ return this;
1638
1789
  }
1639
1790
  /**
1640
- * Validate that an attribute exists when another atteribute has a given value
1791
+ * Clone ErrorBag Instance
1641
1792
  */
1642
- validateRequiredIf(value, parameters) {
1643
- this.requireParameterCount(2, parameters, "required_if");
1644
- const other = deepFind(this.data, parameters[0]);
1645
- if (typeof other === "undefined") return true;
1646
- if (this.parseDependentRuleParameters(other, parameters).indexOf(other) !== -1) return this.validateRequired(value);
1647
- return true;
1793
+ clone() {
1794
+ return new ErrorBag({ ...this.errors }, { ...this.messages }, this.firstMessage, this.withErrorTypes);
1648
1795
  }
1796
+ };
1797
+
1798
+ //#endregion
1799
+ //#region src/Rules/password.ts
1800
+ var Password$1 = class Password$1 extends IRuleContract {
1649
1801
  /**
1650
- * Validate that an attribute exists when another attribute does not have a given value.
1802
+ * The validator performing the validation.
1651
1803
  */
1652
- validateRequiredUnless(value, parameters) {
1653
- this.requireParameterCount(2, parameters, "required_unless");
1654
- let other = deepFind(this.data, parameters[0]);
1655
- other = typeof other === "undefined" ? null : other;
1656
- if (this.parseDependentRuleParameters(other, parameters).indexOf(other) === -1) return this.validateRequired(value);
1657
- return true;
1658
- }
1804
+ validator;
1659
1805
  /**
1660
- * Validate that an attribute exists when any other attribute exists.
1806
+ * The minimum size of the password.
1661
1807
  */
1662
- validateRequiredWith(value, parameters) {
1663
- if (!this.allFailingRequired(parameters)) return this.validateRequired(value);
1664
- return true;
1665
- }
1808
+ minLength = 8;
1666
1809
  /**
1667
- * Validate that an attribute exists when all other attributes exist.
1810
+ * The min amount of lower case letters required in the password
1668
1811
  */
1669
- validateRequiredWithAll(value, parameters) {
1670
- if (!this.anyFailingRequired(parameters)) return this.validateRequired(value);
1671
- return true;
1672
- }
1812
+ minLowerCase = 0;
1673
1813
  /**
1674
- * Validate that an attribute exists when another attribute does not.
1814
+ * The min amount of uppercase letters required in the password
1675
1815
  */
1676
- validateRequiredWithout(value, parameters) {
1677
- if (this.anyFailingRequired(parameters)) return this.validateRequired(value);
1678
- return true;
1679
- }
1816
+ minUpperCase = 0;
1680
1817
  /**
1681
- * Validate that an attribute exists when all other attributes do not.
1818
+ * The min amount of letters required in the password
1682
1819
  */
1683
- validateRequiredWithoutAll(value, parameters) {
1684
- if (this.allFailingRequired(parameters)) return this.validateRequired(value);
1685
- return true;
1686
- }
1820
+ minLetters = 0;
1687
1821
  /**
1688
- * Determine if any of the given attributes fail the required test.
1822
+ * The min amount of letters required in the password
1689
1823
  */
1690
- anyFailingRequired(attributes) {
1691
- for (let i = 0; i < attributes.length; i++) if (!this.validateRequired(deepFind(this.data, attributes[i]))) return true;
1692
- return false;
1693
- }
1824
+ minNumbers = 0;
1694
1825
  /**
1695
- * Determine if all of the given attributes fail the required test.
1826
+ * The min amount of symbols required in the password
1696
1827
  */
1697
- allFailingRequired(attributes) {
1698
- for (let i = 0; i < attributes.length; i++) if (this.validateRequired(deepFind(this.data, attributes[i]))) return false;
1699
- return true;
1700
- }
1828
+ minSymbols = 0;
1701
1829
  /**
1702
- * Validate that an attribute is a string.
1830
+ * Additional validation rules that should be merged into the default rules during validation.
1703
1831
  */
1704
- validateString(value) {
1705
- return typeof value === "string";
1706
- }
1832
+ customRules = [];
1707
1833
  /**
1708
- * Validate the size of an attribute is less than a maximum value.
1834
+ * The callback that will generate the "default" version of the password rule.
1709
1835
  */
1710
- validateMax(value, parameters, attribute) {
1711
- this.requireParameterCount(1, parameters, "max");
1712
- if (isNaN(parameters[0])) throw "Validation rule max requires parameter to be a number.";
1713
- return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) <= Number(parameters[0]);
1836
+ static defaultCallback;
1837
+ /**
1838
+ * Create a new instance of the password class
1839
+ */
1840
+ static create() {
1841
+ return new Password$1();
1714
1842
  }
1715
1843
  /**
1716
- * Validate the size of an attribute is greater than a minimum value.
1844
+ * Set the minimum length of the password
1717
1845
  */
1718
- validateMin(value, parameters, attribute) {
1719
- this.requireParameterCount(1, parameters, "min");
1720
- if (isNaN(parameters[0])) throw "Validation rule min requires parameter to be a number.";
1721
- return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) >= Number(parameters[0]);
1846
+ min(min) {
1847
+ this.minLength = min;
1848
+ return this;
1722
1849
  }
1723
1850
  /**
1724
- * Validate that an attribute is numeric.
1851
+ * Set the min amount of letters required in the password
1725
1852
  */
1726
- validateNumeric(value, parameters, attribute) {
1727
- if (validationRuleParser.hasRule(attribute, "strict", this.rules) && typeof value !== "number") return false;
1728
- return value !== null && isNaN(value) === false;
1853
+ letters(letters = 1) {
1854
+ this.minLetters = letters;
1855
+ return this;
1729
1856
  }
1730
1857
  /**
1731
- * Validate that an attribute is an object
1858
+ * Set the min amount of upper and lower case letters required in the password
1732
1859
  */
1733
- validateObject(value) {
1734
- return isObject(value);
1860
+ mixedCase(upperCase = 1, lowerCase = 1) {
1861
+ this.minUpperCase = upperCase;
1862
+ this.minLowerCase = lowerCase;
1863
+ return this;
1735
1864
  }
1736
1865
  /**
1737
- * Validate that an attribute exists even if not filled.
1866
+ * Set the min amount of numbers required in the password
1738
1867
  */
1739
- validatePresent(value, parameters, attribute) {
1740
- return typeof deepFind(this.data, attribute) !== "undefined";
1868
+ numbers(numbers = 1) {
1869
+ this.minNumbers = numbers;
1870
+ return this;
1741
1871
  }
1742
1872
  /**
1743
- * Validate that an attribute is an integer.
1873
+ * Set the min amount of symbols required in the password
1744
1874
  */
1745
- validateInteger(value, parameters, attribute) {
1746
- if (validationRuleParser.hasRule(attribute, "strict", this.rules) && typeof value !== "number") return false;
1747
- return isInteger(value);
1875
+ symbols(symbols = 1) {
1876
+ this.minSymbols = symbols;
1877
+ return this;
1748
1878
  }
1749
1879
  /**
1750
- * Validate that the attribute is a valid JSON string
1880
+ * Determine if the validation rule passes.
1751
1881
  */
1752
- validateJson(value) {
1753
- if (!value || typeof value !== "string") return false;
1754
- try {
1755
- JSON.parse(value);
1756
- } catch {
1757
- return false;
1882
+ passes(value, attribute) {
1883
+ const validator = new BaseValidator(this.data, { [attribute]: [
1884
+ "string",
1885
+ `min:${this.minLength}`,
1886
+ ...this.customRules
1887
+ ] }, this.validator.customMessages, this.validator.customAttributes).setLang(this.lang);
1888
+ if (!validator.validate()) {
1889
+ const errors = validator.errors().addErrorTypes().get(attribute);
1890
+ for (const key in errors) this.validator.errors().add(attribute, errors[key]);
1891
+ }
1892
+ if (typeof value !== "string") value = "";
1893
+ let pattern;
1894
+ const formattedAttribute = this.validator.getDisplayableAttribute(attribute);
1895
+ if (this.minLowerCase) {
1896
+ pattern = this.minLowerCase === 1 ? /\p{Ll}/u : new RegExp(`(.*\\p{Ll}){${this.minLowerCase}}.*`, "u");
1897
+ if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
1898
+ error_type: "min_lower_case",
1899
+ message: this.trans(`password.${this.minLowerCase === 1 ? "lower_case" : "lower_cases"}`, {
1900
+ attribute: formattedAttribute,
1901
+ amount: this.minLowerCase
1902
+ })
1903
+ });
1904
+ }
1905
+ if (this.minUpperCase) {
1906
+ pattern = this.minUpperCase === 1 ? /\p{Lu}/u : new RegExp(`(.*\\p{Lu}){${this.minUpperCase}}.*`, "u");
1907
+ if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
1908
+ error_type: "min_upper_case",
1909
+ message: this.trans(`password.${this.minUpperCase === 1 ? "upper_case" : "upper_cases"}`, {
1910
+ attribute: formattedAttribute,
1911
+ amount: this.minUpperCase
1912
+ })
1913
+ });
1914
+ }
1915
+ if (this.minLetters) {
1916
+ pattern = this.minLetters === 1 ? /\p{L}/u : new RegExp(`(.*\\p{L}){${this.minLetters}}.*`, "u");
1917
+ if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
1918
+ error_type: "min_letters",
1919
+ message: this.trans(`password.${this.minLetters === 1 ? "letter" : "letters"}`, {
1920
+ attribute: formattedAttribute,
1921
+ amount: this.minLetters
1922
+ })
1923
+ });
1924
+ }
1925
+ if (this.minNumbers) {
1926
+ pattern = this.minNumbers === 1 ? /\p{N}/u : new RegExp(`(.*\\p{N}){${this.minNumbers}}.*`, "u");
1927
+ if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
1928
+ error_type: "min_numbers",
1929
+ message: this.trans(`password.${this.minNumbers === 1 ? "number" : "numbers"}`, {
1930
+ attribute: formattedAttribute,
1931
+ amount: this.minNumbers
1932
+ })
1933
+ });
1934
+ }
1935
+ if (this.minSymbols) {
1936
+ pattern = this.minSymbols === 1 ? /\p{Z}|\p{S}|\p{P}/u : new RegExp(`(.*(\\p{Z}|\\p{S}|\\p{P})){${this.minSymbols}}.*`, "u");
1937
+ if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
1938
+ error_type: "min_symbols",
1939
+ message: this.trans(`password.${this.minSymbols === 1 ? "symbol" : "symbols"}`, {
1940
+ attribute: formattedAttribute,
1941
+ amount: this.minSymbols
1942
+ })
1943
+ });
1758
1944
  }
1945
+ if (this.validator.errors().has(attribute)) return false;
1759
1946
  return true;
1760
1947
  }
1761
1948
  /**
1762
- * Validate that an attribute is greater than another attribute.
1763
- */
1764
- validateGt(value, parameters, attribute) {
1765
- this.requireParameterCount(1, parameters, "gt");
1766
- if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
1767
- const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
1768
- if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) > compartedToValue;
1769
- if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
1770
- return getSize(value) > getSize(compartedToValue);
1771
- }
1772
- /**
1773
- * Validate that an attribute is greater than or equal another attribute.
1949
+ * Specify additional validation rules that should be merged with the default rules during validation.
1774
1950
  */
1775
- validateGte(value, parameters, attribute) {
1776
- this.requireParameterCount(1, parameters, "gte");
1777
- if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
1778
- const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
1779
- if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) >= compartedToValue;
1780
- if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
1781
- return getSize(value) >= getSize(compartedToValue);
1951
+ rules(rules) {
1952
+ this.customRules = rules;
1953
+ return this;
1782
1954
  }
1783
1955
  /**
1784
- * Validate that an attribute is less than another attribute.
1956
+ * Get all the validation error messages related to the password
1785
1957
  */
1786
- validateLt(value, parameters, attribute) {
1787
- this.requireParameterCount(1, parameters, "lt");
1788
- if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
1789
- const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
1790
- if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) < compartedToValue;
1791
- if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
1792
- return getSize(value) < getSize(compartedToValue);
1958
+ getMessage() {
1959
+ return {};
1793
1960
  }
1794
1961
  /**
1795
- * Validate that an attribute is less than or equal another attribute.
1962
+ * Set the validator instance used to validate the password
1796
1963
  */
1797
- validateLte(value, parameters, attribute) {
1798
- this.requireParameterCount(1, parameters, "lte");
1799
- if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
1800
- const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
1801
- if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) <= compartedToValue;
1802
- if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
1803
- return getSize(value) <= getSize(compartedToValue);
1964
+ setValidator(validator) {
1965
+ this.validator = validator;
1966
+ return this;
1804
1967
  }
1805
1968
  /**
1806
- * Validate an attribute is contained within a list of values.
1969
+ * Set the default callback to be used for determining a password's default rules.
1807
1970
  */
1808
- validateIn(value, parameters) {
1809
- this.requireParameterCount(1, parameters, "in");
1810
- if (Array.isArray(value)) {
1811
- for (let index = 0; index < value.length; index++) if (typeof value[index] !== "number" && typeof value[index] !== "string") return false;
1812
- return value.filter((element) => parameters.indexOf(element.toString()) === -1).length === 0;
1971
+ static setDefault(callback = null) {
1972
+ if (callback instanceof Password$1) {
1973
+ this.defaultCallback = callback;
1974
+ return;
1813
1975
  }
1814
- if (typeof value !== "number" && typeof value !== "string") return false;
1815
- return parameters.indexOf(value.toString()) !== -1;
1816
- }
1817
- /**
1818
- * "Indicate" validation should pass if value is null
1819
- *
1820
- * Always returns true, just lets us put "nullable" in rules.
1821
- */
1822
- validateNullable() {
1823
- return true;
1976
+ if (typeof callback !== "function") throw "The given callback should be callable";
1977
+ this.defaultCallback = callback;
1824
1978
  }
1825
1979
  /**
1826
- * Validate an attribute is not contained within a list of values.
1980
+ * Get the default configuration of the password rule.
1827
1981
  */
1828
- validateNotIn(value, parameters) {
1829
- this.requireParameterCount(1, parameters, "not_in");
1830
- const valuesToCheck = [];
1831
- if (Array.isArray(value)) {
1832
- for (let index = 0; index < value.length; index++) if (typeof value[index] === "number" || typeof value[index] === "string") valuesToCheck.push(value[index]);
1833
- if (valuesToCheck.length === 0) return true;
1834
- return valuesToCheck.filter((element) => parameters.indexOf(element.toString()) !== -1).length === 0;
1835
- }
1836
- if (typeof value !== "number" && typeof value !== "string") return true;
1837
- return parameters.indexOf(value.toString()) === -1;
1982
+ static default() {
1983
+ const password = typeof this.defaultCallback === "function" ? this.defaultCallback() : this.defaultCallback;
1984
+ return password instanceof IRuleContract ? password : Password$1.create().min(8);
1838
1985
  }
1986
+ };
1987
+
1988
+ //#endregion
1989
+ //#region src/utilities/build.ts
1990
+ function buildValidationMethodName(rule) {
1991
+ if (!rule) return rule;
1992
+ return rule.split("_").map((rule) => `${rule[0].toUpperCase()}${rule.slice(1)}`).join("");
1993
+ }
1994
+
1995
+ //#endregion
1996
+ //#region src/payloads/replaceAttributePayload.ts
1997
+ var replaceAttributePayload = class {
1839
1998
  /**
1840
- * Always returns true - this method will be used in conbination with other rules
1999
+ * Stores the data object
1841
2000
  */
1842
- validateStrict() {
1843
- return true;
1844
- }
2001
+ data;
1845
2002
  /**
1846
- * Validate that an attribute is a valid URL.
2003
+ * The message in which attributes will be replaced
1847
2004
  */
1848
- validateUrl(value) {
1849
- if (typeof value !== "string") return false;
1850
- return new RegExp("^(https?:\\/\\/)?((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|localhost|((\\d{1,3}\\.){3}\\d{1,3}))(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*(\\?[;&a-z\\d%_.~+=-]*)?(\\#[-a-z\\d_]*)?$", "i").test(value);
1851
- }
2005
+ message;
1852
2006
  /**
1853
- * Determine if a comparison passes between the given values.
2007
+ * Parameters that will be used to replace the attributes
1854
2008
  */
1855
- compareDates(value, parameter, operator, rule) {
1856
- value = toDate(value);
1857
- if (!value) throw `Validation rule ${rule} requires the field under valation to be a date.`;
1858
- const compartedToValue = toDate(deepFind(this.data, parameter) || parameter);
1859
- if (!compartedToValue) throw `Validation rule ${rule} requires the parameter to be a date.`;
1860
- return compare(value.getTime(), compartedToValue.getTime(), operator);
1861
- }
2009
+ parameters;
1862
2010
  /**
1863
- * Require a certain number of parameters to be present
2011
+ * Flag that identifies wether the numeric rule exists or not
1864
2012
  */
1865
- requireParameterCount(count, parameters, rule) {
1866
- if (parameters.length < count) throw `Validation rule ${rule} requires at least ${count} parameters.`;
1867
- }
2013
+ hasNumericRule;
1868
2014
  /**
1869
- * Prepare the values for validation
2015
+ * The function that will be used to format attributes
1870
2016
  */
1871
- parseDependentRuleParameters(other, parameters) {
1872
- let values = parameters.slice(1);
1873
- if (other === null) values = convertValuesToNull(values);
1874
- if (typeof other === "number") values = convertValuesToNumber(values);
1875
- if (typeof other === "boolean") values = convertValuesToBoolean(values);
1876
- return values;
2017
+ getDisplayableAttribute;
2018
+ constructor(data, message, parameters, hasNumericRule, getDisplayableAttribute) {
2019
+ this.data = data;
2020
+ this.message = message;
2021
+ this.parameters = parameters;
2022
+ this.hasNumericRule = hasNumericRule;
2023
+ this.getDisplayableAttribute = getDisplayableAttribute;
1877
2024
  }
1878
2025
  };
1879
2026
 
1880
2027
  //#endregion
1881
2028
  //#region src/BaseValidator.ts
1882
- var BaseValidator = class {
2029
+ var BaseValidator = class BaseValidator {
1883
2030
  /**
1884
2031
  * The lang used to return error messages
1885
2032
  */
@@ -1920,6 +2067,10 @@ var BaseValidator = class {
1920
2067
  * Object of custom attribute name;
1921
2068
  */
1922
2069
  customAttributes;
2070
+ /**
2071
+ * Arbitrary per-validator context for plugins.
2072
+ */
2073
+ context = {};
1923
2074
  constructor(data, rules, customMessages = {}, customAttributes = {}) {
1924
2075
  this.data = data;
1925
2076
  this.customMessages = dotify(customMessages);
@@ -1929,6 +2080,18 @@ var BaseValidator = class {
1929
2080
  this.addRules(rules);
1930
2081
  this.messages = new ErrorBag();
1931
2082
  }
2083
+ static use(plugin) {
2084
+ usePlugin(plugin);
2085
+ return this;
2086
+ }
2087
+ static useContext(context = {}) {
2088
+ useValidatorContext(context);
2089
+ return this;
2090
+ }
2091
+ use(plugin) {
2092
+ BaseValidator.use(plugin);
2093
+ return this;
2094
+ }
1932
2095
  setData(data) {
1933
2096
  this.data = data;
1934
2097
  this.addRules(this.initalRules);
@@ -1943,24 +2106,78 @@ var BaseValidator = class {
1943
2106
  this.lang = lang;
1944
2107
  return this;
1945
2108
  }
2109
+ /**
2110
+ * Set the validator's context.
2111
+ */
2112
+ withContext(context = {}) {
2113
+ this.context = context;
2114
+ return this;
2115
+ }
2116
+ /**
2117
+ * Get the validator's context.
2118
+ * This is useful for custom rules that need access to additional data or services.
2119
+ *
2120
+ * @returns The current context object
2121
+ */
2122
+ getContext() {
2123
+ return {
2124
+ ...getValidatorContext(),
2125
+ ...this.context
2126
+ };
2127
+ }
2128
+ /**
2129
+ * Get the current language used by the validator.
2130
+ *
2131
+ * @returns
2132
+ */
1946
2133
  getLang() {
1947
2134
  return this.lang;
1948
2135
  }
2136
+ /**
2137
+ * Set custom error messages for the validator.
2138
+ *
2139
+ * @param customMessages
2140
+ * @returns
2141
+ */
1949
2142
  setCustomMessages(customMessages = {}) {
1950
2143
  this.customMessages = dotify(customMessages);
1951
2144
  return this;
1952
2145
  }
2146
+ /**
2147
+ * Set custom attribute names for the validator.
2148
+ *
2149
+ * @param customAttributes
2150
+ * @returns
2151
+ */
1953
2152
  setCustomAttributes(customAttributes = {}) {
1954
2153
  this.customAttributes = dotify(customAttributes);
1955
2154
  return this;
1956
2155
  }
2156
+ /**
2157
+ * Set whether the validator should stop validating after the first failure.
2158
+ *
2159
+ * @param stopOnFirstFailure
2160
+ * @returns
2161
+ */
1957
2162
  stopOnFirstFailure(stopOnFirstFailure = true) {
1958
2163
  this.stopOnFirstFailureFlag = stopOnFirstFailure;
1959
2164
  return this;
1960
2165
  }
2166
+ /**
2167
+ * Get the error messages related to the validation.
2168
+ *
2169
+ * @returns
2170
+ */
1961
2171
  errors() {
1962
2172
  return this.messages;
1963
2173
  }
2174
+ /**
2175
+ * Clear the error messages for the given keys.
2176
+ * If no keys are provided, all error messages will be cleared.
2177
+ *
2178
+ * @param keys The keys of the error messages to clear
2179
+ * @returns The updated ErrorBag instance
2180
+ */
1964
2181
  clearErrors(keys = []) {
1965
2182
  this.messages = this.messages.clear(keys).clone();
1966
2183
  return this.messages;
@@ -1985,7 +2202,7 @@ var BaseValidator = class {
1985
2202
  */
1986
2203
  validate(key = "", value = void 0) {
1987
2204
  if (!isObject(this.data)) throw "The data attribute must be an object";
1988
- this.validateAttributes = new validateAttributes(this.data, this.rules);
2205
+ this.validateAttributes = new validateAttributes(this.data, this.rules, this.getContext());
1989
2206
  if (!key) {
1990
2207
  this.runAllValidations();
1991
2208
  return this.messages.keys().length === 0;
@@ -1999,7 +2216,7 @@ var BaseValidator = class {
1999
2216
  */
2000
2217
  async validateAsync(key = "", value = void 0) {
2001
2218
  if (!isObject(this.data)) throw "The data attribute must be an object";
2002
- this.validateAttributes = new validateAttributes(this.data, this.rules);
2219
+ this.validateAttributes = new validateAttributes(this.data, this.rules, this.getContext());
2003
2220
  if (!key) {
2004
2221
  await this.runAllValidationsAsync();
2005
2222
  return this.messages.keys().length === 0;
@@ -2010,6 +2227,9 @@ var BaseValidator = class {
2010
2227
  }
2011
2228
  /**
2012
2229
  * Get the displayable name of the attribute.
2230
+ *
2231
+ * @param attribute
2232
+ * @returns
2013
2233
  */
2014
2234
  getDisplayableAttribute(attribute) {
2015
2235
  const primaryAttribute = this.getPrimaryAttribute(attribute);
@@ -2066,7 +2286,7 @@ var BaseValidator = class {
2066
2286
  */
2067
2287
  runAllValidations() {
2068
2288
  this.messages = new ErrorBag();
2069
- this.validateAttributes = new validateAttributes(this.data, this.rules);
2289
+ this.validateAttributes = new validateAttributes(this.data, this.rules, this.getContext());
2070
2290
  for (const property in this.rules) if (this.runValidation(property) === false) break;
2071
2291
  }
2072
2292
  /**
@@ -2074,7 +2294,7 @@ var BaseValidator = class {
2074
2294
  */
2075
2295
  async runAllValidationsAsync() {
2076
2296
  this.messages = new ErrorBag();
2077
- this.validateAttributes = new validateAttributes(this.data, this.rules);
2297
+ this.validateAttributes = new validateAttributes(this.data, this.rules, this.getContext());
2078
2298
  for (const property in this.rules) if (await this.runValidationAsync(property) === false) break;
2079
2299
  }
2080
2300
  /**
@@ -2256,19 +2476,6 @@ var BaseValidator = class {
2256
2476
  //#region src/Contracts/IDatabaseDriver.ts
2257
2477
  var IDatabaseDriver = class {};
2258
2478
 
2259
- //#endregion
2260
- //#region src/Rules/registerRule.ts
2261
- function register(rule, validate, replaceMessage) {
2262
- const method = buildValidationMethodName(rule);
2263
- if (new validateAttributes()[`validate${method}`]) return false;
2264
- validateAttributes.prototype[`validate${method}`] = validate;
2265
- if (typeof replaceMessage === "function") replaceAttributes[`replace${method}`] = ({ message, parameters, data, getDisplayableAttribute }) => replaceMessage(message, parameters, data, getDisplayableAttribute);
2266
- return true;
2267
- }
2268
- function registerImplicit(rule, validate, replaceMessage) {
2269
- if (register(rule, validate, replaceMessage) === true) addImplicitRule(rule);
2270
- }
2271
-
2272
2479
  //#endregion
2273
2480
  //#region src/Rules/in.ts
2274
2481
  var In = class extends BaseRule {
@@ -2559,19 +2766,6 @@ var ExtendedRules = class extends ValidationRule {
2559
2766
  validate() {}
2560
2767
  };
2561
2768
 
2562
- //#endregion
2563
- //#region src/utilities/helpers.ts
2564
- /**
2565
- * Pluralizes a word based on the count.
2566
- *
2567
- * @param word
2568
- * @param count
2569
- * @returns
2570
- */
2571
- const plural = (word, count) => {
2572
- return count === 1 ? word : `${word}s`;
2573
- };
2574
-
2575
2769
  //#endregion
2576
2770
  //#region src/utilities/MessageBag.ts
2577
2771
  var MessageBag = class {
@@ -2802,6 +2996,7 @@ var Validator = class Validator {
2802
2996
  errorBagName = "default";
2803
2997
  registeredCustomRules = [new ExtendedRules()];
2804
2998
  shouldStopOnFirstFailure = false;
2999
+ context = {};
2805
3000
  constructor(data, rules, messages = {}) {
2806
3001
  register("telephone", function(value) {
2807
3002
  return /^\d{3}-\d{3}-\d{4}$/.test(value);
@@ -2823,6 +3018,38 @@ var Validator = class Validator {
2823
3018
  return Validator;
2824
3019
  }
2825
3020
  /**
3021
+ * Set the validator's context.
3022
+ *
3023
+ * @param context
3024
+ * @returns
3025
+ */
3026
+ static useContext(context = {}) {
3027
+ useValidatorContext(context);
3028
+ return this;
3029
+ }
3030
+ /**
3031
+ * Register a plugin with the validator.
3032
+ * Plugins can add rules, messages, and even override existing rules and messages.
3033
+ *
3034
+ * @param plugin The plugin to register
3035
+ * @returns The Validator class for chaining
3036
+ */
3037
+ static use(plugin) {
3038
+ usePlugin(plugin);
3039
+ return this;
3040
+ }
3041
+ /**
3042
+ * Register a plugin with the validator.
3043
+ * Plugins can add rules, messages, and even override existing rules and messages.
3044
+ *
3045
+ * @param plugin The plugin to register
3046
+ * @returns The Validator instance for chaining
3047
+ */
3048
+ use(plugin) {
3049
+ Validator.use(plugin);
3050
+ return this;
3051
+ }
3052
+ /**
2826
3053
  * Run the validator and store results.
2827
3054
  */
2828
3055
  async passes() {
@@ -2863,12 +3090,39 @@ var Validator = class Validator {
2863
3090
  return this;
2864
3091
  }
2865
3092
  /**
3093
+ * Set the validator's context.
3094
+ * This is useful for custom rules that need access to additional data or services.
3095
+ *
3096
+ * @param context
3097
+ * @returns
3098
+ */
3099
+ withContext(context = {}) {
3100
+ this.context = context;
3101
+ return this;
3102
+ }
3103
+ /**
3104
+ * Get the validator's context.
3105
+ * This is useful for custom rules that need access to additional data or services.
3106
+ *
3107
+ * @returns The current context object
3108
+ */
3109
+ getContext() {
3110
+ return {
3111
+ ...getValidatorContext(),
3112
+ ...this.context
3113
+ };
3114
+ }
3115
+ /**
2866
3116
  * Get the data that passed validation.
2867
3117
  */
2868
3118
  validatedData() {
2869
3119
  const validKeys = Object.keys(this.rules);
2870
3120
  const clean = {};
2871
- for (const key of validKeys) if (this.data[key] !== void 0) clean[key] = this.data[key];
3121
+ for (const key of validKeys) {
3122
+ const value = deepFind(this.data, key);
3123
+ const resolvedValue = typeof value !== "undefined" ? value : deepFind(this.getContext().requestFiles ?? {}, key);
3124
+ if (typeof resolvedValue !== "undefined") deepSet(clean, key, resolvedValue);
3125
+ }
2872
3126
  return clean;
2873
3127
  }
2874
3128
  /**
@@ -2984,7 +3238,7 @@ var Validator = class Validator {
2984
3238
  return this;
2985
3239
  }
2986
3240
  async execute() {
2987
- const instance = make().setData(this.data).setRules(this.rules).setCustomMessages(this.#messages).stopOnFirstFailure(this.shouldStopOnFirstFailure);
3241
+ const instance = make().setData(this.data).setRules(this.rules).setCustomMessages(this.#messages).withContext(this.getContext()).stopOnFirstFailure(this.shouldStopOnFirstFailure);
2988
3242
  this.passing = await instance.validateAsync();
2989
3243
  this.executed = true;
2990
3244
  this.instance = instance;
@@ -2999,6 +3253,7 @@ var ValidationException = class ValidationException extends Error {
2999
3253
  validator;
3000
3254
  response;
3001
3255
  status = 422;
3256
+ statusCode = 422;
3002
3257
  errorBag = "default";
3003
3258
  redirectTo;
3004
3259
  name = "ValidationException";
@@ -3101,13 +3356,19 @@ exports.convertValuesToNull = convertValuesToNull;
3101
3356
  exports.convertValuesToNumber = convertValuesToNumber;
3102
3357
  exports.deepEqual = deepEqual;
3103
3358
  exports.deepFind = deepFind;
3359
+ exports.deepFindMessage = deepFindMessage;
3104
3360
  exports.deepSet = deepSet;
3361
+ exports.definePlugin = definePlugin;
3105
3362
  exports.dotify = dotify;
3106
3363
  exports.getFormattedAttribute = getFormattedAttribute;
3107
3364
  exports.getKeyCombinations = getKeyCombinations;
3108
3365
  exports.getMessage = getMessage;
3109
3366
  exports.getNumericRules = getNumericRules;
3110
3367
  exports.getSize = getSize;
3368
+ exports.getValidationMessageType = getValidationMessageType;
3369
+ exports.getValidationSize = getValidationSize;
3370
+ exports.getValidationValueInspector = getValidationValueInspector;
3371
+ exports.getValidatorContext = getValidatorContext;
3111
3372
  exports.isArrayOfRules = isArrayOfRules;
3112
3373
  exports.isImplicitRule = isImplicitRule;
3113
3374
  exports.isInteger = isInteger;
@@ -3122,9 +3383,13 @@ exports.plural = plural;
3122
3383
  exports.regex = regex;
3123
3384
  exports.register = register;
3124
3385
  exports.registerImplicit = registerImplicit;
3386
+ exports.registerValueInspector = registerValueInspector;
3125
3387
  exports.requiredIf = requiredIf;
3126
3388
  exports.ruleIn = ruleIn;
3127
3389
  exports.ruleNotIn = ruleNotIn;
3390
+ exports.runWithValidatorContext = runWithValidatorContext;
3128
3391
  exports.sameType = sameType;
3129
3392
  exports.toDate = toDate;
3130
- exports.toSnakeCase = toSnakeCase;
3393
+ exports.toSnakeCase = toSnakeCase;
3394
+ exports.usePlugin = usePlugin;
3395
+ exports.useValidatorContext = useValidatorContext;