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/README.md +15 -0
- package/dist/index.cjs +1492 -1227
- package/dist/index.d.ts +194 -22
- package/dist/index.js +1482 -1227
- package/package.json +13 -1
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 =
|
|
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/
|
|
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
|
|
502
|
+
* Convert value to date instance
|
|
513
503
|
*/
|
|
514
|
-
function
|
|
515
|
-
|
|
516
|
-
|
|
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/
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
function
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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/
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
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
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
function
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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
|
-
|
|
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/
|
|
712
|
-
var
|
|
782
|
+
//#region src/validators/validateAttributes.ts
|
|
783
|
+
var validateAttributes = class {
|
|
713
784
|
/**
|
|
714
|
-
*
|
|
785
|
+
* Stores the data object
|
|
715
786
|
*/
|
|
716
|
-
|
|
787
|
+
data;
|
|
717
788
|
/**
|
|
718
|
-
*
|
|
789
|
+
* Stores the rules object
|
|
719
790
|
*/
|
|
720
|
-
|
|
791
|
+
rules;
|
|
721
792
|
/**
|
|
722
|
-
* Stores
|
|
793
|
+
* Stores validator context that plugins can consume.
|
|
723
794
|
*/
|
|
724
|
-
|
|
795
|
+
context;
|
|
796
|
+
constructor(data = {}, rules = {}, context = {}) {
|
|
797
|
+
this.data = data;
|
|
798
|
+
this.rules = rules;
|
|
799
|
+
this.context = context;
|
|
800
|
+
}
|
|
725
801
|
/**
|
|
726
|
-
*
|
|
802
|
+
* Validate that an attribute was "accepted".
|
|
803
|
+
*
|
|
804
|
+
* This validation rule implies the attribute is "required".
|
|
727
805
|
*/
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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
|
-
*
|
|
817
|
+
* Validate that an attribute was "accepted" when another attribute has a given value.
|
|
737
818
|
*/
|
|
738
|
-
|
|
739
|
-
this.
|
|
740
|
-
|
|
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
|
-
*
|
|
827
|
+
* Validate the date is after a given date.
|
|
744
828
|
*/
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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
|
-
*
|
|
834
|
+
* Validate the date is after or equal a given date.
|
|
757
835
|
*/
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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
|
-
*
|
|
841
|
+
* Validate that an attribute contains only alphabetic characters.
|
|
765
842
|
*/
|
|
766
|
-
|
|
767
|
-
return
|
|
843
|
+
validateAlpha(value) {
|
|
844
|
+
return typeof value === "string" && /^[a-zA-Z]+$/.test(value);
|
|
768
845
|
}
|
|
769
846
|
/**
|
|
770
|
-
*
|
|
847
|
+
* Validate that an attribute contains only alpha-numeric characters, dashes, and underscores.
|
|
771
848
|
*/
|
|
772
|
-
|
|
773
|
-
if (
|
|
774
|
-
|
|
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
|
-
*
|
|
854
|
+
* Validate that an attribute contains only alpha-numeric characters.
|
|
779
855
|
*/
|
|
780
|
-
|
|
781
|
-
|
|
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
|
-
*
|
|
861
|
+
* Validate that an attribute is an array
|
|
785
862
|
*/
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
795
|
-
|
|
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
|
-
*
|
|
880
|
+
* Validate the date is before a given date.
|
|
813
881
|
*/
|
|
814
|
-
|
|
815
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
894
|
+
* Validate the size of an attribute is between a set of values
|
|
832
895
|
*/
|
|
833
|
-
|
|
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
|
-
*
|
|
908
|
+
* Validate that an attribute is boolean
|
|
836
909
|
*/
|
|
837
|
-
|
|
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
|
-
*
|
|
922
|
+
* Validate that an attribute has matching confirmation.
|
|
840
923
|
*/
|
|
841
|
-
|
|
924
|
+
validateConfirmed(value, parameters, attribute) {
|
|
925
|
+
return this.validateSame(value, [`${attribute}_confirmation`]) || this.validateSame(value, [`${attribute}Confirmation`]);
|
|
926
|
+
}
|
|
842
927
|
/**
|
|
843
|
-
*
|
|
928
|
+
* Validate that an attribute is a valid date.
|
|
844
929
|
*/
|
|
845
|
-
|
|
930
|
+
validateDate(value) {
|
|
931
|
+
return toDate(value) ? true : false;
|
|
932
|
+
}
|
|
846
933
|
/**
|
|
847
|
-
*
|
|
934
|
+
* Validate that an attribute is equal to another date.
|
|
848
935
|
*/
|
|
849
|
-
|
|
936
|
+
validateDateEquals(value, paramters) {
|
|
937
|
+
this.requireParameterCount(1, paramters, "date_equals");
|
|
938
|
+
return this.compareDates(value, paramters[0], "=", "date_equals");
|
|
939
|
+
}
|
|
850
940
|
/**
|
|
851
|
-
*
|
|
941
|
+
* Validate that an attribute was "declined".
|
|
942
|
+
*
|
|
943
|
+
* This validation rule implies the attribute is "required".
|
|
852
944
|
*/
|
|
853
|
-
|
|
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
|
-
*
|
|
956
|
+
* Validate that an attribute was "declined" when another attribute has a given value.
|
|
856
957
|
*/
|
|
857
|
-
|
|
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
|
-
*
|
|
966
|
+
* Validate that an attribute is different from another attribute.
|
|
860
967
|
*/
|
|
861
|
-
|
|
862
|
-
|
|
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
|
-
*
|
|
976
|
+
* Validate that an attribute has a given number of digits.
|
|
866
977
|
*/
|
|
867
|
-
|
|
868
|
-
this.
|
|
869
|
-
|
|
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
|
-
*
|
|
987
|
+
* Validate that an attribute is between a given number of digits.
|
|
873
988
|
*/
|
|
874
|
-
|
|
875
|
-
this.
|
|
876
|
-
|
|
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
|
-
*
|
|
1003
|
+
* Validate that an attribute is a valid email address.
|
|
880
1004
|
*/
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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
|
-
*
|
|
1014
|
+
* Validate the attribute ends with a given substring.
|
|
888
1015
|
*/
|
|
889
|
-
|
|
890
|
-
this.
|
|
891
|
-
|
|
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
|
-
*
|
|
1024
|
+
* Validate that two attributes match.
|
|
895
1025
|
*/
|
|
896
|
-
|
|
897
|
-
this.
|
|
898
|
-
|
|
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
|
-
*
|
|
1034
|
+
* Validate the size of an attribute.
|
|
902
1035
|
*/
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
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
|
-
*
|
|
1041
|
+
* Validate Optinial attributes. Always return true, just lets us put sometimes in rule.
|
|
971
1042
|
*/
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
return this;
|
|
1043
|
+
validateSometimes() {
|
|
1044
|
+
return true;
|
|
975
1045
|
}
|
|
976
1046
|
/**
|
|
977
|
-
*
|
|
1047
|
+
* Validate the attribute starts with a given substring.
|
|
978
1048
|
*/
|
|
979
|
-
|
|
980
|
-
|
|
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
|
-
*
|
|
1056
|
+
* Validate that a required attribute exists
|
|
984
1057
|
*/
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
return
|
|
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
|
-
*
|
|
1066
|
+
* Validate that an attribute exists when another atteribute has a given value
|
|
991
1067
|
*/
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
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
|
-
*
|
|
1076
|
+
* Validate that an attribute exists when another attribute does not have a given value.
|
|
1002
1077
|
*/
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
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
|
-
*
|
|
1086
|
+
* Validate that an attribute exists when any other attribute exists.
|
|
1021
1087
|
*/
|
|
1022
|
-
|
|
1088
|
+
validateRequiredWith(value, parameters) {
|
|
1089
|
+
if (!this.allFailingRequired(parameters)) return this.validateRequired(value);
|
|
1090
|
+
return true;
|
|
1091
|
+
}
|
|
1023
1092
|
/**
|
|
1024
|
-
*
|
|
1093
|
+
* Validate that an attribute exists when all other attributes exist.
|
|
1025
1094
|
*/
|
|
1026
|
-
|
|
1095
|
+
validateRequiredWithAll(value, parameters) {
|
|
1096
|
+
if (!this.anyFailingRequired(parameters)) return this.validateRequired(value);
|
|
1097
|
+
return true;
|
|
1098
|
+
}
|
|
1027
1099
|
/**
|
|
1028
|
-
*
|
|
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
|
-
*
|
|
1107
|
+
* Validate that an attribute exists when all other attributes do not.
|
|
1033
1108
|
*/
|
|
1034
|
-
|
|
1109
|
+
validateRequiredWithoutAll(value, parameters) {
|
|
1110
|
+
if (this.allFailingRequired(parameters)) return this.validateRequired(value);
|
|
1111
|
+
return true;
|
|
1112
|
+
}
|
|
1035
1113
|
/**
|
|
1036
|
-
*
|
|
1114
|
+
* Determine if any of the given attributes fail the required test.
|
|
1037
1115
|
*/
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
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
|
-
*
|
|
1121
|
+
* Determine if all of the given attributes fail the required test.
|
|
1198
1122
|
*/
|
|
1199
|
-
|
|
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
|
-
*
|
|
1128
|
+
* Validate that an attribute is a string.
|
|
1202
1129
|
*/
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
super();
|
|
1206
|
-
this.callback = callback;
|
|
1130
|
+
validateString(value) {
|
|
1131
|
+
return typeof value === "string";
|
|
1207
1132
|
}
|
|
1208
1133
|
/**
|
|
1209
|
-
*
|
|
1134
|
+
* Validate the size of an attribute is less than a maximum value.
|
|
1210
1135
|
*/
|
|
1211
|
-
|
|
1212
|
-
this.
|
|
1213
|
-
|
|
1214
|
-
|
|
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
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
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
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
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
|
-
*
|
|
1157
|
+
* Validate that an attribute is an object
|
|
1335
1158
|
*/
|
|
1336
|
-
|
|
1159
|
+
validateObject(value) {
|
|
1160
|
+
return isObject(value);
|
|
1161
|
+
}
|
|
1337
1162
|
/**
|
|
1338
|
-
*
|
|
1163
|
+
* Validate that an attribute exists even if not filled.
|
|
1339
1164
|
*/
|
|
1340
|
-
|
|
1341
|
-
|
|
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
|
|
1347
|
-
*
|
|
1348
|
-
* This validation rule implies the attribute is "required".
|
|
1169
|
+
* Validate that an attribute is an integer.
|
|
1349
1170
|
*/
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
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
|
|
1176
|
+
* Validate that the attribute is a valid JSON string
|
|
1362
1177
|
*/
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
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
|
-
*
|
|
1188
|
+
* Validate that an attribute is greater than another attribute.
|
|
1372
1189
|
*/
|
|
1373
|
-
|
|
1374
|
-
this.requireParameterCount(1, parameters, "
|
|
1375
|
-
|
|
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
|
|
1199
|
+
* Validate that an attribute is greater than or equal another attribute.
|
|
1379
1200
|
*/
|
|
1380
|
-
|
|
1381
|
-
this.requireParameterCount(1, parameters, "
|
|
1382
|
-
|
|
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
|
|
1210
|
+
* Validate that an attribute is less than another attribute.
|
|
1386
1211
|
*/
|
|
1387
|
-
|
|
1388
|
-
|
|
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
|
-
*
|
|
1244
|
+
* "Indicate" validation should pass if value is null
|
|
1245
|
+
*
|
|
1246
|
+
* Always returns true, just lets us put "nullable" in rules.
|
|
1392
1247
|
*/
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
return /^(?=.*[a-zA-Z0-9])[a-zA-Z0-9-_]+$/.test(value.toString());
|
|
1248
|
+
validateNullable() {
|
|
1249
|
+
return true;
|
|
1396
1250
|
}
|
|
1397
1251
|
/**
|
|
1398
|
-
* Validate
|
|
1252
|
+
* Validate an attribute is not contained within a list of values.
|
|
1399
1253
|
*/
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
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
|
-
*
|
|
1266
|
+
* Always returns true - this method will be used in conbination with other rules
|
|
1406
1267
|
*/
|
|
1407
|
-
|
|
1408
|
-
return
|
|
1268
|
+
validateStrict() {
|
|
1269
|
+
return true;
|
|
1409
1270
|
}
|
|
1410
1271
|
/**
|
|
1411
|
-
* Validate that an attribute is
|
|
1272
|
+
* Validate that an attribute is a valid URL.
|
|
1412
1273
|
*/
|
|
1413
|
-
|
|
1414
|
-
if (
|
|
1415
|
-
return new
|
|
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
|
-
*
|
|
1279
|
+
* Determine if a comparison passes between the given values.
|
|
1419
1280
|
*/
|
|
1420
|
-
|
|
1421
|
-
|
|
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
|
-
*
|
|
1289
|
+
* Require a certain number of parameters to be present
|
|
1425
1290
|
*/
|
|
1426
|
-
|
|
1427
|
-
|
|
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
|
-
*
|
|
1295
|
+
* Prepare the values for validation
|
|
1432
1296
|
*/
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
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
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
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
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
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
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
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
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
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
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
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
|
-
*
|
|
1486
|
-
*
|
|
1487
|
-
* This validation rule implies the attribute is "required".
|
|
1663
|
+
* All of the registered messages.
|
|
1488
1664
|
*/
|
|
1489
|
-
|
|
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
|
-
*
|
|
1667
|
+
* All Messages
|
|
1501
1668
|
*/
|
|
1502
|
-
|
|
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
|
-
*
|
|
1671
|
+
* Stores the first error message
|
|
1511
1672
|
*/
|
|
1512
|
-
|
|
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
|
-
*
|
|
1675
|
+
* Specify whether error types should be returned or no
|
|
1521
1676
|
*/
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
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
|
-
*
|
|
1685
|
+
* Set withErrorTypes attribute to true
|
|
1532
1686
|
*/
|
|
1533
|
-
|
|
1534
|
-
this.
|
|
1535
|
-
|
|
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
|
-
*
|
|
1692
|
+
* Add new recodrs to the errors and messages objects
|
|
1548
1693
|
*/
|
|
1549
|
-
|
|
1550
|
-
if (
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
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
|
-
*
|
|
1705
|
+
* Get the first error related to a specific key
|
|
1559
1706
|
*/
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
if (
|
|
1563
|
-
|
|
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
|
-
*
|
|
1713
|
+
* Get the error messages keys
|
|
1569
1714
|
*/
|
|
1570
|
-
|
|
1571
|
-
this.
|
|
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
|
-
*
|
|
1719
|
+
* Get all the messages related to a specific key
|
|
1579
1720
|
*/
|
|
1580
|
-
|
|
1581
|
-
this.
|
|
1582
|
-
|
|
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
|
-
*
|
|
1727
|
+
* Check if key exists in messages
|
|
1586
1728
|
*/
|
|
1587
|
-
|
|
1588
|
-
return true;
|
|
1729
|
+
has(key) {
|
|
1730
|
+
return this.messages[key] && this.messages[key].length > 0 ? true : false;
|
|
1589
1731
|
}
|
|
1590
1732
|
/**
|
|
1591
|
-
*
|
|
1733
|
+
* Get all error messages
|
|
1592
1734
|
*/
|
|
1593
|
-
|
|
1594
|
-
this.
|
|
1595
|
-
if (
|
|
1596
|
-
|
|
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
|
-
*
|
|
1741
|
+
* Remove error messages
|
|
1601
1742
|
*/
|
|
1602
|
-
|
|
1603
|
-
if (
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
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
|
-
*
|
|
1761
|
+
* Clone ErrorBag Instance
|
|
1611
1762
|
*/
|
|
1612
|
-
|
|
1613
|
-
this.
|
|
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
|
-
*
|
|
1772
|
+
* The validator performing the validation.
|
|
1621
1773
|
*/
|
|
1622
|
-
|
|
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
|
-
*
|
|
1776
|
+
* The minimum size of the password.
|
|
1631
1777
|
*/
|
|
1632
|
-
|
|
1633
|
-
if (!this.allFailingRequired(parameters)) return this.validateRequired(value);
|
|
1634
|
-
return true;
|
|
1635
|
-
}
|
|
1778
|
+
minLength = 8;
|
|
1636
1779
|
/**
|
|
1637
|
-
*
|
|
1780
|
+
* The min amount of lower case letters required in the password
|
|
1638
1781
|
*/
|
|
1639
|
-
|
|
1640
|
-
if (!this.anyFailingRequired(parameters)) return this.validateRequired(value);
|
|
1641
|
-
return true;
|
|
1642
|
-
}
|
|
1782
|
+
minLowerCase = 0;
|
|
1643
1783
|
/**
|
|
1644
|
-
*
|
|
1784
|
+
* The min amount of uppercase letters required in the password
|
|
1645
1785
|
*/
|
|
1646
|
-
|
|
1647
|
-
if (this.anyFailingRequired(parameters)) return this.validateRequired(value);
|
|
1648
|
-
return true;
|
|
1649
|
-
}
|
|
1786
|
+
minUpperCase = 0;
|
|
1650
1787
|
/**
|
|
1651
|
-
*
|
|
1788
|
+
* The min amount of letters required in the password
|
|
1652
1789
|
*/
|
|
1653
|
-
|
|
1654
|
-
if (this.allFailingRequired(parameters)) return this.validateRequired(value);
|
|
1655
|
-
return true;
|
|
1656
|
-
}
|
|
1790
|
+
minLetters = 0;
|
|
1657
1791
|
/**
|
|
1658
|
-
*
|
|
1792
|
+
* The min amount of letters required in the password
|
|
1659
1793
|
*/
|
|
1660
|
-
|
|
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
|
-
*
|
|
1796
|
+
* The min amount of symbols required in the password
|
|
1666
1797
|
*/
|
|
1667
|
-
|
|
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
|
-
*
|
|
1800
|
+
* Additional validation rules that should be merged into the default rules during validation.
|
|
1673
1801
|
*/
|
|
1674
|
-
|
|
1675
|
-
return typeof value === "string";
|
|
1676
|
-
}
|
|
1802
|
+
customRules = [];
|
|
1677
1803
|
/**
|
|
1678
|
-
*
|
|
1804
|
+
* The callback that will generate the "default" version of the password rule.
|
|
1679
1805
|
*/
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
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
|
-
*
|
|
1814
|
+
* Set the minimum length of the password
|
|
1687
1815
|
*/
|
|
1688
|
-
|
|
1689
|
-
this.
|
|
1690
|
-
|
|
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
|
-
*
|
|
1821
|
+
* Set the min amount of letters required in the password
|
|
1695
1822
|
*/
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
return
|
|
1823
|
+
letters(letters = 1) {
|
|
1824
|
+
this.minLetters = letters;
|
|
1825
|
+
return this;
|
|
1699
1826
|
}
|
|
1700
1827
|
/**
|
|
1701
|
-
*
|
|
1828
|
+
* Set the min amount of upper and lower case letters required in the password
|
|
1702
1829
|
*/
|
|
1703
|
-
|
|
1704
|
-
|
|
1830
|
+
mixedCase(upperCase = 1, lowerCase = 1) {
|
|
1831
|
+
this.minUpperCase = upperCase;
|
|
1832
|
+
this.minLowerCase = lowerCase;
|
|
1833
|
+
return this;
|
|
1705
1834
|
}
|
|
1706
1835
|
/**
|
|
1707
|
-
*
|
|
1836
|
+
* Set the min amount of numbers required in the password
|
|
1708
1837
|
*/
|
|
1709
|
-
|
|
1710
|
-
|
|
1838
|
+
numbers(numbers = 1) {
|
|
1839
|
+
this.minNumbers = numbers;
|
|
1840
|
+
return this;
|
|
1711
1841
|
}
|
|
1712
1842
|
/**
|
|
1713
|
-
*
|
|
1843
|
+
* Set the min amount of symbols required in the password
|
|
1714
1844
|
*/
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
return
|
|
1845
|
+
symbols(symbols = 1) {
|
|
1846
|
+
this.minSymbols = symbols;
|
|
1847
|
+
return this;
|
|
1718
1848
|
}
|
|
1719
1849
|
/**
|
|
1720
|
-
*
|
|
1850
|
+
* Determine if the validation rule passes.
|
|
1721
1851
|
*/
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1746
|
-
this.
|
|
1747
|
-
|
|
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
|
-
*
|
|
1926
|
+
* Get all the validation error messages related to the password
|
|
1755
1927
|
*/
|
|
1756
|
-
|
|
1757
|
-
|
|
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
|
-
*
|
|
1932
|
+
* Set the validator instance used to validate the password
|
|
1766
1933
|
*/
|
|
1767
|
-
|
|
1768
|
-
this.
|
|
1769
|
-
|
|
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
|
-
*
|
|
1939
|
+
* Set the default callback to be used for determining a password's default rules.
|
|
1777
1940
|
*/
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
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
|
|
1785
|
-
|
|
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
|
-
*
|
|
1950
|
+
* Get the default configuration of the password rule.
|
|
1797
1951
|
*/
|
|
1798
|
-
|
|
1799
|
-
this.
|
|
1800
|
-
|
|
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
|
-
*
|
|
1969
|
+
* Stores the data object
|
|
1811
1970
|
*/
|
|
1812
|
-
|
|
1813
|
-
return true;
|
|
1814
|
-
}
|
|
1971
|
+
data;
|
|
1815
1972
|
/**
|
|
1816
|
-
*
|
|
1973
|
+
* The message in which attributes will be replaced
|
|
1817
1974
|
*/
|
|
1818
|
-
|
|
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
|
-
*
|
|
1977
|
+
* Parameters that will be used to replace the attributes
|
|
1824
1978
|
*/
|
|
1825
|
-
|
|
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
|
-
*
|
|
1981
|
+
* Flag that identifies wether the numeric rule exists or not
|
|
1834
1982
|
*/
|
|
1835
|
-
|
|
1836
|
-
if (parameters.length < count) throw `Validation rule ${rule} requires at least ${count} parameters.`;
|
|
1837
|
-
}
|
|
1983
|
+
hasNumericRule;
|
|
1838
1984
|
/**
|
|
1839
|
-
*
|
|
1985
|
+
* The function that will be used to format attributes
|
|
1840
1986
|
*/
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
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)
|
|
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 };
|