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