kanun 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +270 -61
- package/dist/index.d.ts +7 -2
- package/dist/index.js +270 -61
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -27,6 +27,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
27
|
|
|
28
28
|
//#endregion
|
|
29
29
|
let node_async_hooks = require("node:async_hooks");
|
|
30
|
+
let node_url = require("node:url");
|
|
31
|
+
let node_net = require("node:net");
|
|
30
32
|
let dayjs_plugin_customParseFormat_js = require("dayjs/plugin/customParseFormat.js");
|
|
31
33
|
dayjs_plugin_customParseFormat_js = __toESM(dayjs_plugin_customParseFormat_js);
|
|
32
34
|
let dayjs = require("dayjs");
|
|
@@ -103,10 +105,12 @@ var en_default = {
|
|
|
103
105
|
declined: "The :attribute must be declined.",
|
|
104
106
|
declined_if: "The :attribute must be declined when :other is :value.",
|
|
105
107
|
different: "The :attribute and :other must be different.",
|
|
108
|
+
distinct: "The :attribute field has a duplicate value.",
|
|
106
109
|
digits: "The :attribute must be :digits digits.",
|
|
107
110
|
digits_between: "The :attribute must be between :min and :max digits.",
|
|
108
111
|
email: "The :attribute must be a valid email address.",
|
|
109
112
|
ends_with: "The :attribute must end with one of the following: :values.",
|
|
113
|
+
filled: "The :attribute field must have a value.",
|
|
110
114
|
exists: "The selected :attribute is invalid.",
|
|
111
115
|
gt: {
|
|
112
116
|
number: "The :attribute must be greater than :value.",
|
|
@@ -124,6 +128,9 @@ var en_default = {
|
|
|
124
128
|
in: "The :attribute must be one of the following :values.",
|
|
125
129
|
includes: "The :attribute must include one of the following values: :values.",
|
|
126
130
|
integer: "The :attribute must be an integer.",
|
|
131
|
+
ip: "The :attribute must be a valid IP address.",
|
|
132
|
+
ipv4: "The :attribute must be a valid IPv4 address.",
|
|
133
|
+
ipv6: "The :attribute must be a valid IPv6 address.",
|
|
127
134
|
json: "The :attribute must be a valid JSON string.",
|
|
128
135
|
lt: {
|
|
129
136
|
number: "The :attribute must be less than :value.",
|
|
@@ -149,6 +156,7 @@ var en_default = {
|
|
|
149
156
|
array: "The :attribute must have at least :min items.",
|
|
150
157
|
object: "The :attribute must have at least :min items."
|
|
151
158
|
},
|
|
159
|
+
mac_address: "The :attribute must be a valid MAC address.",
|
|
152
160
|
not_in: "The selected :attribute is invalid.",
|
|
153
161
|
not_regex: "The :attribute format is invalid.",
|
|
154
162
|
not_includes: "The :attribute must not include any of the following values: :values.",
|
|
@@ -167,6 +175,10 @@ var en_default = {
|
|
|
167
175
|
upper_cases: "The :attribute must contain at least :amount uppercase letters."
|
|
168
176
|
},
|
|
169
177
|
present: "The :attribute field must be present.",
|
|
178
|
+
presentsame: "The :attribute field must be present.",
|
|
179
|
+
prohibited: "The :attribute field is prohibited.",
|
|
180
|
+
prohibited_unless: "The :attribute field is prohibited unless :other is in :values.",
|
|
181
|
+
prohibits: "The :attribute field prohibits :values from being present.",
|
|
170
182
|
regex: "The :attribute format is invalid.",
|
|
171
183
|
required: "The :attribute field is required.",
|
|
172
184
|
required_if: "The :attribute field is required when :other is :value.",
|
|
@@ -184,8 +196,10 @@ var en_default = {
|
|
|
184
196
|
object: "The :attribute must contain :size items."
|
|
185
197
|
},
|
|
186
198
|
string: "The :attribute must be a string.",
|
|
199
|
+
timezone: "The :attribute must be a valid timezone.",
|
|
187
200
|
unique: "The :attribute has already been taken.",
|
|
188
|
-
url: "The :attribute must have a valid URL format."
|
|
201
|
+
url: "The :attribute must have a valid URL format.",
|
|
202
|
+
multiple_of: "The :attribute must be a multiple of :value."
|
|
189
203
|
};
|
|
190
204
|
|
|
191
205
|
//#endregion
|
|
@@ -654,6 +668,12 @@ const replaceAttributes = {
|
|
|
654
668
|
};
|
|
655
669
|
return message.replace(/:other|:value/gi, (matched) => values[matched]);
|
|
656
670
|
},
|
|
671
|
+
replaceProhibitedUnless: function(payload) {
|
|
672
|
+
return this.replaceRequiredUnless(payload);
|
|
673
|
+
},
|
|
674
|
+
replaceProhibits: function({ message, parameters, getDisplayableAttribute }) {
|
|
675
|
+
return message.replace(":values", parameters.map((attribute) => getDisplayableAttribute(attribute)).join(", "));
|
|
676
|
+
},
|
|
657
677
|
replaceRequiredUnless: function({ message, parameters, getDisplayableAttribute }) {
|
|
658
678
|
const [other] = parameters;
|
|
659
679
|
const values = {
|
|
@@ -668,40 +688,14 @@ const replaceAttributes = {
|
|
|
668
688
|
replaceSize: function({ message, parameters }) {
|
|
669
689
|
return message.replace(":size", parameters[0]);
|
|
670
690
|
},
|
|
691
|
+
replaceMultipleOf: function({ message, parameters }) {
|
|
692
|
+
return message.replace(":value", parameters[0]);
|
|
693
|
+
},
|
|
671
694
|
replaceUnique: function({ message, parameters, data }) {
|
|
672
695
|
return message.replace(":value", data[parameters[1]]);
|
|
673
696
|
}
|
|
674
697
|
};
|
|
675
698
|
|
|
676
|
-
//#endregion
|
|
677
|
-
//#region src/Rules/closureValidationRule.ts
|
|
678
|
-
var ClosureValidationRule = class extends IRuleContract {
|
|
679
|
-
/**
|
|
680
|
-
* The callback that validates the attribute
|
|
681
|
-
*/
|
|
682
|
-
callback;
|
|
683
|
-
/**
|
|
684
|
-
* Indicates if the validation callback failed.
|
|
685
|
-
*/
|
|
686
|
-
failed = false;
|
|
687
|
-
constructor(callback) {
|
|
688
|
-
super();
|
|
689
|
-
this.callback = callback;
|
|
690
|
-
}
|
|
691
|
-
/**
|
|
692
|
-
* Determine if the validation rule passes.
|
|
693
|
-
*/
|
|
694
|
-
passes(value, attribute) {
|
|
695
|
-
this.failed = false;
|
|
696
|
-
const result = this.callback(value, (message) => {
|
|
697
|
-
this.failed = true;
|
|
698
|
-
this.message = message;
|
|
699
|
-
}, attribute);
|
|
700
|
-
if (result instanceof Promise) return result.then(() => !this.failed);
|
|
701
|
-
return !this.failed;
|
|
702
|
-
}
|
|
703
|
-
};
|
|
704
|
-
|
|
705
699
|
//#endregion
|
|
706
700
|
//#region src/validators/validationData.ts
|
|
707
701
|
const validationData = {
|
|
@@ -742,6 +736,35 @@ const validationData = {
|
|
|
742
736
|
}
|
|
743
737
|
};
|
|
744
738
|
|
|
739
|
+
//#endregion
|
|
740
|
+
//#region src/Rules/closureValidationRule.ts
|
|
741
|
+
var ClosureValidationRule = class extends IRuleContract {
|
|
742
|
+
/**
|
|
743
|
+
* The callback that validates the attribute
|
|
744
|
+
*/
|
|
745
|
+
callback;
|
|
746
|
+
/**
|
|
747
|
+
* Indicates if the validation callback failed.
|
|
748
|
+
*/
|
|
749
|
+
failed = false;
|
|
750
|
+
constructor(callback) {
|
|
751
|
+
super();
|
|
752
|
+
this.callback = callback;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Determine if the validation rule passes.
|
|
756
|
+
*/
|
|
757
|
+
passes(value, attribute) {
|
|
758
|
+
this.failed = false;
|
|
759
|
+
const result = this.callback(value, (message) => {
|
|
760
|
+
this.failed = true;
|
|
761
|
+
this.message = message;
|
|
762
|
+
}, attribute);
|
|
763
|
+
if (result instanceof Promise) return result.then(() => !this.failed);
|
|
764
|
+
return !this.failed;
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
|
|
745
768
|
//#endregion
|
|
746
769
|
//#region src/validators/validationRuleParser.ts
|
|
747
770
|
const validationRuleParser = {
|
|
@@ -989,7 +1012,7 @@ var validateAttributes = class {
|
|
|
989
1012
|
*/
|
|
990
1013
|
validateDeclinedIf(value, parameters) {
|
|
991
1014
|
this.requireParameterCount(2, parameters, "declined_if");
|
|
992
|
-
const other =
|
|
1015
|
+
const other = this.getAttributeValue(parameters[0]);
|
|
993
1016
|
if (!other) return true;
|
|
994
1017
|
if (parameters.slice(1).indexOf(other) !== -1) return this.validateDeclined(value);
|
|
995
1018
|
return true;
|
|
@@ -999,7 +1022,7 @@ var validateAttributes = class {
|
|
|
999
1022
|
*/
|
|
1000
1023
|
validateDifferent(value, parameters) {
|
|
1001
1024
|
this.requireParameterCount(1, parameters, "different");
|
|
1002
|
-
const other =
|
|
1025
|
+
const other = this.getAttributeValue(parameters[0]);
|
|
1003
1026
|
if (!sameType(value, other)) return true;
|
|
1004
1027
|
if (value !== null && typeof value === "object") return !deepEqual(value, other);
|
|
1005
1028
|
return value !== other;
|
|
@@ -1034,13 +1057,23 @@ var validateAttributes = class {
|
|
|
1034
1057
|
/**
|
|
1035
1058
|
* Validate that an attribute is a valid email address.
|
|
1036
1059
|
*/
|
|
1037
|
-
validateEmail(value) {
|
|
1060
|
+
validateEmail(value, parameters = []) {
|
|
1038
1061
|
if (typeof value !== "string") return false;
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1062
|
+
const normalized = parameters.map((parameter) => parameter.replace(/[{}]/g, "").toLowerCase());
|
|
1063
|
+
if (normalized.length === 0 || normalized.includes("filter") || normalized.includes("rfc")) {
|
|
1064
|
+
if (!this.isEmailByFilter(value)) return false;
|
|
1065
|
+
}
|
|
1066
|
+
if (normalized.includes("strict") && !this.isStrictEmail(value)) return false;
|
|
1067
|
+
if (normalized.includes("dns") && !this.hasResolvableEmailDomain(value)) return false;
|
|
1068
|
+
if (normalized.includes("spoof") && !this.isSpoofSafeEmail(value)) return false;
|
|
1069
|
+
return true;
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Validate that a present attribute is not empty.
|
|
1073
|
+
*/
|
|
1074
|
+
validateFilled(value) {
|
|
1075
|
+
if (typeof value === "undefined") return true;
|
|
1076
|
+
return this.validateRequired(value);
|
|
1044
1077
|
}
|
|
1045
1078
|
/**
|
|
1046
1079
|
* Validate the attribute ends with a given substring.
|
|
@@ -1100,7 +1133,7 @@ var validateAttributes = class {
|
|
|
1100
1133
|
*/
|
|
1101
1134
|
validateRequiredIf(value, parameters) {
|
|
1102
1135
|
this.requireParameterCount(2, parameters, "required_if");
|
|
1103
|
-
const other =
|
|
1136
|
+
const other = this.getAttributeValue(parameters[0]);
|
|
1104
1137
|
if (typeof other === "undefined") return true;
|
|
1105
1138
|
if (this.parseDependentRuleParameters(other, parameters).indexOf(other) !== -1) return this.validateRequired(value);
|
|
1106
1139
|
return true;
|
|
@@ -1110,7 +1143,7 @@ var validateAttributes = class {
|
|
|
1110
1143
|
*/
|
|
1111
1144
|
validateRequiredUnless(value, parameters) {
|
|
1112
1145
|
this.requireParameterCount(2, parameters, "required_unless");
|
|
1113
|
-
let other =
|
|
1146
|
+
let other = this.getAttributeValue(parameters[0]);
|
|
1114
1147
|
other = typeof other === "undefined" ? null : other;
|
|
1115
1148
|
if (this.parseDependentRuleParameters(other, parameters).indexOf(other) === -1) return this.validateRequired(value);
|
|
1116
1149
|
return true;
|
|
@@ -1147,14 +1180,14 @@ var validateAttributes = class {
|
|
|
1147
1180
|
* Determine if any of the given attributes fail the required test.
|
|
1148
1181
|
*/
|
|
1149
1182
|
anyFailingRequired(attributes) {
|
|
1150
|
-
for (let i = 0; i < attributes.length; i++) if (!this.validateRequired(
|
|
1183
|
+
for (let i = 0; i < attributes.length; i++) if (!this.validateRequired(this.getAttributeValue(attributes[i]))) return true;
|
|
1151
1184
|
return false;
|
|
1152
1185
|
}
|
|
1153
1186
|
/**
|
|
1154
1187
|
* Determine if all of the given attributes fail the required test.
|
|
1155
1188
|
*/
|
|
1156
1189
|
allFailingRequired(attributes) {
|
|
1157
|
-
for (let i = 0; i < attributes.length; i++) if (this.validateRequired(
|
|
1190
|
+
for (let i = 0; i < attributes.length; i++) if (this.validateRequired(this.getAttributeValue(attributes[i]))) return false;
|
|
1158
1191
|
return true;
|
|
1159
1192
|
}
|
|
1160
1193
|
/**
|
|
@@ -1196,7 +1229,13 @@ var validateAttributes = class {
|
|
|
1196
1229
|
* Validate that an attribute exists even if not filled.
|
|
1197
1230
|
*/
|
|
1198
1231
|
validatePresent(value, parameters, attribute) {
|
|
1199
|
-
return typeof
|
|
1232
|
+
return typeof this.getAttributeValue(attribute) !== "undefined";
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Alias of the present rule kept for compatibility with requested rule naming.
|
|
1236
|
+
*/
|
|
1237
|
+
validatePresentsame(value, parameters, attribute) {
|
|
1238
|
+
return this.validatePresent(value, parameters, attribute);
|
|
1200
1239
|
}
|
|
1201
1240
|
/**
|
|
1202
1241
|
* Validate that an attribute is an integer.
|
|
@@ -1218,12 +1257,103 @@ var validateAttributes = class {
|
|
|
1218
1257
|
return true;
|
|
1219
1258
|
}
|
|
1220
1259
|
/**
|
|
1260
|
+
* Validate that an attribute is prohibited.
|
|
1261
|
+
*/
|
|
1262
|
+
validateProhibited(value) {
|
|
1263
|
+
return !this.validateRequired(value);
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Validate that an attribute is prohibited unless another field matches one of the given values.
|
|
1267
|
+
*/
|
|
1268
|
+
validateProhibitedUnless(value, parameters) {
|
|
1269
|
+
this.requireParameterCount(2, parameters, "prohibited_unless");
|
|
1270
|
+
let other = this.getAttributeValue(parameters[0]);
|
|
1271
|
+
other = typeof other === "undefined" ? null : other;
|
|
1272
|
+
if (this.parseDependentRuleParameters(other, parameters).indexOf(other) !== -1) return true;
|
|
1273
|
+
return this.validateProhibited(value);
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Validate that present attributes prohibit other attributes from being present.
|
|
1277
|
+
*/
|
|
1278
|
+
validateProhibits(value, parameters) {
|
|
1279
|
+
this.requireParameterCount(1, parameters, "prohibits");
|
|
1280
|
+
if (!this.validateRequired(value)) return true;
|
|
1281
|
+
return parameters.every((attribute) => this.validateProhibited(this.getAttributeValue(attribute)));
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Validate that an attribute is a valid IP address.
|
|
1285
|
+
*/
|
|
1286
|
+
validateIp(value) {
|
|
1287
|
+
return typeof value === "string" && (0, node_net.isIP)(value) !== 0;
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Validate that an attribute is a valid IPv4 address.
|
|
1291
|
+
*/
|
|
1292
|
+
validateIpv4(value) {
|
|
1293
|
+
return typeof value === "string" && (0, node_net.isIP)(value) === 4;
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Validate that an attribute is a valid IPv6 address.
|
|
1297
|
+
*/
|
|
1298
|
+
validateIpv6(value) {
|
|
1299
|
+
return typeof value === "string" && (0, node_net.isIP)(value) === 6;
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Validate that an attribute is a valid MAC address.
|
|
1303
|
+
*/
|
|
1304
|
+
validateMacAddress(value) {
|
|
1305
|
+
return typeof value === "string" && /^([0-9a-f]{2}[:-]){5}[0-9a-f]{2}$/i.test(value);
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Validate that an attribute is a valid timezone identifier.
|
|
1309
|
+
*/
|
|
1310
|
+
validateTimezone(value) {
|
|
1311
|
+
if (typeof value !== "string" || value.length === 0) return false;
|
|
1312
|
+
if (typeof Intl.supportedValuesOf === "function" && Intl.supportedValuesOf("timeZone").includes(value)) return true;
|
|
1313
|
+
try {
|
|
1314
|
+
new Intl.DateTimeFormat("en-US", { timeZone: value });
|
|
1315
|
+
return true;
|
|
1316
|
+
} catch {
|
|
1317
|
+
return false;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Validate that an attribute is a multiple of the given value.
|
|
1322
|
+
*/
|
|
1323
|
+
validateMultipleOf(value, parameters) {
|
|
1324
|
+
this.requireParameterCount(1, parameters, "multiple_of");
|
|
1325
|
+
const numericValue = Number(value);
|
|
1326
|
+
const divisor = Number(parameters[0]);
|
|
1327
|
+
if (!Number.isFinite(numericValue) || !Number.isFinite(divisor) || divisor === 0) return false;
|
|
1328
|
+
const scale = 10 ** Math.max(this.getDecimalPlaces(value), this.getDecimalPlaces(parameters[0]));
|
|
1329
|
+
const scaledValue = Math.round(numericValue * scale);
|
|
1330
|
+
const scaledDivisor = Math.round(divisor * scale);
|
|
1331
|
+
return scaledDivisor !== 0 && scaledValue % scaledDivisor === 0;
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Validate that values matched by a wildcard attribute are distinct.
|
|
1335
|
+
*/
|
|
1336
|
+
validateDistinct(value, parameters, attribute, primaryAttribute = attribute) {
|
|
1337
|
+
if (Array.isArray(value)) return new Set(value.map((entry) => this.normalizeDistinctValue(entry, parameters))).size === value.length;
|
|
1338
|
+
if (primaryAttribute.indexOf("*") === -1) return true;
|
|
1339
|
+
const strict = parameters.includes("strict");
|
|
1340
|
+
const values = this.getDistinctValues(primaryAttribute);
|
|
1341
|
+
const current = this.normalizeDistinctValue(value, parameters);
|
|
1342
|
+
let matches = 0;
|
|
1343
|
+
for (const candidate of values) {
|
|
1344
|
+
const normalized = this.normalizeDistinctValue(candidate, parameters);
|
|
1345
|
+
if (strict ? normalized === current : normalized == current) matches += 1;
|
|
1346
|
+
if (matches > 1) return false;
|
|
1347
|
+
}
|
|
1348
|
+
return true;
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1221
1351
|
* Validate that an attribute is greater than another attribute.
|
|
1222
1352
|
*/
|
|
1223
1353
|
validateGt(value, parameters, attribute) {
|
|
1224
1354
|
this.requireParameterCount(1, parameters, "gt");
|
|
1225
1355
|
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1226
|
-
const compartedToValue =
|
|
1356
|
+
const compartedToValue = this.getAttributeValue(parameters[0]) || parameters[0];
|
|
1227
1357
|
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) > compartedToValue;
|
|
1228
1358
|
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1229
1359
|
return getSize(value) > getSize(compartedToValue);
|
|
@@ -1234,7 +1364,7 @@ var validateAttributes = class {
|
|
|
1234
1364
|
validateGte(value, parameters, attribute) {
|
|
1235
1365
|
this.requireParameterCount(1, parameters, "gte");
|
|
1236
1366
|
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1237
|
-
const compartedToValue =
|
|
1367
|
+
const compartedToValue = this.getAttributeValue(parameters[0]) || parameters[0];
|
|
1238
1368
|
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) >= compartedToValue;
|
|
1239
1369
|
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1240
1370
|
return getSize(value) >= getSize(compartedToValue);
|
|
@@ -1245,7 +1375,7 @@ var validateAttributes = class {
|
|
|
1245
1375
|
validateLt(value, parameters, attribute) {
|
|
1246
1376
|
this.requireParameterCount(1, parameters, "lt");
|
|
1247
1377
|
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1248
|
-
const compartedToValue =
|
|
1378
|
+
const compartedToValue = this.getAttributeValue(parameters[0]) || parameters[0];
|
|
1249
1379
|
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) < compartedToValue;
|
|
1250
1380
|
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1251
1381
|
return getSize(value) < getSize(compartedToValue);
|
|
@@ -1256,7 +1386,7 @@ var validateAttributes = class {
|
|
|
1256
1386
|
validateLte(value, parameters, attribute) {
|
|
1257
1387
|
this.requireParameterCount(1, parameters, "lte");
|
|
1258
1388
|
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1259
|
-
const compartedToValue =
|
|
1389
|
+
const compartedToValue = this.getAttributeValue(parameters[0]) || parameters[0];
|
|
1260
1390
|
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) <= compartedToValue;
|
|
1261
1391
|
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1262
1392
|
return getSize(value) <= getSize(compartedToValue);
|
|
@@ -1314,7 +1444,7 @@ var validateAttributes = class {
|
|
|
1314
1444
|
compareDates(value, parameter, operator, rule) {
|
|
1315
1445
|
value = toDate(value);
|
|
1316
1446
|
if (!value) throw `Validation rule ${rule} requires the field under valation to be a date.`;
|
|
1317
|
-
const compartedToValue = toDate(
|
|
1447
|
+
const compartedToValue = toDate(this.getAttributeValue(parameter) || parameter);
|
|
1318
1448
|
if (!compartedToValue) throw `Validation rule ${rule} requires the parameter to be a date.`;
|
|
1319
1449
|
return compare(value.getTime(), compartedToValue.getTime(), operator);
|
|
1320
1450
|
}
|
|
@@ -1326,6 +1456,10 @@ var validateAttributes = class {
|
|
|
1326
1456
|
}
|
|
1327
1457
|
/**
|
|
1328
1458
|
* Prepare the values for validation
|
|
1459
|
+
*
|
|
1460
|
+
* @param other The value to compare against
|
|
1461
|
+
* @param parameters The parameters for the validation rule
|
|
1462
|
+
* @returns The prepared values for validation
|
|
1329
1463
|
*/
|
|
1330
1464
|
parseDependentRuleParameters(other, parameters) {
|
|
1331
1465
|
let values = parameters.slice(1);
|
|
@@ -1334,6 +1468,55 @@ var validateAttributes = class {
|
|
|
1334
1468
|
if (typeof other === "boolean") values = convertValuesToBoolean(values);
|
|
1335
1469
|
return values;
|
|
1336
1470
|
}
|
|
1471
|
+
getAttributeValue(attribute) {
|
|
1472
|
+
const dataValue = deepFind(this.data, attribute);
|
|
1473
|
+
if (typeof dataValue !== "undefined") return dataValue;
|
|
1474
|
+
return deepFind(this.context.requestFiles ?? {}, attribute);
|
|
1475
|
+
}
|
|
1476
|
+
getDistinctValues(primaryAttribute) {
|
|
1477
|
+
const gathered = validationData.initializeAndGatherData(primaryAttribute, this.data);
|
|
1478
|
+
const pattern = new RegExp(`^${primaryAttribute.replace(/\./g, "\\.").replace(/\*/g, "[^.]+")}$`);
|
|
1479
|
+
return Object.keys(gathered).filter((attribute) => pattern.test(attribute)).map((attribute) => deepFind(this.data, attribute)).filter((value) => typeof value !== "undefined");
|
|
1480
|
+
}
|
|
1481
|
+
normalizeDistinctValue(value, parameters) {
|
|
1482
|
+
if (parameters.includes("ignore_case") && typeof value === "string") return value.toLowerCase();
|
|
1483
|
+
return value;
|
|
1484
|
+
}
|
|
1485
|
+
isEmailByFilter(value) {
|
|
1486
|
+
return value.toLowerCase().match(/^[^\s@]+@[^\s@]+\.[^\s@]{2,24}$/) !== null;
|
|
1487
|
+
}
|
|
1488
|
+
isStrictEmail(value) {
|
|
1489
|
+
if (!this.isEmailByFilter(value)) return false;
|
|
1490
|
+
const [localPart, domain] = value.split("@");
|
|
1491
|
+
if (!localPart || !domain || localPart.length > 64 || domain.length > 255) return false;
|
|
1492
|
+
if (localPart.startsWith(".") || localPart.endsWith(".") || localPart.includes("..")) return false;
|
|
1493
|
+
return domain.split(".").every((label) => {
|
|
1494
|
+
return label.length > 0 && label.length <= 63 && /^[a-z0-9-]+$/i.test(label) && !label.startsWith("-") && !label.endsWith("-");
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
hasResolvableEmailDomain(value) {
|
|
1498
|
+
if (!this.isStrictEmail(value)) return false;
|
|
1499
|
+
const domain = value.split("@")[1];
|
|
1500
|
+
const asciiDomain = (0, node_url.domainToASCII)(domain);
|
|
1501
|
+
return asciiDomain.length > 0 && asciiDomain.includes(".");
|
|
1502
|
+
}
|
|
1503
|
+
isSpoofSafeEmail(value) {
|
|
1504
|
+
if (!this.isEmailByFilter(value)) return false;
|
|
1505
|
+
const [localPart, domain] = value.split("@");
|
|
1506
|
+
return (0, node_url.domainToASCII)(domain).length > 0 && !this.hasAsciiControlCharacters(value) && localPart.normalize("NFKC") === localPart && domain.normalize("NFKC") === domain;
|
|
1507
|
+
}
|
|
1508
|
+
hasAsciiControlCharacters(value) {
|
|
1509
|
+
for (let index = 0; index < value.length; index++) {
|
|
1510
|
+
const codePoint = value.charCodeAt(index);
|
|
1511
|
+
if (codePoint <= 31 || codePoint === 127) return true;
|
|
1512
|
+
}
|
|
1513
|
+
return false;
|
|
1514
|
+
}
|
|
1515
|
+
getDecimalPlaces(value) {
|
|
1516
|
+
const stringValue = String(value).toLowerCase();
|
|
1517
|
+
if (!stringValue.includes(".")) return 0;
|
|
1518
|
+
return stringValue.split(".")[1]?.length ?? 0;
|
|
1519
|
+
}
|
|
1337
1520
|
};
|
|
1338
1521
|
|
|
1339
1522
|
//#endregion
|
|
@@ -1402,6 +1585,7 @@ const implicitRues = [
|
|
|
1402
1585
|
"declined_if",
|
|
1403
1586
|
"filled",
|
|
1404
1587
|
"present",
|
|
1588
|
+
"presentsame",
|
|
1405
1589
|
"required",
|
|
1406
1590
|
"required_if",
|
|
1407
1591
|
"required_unless",
|
|
@@ -1883,11 +2067,12 @@ var Password$1 = class Password$1 extends IRuleContract {
|
|
|
1883
2067
|
* Determine if the validation rule passes.
|
|
1884
2068
|
*/
|
|
1885
2069
|
passes(value, attribute) {
|
|
1886
|
-
const
|
|
2070
|
+
const passwordRules = [
|
|
1887
2071
|
"string",
|
|
1888
2072
|
`min:${this.minLength}`,
|
|
1889
2073
|
...this.customRules
|
|
1890
|
-
]
|
|
2074
|
+
];
|
|
2075
|
+
const validator = new BaseValidator(this.data, { [attribute]: passwordRules }, this.validator.customMessages, this.validator.customAttributes).setLang(this.lang);
|
|
1891
2076
|
if (!validator.validate()) {
|
|
1892
2077
|
const errors = validator.errors().addErrorTypes().get(attribute);
|
|
1893
2078
|
for (const key in errors) this.validator.errors().add(attribute, errors[key]);
|
|
@@ -2030,6 +2215,7 @@ var replaceAttributePayload = class {
|
|
|
2030
2215
|
//#endregion
|
|
2031
2216
|
//#region src/BaseValidator.ts
|
|
2032
2217
|
var BaseValidator = class BaseValidator {
|
|
2218
|
+
excludedAttributes = /* @__PURE__ */ new Set();
|
|
2033
2219
|
/**
|
|
2034
2220
|
* The lang used to return error messages
|
|
2035
2221
|
*/
|
|
@@ -2114,6 +2300,7 @@ var BaseValidator = class BaseValidator {
|
|
|
2114
2300
|
*/
|
|
2115
2301
|
withContext(context = {}) {
|
|
2116
2302
|
this.context = context;
|
|
2303
|
+
this.addRules(this.initalRules);
|
|
2117
2304
|
return this;
|
|
2118
2305
|
}
|
|
2119
2306
|
/**
|
|
@@ -2174,6 +2361,9 @@ var BaseValidator = class BaseValidator {
|
|
|
2174
2361
|
errors() {
|
|
2175
2362
|
return this.messages;
|
|
2176
2363
|
}
|
|
2364
|
+
getExcludedAttributes() {
|
|
2365
|
+
return [...this.excludedAttributes];
|
|
2366
|
+
}
|
|
2177
2367
|
/**
|
|
2178
2368
|
* Clear the error messages for the given keys.
|
|
2179
2369
|
* If no keys are provided, all error messages will be cleared.
|
|
@@ -2290,6 +2480,7 @@ var BaseValidator = class BaseValidator {
|
|
|
2290
2480
|
runAllValidations() {
|
|
2291
2481
|
this.messages = new ErrorBag();
|
|
2292
2482
|
this.validateAttributes = new validateAttributes(this.data, this.rules, this.getContext());
|
|
2483
|
+
this.excludedAttributes.clear();
|
|
2293
2484
|
for (const property in this.rules) if (this.runValidation(property) === false) break;
|
|
2294
2485
|
}
|
|
2295
2486
|
/**
|
|
@@ -2298,6 +2489,7 @@ var BaseValidator = class BaseValidator {
|
|
|
2298
2489
|
async runAllValidationsAsync() {
|
|
2299
2490
|
this.messages = new ErrorBag();
|
|
2300
2491
|
this.validateAttributes = new validateAttributes(this.data, this.rules, this.getContext());
|
|
2492
|
+
this.excludedAttributes.clear();
|
|
2301
2493
|
for (const property in this.rules) if (await this.runValidationAsync(property) === false) break;
|
|
2302
2494
|
}
|
|
2303
2495
|
/**
|
|
@@ -2320,33 +2512,47 @@ var BaseValidator = class BaseValidator {
|
|
|
2320
2512
|
* Run validation rules for the specified property and stop validation if needed
|
|
2321
2513
|
*/
|
|
2322
2514
|
runValidation(property) {
|
|
2323
|
-
if (Object.prototype.hasOwnProperty.call(this.rules, property) && Array.isArray(this.rules[property]))
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2515
|
+
if (Object.prototype.hasOwnProperty.call(this.rules, property) && Array.isArray(this.rules[property])) {
|
|
2516
|
+
if (validationRuleParser.hasRule(property, ["exclude"], this.rules)) {
|
|
2517
|
+
this.excludedAttributes.add(property);
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
for (let i = 0; i < this.rules[property].length; i++) {
|
|
2521
|
+
this.validateAttribute(property, this.rules[property][i]);
|
|
2522
|
+
if (this.messages.keys().length > 0 && this.stopOnFirstFailureFlag === true) return false;
|
|
2523
|
+
if (this.shouldStopValidating(property)) break;
|
|
2524
|
+
}
|
|
2327
2525
|
}
|
|
2328
2526
|
}
|
|
2329
2527
|
/**
|
|
2330
2528
|
* Run validation rules for the specified property asynchronously and stop validation if needed
|
|
2331
2529
|
*/
|
|
2332
2530
|
async runValidationAsync(property) {
|
|
2333
|
-
if (Object.prototype.hasOwnProperty.call(this.rules, property) && Array.isArray(this.rules[property]))
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2531
|
+
if (Object.prototype.hasOwnProperty.call(this.rules, property) && Array.isArray(this.rules[property])) {
|
|
2532
|
+
if (validationRuleParser.hasRule(property, ["exclude"], this.rules)) {
|
|
2533
|
+
this.excludedAttributes.add(property);
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
for (let i = 0; i < this.rules[property].length; i++) {
|
|
2537
|
+
await this.validateAttribute(property, this.rules[property][i]);
|
|
2538
|
+
if (this.messages.keys().length > 0 && this.stopOnFirstFailureFlag === true) return false;
|
|
2539
|
+
if (this.shouldStopValidating(property)) break;
|
|
2540
|
+
}
|
|
2337
2541
|
}
|
|
2338
2542
|
}
|
|
2339
2543
|
/**
|
|
2340
2544
|
* Check if we should stop further validations on a given attribute.
|
|
2341
2545
|
*/
|
|
2342
2546
|
shouldStopValidating(attribute) {
|
|
2547
|
+
if (this.excludedAttributes.has(attribute)) return true;
|
|
2343
2548
|
return this.messages.has(attribute) && validationRuleParser.hasRule(attribute, ["bail"], this.rules);
|
|
2344
2549
|
}
|
|
2345
2550
|
/**
|
|
2346
2551
|
* Parse the given rules add assign them to the current rules
|
|
2347
2552
|
*/
|
|
2348
2553
|
addRules(rules) {
|
|
2349
|
-
const
|
|
2554
|
+
const availableData = mergeDeep(this.getContext().requestFiles ?? {}, this.data);
|
|
2555
|
+
const response = validationRuleParser.explodeRules(dotify(rules, true), availableData);
|
|
2350
2556
|
this.rules = response.rules;
|
|
2351
2557
|
this.implicitAttributes = response.implicitAttributes;
|
|
2352
2558
|
}
|
|
@@ -2364,7 +2570,7 @@ var BaseValidator = class BaseValidator {
|
|
|
2364
2570
|
const method = `validate${buildValidationMethodName(rule)}`;
|
|
2365
2571
|
if (rule !== "" && typeof this.validateAttributes[method] === "undefined") throw `Rule ${rule} is not valid`;
|
|
2366
2572
|
if (!validatable) return;
|
|
2367
|
-
const validation = this.validateAttributes[method](value, parameters, attribute);
|
|
2573
|
+
const validation = this.validateAttributes[method](value, parameters, attribute, this.getPrimaryAttribute(attribute));
|
|
2368
2574
|
if (validation instanceof Promise) return validation.then((result) => {
|
|
2369
2575
|
if (!result) this.addFailure(attribute, rule, value, parameters);
|
|
2370
2576
|
});
|
|
@@ -3129,8 +3335,10 @@ var Validator = class Validator {
|
|
|
3129
3335
|
*/
|
|
3130
3336
|
validatedData() {
|
|
3131
3337
|
const validKeys = Object.keys(this.rules);
|
|
3338
|
+
const excluded = new Set(this.instance?.getExcludedAttributes() ?? []);
|
|
3132
3339
|
const clean = {};
|
|
3133
3340
|
for (const key of validKeys) {
|
|
3341
|
+
if (excluded.has(key)) continue;
|
|
3134
3342
|
const value = deepFind(this.data, key);
|
|
3135
3343
|
const resolvedValue = typeof value !== "undefined" ? value : deepFind(this.getContext().requestFiles ?? {}, key);
|
|
3136
3344
|
if (typeof resolvedValue !== "undefined") deepSet(clean, key, resolvedValue);
|
|
@@ -3141,7 +3349,8 @@ var Validator = class Validator {
|
|
|
3141
3349
|
* Return all validated input.
|
|
3142
3350
|
*/
|
|
3143
3351
|
validated() {
|
|
3144
|
-
|
|
3352
|
+
const excluded = new Set(this.instance?.getExcludedAttributes() ?? []);
|
|
3353
|
+
return Object.fromEntries(Object.entries(this.data).filter(([key]) => key in this.rules && !excluded.has(key)));
|
|
3145
3354
|
}
|
|
3146
3355
|
/**
|
|
3147
3356
|
* Return a portion of validated input
|
package/dist/index.d.ts
CHANGED
|
@@ -127,8 +127,8 @@ type LiteralUnion<T extends U, U = string> = T | (U & Record<never, never>);
|
|
|
127
127
|
type PluginValidationRuleNameMap = ValidationRuleAutocompleteMap & CustomValidationRuleNameMap;
|
|
128
128
|
type CustomParamableValidationRuleName = Extract<{ [K in keyof PluginValidationRuleNameMap]: PluginValidationRuleNameMap[K] extends 'paramable' ? K : never }[keyof PluginValidationRuleNameMap], string>;
|
|
129
129
|
type CustomPlainRuleName = Extract<{ [K in keyof PluginValidationRuleNameMap]: PluginValidationRuleNameMap[K] extends 'plain' ? K : never }[keyof PluginValidationRuleNameMap], string>;
|
|
130
|
-
type ParamableValidationRuleName = 'accepted_if' | 'after' | 'after_or_equal' | 'before' | 'before_or_equal' | 'between' | 'date_equals' | 'datetime' | 'declined_if' | 'digits_between' | 'different' | 'exists' | 'ends_with' | 'gt' | 'gte' | 'in' | 'includes' | 'lt' | 'lte' | 'max' | 'min' | 'not_in' | 'not_includes' | 'required_if' | 'required_unless' | 'required_with' | 'required_with_all' | 'required_without' | 'required_without_all' | 'same' | 'size' | 'starts_with' | 'unique' | CustomParamableValidationRuleName;
|
|
131
|
-
type PlainRuleName = 'accepted' | 'alpha' | 'alpha_dash' | 'alpha_num' | 'array' | 'array_unique' | 'bail' | 'boolean' | 'confirmed' | 'date' | 'declined' | 'digits' | 'email' | 'integer' | 'json' | 'not_regex' | 'nullable' | 'numeric' | 'object' | 'present' | 'regex' | 'required' | 'sometimes' | 'string' | 'url' | 'hex' | 'uuid' | CustomPlainRuleName;
|
|
130
|
+
type ParamableValidationRuleName = 'accepted_if' | 'after' | 'after_or_equal' | 'before' | 'before_or_equal' | 'between' | 'date_equals' | 'datetime' | 'declined_if' | 'digits_between' | 'different' | 'email' | 'exists' | 'ends_with' | 'gt' | 'gte' | 'in' | 'includes' | 'lt' | 'lte' | 'max' | 'min' | 'not_in' | 'not_includes' | 'multiple_of' | 'prohibited_unless' | 'prohibits' | 'required_if' | 'required_unless' | 'required_with' | 'required_with_all' | 'required_without' | 'required_without_all' | 'same' | 'size' | 'starts_with' | 'unique' | CustomParamableValidationRuleName;
|
|
131
|
+
type PlainRuleName = 'accepted' | 'alpha' | 'alpha_dash' | 'alpha_num' | 'array' | 'array_unique' | 'bail' | 'boolean' | 'confirmed' | 'date' | 'declined' | 'distinct' | 'digits' | 'email' | 'exclude' | 'filled' | 'ip' | 'ipv4' | 'ipv6' | 'integer' | 'json' | 'mac_address' | 'not_regex' | 'nullable' | 'numeric' | 'object' | 'present' | 'presentsame' | 'prohibited' | 'regex' | 'required' | 'sometimes' | 'string' | 'timezone' | 'url' | 'hex' | 'uuid' | CustomPlainRuleName;
|
|
132
132
|
type ValidationRuleName = ParamableValidationRuleName | PlainRuleName;
|
|
133
133
|
type MethodRules = Regex | In | NotIn | RequiredIf;
|
|
134
134
|
type ParamableRuleString = `${ParamableValidationRuleName}:${string}`;
|
|
@@ -308,10 +308,13 @@ interface ReplaceAttributeInterface {
|
|
|
308
308
|
replaceGte: (payload: replaceAttributePayload) => string;
|
|
309
309
|
replaceLte: (payload: replaceAttributePayload) => string;
|
|
310
310
|
replaceRequiredIf: (payload: replaceAttributePayload) => string;
|
|
311
|
+
replaceProhibitedUnless: (payload: replaceAttributePayload) => string;
|
|
312
|
+
replaceProhibits: (payload: replaceAttributePayload) => string;
|
|
311
313
|
replaceStartsWith: (payload: replaceAttributePayload) => string;
|
|
312
314
|
replaceRequiredUnless: (payload: replaceAttributePayload) => string;
|
|
313
315
|
replaceSame: (payload: replaceAttributePayload) => string;
|
|
314
316
|
replaceSize: (payload: replaceAttributePayload) => string;
|
|
317
|
+
replaceMultipleOf: (payload: replaceAttributePayload) => string;
|
|
315
318
|
replaceUnique: (payload: replaceAttributePayload) => string;
|
|
316
319
|
}
|
|
317
320
|
type ValidationCallback = (value: any, fail: (message: string) => void, attribute: string) => void;
|
|
@@ -402,6 +405,7 @@ declare function getValidationSize(value: any, hasNumericRule?: boolean): number
|
|
|
402
405
|
//#endregion
|
|
403
406
|
//#region src/BaseValidator.d.ts
|
|
404
407
|
declare class BaseValidator<D extends GenericObject = GenericObject> {
|
|
408
|
+
private excludedAttributes;
|
|
405
409
|
/**
|
|
406
410
|
* The lang used to return error messages
|
|
407
411
|
*/
|
|
@@ -497,6 +501,7 @@ declare class BaseValidator<D extends GenericObject = GenericObject> {
|
|
|
497
501
|
* @returns
|
|
498
502
|
*/
|
|
499
503
|
errors(): ErrorBag;
|
|
504
|
+
getExcludedAttributes(): string[];
|
|
500
505
|
/**
|
|
501
506
|
* Clear the error messages for the given keys.
|
|
502
507
|
* If no keys are provided, all error messages will be cleared.
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { domainToASCII } from "node:url";
|
|
3
|
+
import { isIP } from "node:net";
|
|
2
4
|
import customParseFormat from "dayjs/plugin/customParseFormat.js";
|
|
3
5
|
import dayjs from "dayjs";
|
|
4
6
|
|
|
@@ -73,10 +75,12 @@ var en_default = {
|
|
|
73
75
|
declined: "The :attribute must be declined.",
|
|
74
76
|
declined_if: "The :attribute must be declined when :other is :value.",
|
|
75
77
|
different: "The :attribute and :other must be different.",
|
|
78
|
+
distinct: "The :attribute field has a duplicate value.",
|
|
76
79
|
digits: "The :attribute must be :digits digits.",
|
|
77
80
|
digits_between: "The :attribute must be between :min and :max digits.",
|
|
78
81
|
email: "The :attribute must be a valid email address.",
|
|
79
82
|
ends_with: "The :attribute must end with one of the following: :values.",
|
|
83
|
+
filled: "The :attribute field must have a value.",
|
|
80
84
|
exists: "The selected :attribute is invalid.",
|
|
81
85
|
gt: {
|
|
82
86
|
number: "The :attribute must be greater than :value.",
|
|
@@ -94,6 +98,9 @@ var en_default = {
|
|
|
94
98
|
in: "The :attribute must be one of the following :values.",
|
|
95
99
|
includes: "The :attribute must include one of the following values: :values.",
|
|
96
100
|
integer: "The :attribute must be an integer.",
|
|
101
|
+
ip: "The :attribute must be a valid IP address.",
|
|
102
|
+
ipv4: "The :attribute must be a valid IPv4 address.",
|
|
103
|
+
ipv6: "The :attribute must be a valid IPv6 address.",
|
|
97
104
|
json: "The :attribute must be a valid JSON string.",
|
|
98
105
|
lt: {
|
|
99
106
|
number: "The :attribute must be less than :value.",
|
|
@@ -119,6 +126,7 @@ var en_default = {
|
|
|
119
126
|
array: "The :attribute must have at least :min items.",
|
|
120
127
|
object: "The :attribute must have at least :min items."
|
|
121
128
|
},
|
|
129
|
+
mac_address: "The :attribute must be a valid MAC address.",
|
|
122
130
|
not_in: "The selected :attribute is invalid.",
|
|
123
131
|
not_regex: "The :attribute format is invalid.",
|
|
124
132
|
not_includes: "The :attribute must not include any of the following values: :values.",
|
|
@@ -137,6 +145,10 @@ var en_default = {
|
|
|
137
145
|
upper_cases: "The :attribute must contain at least :amount uppercase letters."
|
|
138
146
|
},
|
|
139
147
|
present: "The :attribute field must be present.",
|
|
148
|
+
presentsame: "The :attribute field must be present.",
|
|
149
|
+
prohibited: "The :attribute field is prohibited.",
|
|
150
|
+
prohibited_unless: "The :attribute field is prohibited unless :other is in :values.",
|
|
151
|
+
prohibits: "The :attribute field prohibits :values from being present.",
|
|
140
152
|
regex: "The :attribute format is invalid.",
|
|
141
153
|
required: "The :attribute field is required.",
|
|
142
154
|
required_if: "The :attribute field is required when :other is :value.",
|
|
@@ -154,8 +166,10 @@ var en_default = {
|
|
|
154
166
|
object: "The :attribute must contain :size items."
|
|
155
167
|
},
|
|
156
168
|
string: "The :attribute must be a string.",
|
|
169
|
+
timezone: "The :attribute must be a valid timezone.",
|
|
157
170
|
unique: "The :attribute has already been taken.",
|
|
158
|
-
url: "The :attribute must have a valid URL format."
|
|
171
|
+
url: "The :attribute must have a valid URL format.",
|
|
172
|
+
multiple_of: "The :attribute must be a multiple of :value."
|
|
159
173
|
};
|
|
160
174
|
|
|
161
175
|
//#endregion
|
|
@@ -624,6 +638,12 @@ const replaceAttributes = {
|
|
|
624
638
|
};
|
|
625
639
|
return message.replace(/:other|:value/gi, (matched) => values[matched]);
|
|
626
640
|
},
|
|
641
|
+
replaceProhibitedUnless: function(payload) {
|
|
642
|
+
return this.replaceRequiredUnless(payload);
|
|
643
|
+
},
|
|
644
|
+
replaceProhibits: function({ message, parameters, getDisplayableAttribute }) {
|
|
645
|
+
return message.replace(":values", parameters.map((attribute) => getDisplayableAttribute(attribute)).join(", "));
|
|
646
|
+
},
|
|
627
647
|
replaceRequiredUnless: function({ message, parameters, getDisplayableAttribute }) {
|
|
628
648
|
const [other] = parameters;
|
|
629
649
|
const values = {
|
|
@@ -638,40 +658,14 @@ const replaceAttributes = {
|
|
|
638
658
|
replaceSize: function({ message, parameters }) {
|
|
639
659
|
return message.replace(":size", parameters[0]);
|
|
640
660
|
},
|
|
661
|
+
replaceMultipleOf: function({ message, parameters }) {
|
|
662
|
+
return message.replace(":value", parameters[0]);
|
|
663
|
+
},
|
|
641
664
|
replaceUnique: function({ message, parameters, data }) {
|
|
642
665
|
return message.replace(":value", data[parameters[1]]);
|
|
643
666
|
}
|
|
644
667
|
};
|
|
645
668
|
|
|
646
|
-
//#endregion
|
|
647
|
-
//#region src/Rules/closureValidationRule.ts
|
|
648
|
-
var ClosureValidationRule = class extends IRuleContract {
|
|
649
|
-
/**
|
|
650
|
-
* The callback that validates the attribute
|
|
651
|
-
*/
|
|
652
|
-
callback;
|
|
653
|
-
/**
|
|
654
|
-
* Indicates if the validation callback failed.
|
|
655
|
-
*/
|
|
656
|
-
failed = false;
|
|
657
|
-
constructor(callback) {
|
|
658
|
-
super();
|
|
659
|
-
this.callback = callback;
|
|
660
|
-
}
|
|
661
|
-
/**
|
|
662
|
-
* Determine if the validation rule passes.
|
|
663
|
-
*/
|
|
664
|
-
passes(value, attribute) {
|
|
665
|
-
this.failed = false;
|
|
666
|
-
const result = this.callback(value, (message) => {
|
|
667
|
-
this.failed = true;
|
|
668
|
-
this.message = message;
|
|
669
|
-
}, attribute);
|
|
670
|
-
if (result instanceof Promise) return result.then(() => !this.failed);
|
|
671
|
-
return !this.failed;
|
|
672
|
-
}
|
|
673
|
-
};
|
|
674
|
-
|
|
675
669
|
//#endregion
|
|
676
670
|
//#region src/validators/validationData.ts
|
|
677
671
|
const validationData = {
|
|
@@ -712,6 +706,35 @@ const validationData = {
|
|
|
712
706
|
}
|
|
713
707
|
};
|
|
714
708
|
|
|
709
|
+
//#endregion
|
|
710
|
+
//#region src/Rules/closureValidationRule.ts
|
|
711
|
+
var ClosureValidationRule = class extends IRuleContract {
|
|
712
|
+
/**
|
|
713
|
+
* The callback that validates the attribute
|
|
714
|
+
*/
|
|
715
|
+
callback;
|
|
716
|
+
/**
|
|
717
|
+
* Indicates if the validation callback failed.
|
|
718
|
+
*/
|
|
719
|
+
failed = false;
|
|
720
|
+
constructor(callback) {
|
|
721
|
+
super();
|
|
722
|
+
this.callback = callback;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Determine if the validation rule passes.
|
|
726
|
+
*/
|
|
727
|
+
passes(value, attribute) {
|
|
728
|
+
this.failed = false;
|
|
729
|
+
const result = this.callback(value, (message) => {
|
|
730
|
+
this.failed = true;
|
|
731
|
+
this.message = message;
|
|
732
|
+
}, attribute);
|
|
733
|
+
if (result instanceof Promise) return result.then(() => !this.failed);
|
|
734
|
+
return !this.failed;
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
|
|
715
738
|
//#endregion
|
|
716
739
|
//#region src/validators/validationRuleParser.ts
|
|
717
740
|
const validationRuleParser = {
|
|
@@ -959,7 +982,7 @@ var validateAttributes = class {
|
|
|
959
982
|
*/
|
|
960
983
|
validateDeclinedIf(value, parameters) {
|
|
961
984
|
this.requireParameterCount(2, parameters, "declined_if");
|
|
962
|
-
const other =
|
|
985
|
+
const other = this.getAttributeValue(parameters[0]);
|
|
963
986
|
if (!other) return true;
|
|
964
987
|
if (parameters.slice(1).indexOf(other) !== -1) return this.validateDeclined(value);
|
|
965
988
|
return true;
|
|
@@ -969,7 +992,7 @@ var validateAttributes = class {
|
|
|
969
992
|
*/
|
|
970
993
|
validateDifferent(value, parameters) {
|
|
971
994
|
this.requireParameterCount(1, parameters, "different");
|
|
972
|
-
const other =
|
|
995
|
+
const other = this.getAttributeValue(parameters[0]);
|
|
973
996
|
if (!sameType(value, other)) return true;
|
|
974
997
|
if (value !== null && typeof value === "object") return !deepEqual(value, other);
|
|
975
998
|
return value !== other;
|
|
@@ -1004,13 +1027,23 @@ var validateAttributes = class {
|
|
|
1004
1027
|
/**
|
|
1005
1028
|
* Validate that an attribute is a valid email address.
|
|
1006
1029
|
*/
|
|
1007
|
-
validateEmail(value) {
|
|
1030
|
+
validateEmail(value, parameters = []) {
|
|
1008
1031
|
if (typeof value !== "string") return false;
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1032
|
+
const normalized = parameters.map((parameter) => parameter.replace(/[{}]/g, "").toLowerCase());
|
|
1033
|
+
if (normalized.length === 0 || normalized.includes("filter") || normalized.includes("rfc")) {
|
|
1034
|
+
if (!this.isEmailByFilter(value)) return false;
|
|
1035
|
+
}
|
|
1036
|
+
if (normalized.includes("strict") && !this.isStrictEmail(value)) return false;
|
|
1037
|
+
if (normalized.includes("dns") && !this.hasResolvableEmailDomain(value)) return false;
|
|
1038
|
+
if (normalized.includes("spoof") && !this.isSpoofSafeEmail(value)) return false;
|
|
1039
|
+
return true;
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Validate that a present attribute is not empty.
|
|
1043
|
+
*/
|
|
1044
|
+
validateFilled(value) {
|
|
1045
|
+
if (typeof value === "undefined") return true;
|
|
1046
|
+
return this.validateRequired(value);
|
|
1014
1047
|
}
|
|
1015
1048
|
/**
|
|
1016
1049
|
* Validate the attribute ends with a given substring.
|
|
@@ -1070,7 +1103,7 @@ var validateAttributes = class {
|
|
|
1070
1103
|
*/
|
|
1071
1104
|
validateRequiredIf(value, parameters) {
|
|
1072
1105
|
this.requireParameterCount(2, parameters, "required_if");
|
|
1073
|
-
const other =
|
|
1106
|
+
const other = this.getAttributeValue(parameters[0]);
|
|
1074
1107
|
if (typeof other === "undefined") return true;
|
|
1075
1108
|
if (this.parseDependentRuleParameters(other, parameters).indexOf(other) !== -1) return this.validateRequired(value);
|
|
1076
1109
|
return true;
|
|
@@ -1080,7 +1113,7 @@ var validateAttributes = class {
|
|
|
1080
1113
|
*/
|
|
1081
1114
|
validateRequiredUnless(value, parameters) {
|
|
1082
1115
|
this.requireParameterCount(2, parameters, "required_unless");
|
|
1083
|
-
let other =
|
|
1116
|
+
let other = this.getAttributeValue(parameters[0]);
|
|
1084
1117
|
other = typeof other === "undefined" ? null : other;
|
|
1085
1118
|
if (this.parseDependentRuleParameters(other, parameters).indexOf(other) === -1) return this.validateRequired(value);
|
|
1086
1119
|
return true;
|
|
@@ -1117,14 +1150,14 @@ var validateAttributes = class {
|
|
|
1117
1150
|
* Determine if any of the given attributes fail the required test.
|
|
1118
1151
|
*/
|
|
1119
1152
|
anyFailingRequired(attributes) {
|
|
1120
|
-
for (let i = 0; i < attributes.length; i++) if (!this.validateRequired(
|
|
1153
|
+
for (let i = 0; i < attributes.length; i++) if (!this.validateRequired(this.getAttributeValue(attributes[i]))) return true;
|
|
1121
1154
|
return false;
|
|
1122
1155
|
}
|
|
1123
1156
|
/**
|
|
1124
1157
|
* Determine if all of the given attributes fail the required test.
|
|
1125
1158
|
*/
|
|
1126
1159
|
allFailingRequired(attributes) {
|
|
1127
|
-
for (let i = 0; i < attributes.length; i++) if (this.validateRequired(
|
|
1160
|
+
for (let i = 0; i < attributes.length; i++) if (this.validateRequired(this.getAttributeValue(attributes[i]))) return false;
|
|
1128
1161
|
return true;
|
|
1129
1162
|
}
|
|
1130
1163
|
/**
|
|
@@ -1166,7 +1199,13 @@ var validateAttributes = class {
|
|
|
1166
1199
|
* Validate that an attribute exists even if not filled.
|
|
1167
1200
|
*/
|
|
1168
1201
|
validatePresent(value, parameters, attribute) {
|
|
1169
|
-
return typeof
|
|
1202
|
+
return typeof this.getAttributeValue(attribute) !== "undefined";
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Alias of the present rule kept for compatibility with requested rule naming.
|
|
1206
|
+
*/
|
|
1207
|
+
validatePresentsame(value, parameters, attribute) {
|
|
1208
|
+
return this.validatePresent(value, parameters, attribute);
|
|
1170
1209
|
}
|
|
1171
1210
|
/**
|
|
1172
1211
|
* Validate that an attribute is an integer.
|
|
@@ -1188,12 +1227,103 @@ var validateAttributes = class {
|
|
|
1188
1227
|
return true;
|
|
1189
1228
|
}
|
|
1190
1229
|
/**
|
|
1230
|
+
* Validate that an attribute is prohibited.
|
|
1231
|
+
*/
|
|
1232
|
+
validateProhibited(value) {
|
|
1233
|
+
return !this.validateRequired(value);
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Validate that an attribute is prohibited unless another field matches one of the given values.
|
|
1237
|
+
*/
|
|
1238
|
+
validateProhibitedUnless(value, parameters) {
|
|
1239
|
+
this.requireParameterCount(2, parameters, "prohibited_unless");
|
|
1240
|
+
let other = this.getAttributeValue(parameters[0]);
|
|
1241
|
+
other = typeof other === "undefined" ? null : other;
|
|
1242
|
+
if (this.parseDependentRuleParameters(other, parameters).indexOf(other) !== -1) return true;
|
|
1243
|
+
return this.validateProhibited(value);
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Validate that present attributes prohibit other attributes from being present.
|
|
1247
|
+
*/
|
|
1248
|
+
validateProhibits(value, parameters) {
|
|
1249
|
+
this.requireParameterCount(1, parameters, "prohibits");
|
|
1250
|
+
if (!this.validateRequired(value)) return true;
|
|
1251
|
+
return parameters.every((attribute) => this.validateProhibited(this.getAttributeValue(attribute)));
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Validate that an attribute is a valid IP address.
|
|
1255
|
+
*/
|
|
1256
|
+
validateIp(value) {
|
|
1257
|
+
return typeof value === "string" && isIP(value) !== 0;
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Validate that an attribute is a valid IPv4 address.
|
|
1261
|
+
*/
|
|
1262
|
+
validateIpv4(value) {
|
|
1263
|
+
return typeof value === "string" && isIP(value) === 4;
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Validate that an attribute is a valid IPv6 address.
|
|
1267
|
+
*/
|
|
1268
|
+
validateIpv6(value) {
|
|
1269
|
+
return typeof value === "string" && isIP(value) === 6;
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Validate that an attribute is a valid MAC address.
|
|
1273
|
+
*/
|
|
1274
|
+
validateMacAddress(value) {
|
|
1275
|
+
return typeof value === "string" && /^([0-9a-f]{2}[:-]){5}[0-9a-f]{2}$/i.test(value);
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Validate that an attribute is a valid timezone identifier.
|
|
1279
|
+
*/
|
|
1280
|
+
validateTimezone(value) {
|
|
1281
|
+
if (typeof value !== "string" || value.length === 0) return false;
|
|
1282
|
+
if (typeof Intl.supportedValuesOf === "function" && Intl.supportedValuesOf("timeZone").includes(value)) return true;
|
|
1283
|
+
try {
|
|
1284
|
+
new Intl.DateTimeFormat("en-US", { timeZone: value });
|
|
1285
|
+
return true;
|
|
1286
|
+
} catch {
|
|
1287
|
+
return false;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Validate that an attribute is a multiple of the given value.
|
|
1292
|
+
*/
|
|
1293
|
+
validateMultipleOf(value, parameters) {
|
|
1294
|
+
this.requireParameterCount(1, parameters, "multiple_of");
|
|
1295
|
+
const numericValue = Number(value);
|
|
1296
|
+
const divisor = Number(parameters[0]);
|
|
1297
|
+
if (!Number.isFinite(numericValue) || !Number.isFinite(divisor) || divisor === 0) return false;
|
|
1298
|
+
const scale = 10 ** Math.max(this.getDecimalPlaces(value), this.getDecimalPlaces(parameters[0]));
|
|
1299
|
+
const scaledValue = Math.round(numericValue * scale);
|
|
1300
|
+
const scaledDivisor = Math.round(divisor * scale);
|
|
1301
|
+
return scaledDivisor !== 0 && scaledValue % scaledDivisor === 0;
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Validate that values matched by a wildcard attribute are distinct.
|
|
1305
|
+
*/
|
|
1306
|
+
validateDistinct(value, parameters, attribute, primaryAttribute = attribute) {
|
|
1307
|
+
if (Array.isArray(value)) return new Set(value.map((entry) => this.normalizeDistinctValue(entry, parameters))).size === value.length;
|
|
1308
|
+
if (primaryAttribute.indexOf("*") === -1) return true;
|
|
1309
|
+
const strict = parameters.includes("strict");
|
|
1310
|
+
const values = this.getDistinctValues(primaryAttribute);
|
|
1311
|
+
const current = this.normalizeDistinctValue(value, parameters);
|
|
1312
|
+
let matches = 0;
|
|
1313
|
+
for (const candidate of values) {
|
|
1314
|
+
const normalized = this.normalizeDistinctValue(candidate, parameters);
|
|
1315
|
+
if (strict ? normalized === current : normalized == current) matches += 1;
|
|
1316
|
+
if (matches > 1) return false;
|
|
1317
|
+
}
|
|
1318
|
+
return true;
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1191
1321
|
* Validate that an attribute is greater than another attribute.
|
|
1192
1322
|
*/
|
|
1193
1323
|
validateGt(value, parameters, attribute) {
|
|
1194
1324
|
this.requireParameterCount(1, parameters, "gt");
|
|
1195
1325
|
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1196
|
-
const compartedToValue =
|
|
1326
|
+
const compartedToValue = this.getAttributeValue(parameters[0]) || parameters[0];
|
|
1197
1327
|
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) > compartedToValue;
|
|
1198
1328
|
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1199
1329
|
return getSize(value) > getSize(compartedToValue);
|
|
@@ -1204,7 +1334,7 @@ var validateAttributes = class {
|
|
|
1204
1334
|
validateGte(value, parameters, attribute) {
|
|
1205
1335
|
this.requireParameterCount(1, parameters, "gte");
|
|
1206
1336
|
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1207
|
-
const compartedToValue =
|
|
1337
|
+
const compartedToValue = this.getAttributeValue(parameters[0]) || parameters[0];
|
|
1208
1338
|
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) >= compartedToValue;
|
|
1209
1339
|
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1210
1340
|
return getSize(value) >= getSize(compartedToValue);
|
|
@@ -1215,7 +1345,7 @@ var validateAttributes = class {
|
|
|
1215
1345
|
validateLt(value, parameters, attribute) {
|
|
1216
1346
|
this.requireParameterCount(1, parameters, "lt");
|
|
1217
1347
|
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1218
|
-
const compartedToValue =
|
|
1348
|
+
const compartedToValue = this.getAttributeValue(parameters[0]) || parameters[0];
|
|
1219
1349
|
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) < compartedToValue;
|
|
1220
1350
|
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1221
1351
|
return getSize(value) < getSize(compartedToValue);
|
|
@@ -1226,7 +1356,7 @@ var validateAttributes = class {
|
|
|
1226
1356
|
validateLte(value, parameters, attribute) {
|
|
1227
1357
|
this.requireParameterCount(1, parameters, "lte");
|
|
1228
1358
|
if (typeof value !== "number" && typeof value !== "string" && typeof value !== "object") throw "The field under validation must be a number, string, array or object";
|
|
1229
|
-
const compartedToValue =
|
|
1359
|
+
const compartedToValue = this.getAttributeValue(parameters[0]) || parameters[0];
|
|
1230
1360
|
if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) return getSize(value, validationRuleParser.hasRule(attribute, getNumericRules(), this.rules)) <= compartedToValue;
|
|
1231
1361
|
if (sameType(value, compartedToValue) === false) throw "The fields under validation must be of the same type";
|
|
1232
1362
|
return getSize(value) <= getSize(compartedToValue);
|
|
@@ -1284,7 +1414,7 @@ var validateAttributes = class {
|
|
|
1284
1414
|
compareDates(value, parameter, operator, rule) {
|
|
1285
1415
|
value = toDate(value);
|
|
1286
1416
|
if (!value) throw `Validation rule ${rule} requires the field under valation to be a date.`;
|
|
1287
|
-
const compartedToValue = toDate(
|
|
1417
|
+
const compartedToValue = toDate(this.getAttributeValue(parameter) || parameter);
|
|
1288
1418
|
if (!compartedToValue) throw `Validation rule ${rule} requires the parameter to be a date.`;
|
|
1289
1419
|
return compare(value.getTime(), compartedToValue.getTime(), operator);
|
|
1290
1420
|
}
|
|
@@ -1296,6 +1426,10 @@ var validateAttributes = class {
|
|
|
1296
1426
|
}
|
|
1297
1427
|
/**
|
|
1298
1428
|
* Prepare the values for validation
|
|
1429
|
+
*
|
|
1430
|
+
* @param other The value to compare against
|
|
1431
|
+
* @param parameters The parameters for the validation rule
|
|
1432
|
+
* @returns The prepared values for validation
|
|
1299
1433
|
*/
|
|
1300
1434
|
parseDependentRuleParameters(other, parameters) {
|
|
1301
1435
|
let values = parameters.slice(1);
|
|
@@ -1304,6 +1438,55 @@ var validateAttributes = class {
|
|
|
1304
1438
|
if (typeof other === "boolean") values = convertValuesToBoolean(values);
|
|
1305
1439
|
return values;
|
|
1306
1440
|
}
|
|
1441
|
+
getAttributeValue(attribute) {
|
|
1442
|
+
const dataValue = deepFind(this.data, attribute);
|
|
1443
|
+
if (typeof dataValue !== "undefined") return dataValue;
|
|
1444
|
+
return deepFind(this.context.requestFiles ?? {}, attribute);
|
|
1445
|
+
}
|
|
1446
|
+
getDistinctValues(primaryAttribute) {
|
|
1447
|
+
const gathered = validationData.initializeAndGatherData(primaryAttribute, this.data);
|
|
1448
|
+
const pattern = new RegExp(`^${primaryAttribute.replace(/\./g, "\\.").replace(/\*/g, "[^.]+")}$`);
|
|
1449
|
+
return Object.keys(gathered).filter((attribute) => pattern.test(attribute)).map((attribute) => deepFind(this.data, attribute)).filter((value) => typeof value !== "undefined");
|
|
1450
|
+
}
|
|
1451
|
+
normalizeDistinctValue(value, parameters) {
|
|
1452
|
+
if (parameters.includes("ignore_case") && typeof value === "string") return value.toLowerCase();
|
|
1453
|
+
return value;
|
|
1454
|
+
}
|
|
1455
|
+
isEmailByFilter(value) {
|
|
1456
|
+
return value.toLowerCase().match(/^[^\s@]+@[^\s@]+\.[^\s@]{2,24}$/) !== null;
|
|
1457
|
+
}
|
|
1458
|
+
isStrictEmail(value) {
|
|
1459
|
+
if (!this.isEmailByFilter(value)) return false;
|
|
1460
|
+
const [localPart, domain] = value.split("@");
|
|
1461
|
+
if (!localPart || !domain || localPart.length > 64 || domain.length > 255) return false;
|
|
1462
|
+
if (localPart.startsWith(".") || localPart.endsWith(".") || localPart.includes("..")) return false;
|
|
1463
|
+
return domain.split(".").every((label) => {
|
|
1464
|
+
return label.length > 0 && label.length <= 63 && /^[a-z0-9-]+$/i.test(label) && !label.startsWith("-") && !label.endsWith("-");
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
hasResolvableEmailDomain(value) {
|
|
1468
|
+
if (!this.isStrictEmail(value)) return false;
|
|
1469
|
+
const domain = value.split("@")[1];
|
|
1470
|
+
const asciiDomain = domainToASCII(domain);
|
|
1471
|
+
return asciiDomain.length > 0 && asciiDomain.includes(".");
|
|
1472
|
+
}
|
|
1473
|
+
isSpoofSafeEmail(value) {
|
|
1474
|
+
if (!this.isEmailByFilter(value)) return false;
|
|
1475
|
+
const [localPart, domain] = value.split("@");
|
|
1476
|
+
return domainToASCII(domain).length > 0 && !this.hasAsciiControlCharacters(value) && localPart.normalize("NFKC") === localPart && domain.normalize("NFKC") === domain;
|
|
1477
|
+
}
|
|
1478
|
+
hasAsciiControlCharacters(value) {
|
|
1479
|
+
for (let index = 0; index < value.length; index++) {
|
|
1480
|
+
const codePoint = value.charCodeAt(index);
|
|
1481
|
+
if (codePoint <= 31 || codePoint === 127) return true;
|
|
1482
|
+
}
|
|
1483
|
+
return false;
|
|
1484
|
+
}
|
|
1485
|
+
getDecimalPlaces(value) {
|
|
1486
|
+
const stringValue = String(value).toLowerCase();
|
|
1487
|
+
if (!stringValue.includes(".")) return 0;
|
|
1488
|
+
return stringValue.split(".")[1]?.length ?? 0;
|
|
1489
|
+
}
|
|
1307
1490
|
};
|
|
1308
1491
|
|
|
1309
1492
|
//#endregion
|
|
@@ -1372,6 +1555,7 @@ const implicitRues = [
|
|
|
1372
1555
|
"declined_if",
|
|
1373
1556
|
"filled",
|
|
1374
1557
|
"present",
|
|
1558
|
+
"presentsame",
|
|
1375
1559
|
"required",
|
|
1376
1560
|
"required_if",
|
|
1377
1561
|
"required_unless",
|
|
@@ -1853,11 +2037,12 @@ var Password$1 = class Password$1 extends IRuleContract {
|
|
|
1853
2037
|
* Determine if the validation rule passes.
|
|
1854
2038
|
*/
|
|
1855
2039
|
passes(value, attribute) {
|
|
1856
|
-
const
|
|
2040
|
+
const passwordRules = [
|
|
1857
2041
|
"string",
|
|
1858
2042
|
`min:${this.minLength}`,
|
|
1859
2043
|
...this.customRules
|
|
1860
|
-
]
|
|
2044
|
+
];
|
|
2045
|
+
const validator = new BaseValidator(this.data, { [attribute]: passwordRules }, this.validator.customMessages, this.validator.customAttributes).setLang(this.lang);
|
|
1861
2046
|
if (!validator.validate()) {
|
|
1862
2047
|
const errors = validator.errors().addErrorTypes().get(attribute);
|
|
1863
2048
|
for (const key in errors) this.validator.errors().add(attribute, errors[key]);
|
|
@@ -2000,6 +2185,7 @@ var replaceAttributePayload = class {
|
|
|
2000
2185
|
//#endregion
|
|
2001
2186
|
//#region src/BaseValidator.ts
|
|
2002
2187
|
var BaseValidator = class BaseValidator {
|
|
2188
|
+
excludedAttributes = /* @__PURE__ */ new Set();
|
|
2003
2189
|
/**
|
|
2004
2190
|
* The lang used to return error messages
|
|
2005
2191
|
*/
|
|
@@ -2084,6 +2270,7 @@ var BaseValidator = class BaseValidator {
|
|
|
2084
2270
|
*/
|
|
2085
2271
|
withContext(context = {}) {
|
|
2086
2272
|
this.context = context;
|
|
2273
|
+
this.addRules(this.initalRules);
|
|
2087
2274
|
return this;
|
|
2088
2275
|
}
|
|
2089
2276
|
/**
|
|
@@ -2144,6 +2331,9 @@ var BaseValidator = class BaseValidator {
|
|
|
2144
2331
|
errors() {
|
|
2145
2332
|
return this.messages;
|
|
2146
2333
|
}
|
|
2334
|
+
getExcludedAttributes() {
|
|
2335
|
+
return [...this.excludedAttributes];
|
|
2336
|
+
}
|
|
2147
2337
|
/**
|
|
2148
2338
|
* Clear the error messages for the given keys.
|
|
2149
2339
|
* If no keys are provided, all error messages will be cleared.
|
|
@@ -2260,6 +2450,7 @@ var BaseValidator = class BaseValidator {
|
|
|
2260
2450
|
runAllValidations() {
|
|
2261
2451
|
this.messages = new ErrorBag();
|
|
2262
2452
|
this.validateAttributes = new validateAttributes(this.data, this.rules, this.getContext());
|
|
2453
|
+
this.excludedAttributes.clear();
|
|
2263
2454
|
for (const property in this.rules) if (this.runValidation(property) === false) break;
|
|
2264
2455
|
}
|
|
2265
2456
|
/**
|
|
@@ -2268,6 +2459,7 @@ var BaseValidator = class BaseValidator {
|
|
|
2268
2459
|
async runAllValidationsAsync() {
|
|
2269
2460
|
this.messages = new ErrorBag();
|
|
2270
2461
|
this.validateAttributes = new validateAttributes(this.data, this.rules, this.getContext());
|
|
2462
|
+
this.excludedAttributes.clear();
|
|
2271
2463
|
for (const property in this.rules) if (await this.runValidationAsync(property) === false) break;
|
|
2272
2464
|
}
|
|
2273
2465
|
/**
|
|
@@ -2290,33 +2482,47 @@ var BaseValidator = class BaseValidator {
|
|
|
2290
2482
|
* Run validation rules for the specified property and stop validation if needed
|
|
2291
2483
|
*/
|
|
2292
2484
|
runValidation(property) {
|
|
2293
|
-
if (Object.prototype.hasOwnProperty.call(this.rules, property) && Array.isArray(this.rules[property]))
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2485
|
+
if (Object.prototype.hasOwnProperty.call(this.rules, property) && Array.isArray(this.rules[property])) {
|
|
2486
|
+
if (validationRuleParser.hasRule(property, ["exclude"], this.rules)) {
|
|
2487
|
+
this.excludedAttributes.add(property);
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2490
|
+
for (let i = 0; i < this.rules[property].length; i++) {
|
|
2491
|
+
this.validateAttribute(property, this.rules[property][i]);
|
|
2492
|
+
if (this.messages.keys().length > 0 && this.stopOnFirstFailureFlag === true) return false;
|
|
2493
|
+
if (this.shouldStopValidating(property)) break;
|
|
2494
|
+
}
|
|
2297
2495
|
}
|
|
2298
2496
|
}
|
|
2299
2497
|
/**
|
|
2300
2498
|
* Run validation rules for the specified property asynchronously and stop validation if needed
|
|
2301
2499
|
*/
|
|
2302
2500
|
async runValidationAsync(property) {
|
|
2303
|
-
if (Object.prototype.hasOwnProperty.call(this.rules, property) && Array.isArray(this.rules[property]))
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2501
|
+
if (Object.prototype.hasOwnProperty.call(this.rules, property) && Array.isArray(this.rules[property])) {
|
|
2502
|
+
if (validationRuleParser.hasRule(property, ["exclude"], this.rules)) {
|
|
2503
|
+
this.excludedAttributes.add(property);
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2506
|
+
for (let i = 0; i < this.rules[property].length; i++) {
|
|
2507
|
+
await this.validateAttribute(property, this.rules[property][i]);
|
|
2508
|
+
if (this.messages.keys().length > 0 && this.stopOnFirstFailureFlag === true) return false;
|
|
2509
|
+
if (this.shouldStopValidating(property)) break;
|
|
2510
|
+
}
|
|
2307
2511
|
}
|
|
2308
2512
|
}
|
|
2309
2513
|
/**
|
|
2310
2514
|
* Check if we should stop further validations on a given attribute.
|
|
2311
2515
|
*/
|
|
2312
2516
|
shouldStopValidating(attribute) {
|
|
2517
|
+
if (this.excludedAttributes.has(attribute)) return true;
|
|
2313
2518
|
return this.messages.has(attribute) && validationRuleParser.hasRule(attribute, ["bail"], this.rules);
|
|
2314
2519
|
}
|
|
2315
2520
|
/**
|
|
2316
2521
|
* Parse the given rules add assign them to the current rules
|
|
2317
2522
|
*/
|
|
2318
2523
|
addRules(rules) {
|
|
2319
|
-
const
|
|
2524
|
+
const availableData = mergeDeep(this.getContext().requestFiles ?? {}, this.data);
|
|
2525
|
+
const response = validationRuleParser.explodeRules(dotify(rules, true), availableData);
|
|
2320
2526
|
this.rules = response.rules;
|
|
2321
2527
|
this.implicitAttributes = response.implicitAttributes;
|
|
2322
2528
|
}
|
|
@@ -2334,7 +2540,7 @@ var BaseValidator = class BaseValidator {
|
|
|
2334
2540
|
const method = `validate${buildValidationMethodName(rule)}`;
|
|
2335
2541
|
if (rule !== "" && typeof this.validateAttributes[method] === "undefined") throw `Rule ${rule} is not valid`;
|
|
2336
2542
|
if (!validatable) return;
|
|
2337
|
-
const validation = this.validateAttributes[method](value, parameters, attribute);
|
|
2543
|
+
const validation = this.validateAttributes[method](value, parameters, attribute, this.getPrimaryAttribute(attribute));
|
|
2338
2544
|
if (validation instanceof Promise) return validation.then((result) => {
|
|
2339
2545
|
if (!result) this.addFailure(attribute, rule, value, parameters);
|
|
2340
2546
|
});
|
|
@@ -3099,8 +3305,10 @@ var Validator = class Validator {
|
|
|
3099
3305
|
*/
|
|
3100
3306
|
validatedData() {
|
|
3101
3307
|
const validKeys = Object.keys(this.rules);
|
|
3308
|
+
const excluded = new Set(this.instance?.getExcludedAttributes() ?? []);
|
|
3102
3309
|
const clean = {};
|
|
3103
3310
|
for (const key of validKeys) {
|
|
3311
|
+
if (excluded.has(key)) continue;
|
|
3104
3312
|
const value = deepFind(this.data, key);
|
|
3105
3313
|
const resolvedValue = typeof value !== "undefined" ? value : deepFind(this.getContext().requestFiles ?? {}, key);
|
|
3106
3314
|
if (typeof resolvedValue !== "undefined") deepSet(clean, key, resolvedValue);
|
|
@@ -3111,7 +3319,8 @@ var Validator = class Validator {
|
|
|
3111
3319
|
* Return all validated input.
|
|
3112
3320
|
*/
|
|
3113
3321
|
validated() {
|
|
3114
|
-
|
|
3322
|
+
const excluded = new Set(this.instance?.getExcludedAttributes() ?? []);
|
|
3323
|
+
return Object.fromEntries(Object.entries(this.data).filter(([key]) => key in this.rules && !excluded.has(key)));
|
|
3115
3324
|
}
|
|
3116
3325
|
/**
|
|
3117
3326
|
* Return a portion of validated input
|