kanun 0.1.0
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/LICENSE +21 -0
- package/README.md +171 -0
- package/dist/index.cjs +3071 -0
- package/dist/index.d.cts +1435 -0
- package/dist/index.d.mts +1435 -0
- package/dist/index.mjs +3023 -0
- package/package.json +66 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,3023 @@
|
|
|
1
|
+
//#region src/Rules/baseRule.ts
|
|
2
|
+
var BaseRule = class {};
|
|
3
|
+
|
|
4
|
+
//#endregion
|
|
5
|
+
//#region src/locales/en.ts
|
|
6
|
+
var en_default = {
|
|
7
|
+
accepted: "The :attribute must be accepted.",
|
|
8
|
+
accepted_if: "The :attribute must be accepted when :other is :value.",
|
|
9
|
+
after: "The :attribute must be a date after :date.",
|
|
10
|
+
after_or_equal: "The :attribute must be a date after or equal to :date.",
|
|
11
|
+
alpha: "The :attribute must only contain letters.",
|
|
12
|
+
alpha_dash: "The :attribute must only contain at least one letter or one number, and optionally dashes and underscores.",
|
|
13
|
+
alpha_num: "The :attribute must only contain letters and numbers.",
|
|
14
|
+
array: "The :attribute must be an array.",
|
|
15
|
+
array_unique: "The :attribute must be an array with unique values.",
|
|
16
|
+
before: "The :attribute must be a date before :date.",
|
|
17
|
+
before_or_equal: "The :attribute must be a date before or equal to :date.",
|
|
18
|
+
between: {
|
|
19
|
+
number: "The :attribute must be between :min and :max.",
|
|
20
|
+
string: "The :attribute must be between :min and :max characters.",
|
|
21
|
+
array: "The :attribute must have between :min and :max items.",
|
|
22
|
+
object: "The :attribute must have between :min and :max items."
|
|
23
|
+
},
|
|
24
|
+
boolean: "The :attribute field must be true or false.",
|
|
25
|
+
confirmed: "The :attribute confirmation does not match.",
|
|
26
|
+
date: "The :attribute is not a valid date.",
|
|
27
|
+
date_equals: "The :attribute must be a date equal to :date.",
|
|
28
|
+
declined: "The :attribute must be declined.",
|
|
29
|
+
declined_if: "The :attribute must be declined when :other is :value.",
|
|
30
|
+
different: "The :attribute and :other must be different.",
|
|
31
|
+
digits: "The :attribute must be :digits digits.",
|
|
32
|
+
digits_between: "The :attribute must be between :min and :max digits.",
|
|
33
|
+
email: "The :attribute must be a valid email address.",
|
|
34
|
+
ends_with: "The :attribute must end with one of the following: :values.",
|
|
35
|
+
gt: {
|
|
36
|
+
number: "The :attribute must be greater than :value.",
|
|
37
|
+
string: "The :attribute must be greater than :value characters.",
|
|
38
|
+
array: "The :attribute must have more than :value items.",
|
|
39
|
+
object: "The :attribute must have more than :value items."
|
|
40
|
+
},
|
|
41
|
+
gte: {
|
|
42
|
+
number: "The :attribute must be greater than or equal :value.",
|
|
43
|
+
string: "The :attribute must be greater than or equal :value characters.",
|
|
44
|
+
array: "The :attribute must have :value items or more.",
|
|
45
|
+
object: "The :attribute must have :value items or more."
|
|
46
|
+
},
|
|
47
|
+
in: "The :attribute must be one of the following :values.",
|
|
48
|
+
integer: "The :attribute must be an integer.",
|
|
49
|
+
json: "The :attribute must be a valid JSON string.",
|
|
50
|
+
lt: {
|
|
51
|
+
number: "The :attribute must be less than :value.",
|
|
52
|
+
string: "The :attribute must be less than :value characters.",
|
|
53
|
+
array: "The :attribute must have less than :value items.",
|
|
54
|
+
object: "The :attribute must have less than :value items."
|
|
55
|
+
},
|
|
56
|
+
lte: {
|
|
57
|
+
number: "The :attribute must be less than or equal :value.",
|
|
58
|
+
string: "The :attribute must be less than or equal :value characters.",
|
|
59
|
+
array: "The :attribute must have :value items or less.",
|
|
60
|
+
object: "The :attribute must have :value items or less."
|
|
61
|
+
},
|
|
62
|
+
max: {
|
|
63
|
+
number: "The :attribute must not be greater than :max.",
|
|
64
|
+
string: "The :attribute must not be greater than :max characters.",
|
|
65
|
+
array: "The :attribute must not have more than :max items.",
|
|
66
|
+
object: "The :attribute must not have more than :max items."
|
|
67
|
+
},
|
|
68
|
+
min: {
|
|
69
|
+
number: "The :attribute must be at least :min.",
|
|
70
|
+
string: "The :attribute must be at least :min characters.",
|
|
71
|
+
array: "The :attribute must have at least :min items.",
|
|
72
|
+
object: "The :attribute must have at least :min items."
|
|
73
|
+
},
|
|
74
|
+
not_in: "The selected :attribute is invalid.",
|
|
75
|
+
not_regex: "The :attribute format is invalid.",
|
|
76
|
+
numeric: "The :attribute must be a number.",
|
|
77
|
+
object: "The :attribute must be an object.",
|
|
78
|
+
password: {
|
|
79
|
+
letter: "The :attribute must contain at least one letter.",
|
|
80
|
+
letters: "The :attribute must contain at least :amount letters.",
|
|
81
|
+
lower_case: "The :attribute must contain at least one lowercase letter.",
|
|
82
|
+
lower_cases: "The :attribute must contain at least :amount lowercase letters.",
|
|
83
|
+
number: "The :attribute must contain at least one number.",
|
|
84
|
+
numbers: "The :attribute must contain at least :amount numbers.",
|
|
85
|
+
symbol: "The :attribute must contain at least one symbol.",
|
|
86
|
+
symbols: "The :attribute must contain at least :amount symbols.",
|
|
87
|
+
upper_case: "The :attribute must contain at least one uppercase letter.",
|
|
88
|
+
upper_cases: "The :attribute must contain at least :amount uppercase letters."
|
|
89
|
+
},
|
|
90
|
+
present: "The :attribute field must be present.",
|
|
91
|
+
regex: "The :attribute format is invalid.",
|
|
92
|
+
required: "The :attribute field is required.",
|
|
93
|
+
required_if: "The :attribute field is required when :other is :value.",
|
|
94
|
+
required_unless: "The :attribute field is required unless :other is in :values.",
|
|
95
|
+
required_with: "The :attribute field is required when :values is present.",
|
|
96
|
+
required_with_all: "The :attribute field is required when :values are present.",
|
|
97
|
+
required_without: "The :attribute field is required when :values is not present.",
|
|
98
|
+
required_without_all: "The :attribute field is required when none of :values are present.",
|
|
99
|
+
starts_with: "The :attribute must start with one of the following: :values.",
|
|
100
|
+
same: "The :attribute and :other must match.",
|
|
101
|
+
size: {
|
|
102
|
+
number: "The :attribute must be :size.",
|
|
103
|
+
string: "The :attribute must be :size characters.",
|
|
104
|
+
array: "The :attribute must contain :size items.",
|
|
105
|
+
object: "The :attribute must contain :size items."
|
|
106
|
+
},
|
|
107
|
+
string: "The :attribute must be a string.",
|
|
108
|
+
url: "The :attribute must have a valid URL format."
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/locales/ar.ts
|
|
113
|
+
var ar_default = {
|
|
114
|
+
accepted: "يجب قبول الحقل :attribute",
|
|
115
|
+
accepted_if: "الحقل :attribute مقبول في حال ما إذا كان :other يساوي :value.",
|
|
116
|
+
after: "يجب على الحقل :attribute أن يكون تاريخا لاحقا للتاريخ :date.",
|
|
117
|
+
after_or_equal: "الحقل :attribute يجب أن يكون تاريخاً لاحقاً أو مطابقاً للتاريخ :date.",
|
|
118
|
+
alpha: "يجب أن لا يحتوي الحقل :attribute سوى على حروف",
|
|
119
|
+
alpha_dash: "يجب أن يحتوي الحقل :attribute على حرف واحد أو رقم واحد على الأقل، بالإضافة إلى شرطات وشرطات سفلية بشكل اختياري",
|
|
120
|
+
alpha_num: "يجب أن يحتوي :attribute على حروف وأرقام فقط",
|
|
121
|
+
array: "يجب أن يكون الحقل :attribute ًمصفوفة",
|
|
122
|
+
before: "يجب على الحقل :attribute أن يكون تاريخا سابقا للتاريخ :date.",
|
|
123
|
+
before_or_equal: "الحقل :attribute يجب أن يكون تاريخا سابقا أو مطابقا للتاريخ :date",
|
|
124
|
+
between: {
|
|
125
|
+
number: "يجب أن تكون قيمة :attribute بين :min و :max.",
|
|
126
|
+
string: "يجب أن يكون عدد حروف النّص :attribute بين :min و :max",
|
|
127
|
+
array: "يجب أن يحتوي :attribute على عدد من العناصر بين :min و :max",
|
|
128
|
+
object: "يجب أن يحتوي :attribute على عدد من العناصر بين :min و :max"
|
|
129
|
+
},
|
|
130
|
+
boolean: "يجب أن تكون قيمة الحقل :attribute إما true أو false",
|
|
131
|
+
confirmed: "حقل التأكيد غير مُطابق للحقل :attribute",
|
|
132
|
+
date: "الحقل :attribute ليس تاريخًا صحيحًا",
|
|
133
|
+
date_equals: "لا يساوي الحقل :attribute مع :date.",
|
|
134
|
+
declined: "يجب رفض الحقل :attribute",
|
|
135
|
+
declined_if: "الحقل :attribute مرفوض في حال ما إذا كان :other يساوي :value.",
|
|
136
|
+
different: "يجب أن يكون الحقلان :attribute و :other مُختلفان",
|
|
137
|
+
digits: "يجب أن يحتوي الحقل :attribute على :digits رقمًا/أرقام",
|
|
138
|
+
digits_between: "يجب أن يحتوي الحقل :attribute بين :min و :max رقمًا/أرقام",
|
|
139
|
+
email: "يجب أن يكون :attribute عنوان بريد إلكتروني صحيح البُنية",
|
|
140
|
+
ends_with: "حقل :attribute يجب ان ينتهي بأحد القيم التالية :value.",
|
|
141
|
+
gt: {
|
|
142
|
+
number: "حقل :attribute يجب ان يكون اكبر من :value.",
|
|
143
|
+
string: "حقل :attribute يجب ان يكون اكبر من :value حروفٍ/حرفًا.",
|
|
144
|
+
array: "حقل :attribute يجب ان يحتوي علي اكثر من :value عناصر/عنصر.",
|
|
145
|
+
object: "حقل :attribute يجب ان يحتوي علي اكثر من :value عناصر/عنصر."
|
|
146
|
+
},
|
|
147
|
+
gte: {
|
|
148
|
+
number: "حقل :attribute يجب ان يكون اكبر من او يساوي :value.",
|
|
149
|
+
string: "حقل :attribute يجب ان يكون اكبر من او يساوي :value حروفٍ/حرفًا.",
|
|
150
|
+
array: "حقل :attribute يجب ان يحتوي علي :value عناصر/عنصر او اكثر.",
|
|
151
|
+
object: "حقل :attribute يجب ان يحتوي علي :value عناصر/عنصر او اكثر."
|
|
152
|
+
},
|
|
153
|
+
in: "الحقل :attribute غير صالح",
|
|
154
|
+
integer: "يجب أن يكون الحقل :attribute عددًا صحيحًا",
|
|
155
|
+
json: "يجب أن يكون الحقل :attribute نصا من نوع JSON.",
|
|
156
|
+
lt: {
|
|
157
|
+
number: "حقل :attribute يجب ان يكون اقل من :value.",
|
|
158
|
+
string: "حقل :attribute يجب ان يكون اقل من :value حروفٍ/حرفًا.",
|
|
159
|
+
array: "حقل :attribute يجب ان يحتوي علي اقل من :value عناصر/عنصر.",
|
|
160
|
+
object: "حقل :attribute يجب ان يحتوي علي اقل من :value عناصر/عنصر."
|
|
161
|
+
},
|
|
162
|
+
lte: {
|
|
163
|
+
number: "حقل :attribute يجب ان يكون اقل من او يساوي :value.",
|
|
164
|
+
string: "حقل :attribute يجب ان يكون اقل من او يساوي :value حروفٍ/حرفًا.",
|
|
165
|
+
array: "حقل :attribute يجب ان يحتوي علي اكثر من :value عناصر/عنصر.",
|
|
166
|
+
object: "حقل :attribute يجب ان يحتوي علي اكثر من :value عناصر/عنصر."
|
|
167
|
+
},
|
|
168
|
+
max: {
|
|
169
|
+
number: "يجب أن تكون قيمة الحقل :attribute مساوية أو أصغر لـ :max.",
|
|
170
|
+
string: "يجب أن لا يتجاوز طول نص :attribute :max حروفٍ/حرفًا",
|
|
171
|
+
array: "يجب أن لا يحتوي الحقل :attribute على أكثر من :max عناصر/عنصر.",
|
|
172
|
+
object: "يجب أن لا يحتوي الحقل :attribute على أكثر من :max عناصر/عنصر."
|
|
173
|
+
},
|
|
174
|
+
min: {
|
|
175
|
+
number: "يجب أن تكون قيمة الحقل :attribute مساوية أو أكبر لـ :min.",
|
|
176
|
+
string: "يجب أن يكون طول نص :attribute على الأقل :min حروفٍ/حرفًا",
|
|
177
|
+
array: "يجب أن يحتوي الحقل :attribute على الأقل على :min عُنصرًا/عناصر",
|
|
178
|
+
object: "يجب أن يحتوي الحقل :attribute على الأقل على :min عُنصرًا/عناصر"
|
|
179
|
+
},
|
|
180
|
+
not_in: "الحقل :attribute غير صالح",
|
|
181
|
+
not_regex: "الحقل :attribute نوعه غير صالح",
|
|
182
|
+
numeric: "يجب على الحقل :attribute أن يكون رقمًا",
|
|
183
|
+
object: "الحقل :attribute يجب ان يكون من نوع object.",
|
|
184
|
+
password: {
|
|
185
|
+
letter: "يجب ان يشمل حقل :attribute على حرف واحد على الاقل.",
|
|
186
|
+
letters: "يجب ان يشمل حقل :attribute على عدد :amount حروف على الاقل.",
|
|
187
|
+
lower_case: "يجب ان يشمل حقل :attribute على حرف واحد من صيغة صغيرة على الاقل.",
|
|
188
|
+
lower_cases: "يجب ان يشمل حقل :attribute على عدد :amount حروف صغيرة على الاقل.",
|
|
189
|
+
number: "يجب ان يشمل حقل :attribute على رقم واحد على الاقل.",
|
|
190
|
+
numbers: "يجب ان يشمل حقل :attribute على عدد :amount من الارقام على الاقل.",
|
|
191
|
+
symbol: "يجب ان يشمل حقل :attribute على رمز واحد على الاقل.",
|
|
192
|
+
symbols: "يجب ان يشمل حقل :attribute على عدد :amount رموز على الاقل.",
|
|
193
|
+
upper_case: "يجب ان يشمل حقل :attribute على حرف كبير واحد على الاقل.",
|
|
194
|
+
upper_cases: "يجب ان يشمل حقل :attribute على عدد :amount حروف كبيرة على الاقل."
|
|
195
|
+
},
|
|
196
|
+
present: "يجب تقديم الحقل :attribute",
|
|
197
|
+
regex: "صيغة الحقل :attribute .غير صحيحة",
|
|
198
|
+
required: "الحقل :attribute مطلوب.",
|
|
199
|
+
required_if: "الحقل :attribute مطلوب في حال ما إذا كان :other يساوي :value.",
|
|
200
|
+
required_unless: "الحقل :attribute مطلوب في حال ما لم يكن :other يساوي :values.",
|
|
201
|
+
required_with: "الحقل :attribute اجباري إذا توفّر :values.",
|
|
202
|
+
required_with_all: "الحقل :attribute اجباري إذا توفّر :values.",
|
|
203
|
+
required_without: "الحقل :attribute اجباري إذا لم يتوفّر :values.",
|
|
204
|
+
required_without_all: "الحقل :attribute اجباري إذا لم يتوفّر :values.",
|
|
205
|
+
starts_with: "الحقل :attribute يجب ان يبدأ بأحد القيم التالية: :values.",
|
|
206
|
+
same: "يجب أن يتطابق الحقل :attribute مع :other",
|
|
207
|
+
size: {
|
|
208
|
+
number: "يجب أن تكون قيمة الحقل :attribute مساوية لـ :size",
|
|
209
|
+
string: "يجب أن يحتوي النص :attribute على :size حروفٍ/حرفًا بالظبط",
|
|
210
|
+
array: "يجب أن يحتوي الحقل :attribute على :size عنصرٍ/عناصر بالظبط",
|
|
211
|
+
object: "يجب أن يحتوي الحقل :attribute على :size عنصرٍ/عناصر بالظبط"
|
|
212
|
+
},
|
|
213
|
+
string: "يجب أن يكون الحقل :attribute نصآ.",
|
|
214
|
+
url: "صيغة الرابط :attribute غير صحيحة"
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
//#endregion
|
|
218
|
+
//#region src/locales/index.ts
|
|
219
|
+
var locales_default = {
|
|
220
|
+
en: en_default,
|
|
221
|
+
ar: ar_default
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/Lang.ts
|
|
226
|
+
var Lang = class {
|
|
227
|
+
/**
|
|
228
|
+
* Default lang to be used, when lang is not specified
|
|
229
|
+
*/
|
|
230
|
+
static defaultLang = "en";
|
|
231
|
+
/**
|
|
232
|
+
* Determines the locale to be used when tu current one is not available
|
|
233
|
+
*/
|
|
234
|
+
static fallbackLang = "en";
|
|
235
|
+
/**
|
|
236
|
+
* The existing langs that are supported by the library
|
|
237
|
+
*/
|
|
238
|
+
static existingLangs = ["en"];
|
|
239
|
+
/**
|
|
240
|
+
* Store the translations passed by the user
|
|
241
|
+
*/
|
|
242
|
+
static translations = {};
|
|
243
|
+
/**
|
|
244
|
+
* Stores the messages that are already loaded
|
|
245
|
+
*/
|
|
246
|
+
static messages = {};
|
|
247
|
+
/**
|
|
248
|
+
* Stores the default messages
|
|
249
|
+
*/
|
|
250
|
+
static defaultMessages = {};
|
|
251
|
+
/**
|
|
252
|
+
* Stores the fallback messages
|
|
253
|
+
*/
|
|
254
|
+
static fallbackMessages = locales_default.en;
|
|
255
|
+
/**
|
|
256
|
+
* Get messages for lang
|
|
257
|
+
*
|
|
258
|
+
* @param lang
|
|
259
|
+
* @returns
|
|
260
|
+
*/
|
|
261
|
+
static get(lang) {
|
|
262
|
+
lang ??= this.defaultLang;
|
|
263
|
+
this.load(lang);
|
|
264
|
+
return this.messages[lang];
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Set the translation object passed by the user
|
|
268
|
+
*
|
|
269
|
+
* @param translations
|
|
270
|
+
*/
|
|
271
|
+
static setTranslationObject(translations) {
|
|
272
|
+
this.translations = translations;
|
|
273
|
+
this.existingLangs = Array.from(new Set([...this.existingLangs, ...Object.keys(translations)]));
|
|
274
|
+
this.setDefaultLang(this.defaultLang);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Set the default lang that should be used. And assign the default messages
|
|
278
|
+
*
|
|
279
|
+
* @param lang
|
|
280
|
+
*/
|
|
281
|
+
static setDefaultLang(lang) {
|
|
282
|
+
this.defaultLang = lang;
|
|
283
|
+
this.load(lang);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Set the fallback lang to be used. And assign the fallback messages
|
|
287
|
+
*
|
|
288
|
+
* @param lang
|
|
289
|
+
*/
|
|
290
|
+
static setFallbackLang(lang) {
|
|
291
|
+
this.fallbackLang = lang;
|
|
292
|
+
this.fallbackMessages = locales_default.en;
|
|
293
|
+
if (Object.prototype.hasOwnProperty.call(locales_default, lang)) this.fallbackMessages = mergeDeep(this.fallbackMessages, locales_default[lang]);
|
|
294
|
+
if (Object.prototype.hasOwnProperty.call(this.translations, lang)) this.fallbackMessages = mergeDeep(this.fallbackMessages, this.translations[lang]);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Get the default language
|
|
298
|
+
*
|
|
299
|
+
* @returns
|
|
300
|
+
*/
|
|
301
|
+
static getDefaultLang() {
|
|
302
|
+
return this.defaultLang;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Load the messages based on the specified language
|
|
306
|
+
*
|
|
307
|
+
* @param lang
|
|
308
|
+
* @returns
|
|
309
|
+
*/
|
|
310
|
+
static load(lang) {
|
|
311
|
+
if (this.messages[lang]) return;
|
|
312
|
+
if (Object.prototype.hasOwnProperty.call(locales_default, lang)) this.messages[lang] = mergeDeep(this.fallbackMessages, locales_default[lang]);
|
|
313
|
+
else this.messages[lang] = mergeDeep({}, this.fallbackMessages);
|
|
314
|
+
if (Object.prototype.hasOwnProperty.call(this.translations, lang)) this.messages[lang] = mergeDeep(this.messages[lang], this.translations[lang]);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region src/Rules/IRuleContract.ts
|
|
320
|
+
var IRuleContract = class {
|
|
321
|
+
/**
|
|
322
|
+
* The validation error message.
|
|
323
|
+
*/
|
|
324
|
+
message = "";
|
|
325
|
+
/**
|
|
326
|
+
* All of the data under validation.
|
|
327
|
+
*/
|
|
328
|
+
data = {};
|
|
329
|
+
/**
|
|
330
|
+
* The lang used to return error messages
|
|
331
|
+
*/
|
|
332
|
+
lang;
|
|
333
|
+
/**
|
|
334
|
+
* Determine if the validation rule passes.
|
|
335
|
+
*/
|
|
336
|
+
passes(_value, _attribute) {
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Get the validation error message.
|
|
341
|
+
*/
|
|
342
|
+
getMessage() {
|
|
343
|
+
return this.message;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Set the data under validation.
|
|
347
|
+
*/
|
|
348
|
+
setData(data) {
|
|
349
|
+
this.data = data;
|
|
350
|
+
return this;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Set the tranlation language
|
|
354
|
+
*/
|
|
355
|
+
setLang(lang) {
|
|
356
|
+
this.lang = lang;
|
|
357
|
+
return this;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Get the translated error message based on the specified path
|
|
361
|
+
*/
|
|
362
|
+
trans(path, params = {}) {
|
|
363
|
+
let message = deepFind(Lang.get(this.lang), path) || "";
|
|
364
|
+
if (!message) return message;
|
|
365
|
+
for (const key in params) message = message.replace(`:${key}`, params[key]);
|
|
366
|
+
return message;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
//#endregion
|
|
371
|
+
//#region src/utilities/general.ts
|
|
372
|
+
const implicitRues = [
|
|
373
|
+
"accepted",
|
|
374
|
+
"accepted_if",
|
|
375
|
+
"declined",
|
|
376
|
+
"declined_if",
|
|
377
|
+
"filled",
|
|
378
|
+
"present",
|
|
379
|
+
"required",
|
|
380
|
+
"required_if",
|
|
381
|
+
"required_unless",
|
|
382
|
+
"required_with",
|
|
383
|
+
"required_with_all",
|
|
384
|
+
"required_without",
|
|
385
|
+
"required_without_all"
|
|
386
|
+
];
|
|
387
|
+
/**
|
|
388
|
+
* Get the size of a value based on its type
|
|
389
|
+
*/
|
|
390
|
+
function getSize(value, hasNumericRule = false) {
|
|
391
|
+
if (typeof value === "number" || isNaN(value) === false && hasNumericRule === true) return Number(value);
|
|
392
|
+
else if (typeof value === "string" || Array.isArray(value)) return value.length;
|
|
393
|
+
else if (typeof value === "object" && value !== null) return Object.keys(value).length;
|
|
394
|
+
return -1;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Check if two values are of the same type
|
|
398
|
+
*/
|
|
399
|
+
function sameType(value, otherValue) {
|
|
400
|
+
return (Array.isArray(value) ? "array" : value === null ? null : typeof value) === (Array.isArray(otherValue) ? "array" : otherValue === null ? null : typeof otherValue);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Check if Value is an Ineteger
|
|
404
|
+
*/
|
|
405
|
+
function isInteger(value) {
|
|
406
|
+
return value !== null && isNaN(value) === false && value % 1 === 0;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Check if the value can be considered as rule
|
|
410
|
+
*/
|
|
411
|
+
function isRule(value) {
|
|
412
|
+
return typeof value === "string" || typeof value === "function" || value instanceof IRuleContract || value instanceof BaseRule;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Check if the array contain any potentiel valid rule
|
|
416
|
+
*/
|
|
417
|
+
function isArrayOfRules(values) {
|
|
418
|
+
for (let i = 0; i < values.length; i++) if (isRule(values[i])) return true;
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Check if the rule is related to size
|
|
423
|
+
*/
|
|
424
|
+
function isSizeRule(rule) {
|
|
425
|
+
return [
|
|
426
|
+
"size",
|
|
427
|
+
"between",
|
|
428
|
+
"min",
|
|
429
|
+
"max",
|
|
430
|
+
"gt",
|
|
431
|
+
"lt",
|
|
432
|
+
"gte",
|
|
433
|
+
"lte"
|
|
434
|
+
].indexOf(rule) !== -1;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Check if rule implies that the field is required
|
|
438
|
+
*/
|
|
439
|
+
function isImplicitRule(rule) {
|
|
440
|
+
if (rule instanceof IRuleContract && rule.__isImplicitRule === true) return true;
|
|
441
|
+
if (typeof rule === "string") return implicitRues.indexOf(rule) !== -1;
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Add a new implicit rule
|
|
446
|
+
*/
|
|
447
|
+
function addImplicitRule(rule) {
|
|
448
|
+
implicitRues.push(rule);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Returns the numeric rules
|
|
452
|
+
*/
|
|
453
|
+
function getNumericRules() {
|
|
454
|
+
return ["numeric", "integer"];
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Check if the rule is numeric
|
|
458
|
+
*/
|
|
459
|
+
function isNumericRule(rule) {
|
|
460
|
+
return getNumericRules().indexOf(rule) !== -1;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Determine if a comparison passes between the given values.
|
|
464
|
+
*/
|
|
465
|
+
function compare(first, second, operator, strict = false) {
|
|
466
|
+
switch (operator) {
|
|
467
|
+
case "<": return first < second;
|
|
468
|
+
case ">": return first > second;
|
|
469
|
+
case "<=": return first <= second;
|
|
470
|
+
case ">=": return first >= second;
|
|
471
|
+
case "=":
|
|
472
|
+
if (strict === true) return first === second;
|
|
473
|
+
return first == second;
|
|
474
|
+
default: throw "Invalid operator parameter";
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Convert the given values to boolean if they are string "true" / "false".
|
|
479
|
+
*/
|
|
480
|
+
function convertValuesToBoolean(values) {
|
|
481
|
+
return values.map((value) => {
|
|
482
|
+
if (value === "true") return true;
|
|
483
|
+
else if (value === "false") return false;
|
|
484
|
+
return value;
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Convert the given values to numbers if they are numbers in a string "1", "2"
|
|
489
|
+
*/
|
|
490
|
+
function convertValuesToNumber(values) {
|
|
491
|
+
return values.map((value) => {
|
|
492
|
+
if (!isNaN(Number(value))) return Number(value);
|
|
493
|
+
return value;
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Convert the given values to null if they have null values in a string "null", "NULL"
|
|
498
|
+
*/
|
|
499
|
+
function convertValuesToNull(values) {
|
|
500
|
+
return values.map((value) => {
|
|
501
|
+
if (value.toLowerCase() === "null") return null;
|
|
502
|
+
return value;
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
//#endregion
|
|
507
|
+
//#region src/utilities/object.ts
|
|
508
|
+
/**
|
|
509
|
+
* Get value at path of object. If the resolved value is undifined, the returned result will be undefined
|
|
510
|
+
*
|
|
511
|
+
* @param obj
|
|
512
|
+
* @param path
|
|
513
|
+
* @returns
|
|
514
|
+
*/
|
|
515
|
+
function deepFind(obj, path) {
|
|
516
|
+
const paths = path.split(".");
|
|
517
|
+
for (let i = 0; i < paths.length; i++) {
|
|
518
|
+
if (typeof obj[paths[i]] === "undefined") return;
|
|
519
|
+
obj = obj[paths[i]];
|
|
520
|
+
}
|
|
521
|
+
return obj;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Set value at path of object.
|
|
525
|
+
*
|
|
526
|
+
* @param target
|
|
527
|
+
* @param path
|
|
528
|
+
* @param value
|
|
529
|
+
*/
|
|
530
|
+
function deepSet(target, path, value) {
|
|
531
|
+
const paths = typeof path === "string" ? path.split(".") : path;
|
|
532
|
+
const segment = paths.shift();
|
|
533
|
+
if (segment === "*") {
|
|
534
|
+
target = Array.isArray(target) ? target : [];
|
|
535
|
+
if (paths.length > 0) target.forEach((inner) => deepSet(inner, [...paths], value));
|
|
536
|
+
else for (let i = 0; i < target.length; i++) target[i] = value;
|
|
537
|
+
} else if (paths.length > 0 && typeof segment === "string") {
|
|
538
|
+
if (typeof target[segment] !== "object" || target[segment] === null) target[segment] = {};
|
|
539
|
+
deepSet(target[segment], paths, value);
|
|
540
|
+
} else {
|
|
541
|
+
if (typeof target !== "object" || target === null) target = {};
|
|
542
|
+
target[segment] = value;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Flatten a multi-dimensional associative array with dots.
|
|
547
|
+
*
|
|
548
|
+
* @param obj
|
|
549
|
+
* @param ignoreRulesArray
|
|
550
|
+
* @param withBaseObjectType
|
|
551
|
+
* @returns
|
|
552
|
+
*/
|
|
553
|
+
function dotify(obj, ignoreRulesArray = false, withBaseObjectType = false) {
|
|
554
|
+
const res = {};
|
|
555
|
+
(function recurse(obj, current = "") {
|
|
556
|
+
for (const key in obj) {
|
|
557
|
+
const value = obj[key];
|
|
558
|
+
const newKey = current ? `${current}.${key}` : key;
|
|
559
|
+
if (value && typeof value === "object" && !isRule(value) && !(value instanceof Date)) if (ignoreRulesArray === true && Array.isArray(value) && isArrayOfRules(value)) res[newKey] = value;
|
|
560
|
+
else {
|
|
561
|
+
if (withBaseObjectType) res[newKey] = Array.isArray(value) ? "array" : "object";
|
|
562
|
+
recurse(value, newKey);
|
|
563
|
+
}
|
|
564
|
+
else res[newKey] = value;
|
|
565
|
+
}
|
|
566
|
+
})(obj);
|
|
567
|
+
return res;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Check if the value is an object
|
|
571
|
+
*
|
|
572
|
+
* @param value
|
|
573
|
+
* @returns
|
|
574
|
+
*/
|
|
575
|
+
function isObject(value) {
|
|
576
|
+
return value && typeof value === "object" && !Array.isArray(value);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Deeply merge nested objects
|
|
580
|
+
*
|
|
581
|
+
* @param target
|
|
582
|
+
* @param source
|
|
583
|
+
* @returns
|
|
584
|
+
*/
|
|
585
|
+
function mergeDeep(target, source) {
|
|
586
|
+
const output = Object.assign({}, target);
|
|
587
|
+
if (!isObject(target) || !isObject(source)) return output;
|
|
588
|
+
for (const key in source) if (isObject(source[key])) if (!target[key]) Object.assign(output, { [key]: source[key] });
|
|
589
|
+
else output[key] = mergeDeep(target[key], source[key]);
|
|
590
|
+
else Object.assign(output, { [key]: source[key] });
|
|
591
|
+
return output;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Check if objects are deep equal
|
|
595
|
+
*
|
|
596
|
+
* @param firstParam
|
|
597
|
+
* @param secondParam
|
|
598
|
+
* @returns
|
|
599
|
+
*/
|
|
600
|
+
function deepEqual(firstParam, secondParam) {
|
|
601
|
+
const first = dotify(firstParam, false, true);
|
|
602
|
+
const second = dotify(secondParam, false, true);
|
|
603
|
+
if (Object.keys(first).length !== Object.keys(second).length) return false;
|
|
604
|
+
return Object.entries(first).every(([key, value]) => second[key] === value);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
//#endregion
|
|
608
|
+
//#region src/utilities/formatMessages.ts
|
|
609
|
+
/**
|
|
610
|
+
* Get the message type based on the value. The message type is used essentially for size rules
|
|
611
|
+
*/
|
|
612
|
+
function getMesageType(value, hasNumericRule = false) {
|
|
613
|
+
if (typeof value === "number" || typeof value === "undefined" || isNaN(value) === false && hasNumericRule === true) return "number";
|
|
614
|
+
if (Array.isArray(value)) return "array";
|
|
615
|
+
return typeof value;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Get the custom message for a rule if exists
|
|
619
|
+
*/
|
|
620
|
+
function getCustomMessage(attributes, rule, customMessages, messageType, lang) {
|
|
621
|
+
const [attribute, primaryAttribute] = attributes;
|
|
622
|
+
const translatedMessages = dotify(Lang.get(lang)["custom"] || {});
|
|
623
|
+
const keys = getKeyCombinations(`${attribute}.${rule}`);
|
|
624
|
+
let allKeys = keys;
|
|
625
|
+
if (primaryAttribute) {
|
|
626
|
+
allKeys = [];
|
|
627
|
+
const primaryAttributeKeys = getKeyCombinations(`${primaryAttribute}.${rule}`);
|
|
628
|
+
for (let i = 0; i < keys.length; i++) {
|
|
629
|
+
allKeys.push(keys[i]);
|
|
630
|
+
if (keys[i] !== primaryAttributeKeys[i]) allKeys.push(primaryAttributeKeys[i]);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (isSizeRule(rule)) {
|
|
634
|
+
allKeys.pop();
|
|
635
|
+
allKeys.push(`${rule}.${messageType}`);
|
|
636
|
+
allKeys.push(rule);
|
|
637
|
+
}
|
|
638
|
+
let key = "";
|
|
639
|
+
let message = "";
|
|
640
|
+
for (let i = 0; i < allKeys.length; i++) {
|
|
641
|
+
key = allKeys[i];
|
|
642
|
+
if (Object.prototype.hasOwnProperty.call(customMessages, key)) return customMessages[key];
|
|
643
|
+
message = translatedMessages[key];
|
|
644
|
+
if (typeof message === "string") return message;
|
|
645
|
+
}
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Get the validation message for an attribute and rule.
|
|
650
|
+
*/
|
|
651
|
+
function getMessage(attributes, rule, value, customMessages, hasNumericRule, lang) {
|
|
652
|
+
const inlineMessage = getCustomMessage(attributes, rule, customMessages, getMesageType(value, hasNumericRule), lang);
|
|
653
|
+
if (inlineMessage) return inlineMessage;
|
|
654
|
+
const validationMessages = Lang.get(lang);
|
|
655
|
+
if (isSizeRule(rule) === true) return validationMessages[rule][getMesageType(value, hasNumericRule)];
|
|
656
|
+
return validationMessages[rule] || "";
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Convert a string to snake case.
|
|
660
|
+
*/
|
|
661
|
+
function toSnakeCase(string) {
|
|
662
|
+
return string.split(/ |\B(?=[A-Z])/).map((word) => word.toLowerCase()).join("_");
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Get the formatted name of the attribute
|
|
666
|
+
*/
|
|
667
|
+
function getFormattedAttribute(attribute) {
|
|
668
|
+
return toSnakeCase(getPrimaryKeyFromPath(attribute)).replace(/_/g, " ").trim();
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Get the combinations of keys from a main key. For example if the main key is 'user.info.name',
|
|
672
|
+
* the combination will be [user.info.name, info.name, name]
|
|
673
|
+
*/
|
|
674
|
+
function getKeyCombinations(key) {
|
|
675
|
+
const combinations = [key];
|
|
676
|
+
const splittedKey = key.split(".");
|
|
677
|
+
while (splittedKey.length > 1) {
|
|
678
|
+
splittedKey.shift();
|
|
679
|
+
combinations.push(splittedKey.join("."));
|
|
680
|
+
}
|
|
681
|
+
return combinations;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* The purpose of this method if to get the primary key associated with a path
|
|
685
|
+
* For example the primary key for path 'user.info.email' will be 'email'
|
|
686
|
+
*/
|
|
687
|
+
function getPrimaryKeyFromPath(path) {
|
|
688
|
+
const splittedPath = path.split(".");
|
|
689
|
+
if (splittedPath.length <= 1) return path;
|
|
690
|
+
const key = splittedPath.pop();
|
|
691
|
+
if (isNaN(parseInt(key)) === false) return getPrimaryKeyFromPath(splittedPath.join("."));
|
|
692
|
+
return key;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
//#endregion
|
|
696
|
+
//#region src/validators/errorBag.ts
|
|
697
|
+
var ErrorBag = class ErrorBag {
|
|
698
|
+
/**
|
|
699
|
+
* All of the registered messages.
|
|
700
|
+
*/
|
|
701
|
+
errors = {};
|
|
702
|
+
/**
|
|
703
|
+
* All Messages
|
|
704
|
+
*/
|
|
705
|
+
messages = {};
|
|
706
|
+
/**
|
|
707
|
+
* Stores the first error message
|
|
708
|
+
*/
|
|
709
|
+
firstMessage = "";
|
|
710
|
+
/**
|
|
711
|
+
* Specify whether error types should be returned or no
|
|
712
|
+
*/
|
|
713
|
+
withErrorTypes = false;
|
|
714
|
+
constructor(errors = {}, messages = {}, firstMessage = "", withErrorTypes = false) {
|
|
715
|
+
this.errors = errors;
|
|
716
|
+
this.messages = messages;
|
|
717
|
+
this.firstMessage = firstMessage;
|
|
718
|
+
this.withErrorTypes = withErrorTypes;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Set withErrorTypes attribute to true
|
|
722
|
+
*/
|
|
723
|
+
addErrorTypes() {
|
|
724
|
+
this.withErrorTypes = true;
|
|
725
|
+
return this;
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Add new recodrs to the errors and messages objects
|
|
729
|
+
*/
|
|
730
|
+
add(key, error) {
|
|
731
|
+
if (Array.isArray(this.errors[key]) && Array.isArray(this.messages[key])) {
|
|
732
|
+
this.errors[key].push(error);
|
|
733
|
+
this.messages[key].push(error.message);
|
|
734
|
+
} else {
|
|
735
|
+
this.errors[key] = [error];
|
|
736
|
+
this.messages[key] = [error.message];
|
|
737
|
+
}
|
|
738
|
+
this.firstMessage = this.firstMessage || error.message;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Get the first error related to a specific key
|
|
742
|
+
*/
|
|
743
|
+
first(key = null) {
|
|
744
|
+
if (!key) return this.firstMessage;
|
|
745
|
+
if (this.has(key)) return this.messages[key][0];
|
|
746
|
+
return "";
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Get the error messages keys
|
|
750
|
+
*/
|
|
751
|
+
keys() {
|
|
752
|
+
return Object.keys(this.messages);
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Get all the messages related to a specific key
|
|
756
|
+
*/
|
|
757
|
+
get(key, withErrorTypes = this.withErrorTypes) {
|
|
758
|
+
if (!this.has(key)) return [];
|
|
759
|
+
if (withErrorTypes) return this.errors[key];
|
|
760
|
+
return this.messages[key];
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Check if key exists in messages
|
|
764
|
+
*/
|
|
765
|
+
has(key) {
|
|
766
|
+
return this.messages[key] && this.messages[key].length > 0 ? true : false;
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Get all error messages
|
|
770
|
+
*/
|
|
771
|
+
all(allMessages = true, withErrorTypes = this.withErrorTypes) {
|
|
772
|
+
const messages = withErrorTypes ? { ...this.errors } : { ...this.messages };
|
|
773
|
+
if (!allMessages) Object.keys(messages).map((attribute) => messages[attribute] = messages[attribute][0]);
|
|
774
|
+
return messages;
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Remove error messages
|
|
778
|
+
*/
|
|
779
|
+
clear(keys) {
|
|
780
|
+
if (keys.length === 0) {
|
|
781
|
+
this.errors = {};
|
|
782
|
+
this.messages = {};
|
|
783
|
+
this.firstMessage = "";
|
|
784
|
+
return this;
|
|
785
|
+
}
|
|
786
|
+
keys.forEach((key) => {
|
|
787
|
+
if (Object.prototype.hasOwnProperty.call(this.messages, key)) {
|
|
788
|
+
delete this.messages[key];
|
|
789
|
+
delete this.errors[key];
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
this.firstMessage = "";
|
|
793
|
+
if (this.keys().length > 0) this.firstMessage = this.messages[Object.keys(this.messages)[0]][0];
|
|
794
|
+
return this;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Clone ErrorBag Instance
|
|
798
|
+
*/
|
|
799
|
+
clone() {
|
|
800
|
+
return new ErrorBag({ ...this.errors }, { ...this.messages }, this.firstMessage, this.withErrorTypes);
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
//#endregion
|
|
805
|
+
//#region src/Rules/password.ts
|
|
806
|
+
var Password$1 = class Password$1 extends IRuleContract {
|
|
807
|
+
/**
|
|
808
|
+
* The validator performing the validation.
|
|
809
|
+
*/
|
|
810
|
+
validator;
|
|
811
|
+
/**
|
|
812
|
+
* The minimum size of the password.
|
|
813
|
+
*/
|
|
814
|
+
minLength = 8;
|
|
815
|
+
/**
|
|
816
|
+
* The min amount of lower case letters required in the password
|
|
817
|
+
*/
|
|
818
|
+
minLowerCase = 0;
|
|
819
|
+
/**
|
|
820
|
+
* The min amount of uppercase letters required in the password
|
|
821
|
+
*/
|
|
822
|
+
minUpperCase = 0;
|
|
823
|
+
/**
|
|
824
|
+
* The min amount of letters required in the password
|
|
825
|
+
*/
|
|
826
|
+
minLetters = 0;
|
|
827
|
+
/**
|
|
828
|
+
* The min amount of letters required in the password
|
|
829
|
+
*/
|
|
830
|
+
minNumbers = 0;
|
|
831
|
+
/**
|
|
832
|
+
* The min amount of symbols required in the password
|
|
833
|
+
*/
|
|
834
|
+
minSymbols = 0;
|
|
835
|
+
/**
|
|
836
|
+
* Additional validation rules that should be merged into the default rules during validation.
|
|
837
|
+
*/
|
|
838
|
+
customRules = [];
|
|
839
|
+
/**
|
|
840
|
+
* The callback that will generate the "default" version of the password rule.
|
|
841
|
+
*/
|
|
842
|
+
static defaultCallback;
|
|
843
|
+
/**
|
|
844
|
+
* Create a new instance of the password class
|
|
845
|
+
*/
|
|
846
|
+
static create() {
|
|
847
|
+
return new Password$1();
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Set the minimum length of the password
|
|
851
|
+
*/
|
|
852
|
+
min(min) {
|
|
853
|
+
this.minLength = min;
|
|
854
|
+
return this;
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Set the min amount of letters required in the password
|
|
858
|
+
*/
|
|
859
|
+
letters(letters = 1) {
|
|
860
|
+
this.minLetters = letters;
|
|
861
|
+
return this;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Set the min amount of upper and lower case letters required in the password
|
|
865
|
+
*/
|
|
866
|
+
mixedCase(upperCase = 1, lowerCase = 1) {
|
|
867
|
+
this.minUpperCase = upperCase;
|
|
868
|
+
this.minLowerCase = lowerCase;
|
|
869
|
+
return this;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Set the min amount of numbers required in the password
|
|
873
|
+
*/
|
|
874
|
+
numbers(numbers = 1) {
|
|
875
|
+
this.minNumbers = numbers;
|
|
876
|
+
return this;
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Set the min amount of symbols required in the password
|
|
880
|
+
*/
|
|
881
|
+
symbols(symbols = 1) {
|
|
882
|
+
this.minSymbols = symbols;
|
|
883
|
+
return this;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Determine if the validation rule passes.
|
|
887
|
+
*/
|
|
888
|
+
passes(value, attribute) {
|
|
889
|
+
const validator = new BaseValidator(this.data, { [attribute]: [
|
|
890
|
+
"string",
|
|
891
|
+
`min:${this.minLength}`,
|
|
892
|
+
...this.customRules
|
|
893
|
+
] }, this.validator.customMessages, this.validator.customAttributes).setLang(this.lang);
|
|
894
|
+
if (!validator.validate()) {
|
|
895
|
+
const errors = validator.errors().addErrorTypes().get(attribute);
|
|
896
|
+
for (const key in errors) this.validator.errors().add(attribute, errors[key]);
|
|
897
|
+
}
|
|
898
|
+
if (typeof value !== "string") value = "";
|
|
899
|
+
let pattern;
|
|
900
|
+
const formattedAttribute = this.validator.getDisplayableAttribute(attribute);
|
|
901
|
+
if (this.minLowerCase) {
|
|
902
|
+
pattern = this.minLowerCase === 1 ? /\p{Ll}/u : new RegExp(`(.*\\p{Ll}){${this.minLowerCase}}.*`, "u");
|
|
903
|
+
if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
|
|
904
|
+
error_type: "min_lower_case",
|
|
905
|
+
message: this.trans(`password.${this.minLowerCase === 1 ? "lower_case" : "lower_cases"}`, {
|
|
906
|
+
attribute: formattedAttribute,
|
|
907
|
+
amount: this.minLowerCase
|
|
908
|
+
})
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
if (this.minUpperCase) {
|
|
912
|
+
pattern = this.minUpperCase === 1 ? /\p{Lu}/u : new RegExp(`(.*\\p{Lu}){${this.minUpperCase}}.*`, "u");
|
|
913
|
+
if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
|
|
914
|
+
error_type: "min_upper_case",
|
|
915
|
+
message: this.trans(`password.${this.minUpperCase === 1 ? "upper_case" : "upper_cases"}`, {
|
|
916
|
+
attribute: formattedAttribute,
|
|
917
|
+
amount: this.minUpperCase
|
|
918
|
+
})
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
if (this.minLetters) {
|
|
922
|
+
pattern = this.minLetters === 1 ? /\p{L}/u : new RegExp(`(.*\\p{L}){${this.minLetters}}.*`, "u");
|
|
923
|
+
if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
|
|
924
|
+
error_type: "min_letters",
|
|
925
|
+
message: this.trans(`password.${this.minLetters === 1 ? "letter" : "letters"}`, {
|
|
926
|
+
attribute: formattedAttribute,
|
|
927
|
+
amount: this.minLetters
|
|
928
|
+
})
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
if (this.minNumbers) {
|
|
932
|
+
pattern = this.minNumbers === 1 ? /\p{N}/u : new RegExp(`(.*\\p{N}){${this.minNumbers}}.*`, "u");
|
|
933
|
+
if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
|
|
934
|
+
error_type: "min_numbers",
|
|
935
|
+
message: this.trans(`password.${this.minNumbers === 1 ? "number" : "numbers"}`, {
|
|
936
|
+
attribute: formattedAttribute,
|
|
937
|
+
amount: this.minNumbers
|
|
938
|
+
})
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
if (this.minSymbols) {
|
|
942
|
+
pattern = this.minSymbols === 1 ? /\p{Z}|\p{S}|\p{P}/u : new RegExp(`(.*(\\p{Z}|\\p{S}|\\p{P})){${this.minSymbols}}.*`, "u");
|
|
943
|
+
if (!value || pattern.test(value) === false) this.validator.errors().add(attribute, {
|
|
944
|
+
error_type: "min_symbols",
|
|
945
|
+
message: this.trans(`password.${this.minSymbols === 1 ? "symbol" : "symbols"}`, {
|
|
946
|
+
attribute: formattedAttribute,
|
|
947
|
+
amount: this.minSymbols
|
|
948
|
+
})
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
if (this.validator.errors().has(attribute)) return false;
|
|
952
|
+
return true;
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Specify additional validation rules that should be merged with the default rules during validation.
|
|
956
|
+
*/
|
|
957
|
+
rules(rules) {
|
|
958
|
+
this.customRules = rules;
|
|
959
|
+
return this;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Get all the validation error messages related to the password
|
|
963
|
+
*/
|
|
964
|
+
getMessage() {
|
|
965
|
+
return {};
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Set the validator instance used to validate the password
|
|
969
|
+
*/
|
|
970
|
+
setValidator(validator) {
|
|
971
|
+
this.validator = validator;
|
|
972
|
+
return this;
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Set the default callback to be used for determining a password's default rules.
|
|
976
|
+
*/
|
|
977
|
+
static setDefault(callback = null) {
|
|
978
|
+
if (callback instanceof Password$1) {
|
|
979
|
+
this.defaultCallback = callback;
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
if (typeof callback !== "function") throw "The given callback should be callable";
|
|
983
|
+
this.defaultCallback = callback;
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Get the default configuration of the password rule.
|
|
987
|
+
*/
|
|
988
|
+
static default() {
|
|
989
|
+
const password = typeof this.defaultCallback === "function" ? this.defaultCallback() : this.defaultCallback;
|
|
990
|
+
return password instanceof IRuleContract ? password : Password$1.create().min(8);
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
//#endregion
|
|
995
|
+
//#region src/utilities/build.ts
|
|
996
|
+
function buildValidationMethodName(rule) {
|
|
997
|
+
if (!rule) return rule;
|
|
998
|
+
return rule.split("_").map((rule) => `${rule[0].toUpperCase()}${rule.slice(1)}`).join("");
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
//#endregion
|
|
1002
|
+
//#region src/payloads/replaceAttributePayload.ts
|
|
1003
|
+
var replaceAttributePayload = class {
|
|
1004
|
+
/**
|
|
1005
|
+
* Stores the data object
|
|
1006
|
+
*/
|
|
1007
|
+
data;
|
|
1008
|
+
/**
|
|
1009
|
+
* The message in which attributes will be replaced
|
|
1010
|
+
*/
|
|
1011
|
+
message;
|
|
1012
|
+
/**
|
|
1013
|
+
* Parameters that will be used to replace the attributes
|
|
1014
|
+
*/
|
|
1015
|
+
parameters;
|
|
1016
|
+
/**
|
|
1017
|
+
* Flag that identifies wether the numeric rule exists or not
|
|
1018
|
+
*/
|
|
1019
|
+
hasNumericRule;
|
|
1020
|
+
/**
|
|
1021
|
+
* The function that will be used to format attributes
|
|
1022
|
+
*/
|
|
1023
|
+
getDisplayableAttribute;
|
|
1024
|
+
constructor(data, message, parameters, hasNumericRule, getDisplayableAttribute) {
|
|
1025
|
+
this.data = data;
|
|
1026
|
+
this.message = message;
|
|
1027
|
+
this.parameters = parameters;
|
|
1028
|
+
this.hasNumericRule = hasNumericRule;
|
|
1029
|
+
this.getDisplayableAttribute = getDisplayableAttribute;
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
//#endregion
|
|
1034
|
+
//#region src/utilities/date.ts
|
|
1035
|
+
/**
|
|
1036
|
+
* Convert value to date instance
|
|
1037
|
+
*/
|
|
1038
|
+
function toDate(value) {
|
|
1039
|
+
const date = Date.parse(value);
|
|
1040
|
+
return !isNaN(date) ? new Date(date) : null;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
//#endregion
|
|
1044
|
+
//#region src/validators/replaceAttributes.ts
|
|
1045
|
+
const replaceAttributes = {
|
|
1046
|
+
replaceAcceptedIf: function({ data, message, parameters, getDisplayableAttribute }) {
|
|
1047
|
+
const [other] = parameters;
|
|
1048
|
+
const result = deepFind(data, other);
|
|
1049
|
+
const values = {
|
|
1050
|
+
":other": getDisplayableAttribute(other),
|
|
1051
|
+
":value": result
|
|
1052
|
+
};
|
|
1053
|
+
return message.replace(/:other|:value/gi, (matched) => values[matched]);
|
|
1054
|
+
},
|
|
1055
|
+
replaceAfter: function(payload) {
|
|
1056
|
+
return this.replaceBefore(payload);
|
|
1057
|
+
},
|
|
1058
|
+
replaceAfterOrEqual: function(payload) {
|
|
1059
|
+
return this.replaceBefore(payload);
|
|
1060
|
+
},
|
|
1061
|
+
replaceBefore: function({ message, parameters, getDisplayableAttribute }) {
|
|
1062
|
+
if (!toDate(parameters[0])) return message.replace(":date", getDisplayableAttribute(parameters[0]));
|
|
1063
|
+
return message.replace(":date", parameters[0]);
|
|
1064
|
+
},
|
|
1065
|
+
replaceBeforeOrEqual: function(payload) {
|
|
1066
|
+
return this.replaceBefore(payload);
|
|
1067
|
+
},
|
|
1068
|
+
replaceBetween: function({ message, parameters }) {
|
|
1069
|
+
const values = {
|
|
1070
|
+
":min": parameters[0],
|
|
1071
|
+
":max": parameters[1]
|
|
1072
|
+
};
|
|
1073
|
+
return message.replace(/:min|:max/gi, (matched) => values[matched]);
|
|
1074
|
+
},
|
|
1075
|
+
replaceDateEquals: function(payload) {
|
|
1076
|
+
return this.replaceBefore(payload);
|
|
1077
|
+
},
|
|
1078
|
+
replaceDeclinedIf: function({ message, parameters, data, getDisplayableAttribute }) {
|
|
1079
|
+
const [other] = parameters;
|
|
1080
|
+
const result = deepFind(data, other);
|
|
1081
|
+
const values = {
|
|
1082
|
+
":other": getDisplayableAttribute(other),
|
|
1083
|
+
":value": result
|
|
1084
|
+
};
|
|
1085
|
+
return message.replace(/:other|:value/gi, (matched) => values[matched]);
|
|
1086
|
+
},
|
|
1087
|
+
replaceDifferent: function(payload) {
|
|
1088
|
+
return this.replaceSame(payload);
|
|
1089
|
+
},
|
|
1090
|
+
replaceDigits: function({ message, parameters }) {
|
|
1091
|
+
return message.replace(":digits", parameters[0]);
|
|
1092
|
+
},
|
|
1093
|
+
replaceDigitsBetween: function(payload) {
|
|
1094
|
+
return this.replaceBetween(payload);
|
|
1095
|
+
},
|
|
1096
|
+
replaceEndsWith: function({ message, parameters }) {
|
|
1097
|
+
return message.replace(":values", parameters.join(", "));
|
|
1098
|
+
},
|
|
1099
|
+
replaceIn: function({ message, parameters }) {
|
|
1100
|
+
return message.replace(":values", parameters.join(", "));
|
|
1101
|
+
},
|
|
1102
|
+
replaceStartsWith: function({ message, parameters }) {
|
|
1103
|
+
return message.replace(":values", parameters.join(", "));
|
|
1104
|
+
},
|
|
1105
|
+
replaceMin: function({ message, parameters }) {
|
|
1106
|
+
return message.replace(":min", parameters[0]);
|
|
1107
|
+
},
|
|
1108
|
+
replaceMax: function({ message, parameters }) {
|
|
1109
|
+
return message.replace(":max", parameters[0]);
|
|
1110
|
+
},
|
|
1111
|
+
replaceRequiredWith: function({ message, parameters, getDisplayableAttribute }) {
|
|
1112
|
+
return message.replace(":values", parameters.map((attribute) => getDisplayableAttribute(attribute)).join(", "));
|
|
1113
|
+
},
|
|
1114
|
+
replaceRequiredWithAll: function(payload) {
|
|
1115
|
+
return this.replaceRequiredWith(payload);
|
|
1116
|
+
},
|
|
1117
|
+
replaceRequiredWithout: function(payload) {
|
|
1118
|
+
return this.replaceRequiredWith(payload);
|
|
1119
|
+
},
|
|
1120
|
+
replaceRequiredWithoutAll: function(payload) {
|
|
1121
|
+
return this.replaceRequiredWith(payload);
|
|
1122
|
+
},
|
|
1123
|
+
replaceGt: function({ message, parameters, data, hasNumericRule }) {
|
|
1124
|
+
const [value] = parameters;
|
|
1125
|
+
const result = deepFind(data, value);
|
|
1126
|
+
if (typeof result === "undefined") return message.replace(":value", value);
|
|
1127
|
+
return message.replace(":value", getSize(result, hasNumericRule).toString());
|
|
1128
|
+
},
|
|
1129
|
+
replaceLt: function(payload) {
|
|
1130
|
+
return this.replaceGt(payload);
|
|
1131
|
+
},
|
|
1132
|
+
replaceGte: function(payload) {
|
|
1133
|
+
return this.replaceGt(payload);
|
|
1134
|
+
},
|
|
1135
|
+
replaceLte: function(payload) {
|
|
1136
|
+
return this.replaceGt(payload);
|
|
1137
|
+
},
|
|
1138
|
+
replaceRequiredIf: function({ message, parameters, data, getDisplayableAttribute }) {
|
|
1139
|
+
const [other] = parameters;
|
|
1140
|
+
const result = deepFind(data, other);
|
|
1141
|
+
const values = {
|
|
1142
|
+
":other": getDisplayableAttribute(other),
|
|
1143
|
+
":value": result
|
|
1144
|
+
};
|
|
1145
|
+
return message.replace(/:other|:value/gi, (matched) => values[matched]);
|
|
1146
|
+
},
|
|
1147
|
+
replaceRequiredUnless: function({ message, parameters, getDisplayableAttribute }) {
|
|
1148
|
+
const [other] = parameters;
|
|
1149
|
+
const values = {
|
|
1150
|
+
":other": getDisplayableAttribute(other),
|
|
1151
|
+
":values": parameters.slice(1).join(", ")
|
|
1152
|
+
};
|
|
1153
|
+
return message.replace(/:other|:values/gi, (matched) => values[matched]);
|
|
1154
|
+
},
|
|
1155
|
+
replaceSame: function({ message, parameters, getDisplayableAttribute }) {
|
|
1156
|
+
return message.replace(":other", getDisplayableAttribute(parameters[0]));
|
|
1157
|
+
},
|
|
1158
|
+
replaceSize: function({ message, parameters }) {
|
|
1159
|
+
return message.replace(":size", parameters[0]);
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
|
|
1163
|
+
//#endregion
|
|
1164
|
+
//#region src/Rules/closureValidationRule.ts
|
|
1165
|
+
var ClosureValidationRule = class extends IRuleContract {
|
|
1166
|
+
/**
|
|
1167
|
+
* The callback that validates the attribute
|
|
1168
|
+
*/
|
|
1169
|
+
callback;
|
|
1170
|
+
/**
|
|
1171
|
+
* Indicates if the validation callback failed.
|
|
1172
|
+
*/
|
|
1173
|
+
failed = false;
|
|
1174
|
+
constructor(callback) {
|
|
1175
|
+
super();
|
|
1176
|
+
this.callback = callback;
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Determine if the validation rule passes.
|
|
1180
|
+
*/
|
|
1181
|
+
passes(value, attribute) {
|
|
1182
|
+
this.failed = false;
|
|
1183
|
+
const result = this.callback(value, (message) => {
|
|
1184
|
+
this.failed = true;
|
|
1185
|
+
this.message = message;
|
|
1186
|
+
}, attribute);
|
|
1187
|
+
if (result instanceof Promise) return result.then(() => !this.failed);
|
|
1188
|
+
return !this.failed;
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1192
|
+
//#endregion
|
|
1193
|
+
//#region src/validators/validationData.ts
|
|
1194
|
+
const validationData = {
|
|
1195
|
+
initializeAndGatherData: function(attribute, masterData) {
|
|
1196
|
+
const data = dotify(this.initializeAttributeOnData(attribute, masterData));
|
|
1197
|
+
return {
|
|
1198
|
+
...data,
|
|
1199
|
+
...this.extractValuesFromWildCards(masterData, data, attribute)
|
|
1200
|
+
};
|
|
1201
|
+
},
|
|
1202
|
+
initializeAttributeOnData: function(attribute, masterData) {
|
|
1203
|
+
const explicitPath = this.getLeadingExplicitAttributePath(attribute);
|
|
1204
|
+
const data = this.extractDataFromPath(explicitPath, JSON.parse(JSON.stringify(masterData)));
|
|
1205
|
+
if (attribute.indexOf("*") === -1 || attribute.indexOf("*") === attribute.length - 1) return data;
|
|
1206
|
+
deepSet(data, attribute, null);
|
|
1207
|
+
return data;
|
|
1208
|
+
},
|
|
1209
|
+
extractValuesFromWildCards(masterData, data, attribute) {
|
|
1210
|
+
const keys = [];
|
|
1211
|
+
const pattern = new RegExp("^" + attribute.replace(/\*/g, "[^\\.]*"));
|
|
1212
|
+
let result = null;
|
|
1213
|
+
for (const key in data) {
|
|
1214
|
+
result = key.match(pattern);
|
|
1215
|
+
if (result) keys.push(result[0]);
|
|
1216
|
+
}
|
|
1217
|
+
data = {};
|
|
1218
|
+
keys.forEach((key) => data[key] = deepFind(masterData, key));
|
|
1219
|
+
return data;
|
|
1220
|
+
},
|
|
1221
|
+
getLeadingExplicitAttributePath: function(attribute) {
|
|
1222
|
+
return attribute.split("*")[0].replace(/\.$/, "");
|
|
1223
|
+
},
|
|
1224
|
+
extractDataFromPath(path, masterData) {
|
|
1225
|
+
const results = {};
|
|
1226
|
+
const value = deepFind(masterData, path);
|
|
1227
|
+
if (value !== void 0) deepSet(results, path, value);
|
|
1228
|
+
return results;
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
//#endregion
|
|
1233
|
+
//#region src/validators/validationRuleParser.ts
|
|
1234
|
+
const validationRuleParser = {
|
|
1235
|
+
explodeRules: function(rules, data = {}) {
|
|
1236
|
+
const implicitAttributes = {};
|
|
1237
|
+
for (const key in rules) if (key.indexOf("*") !== -1) {
|
|
1238
|
+
rules = this.explodeWildCardRules(rules, key, data, implicitAttributes);
|
|
1239
|
+
delete rules[key];
|
|
1240
|
+
} else if (Object.prototype.hasOwnProperty.call(rules, key) && Array.isArray(rules)) rules[Number(key)] = this.explodeExplicitRules(rules[Number(key)]);
|
|
1241
|
+
else if (Object.prototype.hasOwnProperty.call(rules, key) && !Array.isArray(rules)) rules[key] = this.explodeExplicitRules(rules[key]);
|
|
1242
|
+
return {
|
|
1243
|
+
rules,
|
|
1244
|
+
implicitAttributes
|
|
1245
|
+
};
|
|
1246
|
+
},
|
|
1247
|
+
explodeWildCardRules: function(results, attribute, masterData, implicitAttributes) {
|
|
1248
|
+
const pattern = new RegExp("^" + attribute.replace(/\*/g, "[^.]*") + "$");
|
|
1249
|
+
const data = validationData.initializeAndGatherData(attribute, masterData);
|
|
1250
|
+
const rule = results[attribute];
|
|
1251
|
+
for (const key in data) if (key.slice(0, attribute.length) === attribute || key.match(pattern) !== null) {
|
|
1252
|
+
if (Array.isArray(implicitAttributes[attribute])) implicitAttributes[attribute].push(key);
|
|
1253
|
+
else implicitAttributes[attribute] = [key];
|
|
1254
|
+
results = this.mergeRulesForAttribute(results, key, rule);
|
|
1255
|
+
}
|
|
1256
|
+
return results;
|
|
1257
|
+
},
|
|
1258
|
+
mergeRulesForAttribute(results, attribute, rules) {
|
|
1259
|
+
const merge = this.explodeRules([rules]).rules[0];
|
|
1260
|
+
results[attribute] = [...results[attribute] ? this.explodeExplicitRules(results[attribute]) : [], ...merge];
|
|
1261
|
+
return results;
|
|
1262
|
+
},
|
|
1263
|
+
explodeExplicitRules: function(rules) {
|
|
1264
|
+
if (typeof rules === "string") return rules.split("|");
|
|
1265
|
+
if (!Array.isArray(rules)) return [this.prepareRule(rules)];
|
|
1266
|
+
return rules.map((rule) => this.prepareRule(rule));
|
|
1267
|
+
},
|
|
1268
|
+
prepareRule(rule) {
|
|
1269
|
+
if (rule instanceof IRuleContract) return rule;
|
|
1270
|
+
if (typeof rule === "function") return new ClosureValidationRule(rule);
|
|
1271
|
+
return rule.toString();
|
|
1272
|
+
},
|
|
1273
|
+
parse(rule) {
|
|
1274
|
+
if (rule instanceof IRuleContract) return [rule, []];
|
|
1275
|
+
return this.parseStringRule(rule);
|
|
1276
|
+
},
|
|
1277
|
+
parseStringRule: function(rule) {
|
|
1278
|
+
let parameters = [];
|
|
1279
|
+
let parameter;
|
|
1280
|
+
if (rule.indexOf(":") !== -1) {
|
|
1281
|
+
[rule, parameter] = rule.split(/:(.+)/);
|
|
1282
|
+
parameters = parameter.split(",");
|
|
1283
|
+
}
|
|
1284
|
+
return [rule, parameters];
|
|
1285
|
+
},
|
|
1286
|
+
getRule: function(attribute, searchRules, availableRules) {
|
|
1287
|
+
if (!availableRules[attribute]) return [];
|
|
1288
|
+
searchRules = Array.isArray(searchRules) ? searchRules : [searchRules];
|
|
1289
|
+
for (let i = 0; i < availableRules[attribute].length; i++) {
|
|
1290
|
+
const [rule, parameters] = this.parse(availableRules[attribute][i]);
|
|
1291
|
+
if (searchRules.indexOf(rule) !== -1) return [rule, parameters];
|
|
1292
|
+
}
|
|
1293
|
+
return [];
|
|
1294
|
+
},
|
|
1295
|
+
hasRule: function(attribute, searchRules, availableRules) {
|
|
1296
|
+
return this.getRule(attribute, searchRules, availableRules).length > 0;
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
|
|
1300
|
+
//#endregion
|
|
1301
|
+
//#region src/validators/validateAttributes.ts
|
|
1302
|
+
var validateAttributes = class {
|
|
1303
|
+
/**
|
|
1304
|
+
* Stores the data object
|
|
1305
|
+
*/
|
|
1306
|
+
data;
|
|
1307
|
+
/**
|
|
1308
|
+
* Stores the rules object
|
|
1309
|
+
*/
|
|
1310
|
+
rules;
|
|
1311
|
+
constructor(data = {}, rules = {}) {
|
|
1312
|
+
this.data = data;
|
|
1313
|
+
this.rules = rules;
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Validate that an attribute was "accepted".
|
|
1317
|
+
*
|
|
1318
|
+
* This validation rule implies the attribute is "required".
|
|
1319
|
+
*/
|
|
1320
|
+
validateAccepted(value) {
|
|
1321
|
+
return this.validateRequired(value) && [
|
|
1322
|
+
"yes",
|
|
1323
|
+
"on",
|
|
1324
|
+
"1",
|
|
1325
|
+
1,
|
|
1326
|
+
true,
|
|
1327
|
+
"true"
|
|
1328
|
+
].indexOf(value) !== -1;
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Validate that an attribute was "accepted" when another attribute has a given value.
|
|
1332
|
+
*/
|
|
1333
|
+
validateAcceptedIf(value, parameters) {
|
|
1334
|
+
this.requireParameterCount(2, parameters, "accepted_if");
|
|
1335
|
+
const other = deepFind(this.data, parameters[0]);
|
|
1336
|
+
if (!other) return true;
|
|
1337
|
+
if (parameters.slice(1).indexOf(other) !== -1) return this.validateAccepted(value);
|
|
1338
|
+
return true;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Validate the date is after a given date.
|
|
1342
|
+
*/
|
|
1343
|
+
validateAfter(value, parameters) {
|
|
1344
|
+
this.requireParameterCount(1, parameters, "after");
|
|
1345
|
+
return this.compareDates(value, parameters[0], ">", "after");
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Validate the date is after or equal a given date.
|
|
1349
|
+
*/
|
|
1350
|
+
validateAfterOrEqual(value, parameters) {
|
|
1351
|
+
this.requireParameterCount(1, parameters, "after_or_equal");
|
|
1352
|
+
return this.compareDates(value, parameters[0], ">=", "after_or_equal");
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Validate that an attribute contains only alphabetic characters.
|
|
1356
|
+
*/
|
|
1357
|
+
validateAlpha(value) {
|
|
1358
|
+
return typeof value === "string" && /^[a-zA-Z]+$/.test(value);
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Validate that an attribute contains only alpha-numeric characters, dashes, and underscores.
|
|
1362
|
+
*/
|
|
1363
|
+
validateAlphaDash(value) {
|
|
1364
|
+
if (typeof value != "string" && typeof value != "number") return false;
|
|
1365
|
+
return /^(?=.*[a-zA-Z0-9])[a-zA-Z0-9-_]+$/.test(value.toString());
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Validate that an attribute contains only alpha-numeric characters.
|
|
1369
|
+
*/
|
|
1370
|
+
validateAlphaNum(value) {
|
|
1371
|
+
if (typeof value != "string" && typeof value != "number") return false;
|
|
1372
|
+
return /^[a-zA-Z0-9]+$/.test(value.toString());
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Validate that an attribute is an array
|
|
1376
|
+
*/
|
|
1377
|
+
validateArray(value) {
|
|
1378
|
+
return Array.isArray(value);
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Validate that an attribute is an array and that his values are unique
|
|
1382
|
+
*/
|
|
1383
|
+
validateArrayUnique(value) {
|
|
1384
|
+
if (!Array.isArray(value)) return false;
|
|
1385
|
+
return new Set(value).size === value.length;
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Always returns true - this method will be used in conbination with other rules and will be used to stop validation of first failure
|
|
1389
|
+
*/
|
|
1390
|
+
validateBail() {
|
|
1391
|
+
return true;
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Validate the date is before a given date.
|
|
1395
|
+
*/
|
|
1396
|
+
validateBefore(value, parameters) {
|
|
1397
|
+
this.requireParameterCount(1, parameters, "before");
|
|
1398
|
+
return this.compareDates(value, parameters[0], "<", "before");
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Validate the date is before or equal a given date.
|
|
1402
|
+
*/
|
|
1403
|
+
validateBeforeOrEqual(value, parameters) {
|
|
1404
|
+
this.requireParameterCount(1, parameters, "before_or_equal");
|
|
1405
|
+
return this.compareDates(value, parameters[0], "<=", "before_or_equal");
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Validate the size of an attribute is between a set of values
|
|
1409
|
+
*/
|
|
1410
|
+
validateBetween(value, parameters, attribute) {
|
|
1411
|
+
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.";
|
|
1412
|
+
this.requireParameterCount(2, parameters, "between");
|
|
1413
|
+
let [min, max] = parameters;
|
|
1414
|
+
if (isNaN(min) || isNaN(max)) throw "Validation rule between requires both parameters to be numbers.";
|
|
1415
|
+
min = Number(min);
|
|
1416
|
+
max = Number(max);
|
|
1417
|
+
if (min >= max) throw "Validation rule between requires that the first parameter be greater than the second one.";
|
|
1418
|
+
const size = getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules));
|
|
1419
|
+
return size >= min && size <= max;
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Validate that an attribute is boolean
|
|
1423
|
+
*/
|
|
1424
|
+
validateBoolean(value, parameters, attribute) {
|
|
1425
|
+
if (validationRuleParser.hasRule(attribute, "strict", this.rules)) return typeof value === "boolean";
|
|
1426
|
+
return [
|
|
1427
|
+
true,
|
|
1428
|
+
false,
|
|
1429
|
+
0,
|
|
1430
|
+
1,
|
|
1431
|
+
"0",
|
|
1432
|
+
"1"
|
|
1433
|
+
].indexOf(value) !== -1;
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Validate that an attribute has matching confirmation.
|
|
1437
|
+
*/
|
|
1438
|
+
validateConfirmed(value, parameters, attribute) {
|
|
1439
|
+
return this.validateSame(value, [`${attribute}_confirmation`]) || this.validateSame(value, [`${attribute}Confirmation`]);
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Validate that an attribute is a valid date.
|
|
1443
|
+
*/
|
|
1444
|
+
validateDate(value) {
|
|
1445
|
+
return toDate(value) ? true : false;
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Validate that an attribute is equal to another date.
|
|
1449
|
+
*/
|
|
1450
|
+
validateDateEquals(value, paramters) {
|
|
1451
|
+
this.requireParameterCount(1, paramters, "date_equals");
|
|
1452
|
+
return this.compareDates(value, paramters[0], "=", "date_equals");
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Validate that an attribute was "declined".
|
|
1456
|
+
*
|
|
1457
|
+
* This validation rule implies the attribute is "required".
|
|
1458
|
+
*/
|
|
1459
|
+
validateDeclined(value) {
|
|
1460
|
+
return this.validateRequired(value) && [
|
|
1461
|
+
"no",
|
|
1462
|
+
"off",
|
|
1463
|
+
"0",
|
|
1464
|
+
0,
|
|
1465
|
+
false,
|
|
1466
|
+
"false"
|
|
1467
|
+
].indexOf(value) !== -1;
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Validate that an attribute was "declined" when another attribute has a given value.
|
|
1471
|
+
*/
|
|
1472
|
+
validateDeclinedIf(value, parameters) {
|
|
1473
|
+
this.requireParameterCount(2, parameters, "declined_if");
|
|
1474
|
+
const other = deepFind(this.data, parameters[0]);
|
|
1475
|
+
if (!other) return true;
|
|
1476
|
+
if (parameters.slice(1).indexOf(other) !== -1) return this.validateDeclined(value);
|
|
1477
|
+
return true;
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Validate that an attribute is different from another attribute.
|
|
1481
|
+
*/
|
|
1482
|
+
validateDifferent(value, parameters) {
|
|
1483
|
+
this.requireParameterCount(1, parameters, "different");
|
|
1484
|
+
const other = deepFind(this.data, parameters[0]);
|
|
1485
|
+
if (!sameType(value, other)) return true;
|
|
1486
|
+
if (value !== null && typeof value === "object") return !deepEqual(value, other);
|
|
1487
|
+
return value !== other;
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Validate that an attribute has a given number of digits.
|
|
1491
|
+
*/
|
|
1492
|
+
validateDigits(value, parameters) {
|
|
1493
|
+
this.requireParameterCount(1, parameters, "digits");
|
|
1494
|
+
if (isInteger(parameters[0]) === false) throw "Validation rule digits requires the parameter to be an integer.";
|
|
1495
|
+
if (parameters[0] <= 0) throw "Validation rule digits requires the parameter to be an integer greater than 0.";
|
|
1496
|
+
if (typeof value !== "string" && typeof value !== "number") return false;
|
|
1497
|
+
value = value.toString();
|
|
1498
|
+
return /^\d+$/.test(value) && value.length === parseInt(parameters[0]);
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Validate that an attribute is between a given number of digits.
|
|
1502
|
+
*/
|
|
1503
|
+
validateDigitsBetween(value, parameters) {
|
|
1504
|
+
this.requireParameterCount(2, parameters, "digits_between");
|
|
1505
|
+
let [min, max] = parameters;
|
|
1506
|
+
if (isInteger(min) === false || isInteger(max) === false) throw "Validation rule digits_between requires both parameters to be integers.";
|
|
1507
|
+
min = parseInt(min);
|
|
1508
|
+
max = parseInt(max);
|
|
1509
|
+
if (min <= 0 || max <= 0) throw "Validation rule digits_between requires the parameters to be an integer greater than 0.";
|
|
1510
|
+
if (min >= max) throw "Validation rule digits_between requires the max param to be greater than the min param.";
|
|
1511
|
+
if (typeof value !== "string" && typeof value !== "number") return false;
|
|
1512
|
+
value = value.toString();
|
|
1513
|
+
const valueLength = value.length;
|
|
1514
|
+
return /^\d+$/.test(value) && valueLength >= min && valueLength <= max;
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Validate that an attribute is a valid email address.
|
|
1518
|
+
*/
|
|
1519
|
+
validateEmail(value) {
|
|
1520
|
+
if (typeof value !== "string") return false;
|
|
1521
|
+
/**
|
|
1522
|
+
* Max allowed length for a top-level-domain is 24 characters.
|
|
1523
|
+
* reference to list of top-level-domains: https://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
|
1524
|
+
*/
|
|
1525
|
+
return value.toLowerCase().match(/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,24})+$/) !== null;
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Validate the attribute ends with a given substring.
|
|
1529
|
+
*/
|
|
1530
|
+
validateEndsWith(value, parameters) {
|
|
1531
|
+
this.requireParameterCount(1, parameters, "ends_with");
|
|
1532
|
+
if (typeof value !== "string") throw "The field under validation must be a string";
|
|
1533
|
+
const valueLength = value.length;
|
|
1534
|
+
for (let i = 0; i < parameters.length; i++) if (typeof parameters[i] === "string" && value.indexOf(parameters[i], valueLength - parameters[i].length) !== -1) return true;
|
|
1535
|
+
return false;
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* Validate that two attributes match.
|
|
1539
|
+
*/
|
|
1540
|
+
validateSame(value, paramaters) {
|
|
1541
|
+
this.requireParameterCount(1, paramaters, "same");
|
|
1542
|
+
const other = deepFind(this.data, paramaters[0]);
|
|
1543
|
+
if (!sameType(value, other)) return false;
|
|
1544
|
+
if (value !== null && typeof value === "object") return deepEqual(value, other);
|
|
1545
|
+
return value === other;
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Validate the size of an attribute.
|
|
1549
|
+
*/
|
|
1550
|
+
validateSize(value, parameters, attribute) {
|
|
1551
|
+
this.requireParameterCount(1, parameters, "size");
|
|
1552
|
+
return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) === Number(parameters[0]);
|
|
1553
|
+
}
|
|
1554
|
+
/**
|
|
1555
|
+
* Validate Optinial attributes. Always return true, just lets us put sometimes in rule.
|
|
1556
|
+
*/
|
|
1557
|
+
validateSometimes() {
|
|
1558
|
+
return true;
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Validate the attribute starts with a given substring.
|
|
1562
|
+
*/
|
|
1563
|
+
validateStartsWith(value, parameters) {
|
|
1564
|
+
this.requireParameterCount(1, parameters, "starts_with");
|
|
1565
|
+
if (typeof value !== "string") throw "The field under validation must be a string";
|
|
1566
|
+
for (let i = 0; i < parameters.length; i++) if (typeof parameters[i] === "string" && value.substr(0, parameters[i].length) === parameters[i]) return true;
|
|
1567
|
+
return false;
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Validate that a required attribute exists
|
|
1571
|
+
*/
|
|
1572
|
+
validateRequired(value) {
|
|
1573
|
+
if (value === null || typeof value === "undefined") return false;
|
|
1574
|
+
else if (typeof value === "string" && value.trim() === "") return false;
|
|
1575
|
+
else if (Array.isArray(value) && value.length < 1) return false;
|
|
1576
|
+
else if (typeof value === "object" && Object.keys(value).length < 1) return false;
|
|
1577
|
+
return true;
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Validate that an attribute exists when another atteribute has a given value
|
|
1581
|
+
*/
|
|
1582
|
+
validateRequiredIf(value, parameters) {
|
|
1583
|
+
this.requireParameterCount(2, parameters, "required_if");
|
|
1584
|
+
const other = deepFind(this.data, parameters[0]);
|
|
1585
|
+
if (typeof other === "undefined") return true;
|
|
1586
|
+
if (this.parseDependentRuleParameters(other, parameters).indexOf(other) !== -1) return this.validateRequired(value);
|
|
1587
|
+
return true;
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Validate that an attribute exists when another attribute does not have a given value.
|
|
1591
|
+
*/
|
|
1592
|
+
validateRequiredUnless(value, parameters) {
|
|
1593
|
+
this.requireParameterCount(2, parameters, "required_unless");
|
|
1594
|
+
let other = deepFind(this.data, parameters[0]);
|
|
1595
|
+
other = typeof other === "undefined" ? null : other;
|
|
1596
|
+
if (this.parseDependentRuleParameters(other, parameters).indexOf(other) === -1) return this.validateRequired(value);
|
|
1597
|
+
return true;
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Validate that an attribute exists when any other attribute exists.
|
|
1601
|
+
*/
|
|
1602
|
+
validateRequiredWith(value, parameters) {
|
|
1603
|
+
if (!this.allFailingRequired(parameters)) return this.validateRequired(value);
|
|
1604
|
+
return true;
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Validate that an attribute exists when all other attributes exist.
|
|
1608
|
+
*/
|
|
1609
|
+
validateRequiredWithAll(value, parameters) {
|
|
1610
|
+
if (!this.anyFailingRequired(parameters)) return this.validateRequired(value);
|
|
1611
|
+
return true;
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Validate that an attribute exists when another attribute does not.
|
|
1615
|
+
*/
|
|
1616
|
+
validateRequiredWithout(value, parameters) {
|
|
1617
|
+
if (this.anyFailingRequired(parameters)) return this.validateRequired(value);
|
|
1618
|
+
return true;
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Validate that an attribute exists when all other attributes do not.
|
|
1622
|
+
*/
|
|
1623
|
+
validateRequiredWithoutAll(value, parameters) {
|
|
1624
|
+
if (this.allFailingRequired(parameters)) return this.validateRequired(value);
|
|
1625
|
+
return true;
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Determine if any of the given attributes fail the required test.
|
|
1629
|
+
*/
|
|
1630
|
+
anyFailingRequired(attributes) {
|
|
1631
|
+
for (let i = 0; i < attributes.length; i++) if (!this.validateRequired(deepFind(this.data, attributes[i]))) return true;
|
|
1632
|
+
return false;
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Determine if all of the given attributes fail the required test.
|
|
1636
|
+
*/
|
|
1637
|
+
allFailingRequired(attributes) {
|
|
1638
|
+
for (let i = 0; i < attributes.length; i++) if (this.validateRequired(deepFind(this.data, attributes[i]))) return false;
|
|
1639
|
+
return true;
|
|
1640
|
+
}
|
|
1641
|
+
/**
|
|
1642
|
+
* Validate that an attribute is a string.
|
|
1643
|
+
*/
|
|
1644
|
+
validateString(value) {
|
|
1645
|
+
return typeof value === "string";
|
|
1646
|
+
}
|
|
1647
|
+
/**
|
|
1648
|
+
* Validate the size of an attribute is less than a maximum value.
|
|
1649
|
+
*/
|
|
1650
|
+
validateMax(value, parameters, attribute) {
|
|
1651
|
+
this.requireParameterCount(1, parameters, "max");
|
|
1652
|
+
if (isNaN(parameters[0])) throw "Validation rule max requires parameter to be a number.";
|
|
1653
|
+
return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) <= Number(parameters[0]);
|
|
1654
|
+
}
|
|
1655
|
+
/**
|
|
1656
|
+
* Validate the size of an attribute is greater than a minimum value.
|
|
1657
|
+
*/
|
|
1658
|
+
validateMin(value, parameters, attribute) {
|
|
1659
|
+
this.requireParameterCount(1, parameters, "min");
|
|
1660
|
+
if (isNaN(parameters[0])) throw "Validation rule min requires parameter to be a number.";
|
|
1661
|
+
return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) >= Number(parameters[0]);
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Validate that an attribute is numeric.
|
|
1665
|
+
*/
|
|
1666
|
+
validateNumeric(value, parameters, attribute) {
|
|
1667
|
+
if (validationRuleParser.hasRule(attribute, "strict", this.rules) && typeof value !== "number") return false;
|
|
1668
|
+
return value !== null && isNaN(value) === false;
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Validate that an attribute is an object
|
|
1672
|
+
*/
|
|
1673
|
+
validateObject(value) {
|
|
1674
|
+
return isObject(value);
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Validate that an attribute exists even if not filled.
|
|
1678
|
+
*/
|
|
1679
|
+
validatePresent(value, parameters, attribute) {
|
|
1680
|
+
return typeof deepFind(this.data, attribute) !== "undefined";
|
|
1681
|
+
}
|
|
1682
|
+
/**
|
|
1683
|
+
* Validate that an attribute is an integer.
|
|
1684
|
+
*/
|
|
1685
|
+
validateInteger(value, parameters, attribute) {
|
|
1686
|
+
if (validationRuleParser.hasRule(attribute, "strict", this.rules) && typeof value !== "number") return false;
|
|
1687
|
+
return isInteger(value);
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Validate that the attribute is a valid JSON string
|
|
1691
|
+
*/
|
|
1692
|
+
validateJson(value) {
|
|
1693
|
+
if (!value || typeof value !== "string") return false;
|
|
1694
|
+
try {
|
|
1695
|
+
JSON.parse(value);
|
|
1696
|
+
} catch {
|
|
1697
|
+
return false;
|
|
1698
|
+
}
|
|
1699
|
+
return true;
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Validate that an attribute is greater than another attribute.
|
|
1703
|
+
*/
|
|
1704
|
+
validateGt(value, parameters, attribute) {
|
|
1705
|
+
this.requireParameterCount(1, parameters, "gt");
|
|
1706
|
+
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1707
|
+
const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
|
|
1708
|
+
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) > compartedToValue;
|
|
1709
|
+
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1710
|
+
return getSize(value) > getSize(compartedToValue);
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* Validate that an attribute is greater than or equal another attribute.
|
|
1714
|
+
*/
|
|
1715
|
+
validateGte(value, parameters, attribute) {
|
|
1716
|
+
this.requireParameterCount(1, parameters, "gte");
|
|
1717
|
+
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1718
|
+
const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
|
|
1719
|
+
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) >= compartedToValue;
|
|
1720
|
+
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1721
|
+
return getSize(value) >= getSize(compartedToValue);
|
|
1722
|
+
}
|
|
1723
|
+
/**
|
|
1724
|
+
* Validate that an attribute is less than another attribute.
|
|
1725
|
+
*/
|
|
1726
|
+
validateLt(value, parameters, attribute) {
|
|
1727
|
+
this.requireParameterCount(1, parameters, "lt");
|
|
1728
|
+
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1729
|
+
const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
|
|
1730
|
+
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) < compartedToValue;
|
|
1731
|
+
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1732
|
+
return getSize(value) < getSize(compartedToValue);
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Validate that an attribute is less than or equal another attribute.
|
|
1736
|
+
*/
|
|
1737
|
+
validateLte(value, parameters, attribute) {
|
|
1738
|
+
this.requireParameterCount(1, parameters, "lte");
|
|
1739
|
+
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1740
|
+
const compartedToValue = deepFind(this.data, parameters[0]) || parameters[0];
|
|
1741
|
+
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) <= compartedToValue;
|
|
1742
|
+
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1743
|
+
return getSize(value) <= getSize(compartedToValue);
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Validate an attribute is contained within a list of values.
|
|
1747
|
+
*/
|
|
1748
|
+
validateIn(value, parameters) {
|
|
1749
|
+
this.requireParameterCount(1, parameters, "in");
|
|
1750
|
+
if (Array.isArray(value)) {
|
|
1751
|
+
for (let index = 0; index < value.length; index++) if (typeof value[index] !== "number" && typeof value[index] !== "string") return false;
|
|
1752
|
+
return value.filter((element) => parameters.indexOf(element.toString()) === -1).length === 0;
|
|
1753
|
+
}
|
|
1754
|
+
if (typeof value !== "number" && typeof value !== "string") return false;
|
|
1755
|
+
return parameters.indexOf(value.toString()) !== -1;
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* "Indicate" validation should pass if value is null
|
|
1759
|
+
*
|
|
1760
|
+
* Always returns true, just lets us put "nullable" in rules.
|
|
1761
|
+
*/
|
|
1762
|
+
validateNullable() {
|
|
1763
|
+
return true;
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Validate an attribute is not contained within a list of values.
|
|
1767
|
+
*/
|
|
1768
|
+
validateNotIn(value, parameters) {
|
|
1769
|
+
this.requireParameterCount(1, parameters, "not_in");
|
|
1770
|
+
const valuesToCheck = [];
|
|
1771
|
+
if (Array.isArray(value)) {
|
|
1772
|
+
for (let index = 0; index < value.length; index++) if (typeof value[index] === "number" || typeof value[index] === "string") valuesToCheck.push(value[index]);
|
|
1773
|
+
if (valuesToCheck.length === 0) return true;
|
|
1774
|
+
return valuesToCheck.filter((element) => parameters.indexOf(element.toString()) !== -1).length === 0;
|
|
1775
|
+
}
|
|
1776
|
+
if (typeof value !== "number" && typeof value !== "string") return true;
|
|
1777
|
+
return parameters.indexOf(value.toString()) === -1;
|
|
1778
|
+
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Always returns true - this method will be used in conbination with other rules
|
|
1781
|
+
*/
|
|
1782
|
+
validateStrict() {
|
|
1783
|
+
return true;
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Validate that an attribute is a valid URL.
|
|
1787
|
+
*/
|
|
1788
|
+
validateUrl(value) {
|
|
1789
|
+
if (typeof value !== "string") return false;
|
|
1790
|
+
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);
|
|
1791
|
+
}
|
|
1792
|
+
/**
|
|
1793
|
+
* Determine if a comparison passes between the given values.
|
|
1794
|
+
*/
|
|
1795
|
+
compareDates(value, parameter, operator, rule) {
|
|
1796
|
+
value = toDate(value);
|
|
1797
|
+
if (!value) throw `Validation rule ${rule} requires the field under valation to be a date.`;
|
|
1798
|
+
const compartedToValue = toDate(deepFind(this.data, parameter) || parameter);
|
|
1799
|
+
if (!compartedToValue) throw `Validation rule ${rule} requires the parameter to be a date.`;
|
|
1800
|
+
return compare(value.getTime(), compartedToValue.getTime(), operator);
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* Require a certain number of parameters to be present
|
|
1804
|
+
*/
|
|
1805
|
+
requireParameterCount(count, parameters, rule) {
|
|
1806
|
+
if (parameters.length < count) throw `Validation rule ${rule} requires at least ${count} parameters.`;
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Prepare the values for validation
|
|
1810
|
+
*/
|
|
1811
|
+
parseDependentRuleParameters(other, parameters) {
|
|
1812
|
+
let values = parameters.slice(1);
|
|
1813
|
+
if (other === null) values = convertValuesToNull(values);
|
|
1814
|
+
if (typeof other === "number") values = convertValuesToNumber(values);
|
|
1815
|
+
if (typeof other === "boolean") values = convertValuesToBoolean(values);
|
|
1816
|
+
return values;
|
|
1817
|
+
}
|
|
1818
|
+
};
|
|
1819
|
+
|
|
1820
|
+
//#endregion
|
|
1821
|
+
//#region src/BaseValidator.ts
|
|
1822
|
+
var BaseValidator = class {
|
|
1823
|
+
/**
|
|
1824
|
+
* The lang used to return error messages
|
|
1825
|
+
*/
|
|
1826
|
+
lang;
|
|
1827
|
+
/**
|
|
1828
|
+
* The data object that will be validated
|
|
1829
|
+
*/
|
|
1830
|
+
data;
|
|
1831
|
+
/**
|
|
1832
|
+
* The rules that will be used to check the validity of the data
|
|
1833
|
+
*/
|
|
1834
|
+
rules;
|
|
1835
|
+
/**
|
|
1836
|
+
* This is an unchanged version of the inital rules before being changed for wildcard validations
|
|
1837
|
+
*/
|
|
1838
|
+
initalRules;
|
|
1839
|
+
/**
|
|
1840
|
+
* The array of wildcard attributes with their asterisks expanded.
|
|
1841
|
+
*/
|
|
1842
|
+
implicitAttributes;
|
|
1843
|
+
/**
|
|
1844
|
+
* Hold the error messages
|
|
1845
|
+
*/
|
|
1846
|
+
messages;
|
|
1847
|
+
/**
|
|
1848
|
+
* Stores an instance of the validateAttributes class
|
|
1849
|
+
*/
|
|
1850
|
+
validateAttributes;
|
|
1851
|
+
/**
|
|
1852
|
+
* Flag that defines wether or not validation should stop on first failure
|
|
1853
|
+
*/
|
|
1854
|
+
stopOnFirstFailureFlag;
|
|
1855
|
+
/**
|
|
1856
|
+
* Custom messages returned based on the error
|
|
1857
|
+
*/
|
|
1858
|
+
customMessages;
|
|
1859
|
+
/**
|
|
1860
|
+
* Object of custom attribute name;
|
|
1861
|
+
*/
|
|
1862
|
+
customAttributes;
|
|
1863
|
+
constructor(data, rules, customMessages = {}, customAttributes = {}) {
|
|
1864
|
+
this.data = data;
|
|
1865
|
+
this.customMessages = dotify(customMessages);
|
|
1866
|
+
this.customAttributes = dotify(customAttributes);
|
|
1867
|
+
this.initalRules = rules;
|
|
1868
|
+
this.lang = Lang.getDefaultLang();
|
|
1869
|
+
this.addRules(rules);
|
|
1870
|
+
this.messages = new ErrorBag();
|
|
1871
|
+
}
|
|
1872
|
+
setData(data) {
|
|
1873
|
+
this.data = data;
|
|
1874
|
+
this.addRules(this.initalRules);
|
|
1875
|
+
return this;
|
|
1876
|
+
}
|
|
1877
|
+
setRules(rules) {
|
|
1878
|
+
this.addRules(rules);
|
|
1879
|
+
this.initalRules = rules;
|
|
1880
|
+
return this;
|
|
1881
|
+
}
|
|
1882
|
+
setLang(lang) {
|
|
1883
|
+
this.lang = lang;
|
|
1884
|
+
return this;
|
|
1885
|
+
}
|
|
1886
|
+
getLang() {
|
|
1887
|
+
return this.lang;
|
|
1888
|
+
}
|
|
1889
|
+
setCustomMessages(customMessages = {}) {
|
|
1890
|
+
this.customMessages = dotify(customMessages);
|
|
1891
|
+
return this;
|
|
1892
|
+
}
|
|
1893
|
+
setCustomAttributes(customAttributes = {}) {
|
|
1894
|
+
this.customAttributes = dotify(customAttributes);
|
|
1895
|
+
return this;
|
|
1896
|
+
}
|
|
1897
|
+
stopOnFirstFailure(stopOnFirstFailure = true) {
|
|
1898
|
+
this.stopOnFirstFailureFlag = stopOnFirstFailure;
|
|
1899
|
+
return this;
|
|
1900
|
+
}
|
|
1901
|
+
errors() {
|
|
1902
|
+
return this.messages;
|
|
1903
|
+
}
|
|
1904
|
+
clearErrors(keys = []) {
|
|
1905
|
+
this.messages = this.messages.clear(keys).clone();
|
|
1906
|
+
return this.messages;
|
|
1907
|
+
}
|
|
1908
|
+
/**
|
|
1909
|
+
* Create a new ErrorBag instance and set the custom errors, thus removing previous error messages
|
|
1910
|
+
*/
|
|
1911
|
+
setErrors(errors) {
|
|
1912
|
+
this.messages = new ErrorBag();
|
|
1913
|
+
this.addCustomErrors(errors);
|
|
1914
|
+
return this.messages;
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Append the error messages to the existing ErrorBag instance, thus preserving the old error messages if any
|
|
1918
|
+
*/
|
|
1919
|
+
appendErrors(errors) {
|
|
1920
|
+
this.addCustomErrors(errors, true);
|
|
1921
|
+
return this.messages.clone();
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
* Run the validator's rules against its data.
|
|
1925
|
+
*/
|
|
1926
|
+
validate(key = "", value = void 0) {
|
|
1927
|
+
if (!isObject(this.data)) throw "The data attribute must be an object";
|
|
1928
|
+
this.validateAttributes = new validateAttributes(this.data, this.rules);
|
|
1929
|
+
if (!key) {
|
|
1930
|
+
this.runAllValidations();
|
|
1931
|
+
return this.messages.keys().length === 0;
|
|
1932
|
+
} else {
|
|
1933
|
+
this.runSingleValidation(key, value);
|
|
1934
|
+
return !this.messages.has(key);
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
/**
|
|
1938
|
+
* Run the validator's rules against its data asynchronously.
|
|
1939
|
+
*/
|
|
1940
|
+
async validateAsync(key = "", value = void 0) {
|
|
1941
|
+
if (!isObject(this.data)) throw "The data attribute must be an object";
|
|
1942
|
+
this.validateAttributes = new validateAttributes(this.data, this.rules);
|
|
1943
|
+
if (!key) {
|
|
1944
|
+
await this.runAllValidationsAsync();
|
|
1945
|
+
return this.messages.keys().length === 0;
|
|
1946
|
+
} else {
|
|
1947
|
+
await this.runSingleValidationAsync(key, value);
|
|
1948
|
+
return !this.messages.has(key);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
/**
|
|
1952
|
+
* Get the displayable name of the attribute.
|
|
1953
|
+
*/
|
|
1954
|
+
getDisplayableAttribute(attribute) {
|
|
1955
|
+
const primaryAttribute = this.getPrimaryAttribute(attribute);
|
|
1956
|
+
const attributeCombinations = getKeyCombinations(attribute);
|
|
1957
|
+
const translatedAttributes = dotify(Lang.get(this.lang)["attributes"] || {});
|
|
1958
|
+
let expectedAttributes = attributeCombinations;
|
|
1959
|
+
if (attribute !== primaryAttribute) {
|
|
1960
|
+
expectedAttributes = [];
|
|
1961
|
+
const primaryAttributeCombinations = getKeyCombinations(primaryAttribute);
|
|
1962
|
+
for (let i = 0; i < attributeCombinations.length; i++) {
|
|
1963
|
+
expectedAttributes.push(attributeCombinations[i]);
|
|
1964
|
+
if (attributeCombinations[i] !== primaryAttributeCombinations[i]) expectedAttributes.push(primaryAttributeCombinations[i]);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
let name = "";
|
|
1968
|
+
let line = "";
|
|
1969
|
+
for (let i = 0; i < expectedAttributes.length; i++) {
|
|
1970
|
+
name = expectedAttributes[i];
|
|
1971
|
+
if (Object.prototype.hasOwnProperty.call(this.customAttributes, name)) return this.customAttributes[name];
|
|
1972
|
+
line = translatedAttributes[name];
|
|
1973
|
+
if (typeof line === "string") return line;
|
|
1974
|
+
}
|
|
1975
|
+
return getFormattedAttribute(attribute);
|
|
1976
|
+
}
|
|
1977
|
+
addCustomErrors(errors, shouldClearErrors = false) {
|
|
1978
|
+
let newErrors;
|
|
1979
|
+
if (shouldClearErrors) this.messages.clear(Object.keys(errors));
|
|
1980
|
+
for (const key in errors) {
|
|
1981
|
+
newErrors = typeof errors[key] === "string" ? [errors[key]] : errors[key];
|
|
1982
|
+
newErrors.forEach((error) => {
|
|
1983
|
+
this.messages.add(key, {
|
|
1984
|
+
message: error,
|
|
1985
|
+
error_type: "custom"
|
|
1986
|
+
});
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
/**
|
|
1991
|
+
* Replace all error message place-holders with actual values.
|
|
1992
|
+
*/
|
|
1993
|
+
makeReplacements(message, attribute, rule, parameters = [], hasNumericRule = false) {
|
|
1994
|
+
message = message.replace(":attribute", attribute);
|
|
1995
|
+
const methodName = `replace${buildValidationMethodName(rule)}`;
|
|
1996
|
+
if (typeof replaceAttributes[methodName] === "function") {
|
|
1997
|
+
const payload = new replaceAttributePayload(this.data, message, parameters, hasNumericRule, ((attribute) => {
|
|
1998
|
+
return this.getDisplayableAttribute(attribute);
|
|
1999
|
+
}).bind(this));
|
|
2000
|
+
message = replaceAttributes[methodName](payload);
|
|
2001
|
+
}
|
|
2002
|
+
return message;
|
|
2003
|
+
}
|
|
2004
|
+
/**
|
|
2005
|
+
* Loop through all rules and run validation against each one of them
|
|
2006
|
+
*/
|
|
2007
|
+
runAllValidations() {
|
|
2008
|
+
this.messages = new ErrorBag();
|
|
2009
|
+
this.validateAttributes = new validateAttributes(this.data, this.rules);
|
|
2010
|
+
for (const property in this.rules) if (this.runValidation(property) === false) break;
|
|
2011
|
+
}
|
|
2012
|
+
/**
|
|
2013
|
+
* Loop through all rules and run validation against each one of them asynchronously.
|
|
2014
|
+
*/
|
|
2015
|
+
async runAllValidationsAsync() {
|
|
2016
|
+
this.messages = new ErrorBag();
|
|
2017
|
+
this.validateAttributes = new validateAttributes(this.data, this.rules);
|
|
2018
|
+
for (const property in this.rules) if (await this.runValidationAsync(property) === false) break;
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* Run validation for one specific attribute
|
|
2022
|
+
*/
|
|
2023
|
+
runSingleValidation(key, value = void 0) {
|
|
2024
|
+
this.clearErrors([key]);
|
|
2025
|
+
if (typeof value !== "undefined") deepSet(this.data, key, value);
|
|
2026
|
+
this.runValidation(key);
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* Run validation for one specific attribute asynchronously.
|
|
2030
|
+
*/
|
|
2031
|
+
async runSingleValidationAsync(key, value = void 0) {
|
|
2032
|
+
this.clearErrors([key]);
|
|
2033
|
+
if (typeof value !== "undefined") deepSet(this.data, key, value);
|
|
2034
|
+
await this.runValidationAsync(key);
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Run validation rules for the specified property and stop validation if needed
|
|
2038
|
+
*/
|
|
2039
|
+
runValidation(property) {
|
|
2040
|
+
if (Object.prototype.hasOwnProperty.call(this.rules, property) && Array.isArray(this.rules[property])) for (let i = 0; i < this.rules[property].length; i++) {
|
|
2041
|
+
this.validateAttribute(property, this.rules[property][i]);
|
|
2042
|
+
if (this.messages.keys().length > 0 && this.stopOnFirstFailureFlag === true) return false;
|
|
2043
|
+
if (this.shouldStopValidating(property)) break;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
/**
|
|
2047
|
+
* Run validation rules for the specified property asynchronously and stop validation if needed
|
|
2048
|
+
*/
|
|
2049
|
+
async runValidationAsync(property) {
|
|
2050
|
+
if (Object.prototype.hasOwnProperty.call(this.rules, property) && Array.isArray(this.rules[property])) for (let i = 0; i < this.rules[property].length; i++) {
|
|
2051
|
+
await this.validateAttribute(property, this.rules[property][i]);
|
|
2052
|
+
if (this.messages.keys().length > 0 && this.stopOnFirstFailureFlag === true) return false;
|
|
2053
|
+
if (this.shouldStopValidating(property)) break;
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
/**
|
|
2057
|
+
* Check if we should stop further validations on a given attribute.
|
|
2058
|
+
*/
|
|
2059
|
+
shouldStopValidating(attribute) {
|
|
2060
|
+
return this.messages.has(attribute) && validationRuleParser.hasRule(attribute, ["bail"], this.rules);
|
|
2061
|
+
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Parse the given rules add assign them to the current rules
|
|
2064
|
+
*/
|
|
2065
|
+
addRules(rules) {
|
|
2066
|
+
const response = validationRuleParser.explodeRules(dotify(rules, true), this.data);
|
|
2067
|
+
this.rules = response.rules;
|
|
2068
|
+
this.implicitAttributes = response.implicitAttributes;
|
|
2069
|
+
}
|
|
2070
|
+
/**
|
|
2071
|
+
* validate a given attribute against a rule.
|
|
2072
|
+
*/
|
|
2073
|
+
validateAttribute(attribute, rule) {
|
|
2074
|
+
let parameters = [];
|
|
2075
|
+
[rule, parameters] = validationRuleParser.parse(rule);
|
|
2076
|
+
const keys = this.getExplicitKeys(attribute);
|
|
2077
|
+
if (keys.length > 0 && parameters.length > 0) parameters = this.replaceAsterisksInParameters(parameters, keys);
|
|
2078
|
+
const value = deepFind(this.data, attribute);
|
|
2079
|
+
const validatable = this.isValidatable(attribute, value, rule);
|
|
2080
|
+
if (rule instanceof IRuleContract) return validatable ? this.validateUsingCustomRule(attribute, value, rule) : void 0;
|
|
2081
|
+
const method = `validate${buildValidationMethodName(rule)}`;
|
|
2082
|
+
if (rule !== "" && typeof this.validateAttributes[method] === "undefined") throw `Rule ${rule} is not valid`;
|
|
2083
|
+
if (!validatable) return;
|
|
2084
|
+
const validation = this.validateAttributes[method](value, parameters, attribute);
|
|
2085
|
+
if (validation instanceof Promise) return validation.then((result) => {
|
|
2086
|
+
if (!result) this.addFailure(attribute, rule, value, parameters);
|
|
2087
|
+
});
|
|
2088
|
+
else if (!validation) this.addFailure(attribute, rule, value, parameters);
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Validate an attribute using a custom rule object
|
|
2092
|
+
*/
|
|
2093
|
+
validateUsingCustomRule(attribute, value, rule) {
|
|
2094
|
+
rule.setData(this.data).setLang(this.lang);
|
|
2095
|
+
if (rule instanceof Password$1) rule.setValidator(this);
|
|
2096
|
+
const result = rule.passes(value, attribute);
|
|
2097
|
+
if (result instanceof Promise) return result.then((validationResult) => {
|
|
2098
|
+
if (!validationResult) this.setCustomRuleErrorMessages(attribute, rule);
|
|
2099
|
+
});
|
|
2100
|
+
if (!result) return this.setCustomRuleErrorMessages(attribute, rule);
|
|
2101
|
+
}
|
|
2102
|
+
/**
|
|
2103
|
+
* Set the error message linked to a custom validation rule
|
|
2104
|
+
*/
|
|
2105
|
+
setCustomRuleErrorMessages(attribute, rule) {
|
|
2106
|
+
const result = rule.getMessage();
|
|
2107
|
+
const messages = typeof result === "string" ? [result] : result;
|
|
2108
|
+
for (const key in messages) this.messages.add(attribute, {
|
|
2109
|
+
error_type: rule.constructor.name,
|
|
2110
|
+
message: this.makeReplacements(messages[key], this.getDisplayableAttribute(attribute), rule.constructor.name)
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Add a new error message to the messages object
|
|
2115
|
+
*/
|
|
2116
|
+
addFailure(attribute, rule, value, parameters) {
|
|
2117
|
+
const hasNumericRule = validationRuleParser.hasRule(attribute, getNumericRules(), this.rules);
|
|
2118
|
+
const primaryAttribute = this.getPrimaryAttribute(attribute);
|
|
2119
|
+
const attributes = attribute !== primaryAttribute ? [attribute, primaryAttribute] : [attribute];
|
|
2120
|
+
const error = {
|
|
2121
|
+
error_type: rule,
|
|
2122
|
+
message: this.makeReplacements(getMessage(attributes, rule, value, this.customMessages, hasNumericRule, this.lang), this.getDisplayableAttribute(attribute), rule, parameters, hasNumericRule)
|
|
2123
|
+
};
|
|
2124
|
+
this.messages.add(attribute, error);
|
|
2125
|
+
}
|
|
2126
|
+
/**
|
|
2127
|
+
* Replace each field parameter which has asterisks with the given keys.
|
|
2128
|
+
*
|
|
2129
|
+
* Example: parameters = [name.*.first] and keys = [1], then the result will be name.1.first
|
|
2130
|
+
*/
|
|
2131
|
+
replaceAsterisksInParameters(parameters, keys) {
|
|
2132
|
+
return parameters.map((parameter) => {
|
|
2133
|
+
let result = "";
|
|
2134
|
+
if (parameter.indexOf("*") !== -1) {
|
|
2135
|
+
const parameterArray = parameter.split("*");
|
|
2136
|
+
result = parameterArray[0];
|
|
2137
|
+
for (let i = 1; i < parameterArray.length; i++) result = result.concat((keys[i - 1] || "*") + parameterArray[i]);
|
|
2138
|
+
}
|
|
2139
|
+
return result || parameter;
|
|
2140
|
+
});
|
|
2141
|
+
}
|
|
2142
|
+
/**
|
|
2143
|
+
* Determine if the attribute is validatable.
|
|
2144
|
+
*/
|
|
2145
|
+
isValidatable(attribute, value, rule) {
|
|
2146
|
+
return this.presentOrRuleIsImplicit(attribute, value, rule) && this.passesOptionalCheck(attribute) && this.isNotNullIfMarkedAsNullable(attribute, rule);
|
|
2147
|
+
}
|
|
2148
|
+
/**
|
|
2149
|
+
* Determine if the field is present, or the rule implies required.
|
|
2150
|
+
*/
|
|
2151
|
+
presentOrRuleIsImplicit(attribute, value, rule) {
|
|
2152
|
+
if (typeof value === "string" && value.trim() === "") return isImplicitRule(rule);
|
|
2153
|
+
return typeof deepFind(this.data, attribute) !== "undefined" || isImplicitRule(rule);
|
|
2154
|
+
}
|
|
2155
|
+
/**
|
|
2156
|
+
* Determine if the attribute passes any optional check.
|
|
2157
|
+
*/
|
|
2158
|
+
passesOptionalCheck(attribute) {
|
|
2159
|
+
if (!validationRuleParser.hasRule(attribute, ["sometimes"], this.rules)) return true;
|
|
2160
|
+
const data = validationData.initializeAndGatherData(attribute, this.data);
|
|
2161
|
+
return Object.prototype.hasOwnProperty.call(data, attribute) || Object.prototype.hasOwnProperty.call(this.data, attribute);
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Determine if the attribute fails the nullable check.
|
|
2165
|
+
*/
|
|
2166
|
+
isNotNullIfMarkedAsNullable(attribute, rule) {
|
|
2167
|
+
if (isImplicitRule(rule) || !validationRuleParser.hasRule(attribute, ["nullable"], this.rules)) return true;
|
|
2168
|
+
return deepFind(this.data, attribute) !== null;
|
|
2169
|
+
}
|
|
2170
|
+
/**
|
|
2171
|
+
* Get the primary attribute name
|
|
2172
|
+
*
|
|
2173
|
+
* Example: if "name.0" is given, "name.*" will be returned
|
|
2174
|
+
*/
|
|
2175
|
+
getPrimaryAttribute(attribute) {
|
|
2176
|
+
for (const unparsed in this.implicitAttributes) if (this.implicitAttributes[unparsed].indexOf(attribute) !== -1) return unparsed;
|
|
2177
|
+
return attribute;
|
|
2178
|
+
}
|
|
2179
|
+
/**
|
|
2180
|
+
* Get the explicit keys from an attribute flattened with dot notation.
|
|
2181
|
+
*
|
|
2182
|
+
* Example: 'foo.1.bar.spark.baz' -> [1, 'spark'] for 'foo.*.bar.*.baz'
|
|
2183
|
+
*/
|
|
2184
|
+
getExplicitKeys(attribute) {
|
|
2185
|
+
const pattern = new RegExp("^" + this.getPrimaryAttribute(attribute).replace(/\*/g, "([^.]*)"));
|
|
2186
|
+
const keys = attribute.match(pattern);
|
|
2187
|
+
if (keys) {
|
|
2188
|
+
keys.shift();
|
|
2189
|
+
return keys;
|
|
2190
|
+
}
|
|
2191
|
+
return [];
|
|
2192
|
+
}
|
|
2193
|
+
};
|
|
2194
|
+
|
|
2195
|
+
//#endregion
|
|
2196
|
+
//#region src/Contracts/IDatabaseDriver.ts
|
|
2197
|
+
var IDatabaseDriver = class {};
|
|
2198
|
+
|
|
2199
|
+
//#endregion
|
|
2200
|
+
//#region src/Rules/registerRule.ts
|
|
2201
|
+
function register(rule, validate, replaceMessage) {
|
|
2202
|
+
const method = buildValidationMethodName(rule);
|
|
2203
|
+
if (new validateAttributes()[`validate${method}`]) return false;
|
|
2204
|
+
validateAttributes.prototype[`validate${method}`] = validate;
|
|
2205
|
+
if (typeof replaceMessage === "function") replaceAttributes[`replace${method}`] = ({ message, parameters, data, getDisplayableAttribute }) => replaceMessage(message, parameters, data, getDisplayableAttribute);
|
|
2206
|
+
return true;
|
|
2207
|
+
}
|
|
2208
|
+
function registerImplicit(rule, validate, replaceMessage) {
|
|
2209
|
+
if (register(rule, validate, replaceMessage) === true) addImplicitRule(rule);
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
//#endregion
|
|
2213
|
+
//#region src/Rules/in.ts
|
|
2214
|
+
var In = class extends BaseRule {
|
|
2215
|
+
/**
|
|
2216
|
+
* The name of the rule.
|
|
2217
|
+
*/
|
|
2218
|
+
rule = "in";
|
|
2219
|
+
/**
|
|
2220
|
+
* The accepted values.
|
|
2221
|
+
*/
|
|
2222
|
+
values = [];
|
|
2223
|
+
/**
|
|
2224
|
+
* Create a new In rule instance.
|
|
2225
|
+
*/
|
|
2226
|
+
constructor(values) {
|
|
2227
|
+
super();
|
|
2228
|
+
this.values = values;
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* Convert the rule to a validation string.
|
|
2232
|
+
*/
|
|
2233
|
+
toString() {
|
|
2234
|
+
return `${this.rule}:${this.values.join(",")}`;
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
|
|
2238
|
+
//#endregion
|
|
2239
|
+
//#region src/Rules/notIn.ts
|
|
2240
|
+
var NotIn = class extends BaseRule {
|
|
2241
|
+
/**
|
|
2242
|
+
* The name of the rule.
|
|
2243
|
+
*/
|
|
2244
|
+
rule = "not_in";
|
|
2245
|
+
/**
|
|
2246
|
+
* The accepted values.
|
|
2247
|
+
*/
|
|
2248
|
+
values = [];
|
|
2249
|
+
/**
|
|
2250
|
+
* Create a new NotIn rule instance.
|
|
2251
|
+
*/
|
|
2252
|
+
constructor(values) {
|
|
2253
|
+
super();
|
|
2254
|
+
this.values = values;
|
|
2255
|
+
}
|
|
2256
|
+
/**
|
|
2257
|
+
* Convert the rule to a validation string.
|
|
2258
|
+
*/
|
|
2259
|
+
toString() {
|
|
2260
|
+
return `${this.rule}:${this.values.join(",")}`;
|
|
2261
|
+
}
|
|
2262
|
+
};
|
|
2263
|
+
|
|
2264
|
+
//#endregion
|
|
2265
|
+
//#region src/Rules/regex.ts
|
|
2266
|
+
var Regex = class extends IRuleContract {
|
|
2267
|
+
/**
|
|
2268
|
+
* The Regular expression to validate
|
|
2269
|
+
*/
|
|
2270
|
+
regex;
|
|
2271
|
+
/**
|
|
2272
|
+
* Flag that decides whether the value should match the regular expression or not
|
|
2273
|
+
*/
|
|
2274
|
+
shouldMatch;
|
|
2275
|
+
constructor(regex, shouldMatch = true) {
|
|
2276
|
+
super();
|
|
2277
|
+
this.regex = regex;
|
|
2278
|
+
this.shouldMatch = shouldMatch;
|
|
2279
|
+
}
|
|
2280
|
+
passes(value) {
|
|
2281
|
+
if (this.shouldMatch) return this.regex.test(value);
|
|
2282
|
+
return !this.regex.test(value);
|
|
2283
|
+
}
|
|
2284
|
+
getMessage() {
|
|
2285
|
+
if (this.shouldMatch) return this.trans("regex");
|
|
2286
|
+
return this.trans("not_regex");
|
|
2287
|
+
}
|
|
2288
|
+
};
|
|
2289
|
+
|
|
2290
|
+
//#endregion
|
|
2291
|
+
//#region src/Rules/requiredIf.ts
|
|
2292
|
+
var RequiredIf = class extends BaseRule {
|
|
2293
|
+
/**
|
|
2294
|
+
* The condition that validates the attribute
|
|
2295
|
+
*/
|
|
2296
|
+
condition;
|
|
2297
|
+
/**
|
|
2298
|
+
* Create a new required validation rule based on a condition.
|
|
2299
|
+
*/
|
|
2300
|
+
constructor(condition) {
|
|
2301
|
+
super();
|
|
2302
|
+
this.condition = condition;
|
|
2303
|
+
}
|
|
2304
|
+
/**
|
|
2305
|
+
* Convert the rule to a validation string.
|
|
2306
|
+
*/
|
|
2307
|
+
toString() {
|
|
2308
|
+
if (typeof this.condition === "function") return this.condition() ? "required" : "";
|
|
2309
|
+
return this.condition ? "required" : "";
|
|
2310
|
+
}
|
|
2311
|
+
};
|
|
2312
|
+
|
|
2313
|
+
//#endregion
|
|
2314
|
+
//#region src/rule.ts
|
|
2315
|
+
function requiredIf(callback) {
|
|
2316
|
+
return new RequiredIf(callback);
|
|
2317
|
+
}
|
|
2318
|
+
function ruleIn(values) {
|
|
2319
|
+
return new In(values);
|
|
2320
|
+
}
|
|
2321
|
+
function ruleNotIn(values) {
|
|
2322
|
+
return new NotIn(values);
|
|
2323
|
+
}
|
|
2324
|
+
function regex(value) {
|
|
2325
|
+
return new Regex(value);
|
|
2326
|
+
}
|
|
2327
|
+
function notRegex(value) {
|
|
2328
|
+
return new Regex(value, false);
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
//#endregion
|
|
2332
|
+
//#region src/Core.ts
|
|
2333
|
+
var Password = class extends Password$1 {};
|
|
2334
|
+
function make(data = {}, rules = {}, customMessages = {}, customAttributes = {}) {
|
|
2335
|
+
return new BaseValidator(data, rules, customMessages, customAttributes);
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
//#endregion
|
|
2339
|
+
//#region src/Rules/IImplicitRule.ts
|
|
2340
|
+
var IImplicitRule = class extends IRuleContract {
|
|
2341
|
+
__isImplicitRule = true;
|
|
2342
|
+
};
|
|
2343
|
+
|
|
2344
|
+
//#endregion
|
|
2345
|
+
//#region src/ImplicitRule.ts
|
|
2346
|
+
var ImplicitRule = class extends IImplicitRule {
|
|
2347
|
+
rules = [];
|
|
2348
|
+
};
|
|
2349
|
+
|
|
2350
|
+
//#endregion
|
|
2351
|
+
//#region src/Providers/ValidationServiceProvider.ts
|
|
2352
|
+
/**
|
|
2353
|
+
* Service provider for Validation utilities
|
|
2354
|
+
*/
|
|
2355
|
+
var ValidationServiceProvider = class {
|
|
2356
|
+
registeredCommands;
|
|
2357
|
+
static priority = 895;
|
|
2358
|
+
constructor(app) {
|
|
2359
|
+
this.app = app;
|
|
2360
|
+
}
|
|
2361
|
+
/**
|
|
2362
|
+
* Register URL services in the container
|
|
2363
|
+
*/
|
|
2364
|
+
register() {}
|
|
2365
|
+
/**
|
|
2366
|
+
* Boot URL services
|
|
2367
|
+
*/
|
|
2368
|
+
boot() {}
|
|
2369
|
+
};
|
|
2370
|
+
|
|
2371
|
+
//#endregion
|
|
2372
|
+
//#region src/ValidationRule.ts
|
|
2373
|
+
var ValidationRule = class extends IRuleContract {
|
|
2374
|
+
rules = [];
|
|
2375
|
+
passing = false;
|
|
2376
|
+
/**
|
|
2377
|
+
* Set the data under validation.
|
|
2378
|
+
*/
|
|
2379
|
+
setData(_data) {
|
|
2380
|
+
return this;
|
|
2381
|
+
}
|
|
2382
|
+
passes(value, attribute) {
|
|
2383
|
+
this.passing = true;
|
|
2384
|
+
this.validate(attribute, value, (message) => {
|
|
2385
|
+
this.message = message;
|
|
2386
|
+
this.passing = false;
|
|
2387
|
+
});
|
|
2388
|
+
return this.passing;
|
|
2389
|
+
}
|
|
2390
|
+
};
|
|
2391
|
+
|
|
2392
|
+
//#endregion
|
|
2393
|
+
//#region src/Rules/ExtendedRules.ts
|
|
2394
|
+
var ExtendedRules = class extends ValidationRule {
|
|
2395
|
+
/**
|
|
2396
|
+
* The validator instance.
|
|
2397
|
+
*/
|
|
2398
|
+
validator;
|
|
2399
|
+
setValidator(validator) {
|
|
2400
|
+
this.validator = validator;
|
|
2401
|
+
return this;
|
|
2402
|
+
}
|
|
2403
|
+
resolveTarget(tableRef, attribute, parameters = []) {
|
|
2404
|
+
if (!tableRef) return null;
|
|
2405
|
+
const [connection, table] = tableRef.includes(".") ? tableRef.split(".", 2) : [void 0, tableRef];
|
|
2406
|
+
const [column] = parameters;
|
|
2407
|
+
const attributeColumn = attribute.includes(".") ? attribute.split(".").pop() : attribute;
|
|
2408
|
+
return {
|
|
2409
|
+
table,
|
|
2410
|
+
connection,
|
|
2411
|
+
column: column || attributeColumn
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
rules = [
|
|
2415
|
+
{
|
|
2416
|
+
name: "hex",
|
|
2417
|
+
validator: (value) => {
|
|
2418
|
+
if (typeof value !== "string") return false;
|
|
2419
|
+
return /^[0-9a-fA-F]+$/.test(value.replace("#", ""));
|
|
2420
|
+
},
|
|
2421
|
+
message: "The :attribute must be a valid hexadecimal string."
|
|
2422
|
+
},
|
|
2423
|
+
{
|
|
2424
|
+
name: "includes",
|
|
2425
|
+
validator: (value, parameters = []) => {
|
|
2426
|
+
if (value == null) return false;
|
|
2427
|
+
if (Array.isArray(value)) return parameters.some((param) => value.includes(param));
|
|
2428
|
+
if (typeof value === "string") return parameters.some((param) => value.includes(param));
|
|
2429
|
+
return false;
|
|
2430
|
+
},
|
|
2431
|
+
message: "The :attribute must include one of the following values: :values."
|
|
2432
|
+
},
|
|
2433
|
+
{
|
|
2434
|
+
name: "not_includes",
|
|
2435
|
+
validator: (value, parameters = []) => {
|
|
2436
|
+
if (value == null) return true;
|
|
2437
|
+
if (Array.isArray(value)) return parameters.every((param) => !value.includes(param));
|
|
2438
|
+
if (typeof value === "string") return parameters.every((param) => !value.includes(param));
|
|
2439
|
+
return true;
|
|
2440
|
+
},
|
|
2441
|
+
message: "The :attribute must not include any of the following values: :values."
|
|
2442
|
+
},
|
|
2443
|
+
{
|
|
2444
|
+
name: "datetime",
|
|
2445
|
+
validator: (value, parameters = [], _attribute) => {
|
|
2446
|
+
if (typeof value !== "string") return false;
|
|
2447
|
+
const [format] = parameters;
|
|
2448
|
+
if (!format) return !isNaN(Date.parse(value));
|
|
2449
|
+
try {
|
|
2450
|
+
return new Date(value).toISOString() === new Date(format).toISOString();
|
|
2451
|
+
} catch {
|
|
2452
|
+
return !isNaN(Date.parse(value));
|
|
2453
|
+
}
|
|
2454
|
+
},
|
|
2455
|
+
message: "The :attribute must be a valid date matching the format :format."
|
|
2456
|
+
},
|
|
2457
|
+
{
|
|
2458
|
+
name: "exists",
|
|
2459
|
+
validator: async (value, parameters = [], attribute = "") => {
|
|
2460
|
+
const [tableRef, column, ignore] = parameters;
|
|
2461
|
+
const driver = this.validator?.getDatabaseDriver();
|
|
2462
|
+
if (!driver || !tableRef) return false;
|
|
2463
|
+
const target = this.resolveTarget(tableRef, attribute, [column]);
|
|
2464
|
+
if (!target?.column) return false;
|
|
2465
|
+
return await driver.exists({
|
|
2466
|
+
table: target.table,
|
|
2467
|
+
connection: target.connection,
|
|
2468
|
+
column: target.column,
|
|
2469
|
+
value,
|
|
2470
|
+
ignore,
|
|
2471
|
+
attribute,
|
|
2472
|
+
data: this.validator.getData()
|
|
2473
|
+
});
|
|
2474
|
+
},
|
|
2475
|
+
message: "The :attribute does not exist."
|
|
2476
|
+
},
|
|
2477
|
+
{
|
|
2478
|
+
name: "unique",
|
|
2479
|
+
validator: async (value, parameters = [], attribute = "") => {
|
|
2480
|
+
const [tableRef, column, ignore] = parameters;
|
|
2481
|
+
const driver = this.validator?.getDatabaseDriver();
|
|
2482
|
+
if (!driver || !tableRef) return false;
|
|
2483
|
+
const target = this.resolveTarget(tableRef, attribute, [column]);
|
|
2484
|
+
if (!target?.column) return false;
|
|
2485
|
+
return !await driver.exists({
|
|
2486
|
+
table: target.table,
|
|
2487
|
+
connection: target.connection,
|
|
2488
|
+
column: target.column,
|
|
2489
|
+
value,
|
|
2490
|
+
ignore,
|
|
2491
|
+
attribute,
|
|
2492
|
+
data: this.validator.getData()
|
|
2493
|
+
});
|
|
2494
|
+
},
|
|
2495
|
+
message: "The :attribute has already been taken."
|
|
2496
|
+
}
|
|
2497
|
+
];
|
|
2498
|
+
validate() {}
|
|
2499
|
+
};
|
|
2500
|
+
|
|
2501
|
+
//#endregion
|
|
2502
|
+
//#region src/utilities/helpers.ts
|
|
2503
|
+
/**
|
|
2504
|
+
* Pluralizes a word based on the count.
|
|
2505
|
+
*
|
|
2506
|
+
* @param word
|
|
2507
|
+
* @param count
|
|
2508
|
+
* @returns
|
|
2509
|
+
*/
|
|
2510
|
+
const plural = (word, count) => {
|
|
2511
|
+
return count === 1 ? word : `${word}s`;
|
|
2512
|
+
};
|
|
2513
|
+
|
|
2514
|
+
//#endregion
|
|
2515
|
+
//#region src/utilities/MessageBag.ts
|
|
2516
|
+
var MessageBag = class {
|
|
2517
|
+
/**
|
|
2518
|
+
* All of the registered messages.
|
|
2519
|
+
*/
|
|
2520
|
+
messages = {};
|
|
2521
|
+
/**
|
|
2522
|
+
* Default format for message output.
|
|
2523
|
+
*/
|
|
2524
|
+
format = ":message";
|
|
2525
|
+
/**
|
|
2526
|
+
* Create a new message bag instance.
|
|
2527
|
+
*/
|
|
2528
|
+
constructor(messages = {}) {
|
|
2529
|
+
for (const [key, value] of Object.entries(messages)) {
|
|
2530
|
+
const arr = Array.isArray(value) ? value : [value];
|
|
2531
|
+
this.messages[key] = Array.from(new Set(arr));
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
/**
|
|
2535
|
+
* Get all message keys.
|
|
2536
|
+
*/
|
|
2537
|
+
keys() {
|
|
2538
|
+
return Object.keys(this.messages);
|
|
2539
|
+
}
|
|
2540
|
+
/**
|
|
2541
|
+
* Add a message.
|
|
2542
|
+
*/
|
|
2543
|
+
add(key, message) {
|
|
2544
|
+
if (this.isUnique(key, message)) {
|
|
2545
|
+
if (!this.messages[key]) this.messages[key] = [];
|
|
2546
|
+
this.messages[key].push(message);
|
|
2547
|
+
}
|
|
2548
|
+
return this;
|
|
2549
|
+
}
|
|
2550
|
+
/**
|
|
2551
|
+
* Add a message conditionally.
|
|
2552
|
+
*/
|
|
2553
|
+
addIf(condition, key, message) {
|
|
2554
|
+
return condition ? this.add(key, message) : this;
|
|
2555
|
+
}
|
|
2556
|
+
/**
|
|
2557
|
+
* Check uniqueness of key/message pair.
|
|
2558
|
+
*/
|
|
2559
|
+
isUnique(key, message) {
|
|
2560
|
+
return !this.messages[key] || !this.messages[key].includes(message);
|
|
2561
|
+
}
|
|
2562
|
+
/**
|
|
2563
|
+
* Merge another message source into this one.
|
|
2564
|
+
*/
|
|
2565
|
+
merge(messages) {
|
|
2566
|
+
const incoming = messages.getMessageBag?.()?.getMessages?.() ?? messages;
|
|
2567
|
+
for (const [key, list] of Object.entries(incoming)) {
|
|
2568
|
+
if (!this.messages[key]) this.messages[key] = [];
|
|
2569
|
+
this.messages[key].push(...list);
|
|
2570
|
+
this.messages[key] = Array.from(new Set(this.messages[key]));
|
|
2571
|
+
}
|
|
2572
|
+
return this;
|
|
2573
|
+
}
|
|
2574
|
+
/**
|
|
2575
|
+
* Determine if messages exist for all given keys.
|
|
2576
|
+
*/
|
|
2577
|
+
has(key) {
|
|
2578
|
+
if (this.isEmpty()) return false;
|
|
2579
|
+
if (key == null) return this.any();
|
|
2580
|
+
return (Array.isArray(key) ? key : [key]).every((k) => this.first(k) !== "");
|
|
2581
|
+
}
|
|
2582
|
+
/**
|
|
2583
|
+
* Determine if messages exist for any given key.
|
|
2584
|
+
*/
|
|
2585
|
+
hasAny(keys = []) {
|
|
2586
|
+
if (this.isEmpty()) return false;
|
|
2587
|
+
return (Array.isArray(keys) ? keys : [keys]).some((k) => this.has(k));
|
|
2588
|
+
}
|
|
2589
|
+
/**
|
|
2590
|
+
* Determine if messages don't exist for given keys.
|
|
2591
|
+
*/
|
|
2592
|
+
missing(key) {
|
|
2593
|
+
const keys = Array.isArray(key) ? key : [key];
|
|
2594
|
+
return !this.hasAny(keys);
|
|
2595
|
+
}
|
|
2596
|
+
/**
|
|
2597
|
+
* Get the first message for a given key.
|
|
2598
|
+
*/
|
|
2599
|
+
first(key = null, format = null) {
|
|
2600
|
+
const messages = key == null ? this.all(format) : this.get(key, format);
|
|
2601
|
+
const firstMessage = Array.isArray(messages) ? messages[0] ?? "" : "";
|
|
2602
|
+
return Array.isArray(firstMessage) ? firstMessage[0] ?? "" : firstMessage;
|
|
2603
|
+
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Get all messages for a given key.
|
|
2606
|
+
*/
|
|
2607
|
+
get(key, format = null) {
|
|
2608
|
+
if (this.messages[key]) return this.transform(this.messages[key], this.checkFormat(format), key);
|
|
2609
|
+
if (key.includes("*")) return this.getMessagesForWildcardKey(key, format);
|
|
2610
|
+
return [];
|
|
2611
|
+
}
|
|
2612
|
+
/**
|
|
2613
|
+
* Wildcard key match.
|
|
2614
|
+
*/
|
|
2615
|
+
getMessagesForWildcardKey(key, format) {
|
|
2616
|
+
const regex = new RegExp("^" + key.replace(/\*/g, ".*") + "$");
|
|
2617
|
+
const result = {};
|
|
2618
|
+
for (const [messageKey, messages] of Object.entries(this.messages)) if (regex.test(messageKey)) result[messageKey] = this.transform(messages, this.checkFormat(format), messageKey);
|
|
2619
|
+
return result;
|
|
2620
|
+
}
|
|
2621
|
+
/**
|
|
2622
|
+
* Get all messages.
|
|
2623
|
+
*/
|
|
2624
|
+
all(format = null) {
|
|
2625
|
+
const fmt = this.checkFormat(format);
|
|
2626
|
+
const all = [];
|
|
2627
|
+
for (const [key, messages] of Object.entries(this.messages)) all.push(...this.transform(messages, fmt, key));
|
|
2628
|
+
return all;
|
|
2629
|
+
}
|
|
2630
|
+
/**
|
|
2631
|
+
* Get unique messages.
|
|
2632
|
+
*/
|
|
2633
|
+
unique(format = null) {
|
|
2634
|
+
return Array.from(new Set(this.all(format)));
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* Remove messages for a key.
|
|
2638
|
+
*/
|
|
2639
|
+
forget(key) {
|
|
2640
|
+
delete this.messages[key];
|
|
2641
|
+
return this;
|
|
2642
|
+
}
|
|
2643
|
+
/**
|
|
2644
|
+
* Format an array of messages.
|
|
2645
|
+
*/
|
|
2646
|
+
transform(messages, format, messageKey) {
|
|
2647
|
+
if (format === ":message") return messages;
|
|
2648
|
+
return messages.map((m) => format.replace(":message", m).replace(":key", messageKey));
|
|
2649
|
+
}
|
|
2650
|
+
/**
|
|
2651
|
+
* Get proper format string.
|
|
2652
|
+
*/
|
|
2653
|
+
checkFormat(format) {
|
|
2654
|
+
return format || this.format;
|
|
2655
|
+
}
|
|
2656
|
+
/**
|
|
2657
|
+
* Get raw messages.
|
|
2658
|
+
*/
|
|
2659
|
+
messagesRaw() {
|
|
2660
|
+
return this.messages;
|
|
2661
|
+
}
|
|
2662
|
+
/**
|
|
2663
|
+
* Alias for messagesRaw().
|
|
2664
|
+
*/
|
|
2665
|
+
getMessages() {
|
|
2666
|
+
return this.messagesRaw();
|
|
2667
|
+
}
|
|
2668
|
+
/**
|
|
2669
|
+
* Return message bag instance.
|
|
2670
|
+
*/
|
|
2671
|
+
getMessageBag() {
|
|
2672
|
+
return this;
|
|
2673
|
+
}
|
|
2674
|
+
/**
|
|
2675
|
+
* Get format string.
|
|
2676
|
+
*/
|
|
2677
|
+
getFormat() {
|
|
2678
|
+
return this.format;
|
|
2679
|
+
}
|
|
2680
|
+
/**
|
|
2681
|
+
* Set default message format.
|
|
2682
|
+
*/
|
|
2683
|
+
setFormat(format = ":message") {
|
|
2684
|
+
this.format = format;
|
|
2685
|
+
return this;
|
|
2686
|
+
}
|
|
2687
|
+
/**
|
|
2688
|
+
* Empty checks.
|
|
2689
|
+
*/
|
|
2690
|
+
isEmpty() {
|
|
2691
|
+
return !this.any();
|
|
2692
|
+
}
|
|
2693
|
+
isNotEmpty() {
|
|
2694
|
+
return this.any();
|
|
2695
|
+
}
|
|
2696
|
+
any() {
|
|
2697
|
+
return this.count() > 0;
|
|
2698
|
+
}
|
|
2699
|
+
/**
|
|
2700
|
+
* Count total messages.
|
|
2701
|
+
*/
|
|
2702
|
+
count() {
|
|
2703
|
+
return Object.values(this.messages).reduce((sum, list) => sum + list.length, 0);
|
|
2704
|
+
}
|
|
2705
|
+
/**
|
|
2706
|
+
* Array & JSON conversions.
|
|
2707
|
+
*/
|
|
2708
|
+
toArray() {
|
|
2709
|
+
return this.getMessages();
|
|
2710
|
+
}
|
|
2711
|
+
jsonSerialize() {
|
|
2712
|
+
return this.toArray();
|
|
2713
|
+
}
|
|
2714
|
+
toJson(options = 0) {
|
|
2715
|
+
return JSON.stringify(this.jsonSerialize(), null, options ? 2 : void 0);
|
|
2716
|
+
}
|
|
2717
|
+
toPrettyJson() {
|
|
2718
|
+
return JSON.stringify(this.jsonSerialize(), null, 2);
|
|
2719
|
+
}
|
|
2720
|
+
/**
|
|
2721
|
+
* String representation.
|
|
2722
|
+
*/
|
|
2723
|
+
toString() {
|
|
2724
|
+
return this.toJson();
|
|
2725
|
+
}
|
|
2726
|
+
};
|
|
2727
|
+
|
|
2728
|
+
//#endregion
|
|
2729
|
+
//#region src/Validator.ts
|
|
2730
|
+
var Validator = class Validator {
|
|
2731
|
+
static defaultDatabaseDriver;
|
|
2732
|
+
#messages;
|
|
2733
|
+
#after = [];
|
|
2734
|
+
data;
|
|
2735
|
+
rules;
|
|
2736
|
+
_errors;
|
|
2737
|
+
passing = false;
|
|
2738
|
+
executed = false;
|
|
2739
|
+
instance;
|
|
2740
|
+
databaseDriver;
|
|
2741
|
+
errorBagName = "default";
|
|
2742
|
+
registeredCustomRules = [new ExtendedRules()];
|
|
2743
|
+
shouldStopOnFirstFailure = false;
|
|
2744
|
+
constructor(data, rules, messages = {}) {
|
|
2745
|
+
register("telephone", function(value) {
|
|
2746
|
+
return /^\d{3}-\d{3}-\d{4}$/.test(value);
|
|
2747
|
+
});
|
|
2748
|
+
this.data = data;
|
|
2749
|
+
this.rules = rules;
|
|
2750
|
+
this.#messages = messages;
|
|
2751
|
+
this._errors = new MessageBag();
|
|
2752
|
+
this.bindServices();
|
|
2753
|
+
}
|
|
2754
|
+
/**
|
|
2755
|
+
* Validate the data and return the instance
|
|
2756
|
+
*/
|
|
2757
|
+
static make(data, rules, messages = {}) {
|
|
2758
|
+
return new Validator(data, rules, messages);
|
|
2759
|
+
}
|
|
2760
|
+
static useDatabase(driver) {
|
|
2761
|
+
Validator.defaultDatabaseDriver = driver;
|
|
2762
|
+
return Validator;
|
|
2763
|
+
}
|
|
2764
|
+
/**
|
|
2765
|
+
* Run the validator and store results.
|
|
2766
|
+
*/
|
|
2767
|
+
async passes() {
|
|
2768
|
+
if (this.executed) return this._errors.isEmpty();
|
|
2769
|
+
const exec = await this.execute();
|
|
2770
|
+
for (const after of this.#after) after();
|
|
2771
|
+
return exec.passing;
|
|
2772
|
+
}
|
|
2773
|
+
/**
|
|
2774
|
+
* Opposite of passes()
|
|
2775
|
+
*/
|
|
2776
|
+
async fails() {
|
|
2777
|
+
return !await this.passes();
|
|
2778
|
+
}
|
|
2779
|
+
/**
|
|
2780
|
+
* Throw if validation fails, else return executed data
|
|
2781
|
+
*
|
|
2782
|
+
* @throws ValidationException if validation fails
|
|
2783
|
+
*/
|
|
2784
|
+
async validate() {
|
|
2785
|
+
if (!await this.passes()) throw new ValidationException(this, JSON.stringify(this._errors.toArray()));
|
|
2786
|
+
return this.validatedData();
|
|
2787
|
+
}
|
|
2788
|
+
/**
|
|
2789
|
+
* Run the validator's rules against its data.
|
|
2790
|
+
* @param bagName
|
|
2791
|
+
* @returns
|
|
2792
|
+
*/
|
|
2793
|
+
async validateWithBag(bagName) {
|
|
2794
|
+
this.errorBagName = bagName;
|
|
2795
|
+
return this.validate();
|
|
2796
|
+
}
|
|
2797
|
+
/**
|
|
2798
|
+
* Stop validation on first failure.
|
|
2799
|
+
*/
|
|
2800
|
+
stopOnFirstFailure() {
|
|
2801
|
+
this.shouldStopOnFirstFailure = true;
|
|
2802
|
+
return this;
|
|
2803
|
+
}
|
|
2804
|
+
/**
|
|
2805
|
+
* Get the data that passed validation.
|
|
2806
|
+
*/
|
|
2807
|
+
validatedData() {
|
|
2808
|
+
const validKeys = Object.keys(this.rules);
|
|
2809
|
+
const clean = {};
|
|
2810
|
+
for (const key of validKeys) if (this.data[key] !== void 0) clean[key] = this.data[key];
|
|
2811
|
+
return clean;
|
|
2812
|
+
}
|
|
2813
|
+
/**
|
|
2814
|
+
* Return all validated input.
|
|
2815
|
+
*/
|
|
2816
|
+
validated() {
|
|
2817
|
+
return Object.fromEntries(Object.entries(this.data).filter(([key]) => key in this.rules));
|
|
2818
|
+
}
|
|
2819
|
+
/**
|
|
2820
|
+
* Return a portion of validated input
|
|
2821
|
+
*/
|
|
2822
|
+
safe() {
|
|
2823
|
+
const validated = this.validated();
|
|
2824
|
+
return {
|
|
2825
|
+
only: (keys) => Object.fromEntries(Object.entries(validated).filter(([key]) => keys.includes(key))),
|
|
2826
|
+
except: (keys) => Object.fromEntries(Object.entries(validated).filter(([key]) => !keys.includes(key)))
|
|
2827
|
+
};
|
|
2828
|
+
}
|
|
2829
|
+
/**
|
|
2830
|
+
* Get the message container for the validator.
|
|
2831
|
+
*/
|
|
2832
|
+
async messages() {
|
|
2833
|
+
if (!this.#messages) await this.passes();
|
|
2834
|
+
return this.#messages;
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Add an after validation callback.
|
|
2838
|
+
*
|
|
2839
|
+
* @param callback
|
|
2840
|
+
*/
|
|
2841
|
+
after(callback) {
|
|
2842
|
+
if (Array.isArray(callback)) for (const rule of callback) this.#after.push(() => rule.toString().startsWith("class") ? new rule(this) : rule(this));
|
|
2843
|
+
else if (typeof callback === "function") this.#after.push(() => callback(this));
|
|
2844
|
+
return this;
|
|
2845
|
+
}
|
|
2846
|
+
/**
|
|
2847
|
+
* Get all errors.
|
|
2848
|
+
*/
|
|
2849
|
+
errors() {
|
|
2850
|
+
return this._errors;
|
|
2851
|
+
}
|
|
2852
|
+
errorBag() {
|
|
2853
|
+
return this.errorBagName;
|
|
2854
|
+
}
|
|
2855
|
+
/**
|
|
2856
|
+
* Reset validator with new data.
|
|
2857
|
+
*/
|
|
2858
|
+
setData(data) {
|
|
2859
|
+
this.data = data;
|
|
2860
|
+
this.executed = false;
|
|
2861
|
+
return this;
|
|
2862
|
+
}
|
|
2863
|
+
/**
|
|
2864
|
+
* Set validation rules.
|
|
2865
|
+
*/
|
|
2866
|
+
setRules(rules) {
|
|
2867
|
+
this.rules = rules;
|
|
2868
|
+
this.executed = false;
|
|
2869
|
+
return this;
|
|
2870
|
+
}
|
|
2871
|
+
/**
|
|
2872
|
+
* Add a single rule to existing rules.
|
|
2873
|
+
*/
|
|
2874
|
+
addRule(key, rule) {
|
|
2875
|
+
this.rules[key] = rule;
|
|
2876
|
+
return this;
|
|
2877
|
+
}
|
|
2878
|
+
/**
|
|
2879
|
+
* Merge additional rules.
|
|
2880
|
+
*/
|
|
2881
|
+
mergeRules(rules) {
|
|
2882
|
+
this.rules = {
|
|
2883
|
+
...this.rules,
|
|
2884
|
+
...rules
|
|
2885
|
+
};
|
|
2886
|
+
return this;
|
|
2887
|
+
}
|
|
2888
|
+
/**
|
|
2889
|
+
* Get current data.
|
|
2890
|
+
*/
|
|
2891
|
+
getData() {
|
|
2892
|
+
return this.data;
|
|
2893
|
+
}
|
|
2894
|
+
/**
|
|
2895
|
+
* Get current rules.
|
|
2896
|
+
*/
|
|
2897
|
+
getRules() {
|
|
2898
|
+
return this.rules;
|
|
2899
|
+
}
|
|
2900
|
+
database(driver) {
|
|
2901
|
+
this.databaseDriver = driver;
|
|
2902
|
+
Validator.defaultDatabaseDriver = driver;
|
|
2903
|
+
return this;
|
|
2904
|
+
}
|
|
2905
|
+
getDatabaseDriver() {
|
|
2906
|
+
return this.databaseDriver ?? Validator.defaultDatabaseDriver;
|
|
2907
|
+
}
|
|
2908
|
+
/**
|
|
2909
|
+
* Bind all required services here.
|
|
2910
|
+
*/
|
|
2911
|
+
bindServices() {
|
|
2912
|
+
/**
|
|
2913
|
+
* Register all custom rules
|
|
2914
|
+
*/
|
|
2915
|
+
for (const reged of this.registeredCustomRules) if (reged instanceof ValidationRule) {
|
|
2916
|
+
if (reged.setData) reged.setData(this.data);
|
|
2917
|
+
if (reged.setValidator) reged.setValidator(this);
|
|
2918
|
+
for (const rule of reged.rules) {
|
|
2919
|
+
register(rule.name, rule.validator);
|
|
2920
|
+
if (rule.message) Lang.setTranslationObject({ en: { [rule.name]: rule.message } });
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
return this;
|
|
2924
|
+
}
|
|
2925
|
+
async execute() {
|
|
2926
|
+
const instance = make().setData(this.data).setRules(this.rules).setCustomMessages(this.#messages).stopOnFirstFailure(this.shouldStopOnFirstFailure);
|
|
2927
|
+
this.passing = await instance.validateAsync();
|
|
2928
|
+
this.executed = true;
|
|
2929
|
+
this.instance = instance;
|
|
2930
|
+
if (!this.passing) this._errors = new MessageBag(instance.errors().all());
|
|
2931
|
+
return this;
|
|
2932
|
+
}
|
|
2933
|
+
};
|
|
2934
|
+
|
|
2935
|
+
//#endregion
|
|
2936
|
+
//#region src/ValidationException.ts
|
|
2937
|
+
var ValidationException = class ValidationException extends Error {
|
|
2938
|
+
validator;
|
|
2939
|
+
response;
|
|
2940
|
+
status = 422;
|
|
2941
|
+
errorBag = "default";
|
|
2942
|
+
redirectTo;
|
|
2943
|
+
name = "ValidationException";
|
|
2944
|
+
constructor(validator, response = null, errorBag = "default") {
|
|
2945
|
+
super(ValidationException.summarize(validator));
|
|
2946
|
+
this.validator = validator;
|
|
2947
|
+
this.response = response;
|
|
2948
|
+
this.errorBag = errorBag;
|
|
2949
|
+
Object.setPrototypeOf(this, ValidationException.prototype);
|
|
2950
|
+
}
|
|
2951
|
+
/**
|
|
2952
|
+
* Send a custom response body for this exception
|
|
2953
|
+
*
|
|
2954
|
+
* @param request
|
|
2955
|
+
* @returns
|
|
2956
|
+
*/
|
|
2957
|
+
toResponse() {
|
|
2958
|
+
return {
|
|
2959
|
+
message: this.message,
|
|
2960
|
+
errors: this.errors()
|
|
2961
|
+
};
|
|
2962
|
+
}
|
|
2963
|
+
/**
|
|
2964
|
+
* Create a new validation exception from a plain array of messages.
|
|
2965
|
+
*/
|
|
2966
|
+
static withMessages(messages) {
|
|
2967
|
+
const validator = new Validator({}, {});
|
|
2968
|
+
const bag = new MessageBag();
|
|
2969
|
+
for (const [key, value] of Object.entries(messages)) {
|
|
2970
|
+
const list = Array.isArray(value) ? value : [value];
|
|
2971
|
+
for (const message of list) bag.add(key, message);
|
|
2972
|
+
}
|
|
2973
|
+
validator._errors = bag;
|
|
2974
|
+
return new ValidationException(validator);
|
|
2975
|
+
}
|
|
2976
|
+
/**
|
|
2977
|
+
* Create a readable summary message from the validation errors.
|
|
2978
|
+
*/
|
|
2979
|
+
static summarize(validator) {
|
|
2980
|
+
const messages = validator.errors().all();
|
|
2981
|
+
if (!messages.length || typeof messages[0] !== "string") return "The given data was invalid.";
|
|
2982
|
+
let message = messages.shift();
|
|
2983
|
+
const count = messages.length;
|
|
2984
|
+
if (count > 0) message += ` (and ${count} more ${plural("error", count)})`;
|
|
2985
|
+
return message;
|
|
2986
|
+
}
|
|
2987
|
+
/**
|
|
2988
|
+
* Get all of the validation error messages.
|
|
2989
|
+
*/
|
|
2990
|
+
errors() {
|
|
2991
|
+
return this.validator.errors().getMessages();
|
|
2992
|
+
}
|
|
2993
|
+
/**
|
|
2994
|
+
* Set the HTTP status code to be used for the response.
|
|
2995
|
+
*/
|
|
2996
|
+
setStatus(status) {
|
|
2997
|
+
this.status = status;
|
|
2998
|
+
return this;
|
|
2999
|
+
}
|
|
3000
|
+
/**
|
|
3001
|
+
* Set the error bag on the exception.
|
|
3002
|
+
*/
|
|
3003
|
+
setErrorBag(errorBag) {
|
|
3004
|
+
this.errorBag = errorBag;
|
|
3005
|
+
return this;
|
|
3006
|
+
}
|
|
3007
|
+
/**
|
|
3008
|
+
* Set the URL to redirect to on a validation error.
|
|
3009
|
+
*/
|
|
3010
|
+
setRedirectTo(url) {
|
|
3011
|
+
this.redirectTo = url;
|
|
3012
|
+
return this;
|
|
3013
|
+
}
|
|
3014
|
+
/**
|
|
3015
|
+
* Get the underlying response instance.
|
|
3016
|
+
*/
|
|
3017
|
+
getResponse() {
|
|
3018
|
+
return this.response;
|
|
3019
|
+
}
|
|
3020
|
+
};
|
|
3021
|
+
|
|
3022
|
+
//#endregion
|
|
3023
|
+
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 };
|