@orieljs/validation 0.1.0-alpha.6 → 0.1.0-alpha.8
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/NOTICE +4 -0
- package/README.md +34 -0
- package/TRADEMARKS.md +33 -0
- package/dist/index.d.ts +214 -45
- package/dist/index.js +1652 -244
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -71,15 +71,17 @@ var FormRequest = class {
|
|
|
71
71
|
/**
|
|
72
72
|
* `const data = await StorePostRequest.validate()` (spec §4.3). Resolves the current request and the
|
|
73
73
|
* ValidationFactory from the active container, runs authorize() first (throwing AuthorizationErrorImpl if it
|
|
74
|
-
* returns false), then validates request.all() against rules()/messages()/attributes() and returns the
|
|
75
|
-
*
|
|
74
|
+
* returns false), then validates request.all() against rules()/messages()/attributes() and returns the validated
|
|
75
|
+
* data (or throws the 422 ValidationError — see ValidatorImpl.validated()). The data is typed from rules(): write
|
|
76
|
+
* it without a return type and with `as const` (`return { title: 'required|string' } as const`), so its rule
|
|
77
|
+
* strings keep their text; a rules() typed `Rules` gives `Record<string, unknown>`.
|
|
76
78
|
*/
|
|
77
79
|
static async validate() {
|
|
78
80
|
const instance = new this();
|
|
79
81
|
if (!await instance.authorize()) throw new AuthorizationErrorImpl();
|
|
80
82
|
const request = inject(HttpRequest);
|
|
81
83
|
const factory = inject(ValidationFactory);
|
|
82
|
-
const validator = factory.make(request.all(), instance.rules(), instance.messages(), instance.attributes());
|
|
84
|
+
const validator = factory.make(request.all(), await instance.rules(), instance.messages(), instance.attributes());
|
|
83
85
|
return validator.validated();
|
|
84
86
|
}
|
|
85
87
|
};
|
|
@@ -87,7 +89,7 @@ var FormRequest = class {
|
|
|
87
89
|
// messages.ts
|
|
88
90
|
import { Str } from "@orieljs/support";
|
|
89
91
|
function defaultAttributeName(key) {
|
|
90
|
-
return key.split(".").map((segment) => Str.snake(segment).replace(/_/g, " ")).join(".");
|
|
92
|
+
return key.split(".").map((segment) => segment === "*" ? segment : Str.snake(segment).replace(/_/g, " ")).join(".");
|
|
91
93
|
}
|
|
92
94
|
function applyReplacements(template, replacements) {
|
|
93
95
|
return Object.entries(replacements).reduce((message, [key, value]) => message.replaceAll(`:${key}`, value), template);
|
|
@@ -100,18 +102,59 @@ var DEFAULT_MESSAGES = {
|
|
|
100
102
|
required_with_all: "The :attribute field is required when :values are present.",
|
|
101
103
|
required_without: "The :attribute field is required when :values is not present.",
|
|
102
104
|
required_without_all: "The :attribute field is required when none of :values are present.",
|
|
105
|
+
required_if_accepted: "The :attribute field is required when :other is accepted.",
|
|
106
|
+
required_if_declined: "The :attribute field is required when :other is declined.",
|
|
107
|
+
present: "The :attribute field must be present.",
|
|
108
|
+
present_if: "The :attribute field must be present when :other is :value.",
|
|
109
|
+
present_unless: "The :attribute field must be present unless :other is :value.",
|
|
110
|
+
present_with: "The :attribute field must be present when :values is present.",
|
|
111
|
+
present_with_all: "The :attribute field must be present when :values are present.",
|
|
112
|
+
filled: "The :attribute field must have a value.",
|
|
113
|
+
missing: "The :attribute field must be missing.",
|
|
114
|
+
missing_if: "The :attribute field must be missing when :other is :value.",
|
|
115
|
+
missing_unless: "The :attribute field must be missing unless :other is :value.",
|
|
116
|
+
missing_with: "The :attribute field must be missing when :values is present.",
|
|
117
|
+
missing_with_all: "The :attribute field must be missing when :values are present.",
|
|
118
|
+
prohibited: "The :attribute field is prohibited.",
|
|
119
|
+
prohibited_if: "The :attribute field is prohibited when :other is :value.",
|
|
120
|
+
prohibited_if_accepted: "The :attribute field is prohibited when :other is accepted.",
|
|
121
|
+
prohibited_if_declined: "The :attribute field is prohibited when :other is declined.",
|
|
122
|
+
prohibited_unless: "The :attribute field is prohibited unless :other is in :values.",
|
|
123
|
+
prohibits: "The :attribute field prohibits :other from being present.",
|
|
103
124
|
string: "The :attribute field must be a string.",
|
|
104
125
|
integer: "The :attribute field must be an integer.",
|
|
105
126
|
numeric: "The :attribute field must be a number.",
|
|
106
127
|
boolean: "The :attribute field must be true or false.",
|
|
107
128
|
array: "The :attribute field must be an array.",
|
|
129
|
+
list: "The :attribute field must be a list.",
|
|
130
|
+
required_array_keys: "The :attribute field must contain entries for: :values.",
|
|
131
|
+
in_array_keys: "The :attribute field must contain at least one of the following keys: :values.",
|
|
132
|
+
contains: "The :attribute field is missing a required value.",
|
|
133
|
+
doesnt_contain: "The :attribute field must not contain any of the following: :values.",
|
|
134
|
+
distinct: "The :attribute field has a duplicate value.",
|
|
135
|
+
in_array: "The :attribute field must exist in :other.",
|
|
108
136
|
email: "The :attribute field must be a valid email address.",
|
|
109
137
|
url: "The :attribute field must be a valid URL.",
|
|
110
138
|
uuid: "The :attribute field must be a valid UUID.",
|
|
139
|
+
ulid: "The :attribute field must be a valid ULID.",
|
|
140
|
+
ascii: "The :attribute field must only contain single-byte alphanumeric characters and symbols.",
|
|
141
|
+
hex_color: "The :attribute field must be a valid hexadecimal color.",
|
|
142
|
+
json: "The :attribute field must be a valid JSON string.",
|
|
143
|
+
ip: "The :attribute field must be a valid IP address.",
|
|
144
|
+
ipv4: "The :attribute field must be a valid IPv4 address.",
|
|
145
|
+
ipv6: "The :attribute field must be a valid IPv6 address.",
|
|
146
|
+
mac_address: "The :attribute field must be a valid MAC address.",
|
|
147
|
+
active_url: "The :attribute field must be a valid URL.",
|
|
148
|
+
timezone: "The :attribute field must be a valid timezone.",
|
|
149
|
+
starts_with: "The :attribute field must start with one of the following: :values.",
|
|
150
|
+
ends_with: "The :attribute field must end with one of the following: :values.",
|
|
151
|
+
doesnt_start_with: "The :attribute field must not start with one of the following: :values.",
|
|
152
|
+
doesnt_end_with: "The :attribute field must not end with one of the following: :values.",
|
|
111
153
|
alpha: "The :attribute field must only contain letters.",
|
|
112
154
|
alpha_num: "The :attribute field must only contain letters and numbers.",
|
|
113
|
-
alpha_dash: "The :attribute field must only contain letters, numbers, dashes and underscores.",
|
|
155
|
+
alpha_dash: "The :attribute field must only contain letters, numbers, dashes, and underscores.",
|
|
114
156
|
regex: "The :attribute field format is invalid.",
|
|
157
|
+
not_regex: "The :attribute field format is invalid.",
|
|
115
158
|
lowercase: "The :attribute field must be lowercase.",
|
|
116
159
|
uppercase: "The :attribute field must be uppercase.",
|
|
117
160
|
current_password: "The password is incorrect.",
|
|
@@ -121,14 +164,27 @@ var DEFAULT_MESSAGES = {
|
|
|
121
164
|
"password.symbols": "The :attribute field must contain at least one symbol.",
|
|
122
165
|
confirmed: "The :attribute field confirmation does not match.",
|
|
123
166
|
same: "The :attribute field must match :other.",
|
|
124
|
-
different: "The :attribute field must be different
|
|
167
|
+
different: "The :attribute field and :other must be different.",
|
|
125
168
|
accepted: "The :attribute field must be accepted.",
|
|
169
|
+
accepted_if: "The :attribute field must be accepted when :other is :value.",
|
|
170
|
+
declined: "The :attribute field must be declined.",
|
|
171
|
+
declined_if: "The :attribute field must be declined when :other is :value.",
|
|
126
172
|
digits: "The :attribute field must be :digits digits.",
|
|
173
|
+
digits_between: "The :attribute field must be between :min and :max digits.",
|
|
174
|
+
max_digits: "The :attribute field must not have more than :max digits.",
|
|
175
|
+
min_digits: "The :attribute field must have at least :min digits.",
|
|
176
|
+
decimal: "The :attribute field must have :decimal decimal places.",
|
|
177
|
+
multiple_of: "The :attribute field must be a multiple of :value.",
|
|
127
178
|
date: "The :attribute field must be a valid date.",
|
|
128
179
|
before: "The :attribute field must be a date before :date.",
|
|
180
|
+
before_or_equal: "The :attribute field must be a date before or equal to :date.",
|
|
129
181
|
after: "The :attribute field must be a date after :date.",
|
|
182
|
+
after_or_equal: "The :attribute field must be a date after or equal to :date.",
|
|
183
|
+
date_equals: "The :attribute field must be a date equal to :date.",
|
|
184
|
+
date_format: "The :attribute field must match the format :format.",
|
|
130
185
|
in: "The selected :attribute is invalid.",
|
|
131
186
|
not_in: "The selected :attribute is invalid.",
|
|
187
|
+
enum: "The selected :attribute is invalid.",
|
|
132
188
|
unique: "The :attribute has already been taken.",
|
|
133
189
|
exists: "The selected :attribute is invalid.",
|
|
134
190
|
file: "The :attribute field must be a file.",
|
|
@@ -152,13 +208,29 @@ var DEFAULT_MESSAGES = {
|
|
|
152
208
|
"size.string": "The :attribute field must be :size characters.",
|
|
153
209
|
"size.numeric": "The :attribute field must be :size.",
|
|
154
210
|
"size.array": "The :attribute field must contain :size items.",
|
|
155
|
-
"size.file": "The :attribute field must be :size kilobytes."
|
|
211
|
+
"size.file": "The :attribute field must be :size kilobytes.",
|
|
212
|
+
"gt.array": "The :attribute field must have more than :value items.",
|
|
213
|
+
"gt.file": "The :attribute field must be greater than :value kilobytes.",
|
|
214
|
+
"gt.numeric": "The :attribute field must be greater than :value.",
|
|
215
|
+
"gt.string": "The :attribute field must be greater than :value characters.",
|
|
216
|
+
"gte.array": "The :attribute field must have :value items or more.",
|
|
217
|
+
"gte.file": "The :attribute field must be greater than or equal to :value kilobytes.",
|
|
218
|
+
"gte.numeric": "The :attribute field must be greater than or equal to :value.",
|
|
219
|
+
"gte.string": "The :attribute field must be greater than or equal to :value characters.",
|
|
220
|
+
"lt.array": "The :attribute field must have less than :value items.",
|
|
221
|
+
"lt.file": "The :attribute field must be less than :value kilobytes.",
|
|
222
|
+
"lt.numeric": "The :attribute field must be less than :value.",
|
|
223
|
+
"lt.string": "The :attribute field must be less than :value characters.",
|
|
224
|
+
"lte.array": "The :attribute field must not have more than :value items.",
|
|
225
|
+
"lte.file": "The :attribute field must be less than or equal to :value kilobytes.",
|
|
226
|
+
"lte.numeric": "The :attribute field must be less than or equal to :value.",
|
|
227
|
+
"lte.string": "The :attribute field must be less than or equal to :value characters."
|
|
156
228
|
};
|
|
157
229
|
|
|
158
230
|
// MessageBag.ts
|
|
159
231
|
var MessageBagImpl = class {
|
|
160
|
-
|
|
161
|
-
|
|
232
|
+
// No prototype: a field named `constructor` or `toString` is a key like any other.
|
|
233
|
+
items = /* @__PURE__ */ Object.create(null);
|
|
162
234
|
add(key, message) {
|
|
163
235
|
;
|
|
164
236
|
(this.items[key] ??= []).push(message);
|
|
@@ -190,6 +262,294 @@ var MessageBagImpl = class {
|
|
|
190
262
|
}
|
|
191
263
|
};
|
|
192
264
|
|
|
265
|
+
// rules/acceptance.ts
|
|
266
|
+
import { Arr as Arr3 } from "@orieljs/support";
|
|
267
|
+
|
|
268
|
+
// parse.ts
|
|
269
|
+
import { Arr } from "@orieljs/support";
|
|
270
|
+
function splitParameters(name, raw) {
|
|
271
|
+
return name === "regex" || name === "not_regex" ? [raw] : raw.split(",");
|
|
272
|
+
}
|
|
273
|
+
function splitOnce(value, separator) {
|
|
274
|
+
const index = value.indexOf(separator);
|
|
275
|
+
return index === -1 ? [value, void 0] : [value.slice(0, index), value.slice(index + 1)];
|
|
276
|
+
}
|
|
277
|
+
function isExpandingRule(item) {
|
|
278
|
+
return typeof item === "object" && item !== null && typeof item.expandRules === "function";
|
|
279
|
+
}
|
|
280
|
+
function parseRuleSpec(spec, data = {}) {
|
|
281
|
+
const items = typeof spec === "string" ? spec.split("|") : spec;
|
|
282
|
+
return items.filter((item) => item !== "").flatMap((item) => {
|
|
283
|
+
if (isExpandingRule(item)) return parseRuleSpec(item.expandRules(data), data);
|
|
284
|
+
if (typeof item !== "string") return [{ kind: "object", rule: item }];
|
|
285
|
+
const [name, raw] = splitOnce(item, ":");
|
|
286
|
+
return [{ kind: "named", name, parameters: raw === void 0 ? [] : splitParameters(name, raw) }];
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
function isEmptyValue(value) {
|
|
290
|
+
if (value === null || value === void 0) return true;
|
|
291
|
+
if (typeof value === "string") return value.trim() === "";
|
|
292
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
293
|
+
if (typeof value === "object") {
|
|
294
|
+
const proto = Object.getPrototypeOf(value);
|
|
295
|
+
return (proto === Object.prototype || proto === null) && Object.keys(value).length === 0;
|
|
296
|
+
}
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
function isUploadedFileLike(value) {
|
|
300
|
+
return typeof value === "object" && value !== null && typeof value.clientName === "string" && typeof value.arrayBuffer === "function";
|
|
301
|
+
}
|
|
302
|
+
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
303
|
+
function expandWildcardKeys(pattern, data) {
|
|
304
|
+
const segments = pattern.split(".");
|
|
305
|
+
let paths = [""];
|
|
306
|
+
for (const segment of segments) {
|
|
307
|
+
const next = [];
|
|
308
|
+
for (const base of paths) {
|
|
309
|
+
const prefix = base === "" ? "" : `${base}.`;
|
|
310
|
+
if (segment !== "*") {
|
|
311
|
+
next.push(`${prefix}${segment}`);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const container = Arr.get(data, base === "" ? void 0 : base);
|
|
315
|
+
if (Array.isArray(container)) {
|
|
316
|
+
container.forEach((_, index) => next.push(`${prefix}${index}`));
|
|
317
|
+
} else if (container !== null && typeof container === "object") {
|
|
318
|
+
Object.keys(container).filter((key) => !UNSAFE_KEYS.has(key)).forEach((key) => next.push(`${prefix}${key}`));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
paths = next;
|
|
322
|
+
}
|
|
323
|
+
return paths;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// rules/dependent.ts
|
|
327
|
+
import { Arr as Arr2 } from "@orieljs/support";
|
|
328
|
+
|
|
329
|
+
// rules/type.ts
|
|
330
|
+
var BOOLEAN_VALUES = [true, false, 0, 1, "0", "1"];
|
|
331
|
+
var NUMERIC_STRING_RE = /^\s*[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?\s*$/;
|
|
332
|
+
function isNumeric(value) {
|
|
333
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
334
|
+
return typeof value === "string" && NUMERIC_STRING_RE.test(value);
|
|
335
|
+
}
|
|
336
|
+
function isPlainObject(value) {
|
|
337
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
338
|
+
const proto = Object.getPrototypeOf(value);
|
|
339
|
+
return proto === Object.prototype || proto === null;
|
|
340
|
+
}
|
|
341
|
+
var typeRules = {
|
|
342
|
+
string: { validate: (ctx) => typeof ctx.value === "string", messageKey: () => "string" },
|
|
343
|
+
/** `integer:strict` refuses the text of a number (Laravel's is_int()). */
|
|
344
|
+
integer: {
|
|
345
|
+
validate: (ctx) => ctx.parameters[0] === "strict" ? Number.isInteger(ctx.value) : typeof ctx.value !== "object" && /^-?\d+$/.test(String(ctx.value).trim()),
|
|
346
|
+
messageKey: () => "integer"
|
|
347
|
+
},
|
|
348
|
+
/** `numeric:strict` refuses the text of a number. */
|
|
349
|
+
numeric: {
|
|
350
|
+
validate: (ctx) => ctx.parameters[0] === "strict" ? typeof ctx.value === "number" && Number.isFinite(ctx.value) : isNumeric(ctx.value),
|
|
351
|
+
messageKey: () => "numeric"
|
|
352
|
+
},
|
|
353
|
+
/** `boolean:strict` lets only true and false through. */
|
|
354
|
+
boolean: {
|
|
355
|
+
validate: (ctx) => ctx.parameters[0] === "strict" ? typeof ctx.value === "boolean" : BOOLEAN_VALUES.includes(ctx.value),
|
|
356
|
+
messageKey: () => "boolean"
|
|
357
|
+
},
|
|
358
|
+
/**
|
|
359
|
+
* `array`: a list. `array:name,email`: an object (PHP's associative array) whose keys are all among those named, as
|
|
360
|
+
* Laravel's validateArray() with keys.
|
|
361
|
+
*/
|
|
362
|
+
array: {
|
|
363
|
+
validate: (ctx) => ctx.parameters.length === 0 ? Array.isArray(ctx.value) : isPlainObject(ctx.value) && Object.keys(ctx.value).every((key) => ctx.parameters.includes(key)),
|
|
364
|
+
messageKey: () => "array"
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// rules/dependent.ts
|
|
369
|
+
function truthy(value) {
|
|
370
|
+
if (typeof value === "string") return value !== "" && value !== "0";
|
|
371
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
372
|
+
return Boolean(value);
|
|
373
|
+
}
|
|
374
|
+
function looseEquals(value, parameter) {
|
|
375
|
+
if (typeof parameter === "boolean") return truthy(value) === parameter;
|
|
376
|
+
if (typeof value === "number") return isNumeric(parameter) ? Number(parameter) === value : String(value) === parameter;
|
|
377
|
+
if (typeof value === "string") return isNumeric(value) && isNumeric(parameter) ? Number(value) === Number(parameter) : value === parameter;
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
function otherFieldMatches(ctx) {
|
|
381
|
+
const [field = "", ...listed4] = ctx.parameters;
|
|
382
|
+
const other = Arr2.get(ctx.data, field);
|
|
383
|
+
let values = listed4;
|
|
384
|
+
if (typeof other === "boolean" || ctx.getRule?.(field, "boolean") !== void 0) {
|
|
385
|
+
values = values.map((value) => value === "true" ? true : value === "false" ? false : value);
|
|
386
|
+
}
|
|
387
|
+
if (other === null || other === void 0) {
|
|
388
|
+
values = values.map((value) => typeof value === "string" && value.toLowerCase() === "null" ? null : value);
|
|
389
|
+
return values.includes(null);
|
|
390
|
+
}
|
|
391
|
+
if (typeof other === "boolean") return values.includes(other);
|
|
392
|
+
return values.some((value) => value !== null && looseEquals(other, value));
|
|
393
|
+
}
|
|
394
|
+
function displayableValue(value) {
|
|
395
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
396
|
+
if (value === null || value === void 0) return "empty";
|
|
397
|
+
if (typeof value === "object") return "array";
|
|
398
|
+
return String(value);
|
|
399
|
+
}
|
|
400
|
+
function displayName(ctx, field) {
|
|
401
|
+
return ctx.displayName?.(field) ?? defaultAttributeName(field);
|
|
402
|
+
}
|
|
403
|
+
function otherAndValue(ctx) {
|
|
404
|
+
const field = ctx.parameters[0] ?? "";
|
|
405
|
+
return { other: displayName(ctx, field), value: displayableValue(Arr2.get(ctx.data, field)) };
|
|
406
|
+
}
|
|
407
|
+
function otherAndValues(ctx) {
|
|
408
|
+
return { other: displayName(ctx, ctx.parameters[0] ?? ""), values: ctx.parameters.slice(1).join(", ") };
|
|
409
|
+
}
|
|
410
|
+
function fieldList(ctx) {
|
|
411
|
+
return { values: ctx.parameters.map((field) => displayName(ctx, field)).join(" / ") };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// rules/acceptance.ts
|
|
415
|
+
var ACCEPTED_VALUES = ["yes", "on", 1, "1", true, "true"];
|
|
416
|
+
var DECLINED_VALUES = ["no", "off", 0, "0", false, "false"];
|
|
417
|
+
var isAccepted = (value) => !isEmptyValue(value) && ACCEPTED_VALUES.includes(value);
|
|
418
|
+
var isDeclined = (value) => !isEmptyValue(value) && DECLINED_VALUES.includes(value);
|
|
419
|
+
var otherAccepted = (data, field = "") => isAccepted(Arr3.get(data, field));
|
|
420
|
+
var otherDeclined = (data, field = "") => isDeclined(Arr3.get(data, field));
|
|
421
|
+
var acceptanceRules = {
|
|
422
|
+
accepted: { implicit: true, validate: (ctx) => isAccepted(ctx.value), messageKey: () => "accepted" },
|
|
423
|
+
accepted_if: {
|
|
424
|
+
implicit: true,
|
|
425
|
+
validate: (ctx) => !otherFieldMatches(ctx) || isAccepted(ctx.value),
|
|
426
|
+
messageKey: () => "accepted_if",
|
|
427
|
+
replacements: otherAndValue
|
|
428
|
+
},
|
|
429
|
+
declined: { implicit: true, validate: (ctx) => isDeclined(ctx.value), messageKey: () => "declined" },
|
|
430
|
+
declined_if: {
|
|
431
|
+
implicit: true,
|
|
432
|
+
validate: (ctx) => !otherFieldMatches(ctx) || isDeclined(ctx.value),
|
|
433
|
+
messageKey: () => "declined_if",
|
|
434
|
+
replacements: otherAndValue
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
// rules/array.ts
|
|
439
|
+
import { Arr as Arr4 } from "@orieljs/support";
|
|
440
|
+
function alike(a, b, strict = false) {
|
|
441
|
+
if (strict || a === b) return a === b;
|
|
442
|
+
if (typeof b === "string" || typeof b === "boolean") return looseEquals(a, b);
|
|
443
|
+
if (typeof a === "string" || typeof a === "boolean") return looseEquals(b, a);
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
var listed = (values) => ({ values: values.join(", ") });
|
|
447
|
+
function matches(value, other, comparison3) {
|
|
448
|
+
if (comparison3 === "strict") return value === other;
|
|
449
|
+
if (comparison3 === "loose") return alike(value, other);
|
|
450
|
+
return (typeof other === "string" || typeof other === "number") && String(other).toLowerCase() === String(value).toLowerCase();
|
|
451
|
+
}
|
|
452
|
+
function looseClass(value) {
|
|
453
|
+
if (typeof value === "number") return Number.isFinite(value) ? `n:${value}` : void 0;
|
|
454
|
+
if (typeof value === "string") return isNumeric(value) ? `n:${Number(value)}` : `s:${value}`;
|
|
455
|
+
return void 0;
|
|
456
|
+
}
|
|
457
|
+
var increment = (counts, key) => counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
458
|
+
var PatternValues = class _PatternValues {
|
|
459
|
+
constructor(values) {
|
|
460
|
+
this.values = values;
|
|
461
|
+
for (const value of values) {
|
|
462
|
+
increment(this.identical, value);
|
|
463
|
+
if (typeof value === "string" || typeof value === "number") increment(this.text, String(value).toLowerCase());
|
|
464
|
+
const loose = looseClass(value);
|
|
465
|
+
if (loose !== void 0) increment(this.classes, loose);
|
|
466
|
+
else if (typeof value === "number") this.unclassed.push(value);
|
|
467
|
+
if (typeof value === "boolean") increment(this.booleans, value);
|
|
468
|
+
if (typeof value !== "string") increment(this.truthiness, truthy(value));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
values;
|
|
472
|
+
/** By identity (SameValueZero): strict comparison, and null, undefined and objects under loose comparison. */
|
|
473
|
+
identical = /* @__PURE__ */ new Map();
|
|
474
|
+
/** Strings and numbers by their lower-cased text. */
|
|
475
|
+
text = /* @__PURE__ */ new Map();
|
|
476
|
+
/** Strings and finite numbers by looseClass(). */
|
|
477
|
+
classes = /* @__PURE__ */ new Map();
|
|
478
|
+
/** Booleans by value: what a string, number, null or object equals loosely besides its own class. */
|
|
479
|
+
booleans = /* @__PURE__ */ new Map();
|
|
480
|
+
/** Every value but a string, by PHP truthiness: what a boolean equals loosely. */
|
|
481
|
+
truthiness = /* @__PURE__ */ new Map();
|
|
482
|
+
/** NaN and ±Infinity, compared one by one. */
|
|
483
|
+
unclassed = [];
|
|
484
|
+
/** How many of the values `value` matches under the comparison. */
|
|
485
|
+
count(value, comparison3) {
|
|
486
|
+
if (comparison3 === "strict") return Number.isNaN(value) ? 0 : this.identical.get(value) ?? 0;
|
|
487
|
+
if (comparison3 === "ignore_case") return this.text.get(String(value).toLowerCase()) ?? 0;
|
|
488
|
+
if (typeof value === "boolean") return this.truthiness.get(value) ?? 0;
|
|
489
|
+
const loose = looseClass(value);
|
|
490
|
+
if (loose === void 0 && typeof value === "number") return this.values.filter((other) => alike(value, other)).length;
|
|
491
|
+
const own = loose === void 0 ? this.identical.get(value) ?? 0 : (this.classes.get(loose) ?? 0) + this.unclassed.filter((other) => alike(value, other)).length;
|
|
492
|
+
return own + (this.booleans.get(truthy(value)) ?? 0);
|
|
493
|
+
}
|
|
494
|
+
/** The values of the fields `pattern` names in the context's data, gathered once per validation pass. */
|
|
495
|
+
static of(ctx, pattern) {
|
|
496
|
+
const key = `array:values:${pattern}`;
|
|
497
|
+
const cached = ctx.cache?.get(key);
|
|
498
|
+
if (cached instanceof _PatternValues) return cached;
|
|
499
|
+
const fields = expandWildcardKeys(pattern, ctx.data).filter((field) => Arr4.has(ctx.data, field));
|
|
500
|
+
const values = new _PatternValues(fields.map((field) => Arr4.get(ctx.data, field)));
|
|
501
|
+
ctx.cache?.set(key, values);
|
|
502
|
+
return values;
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
var arrayRules = {
|
|
506
|
+
list: { validate: (ctx) => Array.isArray(ctx.value), messageKey: () => "list" },
|
|
507
|
+
required_array_keys: {
|
|
508
|
+
validate: (ctx) => isPlainObject(ctx.value) && ctx.parameters.every((key) => Object.hasOwn(ctx.value, key)),
|
|
509
|
+
messageKey: () => "required_array_keys",
|
|
510
|
+
replacements: (ctx) => listed(ctx.parameters)
|
|
511
|
+
},
|
|
512
|
+
in_array_keys: {
|
|
513
|
+
validate: (ctx) => isPlainObject(ctx.value) && ctx.parameters.some((key) => Object.hasOwn(ctx.value, key)),
|
|
514
|
+
messageKey: () => "in_array_keys",
|
|
515
|
+
replacements: (ctx) => listed(ctx.parameters)
|
|
516
|
+
},
|
|
517
|
+
/** Every value named is an item of the array (compared as PHP's in_array() does). */
|
|
518
|
+
contains: {
|
|
519
|
+
validate: (ctx) => {
|
|
520
|
+
const items = ctx.value;
|
|
521
|
+
return Array.isArray(items) && ctx.parameters.every((needle) => items.some((item) => alike(item, needle)));
|
|
522
|
+
},
|
|
523
|
+
messageKey: () => "contains"
|
|
524
|
+
},
|
|
525
|
+
doesnt_contain: {
|
|
526
|
+
validate: (ctx) => {
|
|
527
|
+
const items = ctx.value;
|
|
528
|
+
return Array.isArray(items) && !ctx.parameters.some((needle) => items.some((item) => alike(item, needle)));
|
|
529
|
+
},
|
|
530
|
+
messageKey: () => "doesnt_contain",
|
|
531
|
+
replacements: (ctx) => listed(ctx.parameters)
|
|
532
|
+
},
|
|
533
|
+
/**
|
|
534
|
+
* Laravel's validateDistinct(): no other field under the same wildcard holds this value. `distinct:strict` compares
|
|
535
|
+
* with `===`; `distinct:ignore_case` compares text case-insensitively.
|
|
536
|
+
*/
|
|
537
|
+
distinct: {
|
|
538
|
+
validate: (ctx) => {
|
|
539
|
+
const comparison3 = ctx.parameters.includes("ignore_case") ? "ignore_case" : ctx.parameters.includes("strict") ? "strict" : "loose";
|
|
540
|
+
const own = Arr4.has(ctx.data, ctx.attribute) && matches(ctx.value, Arr4.get(ctx.data, ctx.attribute), comparison3) ? 1 : 0;
|
|
541
|
+
return PatternValues.of(ctx, ctx.pattern ?? ctx.attribute).count(ctx.value, comparison3) - own === 0;
|
|
542
|
+
},
|
|
543
|
+
messageKey: () => "distinct"
|
|
544
|
+
},
|
|
545
|
+
/** The value is one of the values of the fields the parameter names (`in_array:colors.*`). */
|
|
546
|
+
in_array: {
|
|
547
|
+
validate: (ctx) => PatternValues.of(ctx, ctx.parameters[0] ?? "").count(ctx.value, "loose") > 0,
|
|
548
|
+
messageKey: () => "in_array",
|
|
549
|
+
replacements: (ctx) => ({ other: displayName(ctx, ctx.parameters[0] ?? "") })
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
|
|
193
553
|
// rules/auth.ts
|
|
194
554
|
import { AuthFactory, Hasher } from "@orieljs/contracts";
|
|
195
555
|
import { inject as inject2 } from "@orieljs/container";
|
|
@@ -238,6 +598,457 @@ var databaseRules = {
|
|
|
238
598
|
}
|
|
239
599
|
};
|
|
240
600
|
|
|
601
|
+
// rules/date.ts
|
|
602
|
+
import { Arr as Arr5 } from "@orieljs/support";
|
|
603
|
+
|
|
604
|
+
// dates/phpFormat.ts
|
|
605
|
+
var DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
606
|
+
var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
607
|
+
var UNSUPPORTED = /* @__PURE__ */ new Set(["z", "W", "t", "L", "o", "X", "x", "B", "e", "I", "T", "Z", "c", "r", "N", "w", "#", "?", "*", "+"]);
|
|
608
|
+
var pad = (value, length2) => String(Math.abs(value)).padStart(length2, "0");
|
|
609
|
+
function suffix(day) {
|
|
610
|
+
if (day % 100 >= 11 && day % 100 <= 13) return "th";
|
|
611
|
+
return ["th", "st", "nd", "rd"][day % 10] ?? "th";
|
|
612
|
+
}
|
|
613
|
+
function formatOffset(offset, colon) {
|
|
614
|
+
const sign = offset < 0 ? "-" : "+";
|
|
615
|
+
return `${sign}${pad(Math.trunc(Math.abs(offset) / 60), 2)}${colon ? ":" : ""}${pad(Math.abs(offset) % 60, 2)}`;
|
|
616
|
+
}
|
|
617
|
+
function parse(format2, value) {
|
|
618
|
+
const fields = { year: 1970, month: 1, day: 1, hour: 0, minute: 0, second: 0, micro: 0, offset: void 0, unix: void 0 };
|
|
619
|
+
let meridiem;
|
|
620
|
+
let twelveHour = false;
|
|
621
|
+
let position = 0;
|
|
622
|
+
const digits = (min, max) => {
|
|
623
|
+
const match = new RegExp(`^\\d{${min},${max}}`).exec(value.slice(position));
|
|
624
|
+
if (!match) return void 0;
|
|
625
|
+
position += match[0].length;
|
|
626
|
+
return Number(match[0]);
|
|
627
|
+
};
|
|
628
|
+
const word = (names, short) => {
|
|
629
|
+
const match = /^[A-Za-z]+/.exec(value.slice(position));
|
|
630
|
+
if (!match) return void 0;
|
|
631
|
+
const text = match[0].toLowerCase();
|
|
632
|
+
const index = names.findIndex((name) => name.toLowerCase() === text || short && name.slice(0, 3).toLowerCase() === text);
|
|
633
|
+
if (index === -1) return void 0;
|
|
634
|
+
position += match[0].length;
|
|
635
|
+
return index;
|
|
636
|
+
};
|
|
637
|
+
const offset = (colon) => {
|
|
638
|
+
const rest = value.slice(position);
|
|
639
|
+
if (colon === "z" && rest.startsWith("Z")) {
|
|
640
|
+
position += 1;
|
|
641
|
+
return 0;
|
|
642
|
+
}
|
|
643
|
+
const match = (colon === "none" ? /^([+-])(\d{2})(\d{2})/ : /^([+-])(\d{2}):(\d{2})/).exec(rest);
|
|
644
|
+
if (!match) return void 0;
|
|
645
|
+
position += match[0].length;
|
|
646
|
+
return (match[1] === "-" ? -1 : 1) * (Number(match[2]) * 60 + Number(match[3]));
|
|
647
|
+
};
|
|
648
|
+
for (let i = 0; i < format2.length; i++) {
|
|
649
|
+
const char = format2[i];
|
|
650
|
+
let read = 0;
|
|
651
|
+
switch (char) {
|
|
652
|
+
case "!":
|
|
653
|
+
case "|":
|
|
654
|
+
continue;
|
|
655
|
+
case "\\":
|
|
656
|
+
i++;
|
|
657
|
+
if (value[position] !== format2[i]) return void 0;
|
|
658
|
+
position++;
|
|
659
|
+
continue;
|
|
660
|
+
case "d":
|
|
661
|
+
case "j":
|
|
662
|
+
read = digits(1, 2);
|
|
663
|
+
if (read !== void 0) fields.day = read;
|
|
664
|
+
break;
|
|
665
|
+
case "D":
|
|
666
|
+
case "l":
|
|
667
|
+
read = word(DAYS, true);
|
|
668
|
+
break;
|
|
669
|
+
case "S":
|
|
670
|
+
if (!/^(st|nd|rd|th)/i.test(value.slice(position))) return void 0;
|
|
671
|
+
position += 2;
|
|
672
|
+
break;
|
|
673
|
+
case "F":
|
|
674
|
+
case "M":
|
|
675
|
+
read = word(MONTHS, true);
|
|
676
|
+
if (read !== void 0) fields.month = read + 1;
|
|
677
|
+
break;
|
|
678
|
+
case "m":
|
|
679
|
+
case "n":
|
|
680
|
+
read = digits(1, 2);
|
|
681
|
+
if (read !== void 0) fields.month = read;
|
|
682
|
+
break;
|
|
683
|
+
case "Y":
|
|
684
|
+
read = digits(1, 4);
|
|
685
|
+
if (read !== void 0) fields.year = read;
|
|
686
|
+
break;
|
|
687
|
+
case "y":
|
|
688
|
+
read = digits(2, 2);
|
|
689
|
+
if (read !== void 0) fields.year = read + (read < 70 ? 2e3 : 1900);
|
|
690
|
+
break;
|
|
691
|
+
case "a":
|
|
692
|
+
case "A": {
|
|
693
|
+
const match = /^(am|pm)/i.exec(value.slice(position));
|
|
694
|
+
if (!match) return void 0;
|
|
695
|
+
position += 2;
|
|
696
|
+
meridiem = match[1].toLowerCase();
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
case "g":
|
|
700
|
+
case "h":
|
|
701
|
+
read = digits(1, 2);
|
|
702
|
+
if (read !== void 0 && (read < 1 || read > 12)) return void 0;
|
|
703
|
+
if (read !== void 0) fields.hour = read;
|
|
704
|
+
twelveHour = true;
|
|
705
|
+
break;
|
|
706
|
+
case "G":
|
|
707
|
+
case "H":
|
|
708
|
+
read = digits(1, 2);
|
|
709
|
+
if (read !== void 0) fields.hour = read;
|
|
710
|
+
break;
|
|
711
|
+
case "i":
|
|
712
|
+
read = digits(2, 2);
|
|
713
|
+
if (read !== void 0) fields.minute = read;
|
|
714
|
+
break;
|
|
715
|
+
case "s":
|
|
716
|
+
read = digits(2, 2);
|
|
717
|
+
if (read !== void 0) fields.second = read;
|
|
718
|
+
break;
|
|
719
|
+
case "v":
|
|
720
|
+
read = digits(3, 3);
|
|
721
|
+
if (read !== void 0) fields.micro = read * 1e3;
|
|
722
|
+
break;
|
|
723
|
+
case "u": {
|
|
724
|
+
const match = /^\d{1,6}/.exec(value.slice(position));
|
|
725
|
+
read = match ? Number(match[0].padEnd(6, "0")) : void 0;
|
|
726
|
+
if (match) position += match[0].length;
|
|
727
|
+
if (read !== void 0) fields.micro = read;
|
|
728
|
+
break;
|
|
729
|
+
}
|
|
730
|
+
case "O":
|
|
731
|
+
read = fields.offset = offset("none");
|
|
732
|
+
break;
|
|
733
|
+
case "P":
|
|
734
|
+
read = fields.offset = offset("required");
|
|
735
|
+
break;
|
|
736
|
+
case "p":
|
|
737
|
+
read = fields.offset = offset("z");
|
|
738
|
+
break;
|
|
739
|
+
case "U": {
|
|
740
|
+
const match = /^-?\d+/.exec(value.slice(position));
|
|
741
|
+
read = match ? Number(match[0]) : void 0;
|
|
742
|
+
if (match) position += match[0].length;
|
|
743
|
+
fields.unix = read;
|
|
744
|
+
break;
|
|
745
|
+
}
|
|
746
|
+
default:
|
|
747
|
+
if (value[position] !== char) return void 0;
|
|
748
|
+
position++;
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
if (read === void 0) return void 0;
|
|
752
|
+
}
|
|
753
|
+
if (position !== value.length) return void 0;
|
|
754
|
+
if (twelveHour && meridiem !== void 0) fields.hour = fields.hour % 12 + (meridiem === "pm" ? 12 : 0);
|
|
755
|
+
return fields;
|
|
756
|
+
}
|
|
757
|
+
function wallClock(fields) {
|
|
758
|
+
if (fields.unix !== void 0) return new Date(fields.unix * 1e3);
|
|
759
|
+
const date = /* @__PURE__ */ new Date(0);
|
|
760
|
+
date.setUTCFullYear(fields.year, fields.month - 1, fields.day);
|
|
761
|
+
date.setUTCHours(fields.hour, fields.minute, fields.second, Math.floor(fields.micro / 1e3));
|
|
762
|
+
return date;
|
|
763
|
+
}
|
|
764
|
+
function format(format2, date, micro, offset) {
|
|
765
|
+
let out = "";
|
|
766
|
+
for (let i = 0; i < format2.length; i++) {
|
|
767
|
+
const char = format2[i];
|
|
768
|
+
const day = date.getUTCDate();
|
|
769
|
+
const hour = date.getUTCHours();
|
|
770
|
+
switch (char) {
|
|
771
|
+
case "!":
|
|
772
|
+
case "|":
|
|
773
|
+
break;
|
|
774
|
+
case "\\":
|
|
775
|
+
i++;
|
|
776
|
+
out += format2[i] ?? "";
|
|
777
|
+
break;
|
|
778
|
+
case "d":
|
|
779
|
+
out += pad(day, 2);
|
|
780
|
+
break;
|
|
781
|
+
case "j":
|
|
782
|
+
out += String(day);
|
|
783
|
+
break;
|
|
784
|
+
case "D":
|
|
785
|
+
out += DAYS[date.getUTCDay()].slice(0, 3);
|
|
786
|
+
break;
|
|
787
|
+
case "l":
|
|
788
|
+
out += DAYS[date.getUTCDay()];
|
|
789
|
+
break;
|
|
790
|
+
case "S":
|
|
791
|
+
out += suffix(day);
|
|
792
|
+
break;
|
|
793
|
+
case "F":
|
|
794
|
+
out += MONTHS[date.getUTCMonth()];
|
|
795
|
+
break;
|
|
796
|
+
case "M":
|
|
797
|
+
out += MONTHS[date.getUTCMonth()].slice(0, 3);
|
|
798
|
+
break;
|
|
799
|
+
case "m":
|
|
800
|
+
out += pad(date.getUTCMonth() + 1, 2);
|
|
801
|
+
break;
|
|
802
|
+
case "n":
|
|
803
|
+
out += String(date.getUTCMonth() + 1);
|
|
804
|
+
break;
|
|
805
|
+
case "Y":
|
|
806
|
+
out += pad(date.getUTCFullYear(), 4);
|
|
807
|
+
break;
|
|
808
|
+
case "y":
|
|
809
|
+
out += pad(date.getUTCFullYear() % 100, 2);
|
|
810
|
+
break;
|
|
811
|
+
case "a":
|
|
812
|
+
out += hour < 12 ? "am" : "pm";
|
|
813
|
+
break;
|
|
814
|
+
case "A":
|
|
815
|
+
out += hour < 12 ? "AM" : "PM";
|
|
816
|
+
break;
|
|
817
|
+
case "g":
|
|
818
|
+
out += String(hour % 12 || 12);
|
|
819
|
+
break;
|
|
820
|
+
case "h":
|
|
821
|
+
out += pad(hour % 12 || 12, 2);
|
|
822
|
+
break;
|
|
823
|
+
case "G":
|
|
824
|
+
out += String(hour);
|
|
825
|
+
break;
|
|
826
|
+
case "H":
|
|
827
|
+
out += pad(hour, 2);
|
|
828
|
+
break;
|
|
829
|
+
case "i":
|
|
830
|
+
out += pad(date.getUTCMinutes(), 2);
|
|
831
|
+
break;
|
|
832
|
+
case "s":
|
|
833
|
+
out += pad(date.getUTCSeconds(), 2);
|
|
834
|
+
break;
|
|
835
|
+
case "v":
|
|
836
|
+
out += pad(Math.floor(micro / 1e3), 3);
|
|
837
|
+
break;
|
|
838
|
+
case "u":
|
|
839
|
+
out += pad(micro, 6);
|
|
840
|
+
break;
|
|
841
|
+
case "O":
|
|
842
|
+
out += formatOffset(offset, false);
|
|
843
|
+
break;
|
|
844
|
+
case "P":
|
|
845
|
+
out += formatOffset(offset, true);
|
|
846
|
+
break;
|
|
847
|
+
case "p":
|
|
848
|
+
out += offset === 0 ? "Z" : formatOffset(offset, true);
|
|
849
|
+
break;
|
|
850
|
+
case "U":
|
|
851
|
+
out += String(Math.floor(date.getTime() / 1e3));
|
|
852
|
+
break;
|
|
853
|
+
default:
|
|
854
|
+
out += char;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
return out;
|
|
858
|
+
}
|
|
859
|
+
function assertSupportedFormat(rule, dateFormat) {
|
|
860
|
+
for (let i = 0; i < dateFormat.length; i++) {
|
|
861
|
+
if (dateFormat[i] === "\\") i++;
|
|
862
|
+
else if (UNSUPPORTED.has(dateFormat[i])) {
|
|
863
|
+
throw new InvalidRuleParameterError(rule, dateFormat, `the format character "${dateFormat[i]}" is not supported`);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
function matchesFormat(dateFormat, value) {
|
|
868
|
+
const fields = parse(dateFormat, value);
|
|
869
|
+
if (!fields) return false;
|
|
870
|
+
const offset = fields.offset ?? 0;
|
|
871
|
+
return format(dateFormat, wallClock(fields), fields.micro, offset) === value;
|
|
872
|
+
}
|
|
873
|
+
function timestampWithFormat(dateFormat, value) {
|
|
874
|
+
const fields = parse(dateFormat, value);
|
|
875
|
+
if (!fields) return Number.NaN;
|
|
876
|
+
if (fields.unix !== void 0) return fields.unix * 1e3;
|
|
877
|
+
const utc = wallClock(fields).getTime();
|
|
878
|
+
if (fields.offset !== void 0) return utc - fields.offset * 6e4;
|
|
879
|
+
const local = /* @__PURE__ */ new Date(0);
|
|
880
|
+
local.setFullYear(fields.year, fields.month - 1, fields.day);
|
|
881
|
+
local.setHours(fields.hour, fields.minute, fields.second, Math.floor(fields.micro / 1e3));
|
|
882
|
+
return local.getTime();
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// rules/date.ts
|
|
886
|
+
var DATE_SHAPES = [
|
|
887
|
+
/^\d{4}-\d{1,2}-\d{1,2}(?:[T\s].*)?$/,
|
|
888
|
+
/^\d{1,2}[/.-]\d{1,2}[/.-]\d{2,4}(?:\s.*)?$/,
|
|
889
|
+
/^(?=.*\d).*\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\b/i
|
|
890
|
+
];
|
|
891
|
+
var DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
892
|
+
function parseDate(value) {
|
|
893
|
+
const trimmed = value.trim();
|
|
894
|
+
if (!DATE_SHAPES.some((shape) => shape.test(trimmed))) return Number.NaN;
|
|
895
|
+
const dateOnly = DATE_ONLY_RE.exec(trimmed);
|
|
896
|
+
if (dateOnly) {
|
|
897
|
+
const [, y, m, d] = dateOnly.map(Number);
|
|
898
|
+
const local = new Date(y, m - 1, d);
|
|
899
|
+
return local.getMonth() === m - 1 && local.getDate() === d ? local.getTime() : Number.NaN;
|
|
900
|
+
}
|
|
901
|
+
return Date.parse(trimmed);
|
|
902
|
+
}
|
|
903
|
+
function startOfDay(offsetDays) {
|
|
904
|
+
const date = /* @__PURE__ */ new Date();
|
|
905
|
+
date.setHours(0, 0, 0, 0);
|
|
906
|
+
date.setDate(date.getDate() + offsetDays);
|
|
907
|
+
return date.getTime();
|
|
908
|
+
}
|
|
909
|
+
var RELATIVE_RE = /^([+-]\d+)\s*(second|minute|hour|day|week|month|year)s?$/i;
|
|
910
|
+
function relativeDate(param) {
|
|
911
|
+
switch (param.trim().toLowerCase()) {
|
|
912
|
+
case "now":
|
|
913
|
+
return Date.now();
|
|
914
|
+
case "today":
|
|
915
|
+
return startOfDay(0);
|
|
916
|
+
case "tomorrow":
|
|
917
|
+
return startOfDay(1);
|
|
918
|
+
case "yesterday":
|
|
919
|
+
return startOfDay(-1);
|
|
920
|
+
}
|
|
921
|
+
const match = RELATIVE_RE.exec(param.trim());
|
|
922
|
+
if (!match) return void 0;
|
|
923
|
+
const amount = Number(match[1]);
|
|
924
|
+
const date = /* @__PURE__ */ new Date();
|
|
925
|
+
switch (match[2].toLowerCase()) {
|
|
926
|
+
case "second":
|
|
927
|
+
date.setSeconds(date.getSeconds() + amount);
|
|
928
|
+
break;
|
|
929
|
+
case "minute":
|
|
930
|
+
date.setMinutes(date.getMinutes() + amount);
|
|
931
|
+
break;
|
|
932
|
+
case "hour":
|
|
933
|
+
date.setHours(date.getHours() + amount);
|
|
934
|
+
break;
|
|
935
|
+
case "day":
|
|
936
|
+
date.setDate(date.getDate() + amount);
|
|
937
|
+
break;
|
|
938
|
+
case "week":
|
|
939
|
+
date.setDate(date.getDate() + amount * 7);
|
|
940
|
+
break;
|
|
941
|
+
case "month":
|
|
942
|
+
date.setMonth(date.getMonth() + amount);
|
|
943
|
+
break;
|
|
944
|
+
case "year":
|
|
945
|
+
date.setFullYear(date.getFullYear() + amount);
|
|
946
|
+
break;
|
|
947
|
+
}
|
|
948
|
+
return date.getTime();
|
|
949
|
+
}
|
|
950
|
+
var FIELD_REFERENCE_RE = /^[A-Za-z_][\w-]*(?:\.[\w-]+)*$/;
|
|
951
|
+
var isDateInput = (value) => typeof value === "string" || typeof value === "number" && Number.isFinite(value);
|
|
952
|
+
function resolveDateParam(ctx, rule, param, format2) {
|
|
953
|
+
const fieldValue = Arr5.get(ctx.data, param);
|
|
954
|
+
if (isDateInput(fieldValue)) {
|
|
955
|
+
const parsed = readDate(fieldValue, ctx.getRule?.(param, "date_format")?.[0] ?? format2);
|
|
956
|
+
if (!Number.isNaN(parsed)) return parsed;
|
|
957
|
+
}
|
|
958
|
+
const relative = relativeDate(param);
|
|
959
|
+
if (relative !== void 0) return relative;
|
|
960
|
+
const literal = readDate(param, format2);
|
|
961
|
+
if (!Number.isNaN(literal)) return literal;
|
|
962
|
+
if (FIELD_REFERENCE_RE.test(param)) return null;
|
|
963
|
+
throw new InvalidRuleParameterError(rule, param, 'expected a date, today/tomorrow/yesterday/now, "+N unit", or a field name');
|
|
964
|
+
}
|
|
965
|
+
function readDate(value, format2) {
|
|
966
|
+
if (format2 !== void 0) {
|
|
967
|
+
const formatted = timestampWithFormat(format2, String(value));
|
|
968
|
+
if (!Number.isNaN(formatted)) return formatted;
|
|
969
|
+
}
|
|
970
|
+
return typeof value === "number" ? value * 1e3 : parseDate(value);
|
|
971
|
+
}
|
|
972
|
+
var COMPARE = {
|
|
973
|
+
before: (value, target) => value < target,
|
|
974
|
+
before_or_equal: (value, target) => value <= target,
|
|
975
|
+
after: (value, target) => value > target,
|
|
976
|
+
after_or_equal: (value, target) => value >= target,
|
|
977
|
+
date_equals: (value, target) => value === target
|
|
978
|
+
};
|
|
979
|
+
function compareDates(ctx, rule) {
|
|
980
|
+
const format2 = ctx.getRule?.(ctx.attribute, "date_format")?.[0];
|
|
981
|
+
const param = ctx.parameters[0] ?? "";
|
|
982
|
+
const target = resolveDateParam(ctx, rule, param, format2);
|
|
983
|
+
if (!isDateInput(ctx.value)) return false;
|
|
984
|
+
if (target === null && format2 !== void 0) {
|
|
985
|
+
return Arr5.get(ctx.data, param) == null;
|
|
986
|
+
}
|
|
987
|
+
const value = readDate(ctx.value, format2);
|
|
988
|
+
if (Number.isNaN(value)) return false;
|
|
989
|
+
if (target === null) {
|
|
990
|
+
return COMPARE[rule](Math.trunc(value / 1e3) === 0 ? 0 : 1, 0);
|
|
991
|
+
}
|
|
992
|
+
return COMPARE[rule](value, target);
|
|
993
|
+
}
|
|
994
|
+
function dateReplacement(ctx) {
|
|
995
|
+
const param = ctx.parameters[0] ?? "";
|
|
996
|
+
const format2 = ctx.getRule?.(ctx.attribute, "date_format")?.[0];
|
|
997
|
+
const isDate = relativeDate(param) !== void 0 || !Number.isNaN(readDate(param, format2));
|
|
998
|
+
return { date: isDate && !Arr5.has(ctx.data, param) ? param : displayName(ctx, param) };
|
|
999
|
+
}
|
|
1000
|
+
var comparison = (rule) => ({
|
|
1001
|
+
validate: (ctx) => compareDates(ctx, rule),
|
|
1002
|
+
messageKey: () => rule,
|
|
1003
|
+
replacements: dateReplacement
|
|
1004
|
+
});
|
|
1005
|
+
var dateRules = {
|
|
1006
|
+
date: {
|
|
1007
|
+
validate: (ctx) => typeof ctx.value === "string" && !Number.isNaN(parseDate(ctx.value)),
|
|
1008
|
+
messageKey: () => "date"
|
|
1009
|
+
},
|
|
1010
|
+
before: comparison("before"),
|
|
1011
|
+
before_or_equal: comparison("before_or_equal"),
|
|
1012
|
+
after: comparison("after"),
|
|
1013
|
+
after_or_equal: comparison("after_or_equal"),
|
|
1014
|
+
date_equals: comparison("date_equals"),
|
|
1015
|
+
/**
|
|
1016
|
+
* Laravel's validateDateFormat(): the value (text, or a number) reads with one of the formats (PHP's characters,
|
|
1017
|
+
* `date_format:Y-m-d,d/m/Y`) and formats back to the same text, so `2026-02-30` and `2026-2-28` fail `Y-m-d`.
|
|
1018
|
+
*/
|
|
1019
|
+
date_format: {
|
|
1020
|
+
validate: (ctx) => {
|
|
1021
|
+
for (const format2 of ctx.parameters) assertSupportedFormat("date_format", format2);
|
|
1022
|
+
if (typeof ctx.value !== "string" && !(typeof ctx.value === "number" && Number.isFinite(ctx.value))) return false;
|
|
1023
|
+
const text = String(ctx.value);
|
|
1024
|
+
return ctx.parameters.some((format2) => matchesFormat(format2, text));
|
|
1025
|
+
},
|
|
1026
|
+
messageKey: () => "date_format",
|
|
1027
|
+
replacements: (ctx) => ({ format: ctx.parameters[0] ?? "" })
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
// rules/exclude.ts
|
|
1032
|
+
import { Arr as Arr6 } from "@orieljs/support";
|
|
1033
|
+
var excludeRules = {
|
|
1034
|
+
exclude: { excludes: true, validate: () => false, messageKey: () => "exclude" },
|
|
1035
|
+
/** Excluded when the other field holds one of the values; nothing happens while the other field is missing. */
|
|
1036
|
+
exclude_if: {
|
|
1037
|
+
excludes: true,
|
|
1038
|
+
validate: (ctx) => !Arr6.has(ctx.data, ctx.parameters[0] ?? "") || !otherFieldMatches(ctx),
|
|
1039
|
+
messageKey: () => "exclude_if"
|
|
1040
|
+
},
|
|
1041
|
+
exclude_unless: { excludes: true, validate: (ctx) => otherFieldMatches(ctx), messageKey: () => "exclude_unless" },
|
|
1042
|
+
/** Excluded while the other field's key is there. */
|
|
1043
|
+
exclude_with: { excludes: true, validate: (ctx) => !Arr6.has(ctx.data, ctx.parameters[0] ?? ""), messageKey: () => "exclude_with" },
|
|
1044
|
+
/** Excluded while any field named is missing or empty. */
|
|
1045
|
+
exclude_without: {
|
|
1046
|
+
excludes: true,
|
|
1047
|
+
validate: (ctx) => !ctx.parameters.some((field) => isEmptyValue(Arr6.get(ctx.data, field))),
|
|
1048
|
+
messageKey: () => "exclude_without"
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
|
|
241
1052
|
// rules/file.ts
|
|
242
1053
|
import { FileType } from "@orieljs/support";
|
|
243
1054
|
|
|
@@ -318,63 +1129,13 @@ function avif(view) {
|
|
|
318
1129
|
const iprp = meta && findBox(view, meta.start + 4, meta.end, "iprp");
|
|
319
1130
|
const ipco = iprp && findBox(view, iprp.start, iprp.end, "ipco");
|
|
320
1131
|
if (!ipco) return void 0;
|
|
321
|
-
let largest;
|
|
322
|
-
for (const box of boxes(view, ipco.start, ipco.end)) {
|
|
323
|
-
if (box.type !== "ispe") continue;
|
|
324
|
-
const size = { width: view.getUint32(box.start + 4), height: view.getUint32(box.start + 8) };
|
|
325
|
-
if (!largest || size.width * size.height > largest.width * largest.height) largest = size;
|
|
326
|
-
}
|
|
327
|
-
return largest;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// parse.ts
|
|
331
|
-
import { Arr } from "@orieljs/support";
|
|
332
|
-
function splitParameters(name, raw) {
|
|
333
|
-
return name === "regex" || name === "not_regex" ? [raw] : raw.split(",");
|
|
334
|
-
}
|
|
335
|
-
function splitOnce(value, separator) {
|
|
336
|
-
const index = value.indexOf(separator);
|
|
337
|
-
return index === -1 ? [value, void 0] : [value.slice(0, index), value.slice(index + 1)];
|
|
338
|
-
}
|
|
339
|
-
function parseRuleSpec(spec) {
|
|
340
|
-
const items = typeof spec === "string" ? spec.split("|") : spec;
|
|
341
|
-
return items.filter((item) => item !== "").map((item) => {
|
|
342
|
-
if (typeof item !== "string") return { kind: "object", rule: item };
|
|
343
|
-
const [name, raw] = splitOnce(item, ":");
|
|
344
|
-
return { kind: "named", name, parameters: raw === void 0 ? [] : splitParameters(name, raw) };
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
function isEmptyValue(value) {
|
|
348
|
-
if (value === null || value === void 0) return true;
|
|
349
|
-
if (typeof value === "string") return value === "";
|
|
350
|
-
if (Array.isArray(value)) return value.length === 0;
|
|
351
|
-
return false;
|
|
352
|
-
}
|
|
353
|
-
function isUploadedFileLike(value) {
|
|
354
|
-
return typeof value === "object" && value !== null && typeof value.clientName === "string" && typeof value.arrayBuffer === "function";
|
|
355
|
-
}
|
|
356
|
-
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
357
|
-
function expandWildcardKeys(pattern, data) {
|
|
358
|
-
const segments = pattern.split(".");
|
|
359
|
-
let paths = [""];
|
|
360
|
-
for (const segment of segments) {
|
|
361
|
-
const next = [];
|
|
362
|
-
for (const base of paths) {
|
|
363
|
-
const prefix = base === "" ? "" : `${base}.`;
|
|
364
|
-
if (segment !== "*") {
|
|
365
|
-
next.push(`${prefix}${segment}`);
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
const container = Arr.get(data, base === "" ? void 0 : base);
|
|
369
|
-
if (Array.isArray(container)) {
|
|
370
|
-
container.forEach((_, index) => next.push(`${prefix}${index}`));
|
|
371
|
-
} else if (container !== null && typeof container === "object") {
|
|
372
|
-
Object.keys(container).filter((key) => !UNSAFE_KEYS.has(key)).forEach((key) => next.push(`${prefix}${key}`));
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
paths = next;
|
|
1132
|
+
let largest;
|
|
1133
|
+
for (const box of boxes(view, ipco.start, ipco.end)) {
|
|
1134
|
+
if (box.type !== "ispe") continue;
|
|
1135
|
+
const size = { width: view.getUint32(box.start + 4), height: view.getUint32(box.start + 8) };
|
|
1136
|
+
if (!largest || size.width * size.height > largest.width * largest.height) largest = size;
|
|
376
1137
|
}
|
|
377
|
-
return
|
|
1138
|
+
return largest;
|
|
378
1139
|
}
|
|
379
1140
|
|
|
380
1141
|
// rules/file.ts
|
|
@@ -414,7 +1175,7 @@ function allowedExtensions(ctx) {
|
|
|
414
1175
|
if (allowed.has("jpg") || allowed.has("jpeg")) allowed.add("jpg").add("jpeg");
|
|
415
1176
|
return allowed;
|
|
416
1177
|
}
|
|
417
|
-
var
|
|
1178
|
+
var listed2 = (ctx) => ({ values: ctx.parameters.join(", ") });
|
|
418
1179
|
var DIMENSION_KEYS = ["width", "height", "min_width", "min_height", "max_width", "max_height", "ratio"];
|
|
419
1180
|
function namedParameter(parameter) {
|
|
420
1181
|
const at = parameter.indexOf("=");
|
|
@@ -471,7 +1232,7 @@ var fileRules = {
|
|
|
471
1232
|
return meetsMimes((await inspect(ctx.value)).type, clientExtension(ctx.value), allowed);
|
|
472
1233
|
},
|
|
473
1234
|
messageKey: () => "mimes",
|
|
474
|
-
replacements:
|
|
1235
|
+
replacements: listed2
|
|
475
1236
|
},
|
|
476
1237
|
/**
|
|
477
1238
|
* The detected MIME type is listed, or its `type/*` wildcard is. `image/*` includes SVG (`image/svg+xml`), as in
|
|
@@ -486,7 +1247,7 @@ var fileRules = {
|
|
|
486
1247
|
return allowed.includes(mime) || allowed.includes(`${mime.split("/")[0]}/*`);
|
|
487
1248
|
},
|
|
488
1249
|
messageKey: () => "mimetypes",
|
|
489
|
-
replacements:
|
|
1250
|
+
replacements: listed2
|
|
490
1251
|
},
|
|
491
1252
|
/**
|
|
492
1253
|
* The pixel size read from the image's header meets every constraint: width, height, min_width, max_width,
|
|
@@ -510,18 +1271,81 @@ var fileRules = {
|
|
|
510
1271
|
return !blocksPhp(ctx.value, allowed) && allowed.includes(clientExtension(ctx.value));
|
|
511
1272
|
},
|
|
512
1273
|
messageKey: () => "extensions",
|
|
513
|
-
replacements:
|
|
1274
|
+
replacements: listed2
|
|
514
1275
|
}
|
|
515
1276
|
};
|
|
516
1277
|
|
|
517
1278
|
// rules/format.ts
|
|
518
|
-
import { Arr as
|
|
519
|
-
var
|
|
520
|
-
var
|
|
1279
|
+
import { Arr as Arr7, Str as Str2 } from "@orieljs/support";
|
|
1280
|
+
var ASCII_RE = /^[\x09\x10\x13\x0A\x0D\x20-\x7E]*$/;
|
|
1281
|
+
var HEX_COLOR_RE = /^#(?:(?:[0-9a-f]{3}){1,2}|(?:[0-9a-f]{4}){1,2})$/i;
|
|
521
1282
|
var ALPHA_RE = /^[A-Za-z]+$/;
|
|
522
1283
|
var ALPHA_NUM_RE = /^[A-Za-z0-9]+$/;
|
|
523
1284
|
var ALPHA_DASH_RE = /^[A-Za-z0-9_-]+$/;
|
|
524
|
-
|
|
1285
|
+
function isEmail(value) {
|
|
1286
|
+
const at = value.indexOf("@");
|
|
1287
|
+
if (at < 1 || value.includes("@", at + 1) || /\s/.test(value)) return false;
|
|
1288
|
+
return value.slice(at + 2, -1).includes(".");
|
|
1289
|
+
}
|
|
1290
|
+
function isUuid(value, version) {
|
|
1291
|
+
if (!Str2.isUuid(value)) return false;
|
|
1292
|
+
if (version === void 0) return true;
|
|
1293
|
+
const hex = value.replaceAll("-", "").toLowerCase();
|
|
1294
|
+
if (version === "nil" || version === "0") return /^0+$/.test(hex);
|
|
1295
|
+
if (version === "max") return /^f+$/.test(hex);
|
|
1296
|
+
return !/^0+$|^f+$/.test(hex) && hex[12] === version && "89ab".includes(hex[16]);
|
|
1297
|
+
}
|
|
1298
|
+
var URL_PROTOCOLS = "aaa|aaas|about|acap|acct|acd|acr|adiumxtra|adt|afp|afs|aim|amss|android|appdata|apt|ark|attachment|aw|barion|beshare|bitcoin|bitcoincash|blob|bolo|browserext|calculator|callto|cap|cast|casts|chrome|chrome-extension|cid|coap|coap\\+tcp|coap\\+ws|coaps|coaps\\+tcp|coaps\\+ws|com-eventbrite-attendee|content|conti|crid|cvs|dab|data|dav|diaspora|dict|did|dis|dlna-playcontainer|dlna-playsingle|dns|dntp|dpp|drm|drop|dtn|dvb|ed2k|elsi|example|facetime|fax|feed|feedready|file|filesystem|finger|first-run-pen-experience|fish|fm|ftp|fuchsia-pkg|geo|gg|git|gizmoproject|go|gopher|graph|gtalk|h323|ham|hcap|hcp|http|https|hxxp|hxxps|hydrazone|iax|icap|icon|im|imap|info|iotdisco|ipn|ipp|ipps|irc|irc6|ircs|iris|iris\\.beep|iris\\.lwz|iris\\.xpc|iris\\.xpcs|isostore|itms|jabber|jar|jms|keyparc|lastfm|ldap|ldaps|leaptofrogans|lorawan|lvlt|magnet|mailserver|mailto|maps|market|message|mid|mms|modem|mongodb|moz|ms-access|ms-browser-extension|ms-calculator|ms-drive-to|ms-enrollment|ms-excel|ms-eyecontrolspeech|ms-gamebarservices|ms-gamingoverlay|ms-getoffice|ms-help|ms-infopath|ms-inputapp|ms-lockscreencomponent-config|ms-media-stream-id|ms-mixedrealitycapture|ms-mobileplans|ms-officeapp|ms-people|ms-project|ms-powerpoint|ms-publisher|ms-restoretabcompanion|ms-screenclip|ms-screensketch|ms-search|ms-search-repair|ms-secondary-screen-controller|ms-secondary-screen-setup|ms-settings|ms-settings-airplanemode|ms-settings-bluetooth|ms-settings-camera|ms-settings-cellular|ms-settings-cloudstorage|ms-settings-connectabledevices|ms-settings-displays-topology|ms-settings-emailandaccounts|ms-settings-language|ms-settings-location|ms-settings-lock|ms-settings-nfctransactions|ms-settings-notifications|ms-settings-power|ms-settings-privacy|ms-settings-proximity|ms-settings-screenrotation|ms-settings-wifi|ms-settings-workplace|ms-spd|ms-sttoverlay|ms-transit-to|ms-useractivityset|ms-virtualtouchpad|ms-visio|ms-walk-to|ms-whiteboard|ms-whiteboard-cmd|ms-word|msnim|msrp|msrps|mss|mtqp|mumble|mupdate|mvn|news|nfs|ni|nih|nntp|notes|ocf|oid|onenote|onenote-cmd|opaquelocktoken|openpgp4fpr|pack|palm|paparazzi|payto|pkcs11|platform|pop|pres|prospero|proxy|pwid|psyc|pttp|qb|query|redis|rediss|reload|res|resource|rmi|rsync|rtmfp|rtmp|rtsp|rtsps|rtspu|s3|secondlife|service|session|sftp|sgn|shttp|sieve|simpleledger|sip|sips|skype|smb|sms|smtp|snews|snmp|soap\\.beep|soap\\.beeps|soldat|spiffe|spotify|ssh|steam|stun|stuns|submit|svn|tag|teamspeak|tel|teliaeid|telnet|tftp|tg|things|thismessage|tip|tn3270|tool|ts3server|turn|turns|tv|udp|unreal|urn|ut2004|v-event|vemmi|ventrilo|videotex|vnc|view-source|wais|webcal|wpid|ws|wss|wtai|wyciwyg|xcon|xcon-userid|xfire|xmlrpc\\.beep|xmlrpc\\.beeps|xmpp|xri|ymsgr|z39\\.50|z39\\.50r|z39\\.50s";
|
|
1299
|
+
var H16 = "[0-9a-f]{1,4}";
|
|
1300
|
+
var OCTET = "(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])";
|
|
1301
|
+
var LS32 = `(?:${H16}:${H16}|(?:${OCTET}\\.){3}${OCTET})`;
|
|
1302
|
+
var IPV6 = [
|
|
1303
|
+
`(?:${H16}:){6}${LS32}`,
|
|
1304
|
+
`::(?:${H16}:){5}${LS32}`,
|
|
1305
|
+
`(?:${H16})?::(?:${H16}:){4}${LS32}`,
|
|
1306
|
+
`(?:(?:${H16}:){0,1}${H16})?::(?:${H16}:){3}${LS32}`,
|
|
1307
|
+
`(?:(?:${H16}:){0,2}${H16})?::(?:${H16}:){2}${LS32}`,
|
|
1308
|
+
`(?:(?:${H16}:){0,3}${H16})?::${H16}:${LS32}`,
|
|
1309
|
+
`(?:(?:${H16}:){0,4}${H16})?::${LS32}`,
|
|
1310
|
+
`(?:(?:${H16}:){0,5}${H16})?::${H16}`,
|
|
1311
|
+
`(?:(?:${H16}:){0,6}${H16})?::`
|
|
1312
|
+
].join("|");
|
|
1313
|
+
var URL_USER = "(?:[_.\\p{L}\\p{N}-]|%[0-9A-Fa-f]{2})+";
|
|
1314
|
+
var URL_DOMAIN = "(?=(?<domain>[\\p{L}\\p{N}\\p{S}\\-_.]+))\\k<domain>(?<=[\\p{L}\\p{N}\\p{S}\\-_.](?:[\\p{L}\\p{N}]|xn--[\\p{L}\\p{N}-]+)\\.?)";
|
|
1315
|
+
function urlPattern(protocols) {
|
|
1316
|
+
return new RegExp(
|
|
1317
|
+
`^(?:${protocols})://(?:(?:${URL_USER}:)?${URL_USER}@)?(?:${URL_DOMAIN}|\\p{Nd}{1,3}\\.\\p{Nd}{1,3}\\.\\p{Nd}{1,3}\\.\\p{Nd}{1,3}|\\[(?:${IPV6})\\])(?::[0-9]+)?(?:/(?:[\\p{L}\\p{N}\\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*(?:\\?(?:[\\p{L}\\p{N}\\-._~!$&'\\[\\]()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?(?:#(?:[\\p{L}\\p{N}\\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*)?\\n?$`,
|
|
1318
|
+
"iu"
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
var URL_RE = urlPattern(URL_PROTOCOLS);
|
|
1322
|
+
var urlPatterns = /* @__PURE__ */ new Map();
|
|
1323
|
+
function isUrl(value, protocols) {
|
|
1324
|
+
if (typeof value !== "string") return false;
|
|
1325
|
+
if (protocols.length === 0) return URL_RE.test(value);
|
|
1326
|
+
const list = protocols.join("|");
|
|
1327
|
+
let pattern = urlPatterns.get(list);
|
|
1328
|
+
if (pattern === void 0) {
|
|
1329
|
+
try {
|
|
1330
|
+
pattern = urlPattern(list);
|
|
1331
|
+
} catch (error) {
|
|
1332
|
+
throw new InvalidRuleParameterError(
|
|
1333
|
+
"url",
|
|
1334
|
+
protocols.join(","),
|
|
1335
|
+
error instanceof Error ? error.message : String(error)
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
urlPatterns.set(list, pattern);
|
|
1339
|
+
}
|
|
1340
|
+
return pattern.test(value);
|
|
1341
|
+
}
|
|
1342
|
+
function edges(value, parameters, end) {
|
|
1343
|
+
if (typeof value !== "string" && typeof value !== "number") return false;
|
|
1344
|
+
const text = String(value);
|
|
1345
|
+
return parameters.some((needle) => needle !== "" && (end ? text.endsWith(needle) : text.startsWith(needle)));
|
|
1346
|
+
}
|
|
1347
|
+
var isText = (value) => typeof value === "string" || typeof value === "number";
|
|
1348
|
+
var listed3 = (ctx) => ({ values: ctx.parameters.join(", ") });
|
|
525
1349
|
function compileRegex(raw) {
|
|
526
1350
|
const match = /^\/(.*)\/([a-z]*)$/.exec(raw);
|
|
527
1351
|
try {
|
|
@@ -530,106 +1354,38 @@ function compileRegex(raw) {
|
|
|
530
1354
|
throw new InvalidRuleParameterError("regex", raw, error instanceof Error ? error.message : String(error));
|
|
531
1355
|
}
|
|
532
1356
|
}
|
|
533
|
-
var DATE_SHAPES = [
|
|
534
|
-
/^\d{4}-\d{1,2}-\d{1,2}(?:[T\s].*)?$/,
|
|
535
|
-
/^\d{1,2}[/.-]\d{1,2}[/.-]\d{2,4}(?:\s.*)?$/,
|
|
536
|
-
/^(?=.*\d).*\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\b/i
|
|
537
|
-
];
|
|
538
|
-
var DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
539
|
-
function parseDate(value) {
|
|
540
|
-
const trimmed = value.trim();
|
|
541
|
-
if (!DATE_SHAPES.some((shape) => shape.test(trimmed))) return Number.NaN;
|
|
542
|
-
const dateOnly = DATE_ONLY_RE.exec(trimmed);
|
|
543
|
-
if (dateOnly) {
|
|
544
|
-
const [, y, m, d] = dateOnly.map(Number);
|
|
545
|
-
const local = new Date(y, m - 1, d);
|
|
546
|
-
return local.getMonth() === m - 1 && local.getDate() === d ? local.getTime() : Number.NaN;
|
|
547
|
-
}
|
|
548
|
-
return Date.parse(trimmed);
|
|
549
|
-
}
|
|
550
|
-
function startOfDay(offsetDays) {
|
|
551
|
-
const date = /* @__PURE__ */ new Date();
|
|
552
|
-
date.setHours(0, 0, 0, 0);
|
|
553
|
-
date.setDate(date.getDate() + offsetDays);
|
|
554
|
-
return date.getTime();
|
|
555
|
-
}
|
|
556
|
-
var RELATIVE_RE = /^([+-]\d+)\s*(second|minute|hour|day|week|month|year)s?$/i;
|
|
557
|
-
function relativeDate(param) {
|
|
558
|
-
switch (param.trim().toLowerCase()) {
|
|
559
|
-
case "now":
|
|
560
|
-
return Date.now();
|
|
561
|
-
case "today":
|
|
562
|
-
return startOfDay(0);
|
|
563
|
-
case "tomorrow":
|
|
564
|
-
return startOfDay(1);
|
|
565
|
-
case "yesterday":
|
|
566
|
-
return startOfDay(-1);
|
|
567
|
-
}
|
|
568
|
-
const match = RELATIVE_RE.exec(param.trim());
|
|
569
|
-
if (!match) return void 0;
|
|
570
|
-
const amount = Number(match[1]);
|
|
571
|
-
const date = /* @__PURE__ */ new Date();
|
|
572
|
-
switch (match[2].toLowerCase()) {
|
|
573
|
-
case "second":
|
|
574
|
-
date.setSeconds(date.getSeconds() + amount);
|
|
575
|
-
break;
|
|
576
|
-
case "minute":
|
|
577
|
-
date.setMinutes(date.getMinutes() + amount);
|
|
578
|
-
break;
|
|
579
|
-
case "hour":
|
|
580
|
-
date.setHours(date.getHours() + amount);
|
|
581
|
-
break;
|
|
582
|
-
case "day":
|
|
583
|
-
date.setDate(date.getDate() + amount);
|
|
584
|
-
break;
|
|
585
|
-
case "week":
|
|
586
|
-
date.setDate(date.getDate() + amount * 7);
|
|
587
|
-
break;
|
|
588
|
-
case "month":
|
|
589
|
-
date.setMonth(date.getMonth() + amount);
|
|
590
|
-
break;
|
|
591
|
-
case "year":
|
|
592
|
-
date.setFullYear(date.getFullYear() + amount);
|
|
593
|
-
break;
|
|
594
|
-
}
|
|
595
|
-
return date.getTime();
|
|
596
|
-
}
|
|
597
|
-
var FIELD_REFERENCE_RE = /^[A-Za-z_][\w-]*(?:\.[\w-]+)*$/;
|
|
598
|
-
function resolveDateParam(rule, param, data) {
|
|
599
|
-
const fieldValue = Arr2.get(data, param);
|
|
600
|
-
if (typeof fieldValue === "string") {
|
|
601
|
-
const parsed = parseDate(fieldValue);
|
|
602
|
-
if (!Number.isNaN(parsed)) return parsed;
|
|
603
|
-
}
|
|
604
|
-
const relative = relativeDate(param);
|
|
605
|
-
if (relative !== void 0) return relative;
|
|
606
|
-
const literal = parseDate(param);
|
|
607
|
-
if (!Number.isNaN(literal)) return literal;
|
|
608
|
-
if (FIELD_REFERENCE_RE.test(param)) return Number.NaN;
|
|
609
|
-
throw new InvalidRuleParameterError(rule, param, 'expected a date, today/tomorrow/yesterday/now, "+N unit", or a field name');
|
|
610
|
-
}
|
|
611
|
-
function compareDate(ctx, rule) {
|
|
612
|
-
const target = resolveDateParam(rule, ctx.parameters[0] ?? "", ctx.data);
|
|
613
|
-
if (typeof ctx.value !== "string") return false;
|
|
614
|
-
const value = parseDate(ctx.value);
|
|
615
|
-
if (Number.isNaN(value) || Number.isNaN(target)) return false;
|
|
616
|
-
return rule === "before" ? value < target : value > target;
|
|
617
|
-
}
|
|
618
1357
|
var formatRules = {
|
|
619
|
-
email: { validate: (ctx) => typeof ctx.value === "string" &&
|
|
620
|
-
url: {
|
|
1358
|
+
email: { validate: (ctx) => typeof ctx.value === "string" && isEmail(ctx.value), messageKey: () => "email" },
|
|
1359
|
+
url: { validate: (ctx) => isUrl(ctx.value, ctx.parameters), messageKey: () => "url" },
|
|
1360
|
+
uuid: { validate: (ctx) => isUuid(ctx.value, ctx.parameters[0]), messageKey: () => "uuid" },
|
|
1361
|
+
ulid: { validate: (ctx) => Str2.isUlid(ctx.value), messageKey: () => "ulid" },
|
|
1362
|
+
ascii: { validate: (ctx) => isText(ctx.value) && ASCII_RE.test(String(ctx.value)), messageKey: () => "ascii" },
|
|
1363
|
+
hex_color: { validate: (ctx) => typeof ctx.value === "string" && HEX_COLOR_RE.test(ctx.value), messageKey: () => "hex_color" },
|
|
1364
|
+
/** Text that parses as JSON (a JSON body's own numbers and objects are not "a JSON string"). */
|
|
1365
|
+
json: {
|
|
621
1366
|
validate: (ctx) => {
|
|
622
1367
|
if (typeof ctx.value !== "string") return false;
|
|
623
1368
|
try {
|
|
624
|
-
|
|
1369
|
+
JSON.parse(ctx.value);
|
|
625
1370
|
return true;
|
|
626
1371
|
} catch {
|
|
627
1372
|
return false;
|
|
628
1373
|
}
|
|
629
1374
|
},
|
|
630
|
-
messageKey: () => "
|
|
1375
|
+
messageKey: () => "json"
|
|
1376
|
+
},
|
|
1377
|
+
starts_with: { validate: (ctx) => edges(ctx.value, ctx.parameters, false), messageKey: () => "starts_with", replacements: listed3 },
|
|
1378
|
+
ends_with: { validate: (ctx) => edges(ctx.value, ctx.parameters, true), messageKey: () => "ends_with", replacements: listed3 },
|
|
1379
|
+
doesnt_start_with: {
|
|
1380
|
+
validate: (ctx) => isText(ctx.value) && !edges(ctx.value, ctx.parameters, false),
|
|
1381
|
+
messageKey: () => "doesnt_start_with",
|
|
1382
|
+
replacements: listed3
|
|
1383
|
+
},
|
|
1384
|
+
doesnt_end_with: {
|
|
1385
|
+
validate: (ctx) => isText(ctx.value) && !edges(ctx.value, ctx.parameters, true),
|
|
1386
|
+
messageKey: () => "doesnt_end_with",
|
|
1387
|
+
replacements: listed3
|
|
631
1388
|
},
|
|
632
|
-
uuid: { validate: (ctx) => typeof ctx.value === "string" && UUID_RE.test(ctx.value), messageKey: () => "uuid" },
|
|
633
1389
|
alpha: { validate: (ctx) => typeof ctx.value === "string" && ALPHA_RE.test(ctx.value), messageKey: () => "alpha" },
|
|
634
1390
|
alpha_num: {
|
|
635
1391
|
validate: (ctx) => typeof ctx.value === "string" && ALPHA_NUM_RE.test(ctx.value),
|
|
@@ -651,46 +1407,120 @@ var formatRules = {
|
|
|
651
1407
|
validate: (ctx) => typeof ctx.value === "string" && compileRegex(ctx.parameters[0] ?? "").test(ctx.value),
|
|
652
1408
|
messageKey: () => "regex"
|
|
653
1409
|
},
|
|
1410
|
+
/** Laravel's validateNotRegex(): text or a number that the pattern does not match. */
|
|
1411
|
+
not_regex: {
|
|
1412
|
+
validate: (ctx) => isText(ctx.value) && !compileRegex(ctx.parameters[0] ?? "").test(String(ctx.value)),
|
|
1413
|
+
messageKey: () => "not_regex"
|
|
1414
|
+
},
|
|
1415
|
+
/** `confirmed` matches `<field>_confirmation`; `confirmed:repeat_password` names the other field (Laravel 12). */
|
|
654
1416
|
confirmed: {
|
|
655
|
-
validate: (ctx) => ctx.value ===
|
|
1417
|
+
validate: (ctx) => ctx.value === Arr7.get(ctx.data, ctx.parameters[0] || `${ctx.attribute}_confirmation`),
|
|
656
1418
|
messageKey: () => "confirmed"
|
|
657
1419
|
},
|
|
658
1420
|
same: {
|
|
659
|
-
validate: (ctx) => ctx.value ===
|
|
1421
|
+
validate: (ctx) => ctx.value === Arr7.get(ctx.data, ctx.parameters[0] ?? ""),
|
|
660
1422
|
messageKey: () => "same",
|
|
661
|
-
replacements: (ctx) => ({ other:
|
|
1423
|
+
replacements: (ctx) => ({ other: displayName(ctx, ctx.parameters[0] ?? "") })
|
|
662
1424
|
},
|
|
663
1425
|
different: {
|
|
664
|
-
validate: (ctx) => ctx.value !==
|
|
1426
|
+
validate: (ctx) => ctx.value !== Arr7.get(ctx.data, ctx.parameters[0] ?? ""),
|
|
665
1427
|
messageKey: () => "different",
|
|
666
|
-
replacements: (ctx) => ({ other:
|
|
1428
|
+
replacements: (ctx) => ({ other: displayName(ctx, ctx.parameters[0] ?? "") })
|
|
667
1429
|
},
|
|
668
|
-
accepted: { implicit: true, validate: (ctx) => ACCEPTED_VALUES.includes(ctx.value), messageKey: () => "accepted" },
|
|
669
1430
|
digits: {
|
|
670
1431
|
validate: (ctx) => typeof ctx.value !== "object" && new RegExp(`^\\d{${ctx.parameters[0]}}$`).test(String(ctx.value)),
|
|
671
1432
|
messageKey: () => "digits",
|
|
672
1433
|
replacements: (ctx) => ({ digits: ctx.parameters[0] ?? "" })
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
|
|
1437
|
+
// rules/network.ts
|
|
1438
|
+
import { lookup } from "dns/promises";
|
|
1439
|
+
import { isIP, isIPv4, isIPv6 } from "net";
|
|
1440
|
+
var MAC_RE = /^(?:[0-9a-f]{2}([-:])(?:[0-9a-f]{2}\1){4}[0-9a-f]{2}|[0-9a-f]{4}\.[0-9a-f]{4}\.[0-9a-f]{4})$/i;
|
|
1441
|
+
var resolver = { lookup };
|
|
1442
|
+
async function hasAddress(value) {
|
|
1443
|
+
if (typeof value !== "string") return false;
|
|
1444
|
+
let host;
|
|
1445
|
+
try {
|
|
1446
|
+
host = new URL(value).hostname;
|
|
1447
|
+
} catch {
|
|
1448
|
+
return false;
|
|
1449
|
+
}
|
|
1450
|
+
if (host === "") return false;
|
|
1451
|
+
try {
|
|
1452
|
+
return (await resolver.lookup(host.replace(/^\[|\]$/g, ""), { all: true })).length > 0;
|
|
1453
|
+
} catch {
|
|
1454
|
+
return false;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
var networkRules = {
|
|
1458
|
+
ip: { validate: (ctx) => typeof ctx.value === "string" && isIP(ctx.value) !== 0, messageKey: () => "ip" },
|
|
1459
|
+
ipv4: { validate: (ctx) => typeof ctx.value === "string" && isIPv4(ctx.value), messageKey: () => "ipv4" },
|
|
1460
|
+
ipv6: { validate: (ctx) => typeof ctx.value === "string" && isIPv6(ctx.value), messageKey: () => "ipv6" },
|
|
1461
|
+
mac_address: { validate: (ctx) => typeof ctx.value === "string" && MAC_RE.test(ctx.value), messageKey: () => "mac_address" },
|
|
1462
|
+
active_url: { validate: (ctx) => hasAddress(ctx.value), messageKey: () => "active_url" }
|
|
1463
|
+
};
|
|
1464
|
+
|
|
1465
|
+
// rules/numeric.ts
|
|
1466
|
+
var onlyDigits = (value) => (typeof value === "string" || typeof value === "number") && /^\d+$/.test(String(value));
|
|
1467
|
+
var MAX_EXPONENT = 1e3;
|
|
1468
|
+
function decimal(text) {
|
|
1469
|
+
const [, sign = "", whole = "", fraction = "", power = "0"] = /^\s*([+-]?)(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?\s*$/.exec(text) ?? [];
|
|
1470
|
+
if (Math.abs(Number(power)) > MAX_EXPONENT) return void 0;
|
|
1471
|
+
return { digits: BigInt(`${sign}${whole}${fraction}` || "0"), exponent: Number(power) - fraction.length };
|
|
1472
|
+
}
|
|
1473
|
+
function isMultipleOf(value, divisor) {
|
|
1474
|
+
if (!isNumeric(value) || !isNumeric(divisor)) return false;
|
|
1475
|
+
const a = decimal(String(value));
|
|
1476
|
+
const b = decimal(divisor);
|
|
1477
|
+
if (a === void 0 || b === void 0) return false;
|
|
1478
|
+
const exponent = Math.min(a.exponent, b.exponent);
|
|
1479
|
+
const numerator = a.digits * 10n ** BigInt(a.exponent - exponent);
|
|
1480
|
+
const denominator = b.digits * 10n ** BigInt(b.exponent - exponent);
|
|
1481
|
+
if (denominator === 0n) return false;
|
|
1482
|
+
return numerator % denominator === 0n;
|
|
1483
|
+
}
|
|
1484
|
+
function hasDecimals(value, min, max) {
|
|
1485
|
+
if (!isNumeric(value)) return false;
|
|
1486
|
+
const match = /^[+-]?\d*(?:\.(\d*))?$/.exec(String(value).trim());
|
|
1487
|
+
if (match === null) return false;
|
|
1488
|
+
const places = match[1] ?? "";
|
|
1489
|
+
return max === void 0 ? places.length === Number(min) : places.length >= Number(min) && places.length <= Number(max);
|
|
1490
|
+
}
|
|
1491
|
+
var length = (value) => String(value).length;
|
|
1492
|
+
var numericRules = {
|
|
1493
|
+
decimal: {
|
|
1494
|
+
validate: (ctx) => hasDecimals(ctx.value, ctx.parameters[0] ?? "", ctx.parameters[1]),
|
|
1495
|
+
messageKey: () => "decimal",
|
|
1496
|
+
replacements: (ctx) => ({ decimal: ctx.parameters[1] === void 0 ? ctx.parameters[0] ?? "" : `${ctx.parameters[0]}-${ctx.parameters[1]}` })
|
|
673
1497
|
},
|
|
674
|
-
|
|
675
|
-
validate: (ctx) =>
|
|
676
|
-
messageKey: () => "
|
|
1498
|
+
digits_between: {
|
|
1499
|
+
validate: (ctx) => onlyDigits(ctx.value) && length(ctx.value) >= Number(ctx.parameters[0]) && length(ctx.value) <= Number(ctx.parameters[1]),
|
|
1500
|
+
messageKey: () => "digits_between",
|
|
1501
|
+
replacements: (ctx) => ({ min: ctx.parameters[0] ?? "", max: ctx.parameters[1] ?? "" })
|
|
1502
|
+
},
|
|
1503
|
+
max_digits: {
|
|
1504
|
+
validate: (ctx) => onlyDigits(ctx.value) && length(ctx.value) <= Number(ctx.parameters[0]),
|
|
1505
|
+
messageKey: () => "max_digits",
|
|
1506
|
+
replacements: (ctx) => ({ max: ctx.parameters[0] ?? "" })
|
|
677
1507
|
},
|
|
678
|
-
|
|
679
|
-
validate: (ctx) =>
|
|
680
|
-
messageKey: () => "
|
|
681
|
-
replacements: (ctx) => ({
|
|
1508
|
+
min_digits: {
|
|
1509
|
+
validate: (ctx) => onlyDigits(ctx.value) && length(ctx.value) >= Number(ctx.parameters[0]),
|
|
1510
|
+
messageKey: () => "min_digits",
|
|
1511
|
+
replacements: (ctx) => ({ min: ctx.parameters[0] ?? "" })
|
|
682
1512
|
},
|
|
683
|
-
|
|
684
|
-
validate: (ctx) =>
|
|
685
|
-
messageKey: () => "
|
|
686
|
-
replacements: (ctx) => ({
|
|
1513
|
+
multiple_of: {
|
|
1514
|
+
validate: (ctx) => isMultipleOf(ctx.value, ctx.parameters[0] ?? ""),
|
|
1515
|
+
messageKey: () => "multiple_of",
|
|
1516
|
+
replacements: (ctx) => ({ value: ctx.parameters[0] ?? "" })
|
|
687
1517
|
}
|
|
688
1518
|
};
|
|
689
1519
|
|
|
690
1520
|
// rules/presence.ts
|
|
691
|
-
import { Arr as
|
|
1521
|
+
import { Arr as Arr8 } from "@orieljs/support";
|
|
692
1522
|
function isFieldPresent(field, data) {
|
|
693
|
-
return
|
|
1523
|
+
return Arr8.has(data, field) && !isEmptyValue(Arr8.get(data, field));
|
|
694
1524
|
}
|
|
695
1525
|
function anyPresent(fields, data) {
|
|
696
1526
|
return fields.some((field) => isFieldPresent(field, data));
|
|
@@ -698,6 +1528,13 @@ function anyPresent(fields, data) {
|
|
|
698
1528
|
function allPresent(fields, data) {
|
|
699
1529
|
return fields.every((field) => isFieldPresent(field, data));
|
|
700
1530
|
}
|
|
1531
|
+
var hasAnyKey = (fields, data) => fields.some((field) => Arr8.has(data, field));
|
|
1532
|
+
var hasEveryKey = (fields, data) => fields.length > 0 && fields.every((field) => Arr8.has(data, field));
|
|
1533
|
+
var otherAndFirstValue = (ctx) => ({
|
|
1534
|
+
other: displayName(ctx, ctx.parameters[0] ?? ""),
|
|
1535
|
+
value: displayableValue(ctx.parameters[1])
|
|
1536
|
+
});
|
|
1537
|
+
var otherName = (ctx) => ({ other: displayName(ctx, ctx.parameters[0] ?? "") });
|
|
701
1538
|
var presenceRules = {
|
|
702
1539
|
required: {
|
|
703
1540
|
implicit: true,
|
|
@@ -706,89 +1543,160 @@ var presenceRules = {
|
|
|
706
1543
|
},
|
|
707
1544
|
required_if: {
|
|
708
1545
|
implicit: true,
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
const applies = values.includes(String(Arr3.get(ctx.data, other)));
|
|
712
|
-
return !applies || !isEmptyValue(ctx.value);
|
|
713
|
-
},
|
|
1546
|
+
// Laravel: nothing is required while the other field is missing.
|
|
1547
|
+
validate: (ctx) => !Arr8.has(ctx.data, ctx.parameters[0] ?? "") || !otherFieldMatches(ctx) || !isEmptyValue(ctx.value),
|
|
714
1548
|
messageKey: () => "required_if",
|
|
715
|
-
replacements:
|
|
1549
|
+
replacements: otherAndValue
|
|
716
1550
|
},
|
|
717
1551
|
required_unless: {
|
|
718
1552
|
implicit: true,
|
|
719
|
-
validate: (ctx) =>
|
|
720
|
-
const [other, ...values] = ctx.parameters;
|
|
721
|
-
const applies = !values.includes(String(Arr3.get(ctx.data, other)));
|
|
722
|
-
return !applies || !isEmptyValue(ctx.value);
|
|
723
|
-
},
|
|
1553
|
+
validate: (ctx) => otherFieldMatches(ctx) || !isEmptyValue(ctx.value),
|
|
724
1554
|
messageKey: () => "required_unless",
|
|
725
|
-
replacements:
|
|
1555
|
+
replacements: otherAndValues
|
|
726
1556
|
},
|
|
727
1557
|
required_with: {
|
|
728
1558
|
implicit: true,
|
|
729
1559
|
validate: (ctx) => !anyPresent(ctx.parameters, ctx.data) || !isEmptyValue(ctx.value),
|
|
730
1560
|
messageKey: () => "required_with",
|
|
731
|
-
replacements:
|
|
1561
|
+
replacements: fieldList
|
|
732
1562
|
},
|
|
733
1563
|
required_with_all: {
|
|
734
1564
|
implicit: true,
|
|
735
1565
|
validate: (ctx) => !allPresent(ctx.parameters, ctx.data) || !isEmptyValue(ctx.value),
|
|
736
1566
|
messageKey: () => "required_with_all",
|
|
737
|
-
replacements:
|
|
1567
|
+
replacements: fieldList
|
|
738
1568
|
},
|
|
739
1569
|
required_without: {
|
|
740
1570
|
implicit: true,
|
|
741
1571
|
validate: (ctx) => ctx.parameters.every((f) => isFieldPresent(f, ctx.data)) || !isEmptyValue(ctx.value),
|
|
742
1572
|
messageKey: () => "required_without",
|
|
743
|
-
replacements:
|
|
1573
|
+
replacements: fieldList
|
|
744
1574
|
},
|
|
745
1575
|
required_without_all: {
|
|
746
1576
|
implicit: true,
|
|
747
1577
|
validate: (ctx) => anyPresent(ctx.parameters, ctx.data) || !isEmptyValue(ctx.value),
|
|
748
1578
|
messageKey: () => "required_without_all",
|
|
749
|
-
replacements:
|
|
1579
|
+
replacements: fieldList
|
|
1580
|
+
},
|
|
1581
|
+
required_if_accepted: {
|
|
1582
|
+
implicit: true,
|
|
1583
|
+
validate: (ctx) => !otherAccepted(ctx.data, ctx.parameters[0]) || !isEmptyValue(ctx.value),
|
|
1584
|
+
messageKey: () => "required_if_accepted",
|
|
1585
|
+
replacements: otherName
|
|
1586
|
+
},
|
|
1587
|
+
required_if_declined: {
|
|
1588
|
+
implicit: true,
|
|
1589
|
+
validate: (ctx) => !otherDeclined(ctx.data, ctx.parameters[0]) || !isEmptyValue(ctx.value),
|
|
1590
|
+
messageKey: () => "required_if_declined",
|
|
1591
|
+
replacements: otherName
|
|
1592
|
+
},
|
|
1593
|
+
/** The key is there, whatever its value (null and '' included). */
|
|
1594
|
+
present: { implicit: true, validate: (ctx) => Arr8.has(ctx.data, ctx.attribute), messageKey: () => "present" },
|
|
1595
|
+
present_if: {
|
|
1596
|
+
implicit: true,
|
|
1597
|
+
validate: (ctx) => !otherFieldMatches(ctx) || Arr8.has(ctx.data, ctx.attribute),
|
|
1598
|
+
messageKey: () => "present_if",
|
|
1599
|
+
replacements: otherAndValue
|
|
1600
|
+
},
|
|
1601
|
+
present_unless: {
|
|
1602
|
+
implicit: true,
|
|
1603
|
+
validate: (ctx) => otherFieldMatches(ctx) || Arr8.has(ctx.data, ctx.attribute),
|
|
1604
|
+
messageKey: () => "present_unless",
|
|
1605
|
+
replacements: otherAndFirstValue
|
|
1606
|
+
},
|
|
1607
|
+
present_with: {
|
|
1608
|
+
implicit: true,
|
|
1609
|
+
validate: (ctx) => !hasAnyKey(ctx.parameters, ctx.data) || Arr8.has(ctx.data, ctx.attribute),
|
|
1610
|
+
messageKey: () => "present_with",
|
|
1611
|
+
replacements: fieldList
|
|
1612
|
+
},
|
|
1613
|
+
present_with_all: {
|
|
1614
|
+
implicit: true,
|
|
1615
|
+
validate: (ctx) => !hasEveryKey(ctx.parameters, ctx.data) || Arr8.has(ctx.data, ctx.attribute),
|
|
1616
|
+
messageKey: () => "present_with_all",
|
|
1617
|
+
replacements: fieldList
|
|
1618
|
+
},
|
|
1619
|
+
/** A key that is there must not be empty; a missing key passes. */
|
|
1620
|
+
filled: { implicit: true, validate: (ctx) => !Arr8.has(ctx.data, ctx.attribute) || !isEmptyValue(ctx.value), messageKey: () => "filled" },
|
|
1621
|
+
/** The key is not there at all (null counts as there). */
|
|
1622
|
+
missing: { implicit: true, validate: (ctx) => !Arr8.has(ctx.data, ctx.attribute), messageKey: () => "missing" },
|
|
1623
|
+
missing_if: {
|
|
1624
|
+
implicit: true,
|
|
1625
|
+
validate: (ctx) => !otherFieldMatches(ctx) || !Arr8.has(ctx.data, ctx.attribute),
|
|
1626
|
+
messageKey: () => "missing_if",
|
|
1627
|
+
replacements: otherAndValue
|
|
1628
|
+
},
|
|
1629
|
+
missing_unless: {
|
|
1630
|
+
implicit: true,
|
|
1631
|
+
validate: (ctx) => otherFieldMatches(ctx) || !Arr8.has(ctx.data, ctx.attribute),
|
|
1632
|
+
messageKey: () => "missing_unless",
|
|
1633
|
+
replacements: otherAndFirstValue
|
|
1634
|
+
},
|
|
1635
|
+
missing_with: {
|
|
1636
|
+
implicit: true,
|
|
1637
|
+
validate: (ctx) => !hasAnyKey(ctx.parameters, ctx.data) || !Arr8.has(ctx.data, ctx.attribute),
|
|
1638
|
+
messageKey: () => "missing_with",
|
|
1639
|
+
replacements: fieldList
|
|
1640
|
+
},
|
|
1641
|
+
missing_with_all: {
|
|
1642
|
+
implicit: true,
|
|
1643
|
+
validate: (ctx) => !hasEveryKey(ctx.parameters, ctx.data) || !Arr8.has(ctx.data, ctx.attribute),
|
|
1644
|
+
messageKey: () => "missing_with_all",
|
|
1645
|
+
replacements: fieldList
|
|
750
1646
|
}
|
|
751
1647
|
};
|
|
752
1648
|
|
|
753
|
-
// rules/
|
|
754
|
-
|
|
755
|
-
var
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
string: { validate: (ctx) => typeof ctx.value === "string", messageKey: () => "string" },
|
|
762
|
-
integer: {
|
|
763
|
-
validate: (ctx) => typeof ctx.value !== "object" && /^-?\d+$/.test(String(ctx.value).trim()),
|
|
764
|
-
messageKey: () => "integer"
|
|
1649
|
+
// rules/prohibited.ts
|
|
1650
|
+
import { Arr as Arr9 } from "@orieljs/support";
|
|
1651
|
+
var prohibitedRules = {
|
|
1652
|
+
prohibited: { validate: (ctx) => isEmptyValue(ctx.value), messageKey: () => "prohibited" },
|
|
1653
|
+
prohibited_if: {
|
|
1654
|
+
validate: (ctx) => !otherFieldMatches(ctx) || isEmptyValue(ctx.value),
|
|
1655
|
+
messageKey: () => "prohibited_if",
|
|
1656
|
+
replacements: otherAndValue
|
|
765
1657
|
},
|
|
766
|
-
|
|
767
|
-
validate: (ctx) =>
|
|
768
|
-
messageKey: () => "
|
|
1658
|
+
prohibited_unless: {
|
|
1659
|
+
validate: (ctx) => otherFieldMatches(ctx) || isEmptyValue(ctx.value),
|
|
1660
|
+
messageKey: () => "prohibited_unless",
|
|
1661
|
+
replacements: otherAndValues
|
|
1662
|
+
},
|
|
1663
|
+
prohibited_if_accepted: {
|
|
1664
|
+
validate: (ctx) => !otherAccepted(ctx.data, ctx.parameters[0]) || isEmptyValue(ctx.value),
|
|
1665
|
+
messageKey: () => "prohibited_if_accepted",
|
|
1666
|
+
replacements: (ctx) => ({ other: displayName(ctx, ctx.parameters[0] ?? "") })
|
|
1667
|
+
},
|
|
1668
|
+
prohibited_if_declined: {
|
|
1669
|
+
validate: (ctx) => !otherDeclined(ctx.data, ctx.parameters[0]) || isEmptyValue(ctx.value),
|
|
1670
|
+
messageKey: () => "prohibited_if_declined",
|
|
1671
|
+
replacements: (ctx) => ({ other: displayName(ctx, ctx.parameters[0] ?? "") })
|
|
769
1672
|
},
|
|
770
|
-
|
|
771
|
-
|
|
1673
|
+
/** While this field has a value, every field named must be missing or empty. */
|
|
1674
|
+
prohibits: {
|
|
1675
|
+
validate: (ctx) => isEmptyValue(ctx.value) || ctx.parameters.every((field) => isEmptyValue(Arr9.get(ctx.data, field))),
|
|
1676
|
+
messageKey: () => "prohibits",
|
|
1677
|
+
replacements: (ctx) => ({ other: fieldList(ctx).values })
|
|
1678
|
+
}
|
|
772
1679
|
};
|
|
773
1680
|
|
|
774
1681
|
// rules/size.ts
|
|
1682
|
+
import { Arr as Arr10 } from "@orieljs/support";
|
|
775
1683
|
function hasAny(ctx, names) {
|
|
776
1684
|
return names.some((name) => ctx.ruleNames.includes(name));
|
|
777
1685
|
}
|
|
778
|
-
|
|
1686
|
+
var NUMERIC_RULES = ["numeric", "integer", "decimal"];
|
|
1687
|
+
function isPlainObject2(value) {
|
|
779
1688
|
if (typeof value !== "object" || value === null) return false;
|
|
780
1689
|
const proto = Object.getPrototypeOf(value);
|
|
781
1690
|
return proto === Object.prototype || proto === null;
|
|
782
1691
|
}
|
|
783
|
-
function measure(ctx) {
|
|
784
|
-
|
|
785
|
-
if (hasAny(ctx, ["numeric", "integer"])) {
|
|
1692
|
+
function measure(ctx, value = ctx.value, numeric = hasAny(ctx, NUMERIC_RULES)) {
|
|
1693
|
+
if (numeric) {
|
|
786
1694
|
return { type: "numeric", size: isNumeric(value) ? Number(value) : stringLength(value) };
|
|
787
1695
|
}
|
|
788
1696
|
if (Array.isArray(value)) return { type: "array", size: value.length };
|
|
789
1697
|
if (isUploadedFileLike(value)) return { type: "file", size: value.size / 1024 };
|
|
790
1698
|
if (hasAny(ctx, ["file", "image"])) return { type: "file", size: Number.NaN };
|
|
791
|
-
if (
|
|
1699
|
+
if (isPlainObject2(value)) return { type: "array", size: Object.keys(value).length };
|
|
792
1700
|
if (hasAny(ctx, ["array"])) return { type: "array", size: stringLength(value) };
|
|
793
1701
|
return { type: "string", size: stringLength(value) };
|
|
794
1702
|
}
|
|
@@ -799,6 +1707,41 @@ function stringLength(value) {
|
|
|
799
1707
|
function sizeOf(ctx) {
|
|
800
1708
|
return measure(ctx).size;
|
|
801
1709
|
}
|
|
1710
|
+
var COMPARE2 = {
|
|
1711
|
+
gt: (a, b) => a > b,
|
|
1712
|
+
gte: (a, b) => a >= b,
|
|
1713
|
+
lt: (a, b) => a < b,
|
|
1714
|
+
lte: (a, b) => a <= b
|
|
1715
|
+
};
|
|
1716
|
+
function kind(value) {
|
|
1717
|
+
if (value === null || value === void 0) return "null";
|
|
1718
|
+
if (Array.isArray(value) || isPlainObject2(value)) return "array";
|
|
1719
|
+
if (typeof value === "number") return Number.isInteger(value) ? "integer" : "double";
|
|
1720
|
+
return typeof value;
|
|
1721
|
+
}
|
|
1722
|
+
function compareSizes(ctx, rule) {
|
|
1723
|
+
const parameter = ctx.parameters[0] ?? "";
|
|
1724
|
+
const other = Arr10.get(ctx.data, parameter);
|
|
1725
|
+
const numeric = hasAny(ctx, NUMERIC_RULES) || isNumeric(ctx.value);
|
|
1726
|
+
if ((other === null || other === void 0) && isNumeric(ctx.value) && isNumeric(parameter)) {
|
|
1727
|
+
return COMPARE2[rule](Number(String(ctx.value).trim()), Number(parameter.trim()));
|
|
1728
|
+
}
|
|
1729
|
+
if (isNumeric(parameter)) return false;
|
|
1730
|
+
if (numeric && isNumeric(ctx.value) && isNumeric(other)) return COMPARE2[rule](Number(String(ctx.value).trim()), Number(String(other).trim()));
|
|
1731
|
+
if (kind(ctx.value) !== kind(other)) return false;
|
|
1732
|
+
return COMPARE2[rule](measure(ctx, ctx.value, numeric).size, measure(ctx, other, numeric).size);
|
|
1733
|
+
}
|
|
1734
|
+
function comparedValue(ctx) {
|
|
1735
|
+
const parameter = ctx.parameters[0] ?? "";
|
|
1736
|
+
const other = Arr10.get(ctx.data, parameter);
|
|
1737
|
+
if (other === null || other === void 0) return { value: displayName(ctx, parameter) };
|
|
1738
|
+
return { value: String(measure(ctx, other, hasAny(ctx, NUMERIC_RULES) || isNumeric(ctx.value)).size) };
|
|
1739
|
+
}
|
|
1740
|
+
var comparison2 = (rule) => ({
|
|
1741
|
+
validate: (ctx) => compareSizes(ctx, rule),
|
|
1742
|
+
messageKey: (ctx) => `${rule}.${measure(ctx, ctx.value, hasAny(ctx, NUMERIC_RULES) || isNumeric(ctx.value)).type}`,
|
|
1743
|
+
replacements: comparedValue
|
|
1744
|
+
});
|
|
802
1745
|
function sizeType(ctx) {
|
|
803
1746
|
return measure(ctx).type;
|
|
804
1747
|
}
|
|
@@ -826,16 +1769,100 @@ var sizeRules = {
|
|
|
826
1769
|
messageKey: (ctx) => `size.${sizeType(ctx)}`,
|
|
827
1770
|
replacements: (ctx) => ({ size: ctx.parameters[0] ?? "" })
|
|
828
1771
|
},
|
|
829
|
-
|
|
830
|
-
|
|
1772
|
+
gt: comparison2("gt"),
|
|
1773
|
+
gte: comparison2("gte"),
|
|
1774
|
+
lt: comparison2("lt"),
|
|
1775
|
+
lte: comparison2("lte"),
|
|
1776
|
+
in: { validate: (ctx) => isListed(ctx.value, ctx.parameters, ctx.ruleNames), messageKey: () => "in" },
|
|
1777
|
+
not_in: { validate: (ctx) => !isListed(ctx.value, ctx.parameters, ctx.ruleNames), messageKey: () => "not_in" }
|
|
1778
|
+
};
|
|
1779
|
+
function isListed(value, list, ruleNames) {
|
|
1780
|
+
if (Array.isArray(value) && ruleNames.includes("array")) {
|
|
1781
|
+
return value.every((item) => (typeof item !== "object" || item === null) && list.includes(String(item)));
|
|
1782
|
+
}
|
|
1783
|
+
return (typeof value !== "object" || value === null) && list.includes(String(value));
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
// rules/timezone.ts
|
|
1787
|
+
var REGIONS = ["Africa", "America", "Antarctica", "Arctic", "Asia", "Atlantic", "Australia", "Europe", "Indian", "Pacific"];
|
|
1788
|
+
var ZONE_RE = /^[A-Z][A-Za-z_-]*(?:\/[A-Z][A-Za-z0-9_+-]*)+$/;
|
|
1789
|
+
var ICU_NAMES = {
|
|
1790
|
+
"Africa/Asmera": "Africa/Asmara",
|
|
1791
|
+
"America/Buenos_Aires": "America/Argentina/Buenos_Aires",
|
|
1792
|
+
"America/Catamarca": "America/Argentina/Catamarca",
|
|
1793
|
+
"America/Cordoba": "America/Argentina/Cordoba",
|
|
1794
|
+
"America/Jujuy": "America/Argentina/Jujuy",
|
|
1795
|
+
"America/Mendoza": "America/Argentina/Mendoza",
|
|
1796
|
+
"America/Coral_Harbour": "America/Atikokan",
|
|
1797
|
+
"America/Indianapolis": "America/Indiana/Indianapolis",
|
|
1798
|
+
"America/Louisville": "America/Kentucky/Louisville",
|
|
1799
|
+
"America/Godthab": "America/Nuuk",
|
|
1800
|
+
"Asia/Saigon": "Asia/Ho_Chi_Minh",
|
|
1801
|
+
"Asia/Katmandu": "Asia/Kathmandu",
|
|
1802
|
+
"Asia/Calcutta": "Asia/Kolkata",
|
|
1803
|
+
"Asia/Rangoon": "Asia/Yangon",
|
|
1804
|
+
"Atlantic/Faeroe": "Atlantic/Faroe",
|
|
1805
|
+
"Europe/Kiev": "Europe/Kyiv",
|
|
1806
|
+
"Pacific/Truk": "Pacific/Chuuk",
|
|
1807
|
+
"Pacific/Enderbury": "Pacific/Kanton",
|
|
1808
|
+
"Pacific/Ponape": "Pacific/Pohnpei"
|
|
1809
|
+
};
|
|
1810
|
+
function resolve(zone) {
|
|
1811
|
+
try {
|
|
1812
|
+
return new Intl.DateTimeFormat("en-US", { timeZone: zone }).resolvedOptions().timeZone;
|
|
1813
|
+
} catch {
|
|
1814
|
+
return void 0;
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
function canonical(zone) {
|
|
1818
|
+
const name = resolve(zone);
|
|
1819
|
+
return name === void 0 ? void 0 : ICU_NAMES[name] ?? name;
|
|
1820
|
+
}
|
|
1821
|
+
function isInOwnCase(value, name) {
|
|
1822
|
+
const lower2 = value.toLowerCase();
|
|
1823
|
+
for (const spelling of [name, ICU_NAMES[name]]) {
|
|
1824
|
+
if (spelling !== void 0 && spelling.toLowerCase() === lower2) return spelling === value;
|
|
1825
|
+
}
|
|
1826
|
+
return true;
|
|
1827
|
+
}
|
|
1828
|
+
function countryZones(country) {
|
|
1829
|
+
const locale = new Intl.Locale(`und-${country.toUpperCase()}`);
|
|
1830
|
+
return locale.getTimeZones?.() ?? locale.timeZones ?? [];
|
|
1831
|
+
}
|
|
1832
|
+
var inRegion = (zone, region) => zone === "UTC" ? region === void 0 || region === "UTC" : ZONE_RE.test(zone) && (region === void 0 ? REGIONS : [region]).includes(zone.split("/")[0]);
|
|
1833
|
+
function isTimezone(value, group = "all", country) {
|
|
1834
|
+
if (typeof value !== "string") return false;
|
|
1835
|
+
const resolved = resolve(value);
|
|
1836
|
+
if (resolved === void 0 || !isInOwnCase(value, resolved)) return false;
|
|
1837
|
+
const name = group.toLowerCase();
|
|
1838
|
+
if (name === "all") return inRegion(value);
|
|
1839
|
+
if (name === "all_with_bc") return /^[A-Z]/.test(value);
|
|
1840
|
+
if (name === "per_country") {
|
|
1841
|
+
const zone = canonical(value);
|
|
1842
|
+
return inRegion(value) && countryZones(country ?? "").some((candidate) => canonical(candidate) === zone);
|
|
1843
|
+
}
|
|
1844
|
+
const region = name === "utc" ? "UTC" : REGIONS.find((candidate) => candidate.toLowerCase() === name);
|
|
1845
|
+
if (region === void 0) throw new InvalidRuleParameterError("timezone", group, "expected all, all_with_bc, per_country, utc or a region such as Europe");
|
|
1846
|
+
return inRegion(value, region);
|
|
1847
|
+
}
|
|
1848
|
+
var timezoneRules = {
|
|
1849
|
+
timezone: { validate: (ctx) => isTimezone(ctx.value, ctx.parameters[0], ctx.parameters[1]), messageKey: () => "timezone" }
|
|
831
1850
|
};
|
|
832
1851
|
|
|
833
1852
|
// registry.ts
|
|
834
1853
|
var builtinRules = {
|
|
835
1854
|
...presenceRules,
|
|
1855
|
+
...acceptanceRules,
|
|
1856
|
+
...prohibitedRules,
|
|
1857
|
+
...excludeRules,
|
|
836
1858
|
...typeRules,
|
|
1859
|
+
...arrayRules,
|
|
837
1860
|
...formatRules,
|
|
1861
|
+
...dateRules,
|
|
1862
|
+
...networkRules,
|
|
1863
|
+
...timezoneRules,
|
|
838
1864
|
...sizeRules,
|
|
1865
|
+
...numericRules,
|
|
839
1866
|
...fileRules,
|
|
840
1867
|
...databaseRules,
|
|
841
1868
|
...authRules
|
|
@@ -843,6 +1870,9 @@ var builtinRules = {
|
|
|
843
1870
|
|
|
844
1871
|
// rules/password.ts
|
|
845
1872
|
var defaultRule;
|
|
1873
|
+
function hasMixedCase(value) {
|
|
1874
|
+
return value.split(/[\n\r\u2028\u2029]/).some((line) => /\p{Ll}/u.test(line) && /\p{Lu}/u.test(line));
|
|
1875
|
+
}
|
|
846
1876
|
var PasswordRule = class _PasswordRule {
|
|
847
1877
|
#min;
|
|
848
1878
|
#max;
|
|
@@ -893,14 +1923,13 @@ var PasswordRule = class _PasswordRule {
|
|
|
893
1923
|
this.#symbols = true;
|
|
894
1924
|
return this;
|
|
895
1925
|
}
|
|
896
|
-
validate(
|
|
897
|
-
const
|
|
898
|
-
const say = (key, replacements = {}) => fail(applyReplacements(DEFAULT_MESSAGES[key], { attribute: name, ...replacements }));
|
|
1926
|
+
validate(_attribute, value, fail) {
|
|
1927
|
+
const say = (key, replacements = {}) => fail(applyReplacements(DEFAULT_MESSAGES[key], replacements));
|
|
899
1928
|
if (typeof value !== "string") return say("string");
|
|
900
|
-
const
|
|
901
|
-
if (
|
|
902
|
-
if (this.#max !== void 0 &&
|
|
903
|
-
if (this.#mixedCase &&
|
|
1929
|
+
const length2 = [...value].length;
|
|
1930
|
+
if (length2 < this.#min) say("min.string", { min: String(this.#min) });
|
|
1931
|
+
if (this.#max !== void 0 && length2 > this.#max) say("max.string", { max: String(this.#max) });
|
|
1932
|
+
if (this.#mixedCase && !hasMixedCase(value)) say("password.mixed");
|
|
904
1933
|
if (this.#letters && !/\p{L}/u.test(value)) say("password.letters");
|
|
905
1934
|
if (this.#symbols && !/\p{Z}|\p{S}|\p{P}/u.test(value)) say("password.symbols");
|
|
906
1935
|
if (this.#numbers && !/\p{N}/u.test(value)) say("password.numbers");
|
|
@@ -918,11 +1947,15 @@ var PasswordRule = class _PasswordRule {
|
|
|
918
1947
|
|
|
919
1948
|
// Rule.ts
|
|
920
1949
|
var BuiltinRuleObject = class {
|
|
1950
|
+
/** The parameters its message shows (`:values` of Rule.doesntContain()), as the string rule's would be. */
|
|
1951
|
+
messageParameters() {
|
|
1952
|
+
return [];
|
|
1953
|
+
}
|
|
921
1954
|
// Declares all 4 contract parameters so a subclass's inferred type (from `Rule.in(...)` etc.) still accepts a
|
|
922
1955
|
// call with the full 4-arg ValidationRule.validate signature, as Rule.test.ts makes.
|
|
923
1956
|
async validate(attribute, value, fail, data, expandedFromWildcard = false) {
|
|
924
1957
|
if (await this.passes(attribute, value, data, expandedFromWildcard)) return;
|
|
925
|
-
fail(applyReplacements(DEFAULT_MESSAGES[this.ruleName], { attribute: defaultAttributeName(attribute) }));
|
|
1958
|
+
fail(applyReplacements(DEFAULT_MESSAGES[this.ruleName], { attribute: defaultAttributeName(attribute), values: this.messageParameters().join(", ") }));
|
|
926
1959
|
}
|
|
927
1960
|
};
|
|
928
1961
|
var InRule = class extends BuiltinRuleObject {
|
|
@@ -932,8 +1965,145 @@ var InRule = class extends BuiltinRuleObject {
|
|
|
932
1965
|
}
|
|
933
1966
|
values;
|
|
934
1967
|
ruleName = "in";
|
|
1968
|
+
passes(_attribute, value, _data, _expanded, ruleNames = []) {
|
|
1969
|
+
return isListed(value, this.values.map(String), ruleNames);
|
|
1970
|
+
}
|
|
1971
|
+
};
|
|
1972
|
+
var NotInRule = class extends BuiltinRuleObject {
|
|
1973
|
+
constructor(values) {
|
|
1974
|
+
super();
|
|
1975
|
+
this.values = values;
|
|
1976
|
+
}
|
|
1977
|
+
values;
|
|
1978
|
+
ruleName = "not_in";
|
|
1979
|
+
passes(_attribute, value, _data, _expanded, ruleNames = []) {
|
|
1980
|
+
return !isListed(value, this.values.map(String), ruleNames);
|
|
1981
|
+
}
|
|
1982
|
+
};
|
|
1983
|
+
var enumValues = (type) => Object.entries(type).filter(([key, value]) => !(typeof value === "string" && typeof type[value] === "number" && String(type[value]) === key)).map(([, value]) => value);
|
|
1984
|
+
var EnumRule = class extends BuiltinRuleObject {
|
|
1985
|
+
constructor(type) {
|
|
1986
|
+
super();
|
|
1987
|
+
this.type = type;
|
|
1988
|
+
}
|
|
1989
|
+
type;
|
|
1990
|
+
ruleName = "enum";
|
|
1991
|
+
#only = [];
|
|
1992
|
+
#except = [];
|
|
1993
|
+
only(values) {
|
|
1994
|
+
this.#only = Array.isArray(values) ? [...values] : [values];
|
|
1995
|
+
return this;
|
|
1996
|
+
}
|
|
1997
|
+
except(values) {
|
|
1998
|
+
this.#except = Array.isArray(values) ? [...values] : [values];
|
|
1999
|
+
return this;
|
|
2000
|
+
}
|
|
2001
|
+
passes(_attribute, value) {
|
|
2002
|
+
const match = enumValues(this.type).find((candidate) => candidate === value || typeof candidate === "number" && typeof value === "string" && String(candidate) === value);
|
|
2003
|
+
if (match === void 0) return false;
|
|
2004
|
+
return this.#only.length > 0 ? this.#only.includes(match) : !this.#except.includes(match);
|
|
2005
|
+
}
|
|
2006
|
+
};
|
|
2007
|
+
var ContainsRule = class extends BuiltinRuleObject {
|
|
2008
|
+
constructor(ruleName, values) {
|
|
2009
|
+
super();
|
|
2010
|
+
this.ruleName = ruleName;
|
|
2011
|
+
this.values = values;
|
|
2012
|
+
}
|
|
2013
|
+
ruleName;
|
|
2014
|
+
values;
|
|
935
2015
|
passes(_attribute, value) {
|
|
936
|
-
|
|
2016
|
+
if (!Array.isArray(value)) return false;
|
|
2017
|
+
const found = (needle) => value.some((item) => alike(item, needle));
|
|
2018
|
+
return this.ruleName === "contains" ? this.values.every(found) : !this.values.some(found);
|
|
2019
|
+
}
|
|
2020
|
+
messageParameters() {
|
|
2021
|
+
return this.values.map(String);
|
|
2022
|
+
}
|
|
2023
|
+
};
|
|
2024
|
+
var ExpandingRuleObject = class {
|
|
2025
|
+
validate() {
|
|
2026
|
+
throw new Error(`${this.constructor.name} stands for string rules: use it in a Validator's rules.`);
|
|
2027
|
+
}
|
|
2028
|
+
};
|
|
2029
|
+
var ConditionalRule = class extends ExpandingRuleObject {
|
|
2030
|
+
constructor(rule, condition) {
|
|
2031
|
+
super();
|
|
2032
|
+
this.rule = rule;
|
|
2033
|
+
this.condition = condition;
|
|
2034
|
+
}
|
|
2035
|
+
rule;
|
|
2036
|
+
condition;
|
|
2037
|
+
expandRules() {
|
|
2038
|
+
return (typeof this.condition === "function" ? this.condition() : this.condition) ? this.rule : "";
|
|
2039
|
+
}
|
|
2040
|
+
};
|
|
2041
|
+
var ConditionalRules = class extends ExpandingRuleObject {
|
|
2042
|
+
constructor(condition, rules, defaultRules, negate = false) {
|
|
2043
|
+
super();
|
|
2044
|
+
this.condition = condition;
|
|
2045
|
+
this.rules = rules;
|
|
2046
|
+
this.defaultRules = defaultRules;
|
|
2047
|
+
this.negate = negate;
|
|
2048
|
+
}
|
|
2049
|
+
condition;
|
|
2050
|
+
rules;
|
|
2051
|
+
defaultRules;
|
|
2052
|
+
negate;
|
|
2053
|
+
expandRules(data) {
|
|
2054
|
+
const holds = (typeof this.condition === "function" ? this.condition(data) : this.condition) !== this.negate;
|
|
2055
|
+
const chosen = holds ? this.rules : this.defaultRules;
|
|
2056
|
+
return typeof chosen === "function" ? chosen(data) : chosen;
|
|
2057
|
+
}
|
|
2058
|
+
};
|
|
2059
|
+
var ArrayRule = class extends ExpandingRuleObject {
|
|
2060
|
+
constructor(keys) {
|
|
2061
|
+
super();
|
|
2062
|
+
this.keys = keys;
|
|
2063
|
+
}
|
|
2064
|
+
keys;
|
|
2065
|
+
expandRules() {
|
|
2066
|
+
return this.keys.length === 0 ? "array" : `array:${this.keys.join(",")}`;
|
|
2067
|
+
}
|
|
2068
|
+
};
|
|
2069
|
+
var DimensionsRule = class extends ExpandingRuleObject {
|
|
2070
|
+
#constraints;
|
|
2071
|
+
constructor(constraints) {
|
|
2072
|
+
super();
|
|
2073
|
+
this.#constraints = { ...constraints };
|
|
2074
|
+
}
|
|
2075
|
+
width(pixels) {
|
|
2076
|
+
return this.set("width", pixels);
|
|
2077
|
+
}
|
|
2078
|
+
height(pixels) {
|
|
2079
|
+
return this.set("height", pixels);
|
|
2080
|
+
}
|
|
2081
|
+
minWidth(pixels) {
|
|
2082
|
+
return this.set("min_width", pixels);
|
|
2083
|
+
}
|
|
2084
|
+
minHeight(pixels) {
|
|
2085
|
+
return this.set("min_height", pixels);
|
|
2086
|
+
}
|
|
2087
|
+
maxWidth(pixels) {
|
|
2088
|
+
return this.set("max_width", pixels);
|
|
2089
|
+
}
|
|
2090
|
+
maxHeight(pixels) {
|
|
2091
|
+
return this.set("max_height", pixels);
|
|
2092
|
+
}
|
|
2093
|
+
/** Width over height: `ratio(3 / 2)`. */
|
|
2094
|
+
ratio(ratio) {
|
|
2095
|
+
return this.set("ratio", ratio);
|
|
2096
|
+
}
|
|
2097
|
+
/** `dimensions:max_width=1000,ratio=1.5`, Laravel's __toString(). */
|
|
2098
|
+
toString() {
|
|
2099
|
+
return `dimensions:${Object.entries(this.#constraints).map(([key, value]) => `${key}=${value}`).join(",")}`;
|
|
2100
|
+
}
|
|
2101
|
+
expandRules() {
|
|
2102
|
+
return this.toString();
|
|
2103
|
+
}
|
|
2104
|
+
set(key, value) {
|
|
2105
|
+
this.#constraints[key] = value;
|
|
2106
|
+
return this;
|
|
937
2107
|
}
|
|
938
2108
|
};
|
|
939
2109
|
function isModelInstance(value) {
|
|
@@ -986,7 +2156,30 @@ var ExistsRule = class extends BuiltinRuleObject {
|
|
|
986
2156
|
}
|
|
987
2157
|
};
|
|
988
2158
|
var Rule = {
|
|
2159
|
+
/** One of `values`, compared as text (as `in:a,b`); typed as those values: `Rule.in(['draft', 'publish'])`. */
|
|
989
2160
|
in: (values) => new InRule(values),
|
|
2161
|
+
/** None of `values`, compared as text (as `not_in:a,b`). */
|
|
2162
|
+
notIn: (values) => new NotInRule(values),
|
|
2163
|
+
/** One of a TypeScript enum's values (or an `as const` object's): `Rule.enum(Status)`, `.only([…])`, `.except([…])`. */
|
|
2164
|
+
enum: (type) => new EnumRule(type),
|
|
2165
|
+
/** An array holding every one of `values`. */
|
|
2166
|
+
contains: (values) => new ContainsRule("contains", values),
|
|
2167
|
+
/** An array holding none of `values`. */
|
|
2168
|
+
doesntContain: (values) => new ContainsRule("doesnt_contain", values),
|
|
2169
|
+
/** `required` when the condition (or the callback) holds. */
|
|
2170
|
+
requiredIf: (condition) => new ConditionalRule("required", condition),
|
|
2171
|
+
/** `exclude` when the condition (or the callback) holds; the key is optional, as `exclude_if` makes it. */
|
|
2172
|
+
excludeIf: (condition) => new ConditionalRule("exclude", condition),
|
|
2173
|
+
/** `prohibited` when the condition (or the callback) holds. */
|
|
2174
|
+
prohibitedIf: (condition) => new ConditionalRule("prohibited", condition),
|
|
2175
|
+
/** `rules` when the condition (or the callback, given the data) holds, else `defaultRules`. */
|
|
2176
|
+
when: (condition, rules, defaultRules = []) => new ConditionalRules(condition, rules, defaultRules),
|
|
2177
|
+
/** `rules` unless the condition (or the callback, given the data) holds, else `defaultRules`. */
|
|
2178
|
+
unless: (condition, rules, defaultRules = []) => new ConditionalRules(condition, rules, defaultRules, true),
|
|
2179
|
+
/** `array`, or with keys `array:name,email` (an object whose keys are among those named). */
|
|
2180
|
+
array: (keys = []) => new ArrayRule(keys),
|
|
2181
|
+
/** The dimensions rule: `Rule.dimensions().maxWidth(1000).ratio(3 / 2)`, or `Rule.dimensions({ min_width: 100 })`. */
|
|
2182
|
+
dimensions: (constraints = {}) => new DimensionsRule(constraints),
|
|
990
2183
|
unique: (table, column) => new UniqueRule(table, column),
|
|
991
2184
|
exists: (table, column) => new ExistsRule(table, column),
|
|
992
2185
|
/** Laravel's Password::defaults(): the application's password rule (at least 8 characters unless PasswordRule.defaults() changed it). */
|
|
@@ -994,8 +2187,64 @@ var Rule = {
|
|
|
994
2187
|
};
|
|
995
2188
|
|
|
996
2189
|
// Validator.ts
|
|
997
|
-
import { Arr as
|
|
2190
|
+
import { Arr as Arr11 } from "@orieljs/support";
|
|
998
2191
|
var SKIPPED_MODIFIERS = /* @__PURE__ */ new Set(["bail", "nullable", "sometimes"]);
|
|
2192
|
+
var DEPENDENT_RULES = /* @__PURE__ */ new Set([
|
|
2193
|
+
"after",
|
|
2194
|
+
"after_or_equal",
|
|
2195
|
+
"before",
|
|
2196
|
+
"before_or_equal",
|
|
2197
|
+
"confirmed",
|
|
2198
|
+
"different",
|
|
2199
|
+
"exclude_if",
|
|
2200
|
+
"exclude_unless",
|
|
2201
|
+
"exclude_with",
|
|
2202
|
+
"exclude_without",
|
|
2203
|
+
"gt",
|
|
2204
|
+
"gte",
|
|
2205
|
+
"lt",
|
|
2206
|
+
"lte",
|
|
2207
|
+
"accepted_if",
|
|
2208
|
+
"declined_if",
|
|
2209
|
+
"required_if",
|
|
2210
|
+
"required_if_accepted",
|
|
2211
|
+
"required_if_declined",
|
|
2212
|
+
"required_unless",
|
|
2213
|
+
"required_with",
|
|
2214
|
+
"required_with_all",
|
|
2215
|
+
"required_without",
|
|
2216
|
+
"required_without_all",
|
|
2217
|
+
"present_if",
|
|
2218
|
+
"present_unless",
|
|
2219
|
+
"present_with",
|
|
2220
|
+
"present_with_all",
|
|
2221
|
+
"prohibited",
|
|
2222
|
+
"prohibited_if",
|
|
2223
|
+
"prohibited_if_accepted",
|
|
2224
|
+
"prohibited_if_declined",
|
|
2225
|
+
"prohibited_unless",
|
|
2226
|
+
"prohibits",
|
|
2227
|
+
"missing_if",
|
|
2228
|
+
"missing_unless",
|
|
2229
|
+
"missing_with",
|
|
2230
|
+
"missing_with_all",
|
|
2231
|
+
"same",
|
|
2232
|
+
"unique"
|
|
2233
|
+
]);
|
|
2234
|
+
function wildcardKeys(pattern, field) {
|
|
2235
|
+
const fieldSegments = field.split(".");
|
|
2236
|
+
return pattern.split(".").flatMap((segment, index) => segment === "*" ? [fieldSegments[index] ?? "*"] : []);
|
|
2237
|
+
}
|
|
2238
|
+
function fillWildcards(parameters, keys) {
|
|
2239
|
+
if (keys.length === 0) return parameters;
|
|
2240
|
+
return parameters.map((parameter) => {
|
|
2241
|
+
let index = 0;
|
|
2242
|
+
return parameter.replace(/\*/g, () => keys[index++] ?? "*");
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
function patternMatcher(pattern) {
|
|
2246
|
+
return new RegExp(`^${pattern.split(".").map((segment) => segment === "*" ? "[^.]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\.")}$`);
|
|
2247
|
+
}
|
|
999
2248
|
function buildNested(flat) {
|
|
1000
2249
|
const root = {};
|
|
1001
2250
|
const created = /* @__PURE__ */ new Set([root]);
|
|
@@ -1032,6 +2281,49 @@ function cloneContainer(existing) {
|
|
|
1032
2281
|
}
|
|
1033
2282
|
return container;
|
|
1034
2283
|
}
|
|
2284
|
+
var EXCLUDED = /* @__PURE__ */ Symbol("excluded");
|
|
2285
|
+
function withoutPaths(data, paths) {
|
|
2286
|
+
const root = { ...data };
|
|
2287
|
+
const copies = /* @__PURE__ */ new Set([root]);
|
|
2288
|
+
for (const path of paths) {
|
|
2289
|
+
const parts = path.split(".");
|
|
2290
|
+
const leaf = parts.pop();
|
|
2291
|
+
let node = root;
|
|
2292
|
+
for (const part of parts) {
|
|
2293
|
+
const child = Object.hasOwn(node, part) ? node[part] : void 0;
|
|
2294
|
+
if (!isPlainContainer(child)) {
|
|
2295
|
+
node = void 0;
|
|
2296
|
+
break;
|
|
2297
|
+
}
|
|
2298
|
+
const copy = copies.has(child) ? child : Array.isArray(child) ? [...child] : { ...child };
|
|
2299
|
+
copies.add(copy);
|
|
2300
|
+
node[part] = copy;
|
|
2301
|
+
node = copy;
|
|
2302
|
+
}
|
|
2303
|
+
if (node === void 0 || !Object.hasOwn(node, leaf)) continue;
|
|
2304
|
+
if (Array.isArray(node)) node[Number(leaf)] = EXCLUDED;
|
|
2305
|
+
else delete node[leaf];
|
|
2306
|
+
}
|
|
2307
|
+
return root;
|
|
2308
|
+
}
|
|
2309
|
+
function dropExcluded(node, seen = /* @__PURE__ */ new Set()) {
|
|
2310
|
+
if (typeof node !== "object" || node === null || seen.has(node)) return;
|
|
2311
|
+
seen.add(node);
|
|
2312
|
+
if (Array.isArray(node)) {
|
|
2313
|
+
if (node.includes(EXCLUDED)) {
|
|
2314
|
+
let kept = 0;
|
|
2315
|
+
for (const item of node) if (item !== EXCLUDED) node[kept++] = item;
|
|
2316
|
+
node.length = kept;
|
|
2317
|
+
}
|
|
2318
|
+
} else if (isPlainContainer(node)) {
|
|
2319
|
+
for (const [key, value] of Object.entries(node)) if (value === EXCLUDED) delete node[key];
|
|
2320
|
+
}
|
|
2321
|
+
if (isPlainContainer(node)) for (const value of Object.values(node)) dropExcluded(value, seen);
|
|
2322
|
+
}
|
|
2323
|
+
function segmentEnd(path, start) {
|
|
2324
|
+
const dot = path.indexOf(".", start);
|
|
2325
|
+
return dot === -1 ? path.length : dot;
|
|
2326
|
+
}
|
|
1035
2327
|
function arrayify(node, created) {
|
|
1036
2328
|
if (node === null || typeof node !== "object" || !created.has(node)) return node;
|
|
1037
2329
|
const entries = Object.entries(node).map(([k, v]) => [k, arrayify(v, created)]);
|
|
@@ -1057,11 +2349,18 @@ var ValidatorImpl = class {
|
|
|
1057
2349
|
bag;
|
|
1058
2350
|
ranPromise;
|
|
1059
2351
|
afterCallbacks = [];
|
|
2352
|
+
parsedRules;
|
|
2353
|
+
/** The fields an exclude rule took out (with their children). */
|
|
2354
|
+
excluded = /* @__PURE__ */ new Set();
|
|
2355
|
+
/** The same fields as a tree of segment runs from the root, so a lookup reads the field once. */
|
|
2356
|
+
excludedRuns = { run: "", next: /* @__PURE__ */ new Map(), excluded: false };
|
|
2357
|
+
/** What the rules keep while one pass runs (RuleContext.cache); a new pass starts with a new one. */
|
|
2358
|
+
cache = /* @__PURE__ */ new Map();
|
|
1060
2359
|
after(callback) {
|
|
1061
2360
|
this.afterCallbacks.push(callback);
|
|
1062
2361
|
return this;
|
|
1063
2362
|
}
|
|
1064
|
-
/**
|
|
2363
|
+
/** Lets an after() hook add a failure. */
|
|
1065
2364
|
fail(field, message) {
|
|
1066
2365
|
this.bag ??= new MessageBagImpl();
|
|
1067
2366
|
this.bag.add(field, message);
|
|
@@ -1078,34 +2377,138 @@ var ValidatorImpl = class {
|
|
|
1078
2377
|
}
|
|
1079
2378
|
async validated() {
|
|
1080
2379
|
if (!await this.passes()) throw new ValidationErrorImpl(this.bag.toObject(), this.errorBagName);
|
|
2380
|
+
const data = this.excluded.size === 0 ? this.data : withoutPaths(this.data, this.excluded);
|
|
1081
2381
|
const flat = {};
|
|
1082
2382
|
for (const pattern of Object.keys(this.rules)) {
|
|
1083
2383
|
for (const field of this.fieldsFor(pattern)) {
|
|
1084
|
-
if (
|
|
2384
|
+
if (!this.isExcluded(field) && Arr11.has(data, field)) flat[field] = Arr11.get(data, field);
|
|
1085
2385
|
}
|
|
1086
2386
|
}
|
|
1087
|
-
|
|
2387
|
+
for (const path of this.excluded) {
|
|
2388
|
+
const parent = path.includes(".") ? path.slice(0, path.lastIndexOf(".")) : void 0;
|
|
2389
|
+
if (parent === void 0 || this.isExcluded(parent) || !Arr11.has(this.data, path)) continue;
|
|
2390
|
+
if (Array.isArray(Arr11.get(this.data, parent))) flat[path] = EXCLUDED;
|
|
2391
|
+
}
|
|
2392
|
+
const nested = buildNested(flat);
|
|
2393
|
+
if (this.excluded.size > 0) dropExcluded(nested);
|
|
2394
|
+
return nested;
|
|
2395
|
+
}
|
|
2396
|
+
/** Takes the field (with its children) out of validation and of the validated data. */
|
|
2397
|
+
exclude(field) {
|
|
2398
|
+
this.excluded.add(field);
|
|
2399
|
+
let node = this.excludedRuns;
|
|
2400
|
+
for (let start = 0; ; ) {
|
|
2401
|
+
const first = field.slice(start, segmentEnd(field, start));
|
|
2402
|
+
let next = node.next.get(first);
|
|
2403
|
+
if (next === void 0) {
|
|
2404
|
+
node.next.set(first, { run: field.slice(start), next: /* @__PURE__ */ new Map(), excluded: true });
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
let same = 0;
|
|
2408
|
+
while (same < next.run.length && start + same < field.length && next.run[same] === field[start + same]) same++;
|
|
2409
|
+
const runEnds = same === next.run.length || next.run[same] === ".";
|
|
2410
|
+
const fieldEnds = start + same === field.length || field[start + same] === ".";
|
|
2411
|
+
if (!runEnds || !fieldEnds) same = next.run.lastIndexOf(".", same - 1);
|
|
2412
|
+
if (same < next.run.length) {
|
|
2413
|
+
const rest = next.run.slice(same + 1);
|
|
2414
|
+
const shared = { run: next.run.slice(0, same), next: /* @__PURE__ */ new Map([[rest.slice(0, segmentEnd(rest, 0)), next]]), excluded: false };
|
|
2415
|
+
next.run = rest;
|
|
2416
|
+
node.next.set(first, shared);
|
|
2417
|
+
next = shared;
|
|
2418
|
+
}
|
|
2419
|
+
start += same;
|
|
2420
|
+
if (start === field.length) {
|
|
2421
|
+
next.excluded = true;
|
|
2422
|
+
return;
|
|
2423
|
+
}
|
|
2424
|
+
node = next;
|
|
2425
|
+
start++;
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
/**
|
|
2429
|
+
* Laravel's shouldBeExcluded(): the field, or one of its parents, was taken out by an exclude rule. The field's
|
|
2430
|
+
* segments are followed from the root (so `user` takes out `user.name`, not `username`) and each is read once,
|
|
2431
|
+
* however many fields were excluded and however many dots the field holds.
|
|
2432
|
+
*/
|
|
2433
|
+
isExcluded(field) {
|
|
2434
|
+
if (this.excluded.size === 0) return false;
|
|
2435
|
+
let node = this.excludedRuns;
|
|
2436
|
+
for (let start = 0; ; ) {
|
|
2437
|
+
const next = node.next.get(field.slice(start, segmentEnd(field, start)));
|
|
2438
|
+
if (next === void 0) return false;
|
|
2439
|
+
const end = start + next.run.length;
|
|
2440
|
+
if (!field.startsWith(next.run, start) || end < field.length && field[end] !== ".") return false;
|
|
2441
|
+
if (next.excluded) return true;
|
|
2442
|
+
if (end === field.length) return false;
|
|
2443
|
+
node = next;
|
|
2444
|
+
start = end + 1;
|
|
2445
|
+
}
|
|
1088
2446
|
}
|
|
1089
2447
|
fieldsFor(pattern) {
|
|
1090
2448
|
return pattern.includes("*") ? expandWildcardKeys(pattern, this.data) : [pattern];
|
|
1091
2449
|
}
|
|
2450
|
+
parsed() {
|
|
2451
|
+
this.parsedRules ??= new Map(Object.entries(this.rules).map(([pattern, spec]) => [pattern, parseRuleSpec(spec, this.data)]));
|
|
2452
|
+
return this.parsedRules;
|
|
2453
|
+
}
|
|
2454
|
+
/** The rule key a concrete field falls under: itself, else the first wildcard key that matches it. */
|
|
2455
|
+
patternOf(field) {
|
|
2456
|
+
const parsed = this.parsed();
|
|
2457
|
+
if (parsed.has(field)) return field;
|
|
2458
|
+
for (const pattern of parsed.keys()) if (pattern.includes("*") && patternMatcher(pattern).test(field)) return pattern;
|
|
2459
|
+
return void 0;
|
|
2460
|
+
}
|
|
2461
|
+
/** Laravel's getRule(): the parameters of rule `name` on `field`, or undefined when the field has no such rule. */
|
|
2462
|
+
ruleParameters(field, name) {
|
|
2463
|
+
const pattern = this.patternOf(field);
|
|
2464
|
+
const rule = pattern === void 0 ? void 0 : this.parsed().get(pattern).find((r) => r.kind === "named" && r.name === name);
|
|
2465
|
+
return rule?.kind === "named" ? rule.parameters : void 0;
|
|
2466
|
+
}
|
|
2467
|
+
/** Laravel's getDisplayableAttribute(): a custom attribute for the field or its rule key, else the humanized key. */
|
|
2468
|
+
displayName(field) {
|
|
2469
|
+
const pattern = this.patternOf(field);
|
|
2470
|
+
return this.customAttributes[field] ?? (pattern === void 0 ? void 0 : this.customAttributes[pattern]) ?? defaultAttributeName(field);
|
|
2471
|
+
}
|
|
2472
|
+
context(field, pattern, value, parameters, ruleNames) {
|
|
2473
|
+
return {
|
|
2474
|
+
attribute: field,
|
|
2475
|
+
expandedFromWildcard: pattern.includes("*"),
|
|
2476
|
+
value,
|
|
2477
|
+
parameters,
|
|
2478
|
+
data: this.data,
|
|
2479
|
+
ruleNames,
|
|
2480
|
+
pattern,
|
|
2481
|
+
cache: this.cache,
|
|
2482
|
+
getRule: (other, name) => this.ruleParameters(other, name),
|
|
2483
|
+
displayName: (other) => this.displayName(other)
|
|
2484
|
+
};
|
|
2485
|
+
}
|
|
1092
2486
|
async run() {
|
|
1093
2487
|
this.bag = new MessageBagImpl();
|
|
1094
|
-
|
|
1095
|
-
|
|
2488
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
2489
|
+
for (const [pattern, parsed] of this.parsed()) {
|
|
1096
2490
|
const names = parsed.filter((r) => r.kind === "named").map((r) => r.name);
|
|
1097
2491
|
const bail = names.includes("bail");
|
|
1098
2492
|
const nullable = names.includes("nullable");
|
|
1099
2493
|
const sometimes = names.includes("sometimes");
|
|
1100
2494
|
for (const field of this.fieldsFor(pattern)) {
|
|
1101
|
-
|
|
2495
|
+
if (this.isExcluded(field)) continue;
|
|
2496
|
+
const present = Arr11.has(this.data, field);
|
|
1102
2497
|
if (sometimes && !present) continue;
|
|
1103
|
-
const value =
|
|
2498
|
+
const value = Arr11.get(this.data, field);
|
|
1104
2499
|
const isNull = value === null || value === void 0;
|
|
1105
2500
|
const isBlank = typeof value === "string" && value.trim() === "";
|
|
1106
2501
|
for (const rule of parsed) {
|
|
1107
2502
|
if (rule.kind === "named" && SKIPPED_MODIFIERS.has(rule.name)) continue;
|
|
1108
|
-
const
|
|
2503
|
+
const builtin = rule.kind === "named" ? this.registry[rule.name] : void 0;
|
|
2504
|
+
if (builtin?.excludes) {
|
|
2505
|
+
if (!await this.passesExcludeRule(rule, builtin, field, pattern, value, names)) {
|
|
2506
|
+
this.exclude(field);
|
|
2507
|
+
break;
|
|
2508
|
+
}
|
|
2509
|
+
continue;
|
|
2510
|
+
}
|
|
2511
|
+
const implicit = builtin?.implicit ?? false;
|
|
1109
2512
|
if (!implicit && (!present || isBlank)) continue;
|
|
1110
2513
|
if (!implicit && nullable && isNull) continue;
|
|
1111
2514
|
const messages = await this.runRule(rule, field, pattern, value, names);
|
|
@@ -1119,21 +2522,26 @@ var ValidatorImpl = class {
|
|
|
1119
2522
|
for (const callback of this.afterCallbacks) await callback(this);
|
|
1120
2523
|
return this.bag.isEmpty();
|
|
1121
2524
|
}
|
|
2525
|
+
async passesExcludeRule(rule, builtin, field, pattern, value, ruleNames) {
|
|
2526
|
+
return builtin.validate(this.context(field, pattern, value, fillWildcards(rule.parameters, wildcardKeys(pattern, field)), ruleNames));
|
|
2527
|
+
}
|
|
1122
2528
|
async runRule(rule, field, pattern, value, ruleNames) {
|
|
1123
2529
|
const expanded = pattern.includes("*");
|
|
1124
2530
|
if (rule.kind === "object") {
|
|
1125
2531
|
if (rule.rule instanceof BuiltinRuleObject) {
|
|
1126
|
-
if (await rule.rule.passes(field, value, this.data, expanded)) return [];
|
|
1127
|
-
const ctx2 =
|
|
2532
|
+
if (await rule.rule.passes(field, value, this.data, expanded, ruleNames)) return [];
|
|
2533
|
+
const ctx2 = this.context(field, pattern, value, rule.rule.messageParameters(), ruleNames);
|
|
1128
2534
|
return [this.failureMessage(rule.rule.ruleName, this.registry[rule.rule.ruleName], ctx2, pattern)];
|
|
1129
2535
|
}
|
|
1130
2536
|
const failures = [];
|
|
1131
|
-
|
|
2537
|
+
const attribute = this.displayName(field);
|
|
2538
|
+
await rule.rule.validate(field, value, (message) => void failures.push(applyReplacements(message, { attribute })), this.data);
|
|
1132
2539
|
return failures;
|
|
1133
2540
|
}
|
|
1134
2541
|
const builtin = this.registry[rule.name];
|
|
1135
2542
|
if (!builtin) throw new UnknownValidationRuleError(rule.name);
|
|
1136
|
-
const
|
|
2543
|
+
const parameters = DEPENDENT_RULES.has(rule.name) ? fillWildcards(rule.parameters, wildcardKeys(pattern, field)) : rule.parameters;
|
|
2544
|
+
const ctx = this.context(field, pattern, value, parameters, ruleNames);
|
|
1137
2545
|
if (await builtin.validate(ctx)) return [];
|
|
1138
2546
|
return [this.failureMessage(rule.name, builtin, ctx, pattern)];
|
|
1139
2547
|
}
|