@orieljs/validation 0.1.0-alpha.1 → 0.1.0-alpha.10
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 +7 -0
- package/README.md +34 -0
- package/TRADEMARKS.md +33 -0
- package/dist/index.d.ts +258 -43
- package/dist/index.js +1923 -179
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
package/dist/index.js
CHANGED
|
@@ -13,6 +13,23 @@ var ValidationErrorImpl = class extends Error {
|
|
|
13
13
|
status = 422;
|
|
14
14
|
name = "ValidationError";
|
|
15
15
|
};
|
|
16
|
+
var ValidationException = class _ValidationException extends ValidationErrorImpl {
|
|
17
|
+
/** Messages per field, each a string or a list. */
|
|
18
|
+
static withMessages(messages) {
|
|
19
|
+
const errors = Object.fromEntries(Object.entries(messages).map(([field, list]) => [field, typeof list === "string" ? [list] : [...list]]));
|
|
20
|
+
return new _ValidationException(errors);
|
|
21
|
+
}
|
|
22
|
+
constructor(errors, errorBag = "default", redirectTo) {
|
|
23
|
+
super(errors, errorBag, redirectTo);
|
|
24
|
+
this.message = summarize(errors);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function summarize(errors) {
|
|
28
|
+
const messages = Object.values(errors).flat();
|
|
29
|
+
if (messages.length === 0) return "The given data was invalid.";
|
|
30
|
+
const more = messages.length - 1;
|
|
31
|
+
return more === 0 ? messages[0] : `${messages[0]} (and ${more} more ${more === 1 ? "error" : "errors"})`;
|
|
32
|
+
}
|
|
16
33
|
var AuthorizationErrorImpl = class extends Error {
|
|
17
34
|
kind = "Authorization";
|
|
18
35
|
status = 403;
|
|
@@ -54,15 +71,17 @@ var FormRequest = class {
|
|
|
54
71
|
/**
|
|
55
72
|
* `const data = await StorePostRequest.validate()` (spec §4.3). Resolves the current request and the
|
|
56
73
|
* ValidationFactory from the active container, runs authorize() first (throwing AuthorizationErrorImpl if it
|
|
57
|
-
* returns false), then validates request.all() against rules()/messages()/attributes() and returns the
|
|
58
|
-
*
|
|
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>`.
|
|
59
78
|
*/
|
|
60
79
|
static async validate() {
|
|
61
80
|
const instance = new this();
|
|
62
81
|
if (!await instance.authorize()) throw new AuthorizationErrorImpl();
|
|
63
82
|
const request = inject(HttpRequest);
|
|
64
83
|
const factory = inject(ValidationFactory);
|
|
65
|
-
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());
|
|
66
85
|
return validator.validated();
|
|
67
86
|
}
|
|
68
87
|
};
|
|
@@ -70,7 +89,7 @@ var FormRequest = class {
|
|
|
70
89
|
// messages.ts
|
|
71
90
|
import { Str } from "@orieljs/support";
|
|
72
91
|
function defaultAttributeName(key) {
|
|
73
|
-
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(".");
|
|
74
93
|
}
|
|
75
94
|
function applyReplacements(template, replacements) {
|
|
76
95
|
return Object.entries(replacements).reduce((message, [key, value]) => message.replaceAll(`:${key}`, value), template);
|
|
@@ -83,33 +102,97 @@ var DEFAULT_MESSAGES = {
|
|
|
83
102
|
required_with_all: "The :attribute field is required when :values are present.",
|
|
84
103
|
required_without: "The :attribute field is required when :values is not present.",
|
|
85
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.",
|
|
86
124
|
string: "The :attribute field must be a string.",
|
|
87
125
|
integer: "The :attribute field must be an integer.",
|
|
88
126
|
numeric: "The :attribute field must be a number.",
|
|
89
127
|
boolean: "The :attribute field must be true or false.",
|
|
90
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.",
|
|
91
136
|
email: "The :attribute field must be a valid email address.",
|
|
92
137
|
url: "The :attribute field must be a valid URL.",
|
|
93
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.",
|
|
94
153
|
alpha: "The :attribute field must only contain letters.",
|
|
95
154
|
alpha_num: "The :attribute field must only contain letters and numbers.",
|
|
96
|
-
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.",
|
|
97
156
|
regex: "The :attribute field format is invalid.",
|
|
157
|
+
not_regex: "The :attribute field format is invalid.",
|
|
158
|
+
lowercase: "The :attribute field must be lowercase.",
|
|
159
|
+
uppercase: "The :attribute field must be uppercase.",
|
|
160
|
+
current_password: "The password is incorrect.",
|
|
161
|
+
"password.letters": "The :attribute field must contain at least one letter.",
|
|
162
|
+
"password.mixed": "The :attribute field must contain at least one uppercase and one lowercase letter.",
|
|
163
|
+
"password.numbers": "The :attribute field must contain at least one number.",
|
|
164
|
+
"password.symbols": "The :attribute field must contain at least one symbol.",
|
|
98
165
|
confirmed: "The :attribute field confirmation does not match.",
|
|
99
166
|
same: "The :attribute field must match :other.",
|
|
100
|
-
different: "The :attribute field must be different
|
|
167
|
+
different: "The :attribute field and :other must be different.",
|
|
101
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.",
|
|
102
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.",
|
|
103
178
|
date: "The :attribute field must be a valid date.",
|
|
104
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.",
|
|
105
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.",
|
|
106
185
|
in: "The selected :attribute is invalid.",
|
|
107
186
|
not_in: "The selected :attribute is invalid.",
|
|
187
|
+
enum: "The selected :attribute is invalid.",
|
|
108
188
|
unique: "The :attribute has already been taken.",
|
|
109
189
|
exists: "The selected :attribute is invalid.",
|
|
110
190
|
file: "The :attribute field must be a file.",
|
|
111
191
|
image: "The :attribute field must be an image.",
|
|
112
192
|
mimes: "The :attribute field must be a file of type: :values.",
|
|
193
|
+
mimetypes: "The :attribute field must be a file of type: :values.",
|
|
194
|
+
extensions: "The :attribute field must have one of the following extensions: :values.",
|
|
195
|
+
dimensions: "The :attribute field has invalid image dimensions.",
|
|
113
196
|
"max.string": "The :attribute field must not be greater than :max characters.",
|
|
114
197
|
"max.numeric": "The :attribute field must not be greater than :max.",
|
|
115
198
|
"max.array": "The :attribute field must not have more than :max items.",
|
|
@@ -125,13 +208,29 @@ var DEFAULT_MESSAGES = {
|
|
|
125
208
|
"size.string": "The :attribute field must be :size characters.",
|
|
126
209
|
"size.numeric": "The :attribute field must be :size.",
|
|
127
210
|
"size.array": "The :attribute field must contain :size items.",
|
|
128
|
-
"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."
|
|
129
228
|
};
|
|
130
229
|
|
|
131
230
|
// MessageBag.ts
|
|
132
231
|
var MessageBagImpl = class {
|
|
133
|
-
|
|
134
|
-
|
|
232
|
+
// No prototype: a field named `constructor` or `toString` is a key like any other.
|
|
233
|
+
items = /* @__PURE__ */ Object.create(null);
|
|
135
234
|
add(key, message) {
|
|
136
235
|
;
|
|
137
236
|
(this.items[key] ??= []).push(message);
|
|
@@ -163,38 +262,8 @@ var MessageBagImpl = class {
|
|
|
163
262
|
}
|
|
164
263
|
};
|
|
165
264
|
|
|
166
|
-
// rules/
|
|
167
|
-
import {
|
|
168
|
-
import { inject as inject2 } from "@orieljs/container";
|
|
169
|
-
function guessColumn(attribute, expandedFromWildcard = false) {
|
|
170
|
-
if (!expandedFromWildcard) return attribute;
|
|
171
|
-
return attribute.split(".").filter((segment) => !/^\d+$/.test(segment)).at(-1) ?? attribute;
|
|
172
|
-
}
|
|
173
|
-
function isNoExcept(except) {
|
|
174
|
-
return except === void 0 || except === null || except === "" || except === "NULL";
|
|
175
|
-
}
|
|
176
|
-
async function matchingRecordExists(table, column, value, except, idColumn = "id") {
|
|
177
|
-
const db = inject2(DatabaseManager);
|
|
178
|
-
let query = db.connection().table(table).where(column, value);
|
|
179
|
-
if (!isNoExcept(except)) query = query.where(idColumn, "!=", except);
|
|
180
|
-
return query.exists();
|
|
181
|
-
}
|
|
182
|
-
var databaseRules = {
|
|
183
|
-
unique: {
|
|
184
|
-
validate: async (ctx) => {
|
|
185
|
-
const [table, column, except, idColumn] = ctx.parameters;
|
|
186
|
-
return !await matchingRecordExists(table, column || guessColumn(ctx.attribute, ctx.expandedFromWildcard), ctx.value, except, idColumn || "id");
|
|
187
|
-
},
|
|
188
|
-
messageKey: () => "unique"
|
|
189
|
-
},
|
|
190
|
-
exists: {
|
|
191
|
-
validate: async (ctx) => {
|
|
192
|
-
const [table, column] = ctx.parameters;
|
|
193
|
-
return matchingRecordExists(table, column || guessColumn(ctx.attribute, ctx.expandedFromWildcard), ctx.value);
|
|
194
|
-
},
|
|
195
|
-
messageKey: () => "exists"
|
|
196
|
-
}
|
|
197
|
-
};
|
|
265
|
+
// rules/acceptance.ts
|
|
266
|
+
import { Arr as Arr3 } from "@orieljs/support";
|
|
198
267
|
|
|
199
268
|
// parse.ts
|
|
200
269
|
import { Arr } from "@orieljs/support";
|
|
@@ -205,18 +274,26 @@ function splitOnce(value, separator) {
|
|
|
205
274
|
const index = value.indexOf(separator);
|
|
206
275
|
return index === -1 ? [value, void 0] : [value.slice(0, index), value.slice(index + 1)];
|
|
207
276
|
}
|
|
208
|
-
function
|
|
277
|
+
function isExpandingRule(item) {
|
|
278
|
+
return typeof item === "object" && item !== null && typeof item.expandRules === "function";
|
|
279
|
+
}
|
|
280
|
+
function parseRuleSpec(spec, data = {}) {
|
|
209
281
|
const items = typeof spec === "string" ? spec.split("|") : spec;
|
|
210
|
-
return items.filter((item) => item !== "").
|
|
211
|
-
if (
|
|
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 }];
|
|
212
285
|
const [name, raw] = splitOnce(item, ":");
|
|
213
|
-
return { kind: "named", name, parameters: raw === void 0 ? [] : splitParameters(name, raw) };
|
|
286
|
+
return [{ kind: "named", name, parameters: raw === void 0 ? [] : splitParameters(name, raw) }];
|
|
214
287
|
});
|
|
215
288
|
}
|
|
216
289
|
function isEmptyValue(value) {
|
|
217
290
|
if (value === null || value === void 0) return true;
|
|
218
|
-
if (typeof value === "string") return value === "";
|
|
291
|
+
if (typeof value === "string") return value.trim() === "";
|
|
219
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
|
+
}
|
|
220
297
|
return false;
|
|
221
298
|
}
|
|
222
299
|
function isUploadedFileLike(value) {
|
|
@@ -246,45 +323,566 @@ function expandWildcardKeys(pattern, data) {
|
|
|
246
323
|
return paths;
|
|
247
324
|
}
|
|
248
325
|
|
|
249
|
-
// rules/
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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: {
|
|
254
519
|
validate: (ctx) => {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
return IMAGE_EXTENSIONS.has(ctx.value.extension().toLowerCase());
|
|
520
|
+
const items = ctx.value;
|
|
521
|
+
return Array.isArray(items) && ctx.parameters.every((needle) => items.some((item) => alike(item, needle)));
|
|
258
522
|
},
|
|
259
|
-
messageKey: () => "
|
|
523
|
+
messageKey: () => "contains"
|
|
260
524
|
},
|
|
261
|
-
|
|
525
|
+
doesnt_contain: {
|
|
262
526
|
validate: (ctx) => {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return allowed.includes(ctx.value.extension().toLowerCase());
|
|
527
|
+
const items = ctx.value;
|
|
528
|
+
return Array.isArray(items) && !ctx.parameters.some((needle) => items.some((item) => alike(item, needle)));
|
|
266
529
|
},
|
|
267
|
-
messageKey: () => "
|
|
268
|
-
replacements: (ctx) => (
|
|
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] ?? "") })
|
|
269
550
|
}
|
|
270
551
|
};
|
|
271
552
|
|
|
272
|
-
// rules/
|
|
273
|
-
import {
|
|
274
|
-
|
|
275
|
-
var
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
} catch (error) {
|
|
285
|
-
throw new InvalidRuleParameterError("regex", raw, error instanceof Error ? error.message : String(error));
|
|
553
|
+
// rules/auth.ts
|
|
554
|
+
import { AuthFactory, Hasher } from "@orieljs/contracts";
|
|
555
|
+
import { inject as inject2 } from "@orieljs/container";
|
|
556
|
+
var authRules = {
|
|
557
|
+
current_password: {
|
|
558
|
+
validate: async (ctx) => {
|
|
559
|
+
const guard = inject2(AuthFactory).guard(ctx.parameters[0]);
|
|
560
|
+
if (await guard.guest()) return false;
|
|
561
|
+
const user = await guard.user();
|
|
562
|
+
return user !== void 0 && typeof ctx.value === "string" && inject2(Hasher).check(ctx.value, user.getAuthPassword());
|
|
563
|
+
},
|
|
564
|
+
messageKey: () => "current_password"
|
|
286
565
|
}
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
// rules/database.ts
|
|
569
|
+
import { DatabaseManager } from "@orieljs/contracts";
|
|
570
|
+
import { inject as inject3 } from "@orieljs/container";
|
|
571
|
+
function guessColumn(attribute, expandedFromWildcard = false) {
|
|
572
|
+
if (!expandedFromWildcard) return attribute;
|
|
573
|
+
return attribute.split(".").filter((segment) => !/^\d+$/.test(segment)).at(-1) ?? attribute;
|
|
574
|
+
}
|
|
575
|
+
function isNoExcept(except) {
|
|
576
|
+
return except === void 0 || except === null || except === "" || except === "NULL";
|
|
577
|
+
}
|
|
578
|
+
async function matchingRecordExists(table, column, value, except, idColumn = "id") {
|
|
579
|
+
const db = inject3(DatabaseManager);
|
|
580
|
+
let query = db.connection().table(table).where(column, value);
|
|
581
|
+
if (!isNoExcept(except)) query = query.where(idColumn, "!=", except);
|
|
582
|
+
return query.exists();
|
|
583
|
+
}
|
|
584
|
+
var databaseRules = {
|
|
585
|
+
unique: {
|
|
586
|
+
validate: async (ctx) => {
|
|
587
|
+
const [table, column, except, idColumn] = ctx.parameters;
|
|
588
|
+
return !await matchingRecordExists(table, column || guessColumn(ctx.attribute, ctx.expandedFromWildcard), ctx.value, except, idColumn || "id");
|
|
589
|
+
},
|
|
590
|
+
messageKey: () => "unique"
|
|
591
|
+
},
|
|
592
|
+
exists: {
|
|
593
|
+
validate: async (ctx) => {
|
|
594
|
+
const [table, column] = ctx.parameters;
|
|
595
|
+
return matchingRecordExists(table, column || guessColumn(ctx.attribute, ctx.expandedFromWildcard), ctx.value);
|
|
596
|
+
},
|
|
597
|
+
messageKey: () => "exists"
|
|
598
|
+
}
|
|
599
|
+
};
|
|
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;
|
|
287
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
|
|
288
886
|
var DATE_SHAPES = [
|
|
289
887
|
/^\d{4}-\d{1,2}-\d{1,2}(?:[T\s].*)?$/,
|
|
290
888
|
/^\d{1,2}[/.-]\d{1,2}[/.-]\d{2,4}(?:\s.*)?$/,
|
|
@@ -350,41 +948,444 @@ function relativeDate(param) {
|
|
|
350
948
|
return date.getTime();
|
|
351
949
|
}
|
|
352
950
|
var FIELD_REFERENCE_RE = /^[A-Za-z_][\w-]*(?:\.[\w-]+)*$/;
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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);
|
|
357
956
|
if (!Number.isNaN(parsed)) return parsed;
|
|
358
957
|
}
|
|
359
958
|
const relative = relativeDate(param);
|
|
360
959
|
if (relative !== void 0) return relative;
|
|
361
|
-
const literal =
|
|
960
|
+
const literal = readDate(param, format2);
|
|
362
961
|
if (!Number.isNaN(literal)) return literal;
|
|
363
|
-
if (FIELD_REFERENCE_RE.test(param)) return
|
|
962
|
+
if (FIELD_REFERENCE_RE.test(param)) return null;
|
|
364
963
|
throw new InvalidRuleParameterError(rule, param, 'expected a date, today/tomorrow/yesterday/now, "+N unit", or a field name');
|
|
365
964
|
}
|
|
366
|
-
function
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
return
|
|
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
|
+
|
|
1052
|
+
// rules/file.ts
|
|
1053
|
+
import { FileType } from "@orieljs/support";
|
|
1054
|
+
|
|
1055
|
+
// files/dimensions.ts
|
|
1056
|
+
var FRAME_MARKERS = /* @__PURE__ */ new Set([192, 193, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207]);
|
|
1057
|
+
var MAX_JPEG_STEPS = 1024;
|
|
1058
|
+
var MAX_BOXES = 1024;
|
|
1059
|
+
var fourcc = (view, offset) => String.fromCharCode(view.getUint8(offset), view.getUint8(offset + 1), view.getUint8(offset + 2), view.getUint8(offset + 3));
|
|
1060
|
+
var u24le = (view, offset) => view.getUint8(offset) | view.getUint8(offset + 1) << 8 | view.getUint8(offset + 2) << 16;
|
|
1061
|
+
var READERS = {
|
|
1062
|
+
"image/png": (view) => fourcc(view, 12) === "IHDR" ? { width: view.getUint32(16), height: view.getUint32(20) } : void 0,
|
|
1063
|
+
"image/gif": (view) => ({ width: view.getUint16(6, true), height: view.getUint16(8, true) }),
|
|
1064
|
+
"image/bmp": (view) => view.getUint32(14, true) === 12 ? { width: view.getUint16(18, true), height: view.getUint16(20, true) } : { width: Math.abs(view.getInt32(18, true)), height: Math.abs(view.getInt32(22, true)) },
|
|
1065
|
+
"image/jpeg": jpeg,
|
|
1066
|
+
"image/webp": webp,
|
|
1067
|
+
"image/avif": avif
|
|
1068
|
+
};
|
|
1069
|
+
function imageDimensions(bytes, mime) {
|
|
1070
|
+
const reader = READERS[mime];
|
|
1071
|
+
if (!reader) return void 0;
|
|
1072
|
+
try {
|
|
1073
|
+
const size = reader(new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength));
|
|
1074
|
+
return size && size.width > 0 && size.height > 0 ? size : void 0;
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
if (error instanceof RangeError) return void 0;
|
|
1077
|
+
throw error;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
function jpeg(view) {
|
|
1081
|
+
let offset = 2;
|
|
1082
|
+
for (let step = 0; step < MAX_JPEG_STEPS && offset + 4 <= view.byteLength; step++) {
|
|
1083
|
+
if (view.getUint8(offset) !== 255) return void 0;
|
|
1084
|
+
const marker = view.getUint8(offset + 1);
|
|
1085
|
+
if (marker === 255) offset += 1;
|
|
1086
|
+
else if (marker === 216 || marker === 1 || marker >= 208 && marker <= 215) offset += 2;
|
|
1087
|
+
else if (marker === 217 || marker === 218) return void 0;
|
|
1088
|
+
else if (FRAME_MARKERS.has(marker)) return { height: view.getUint16(offset + 5), width: view.getUint16(offset + 7) };
|
|
1089
|
+
else offset += 2 + view.getUint16(offset + 2);
|
|
1090
|
+
}
|
|
1091
|
+
return void 0;
|
|
1092
|
+
}
|
|
1093
|
+
function webp(view) {
|
|
1094
|
+
const chunk = fourcc(view, 12);
|
|
1095
|
+
if (chunk === "VP8 ") {
|
|
1096
|
+
if (view.getUint8(23) !== 157 || view.getUint8(24) !== 1 || view.getUint8(25) !== 42) return void 0;
|
|
1097
|
+
return { width: view.getUint16(26, true) & 16383, height: view.getUint16(28, true) & 16383 };
|
|
1098
|
+
}
|
|
1099
|
+
if (chunk === "VP8L") {
|
|
1100
|
+
if (view.getUint8(20) !== 47) return void 0;
|
|
1101
|
+
const bits = view.getUint32(21, true);
|
|
1102
|
+
return { width: (bits & 16383) + 1, height: (bits >>> 14 & 16383) + 1 };
|
|
1103
|
+
}
|
|
1104
|
+
if (chunk === "VP8X") return { width: u24le(view, 24) + 1, height: u24le(view, 27) + 1 };
|
|
1105
|
+
return void 0;
|
|
1106
|
+
}
|
|
1107
|
+
function* boxes(view, start, end) {
|
|
1108
|
+
let offset = start;
|
|
1109
|
+
for (let count = 0; count < MAX_BOXES && offset + 8 <= end; count++) {
|
|
1110
|
+
let size = view.getUint32(offset);
|
|
1111
|
+
let header = 8;
|
|
1112
|
+
if (size === 1) {
|
|
1113
|
+
size = Number(view.getBigUint64(offset + 8));
|
|
1114
|
+
header = 16;
|
|
1115
|
+
} else if (size === 0) {
|
|
1116
|
+
size = end - offset;
|
|
1117
|
+
}
|
|
1118
|
+
if (size < header || offset + size > end) return;
|
|
1119
|
+
yield { type: fourcc(view, offset + 4), start: offset + header, end: offset + size };
|
|
1120
|
+
offset += size;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
var findBox = (view, start, end, type) => {
|
|
1124
|
+
for (const box of boxes(view, start, end)) if (box.type === type) return box;
|
|
1125
|
+
return void 0;
|
|
1126
|
+
};
|
|
1127
|
+
function avif(view) {
|
|
1128
|
+
const meta = findBox(view, 0, view.byteLength, "meta");
|
|
1129
|
+
const iprp = meta && findBox(view, meta.start + 4, meta.end, "iprp");
|
|
1130
|
+
const ipco = iprp && findBox(view, iprp.start, iprp.end, "ipco");
|
|
1131
|
+
if (!ipco) return void 0;
|
|
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;
|
|
1137
|
+
}
|
|
1138
|
+
return largest;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// rules/file.ts
|
|
1142
|
+
var { BINARY_AND_MARKUP_TYPES, detectFileType, EMPTY, RASTER_IMAGES, SVG, TEXT } = FileType;
|
|
1143
|
+
var inspections = /* @__PURE__ */ new WeakMap();
|
|
1144
|
+
function inspect(file) {
|
|
1145
|
+
let inspection = inspections.get(file);
|
|
1146
|
+
if (!inspection) {
|
|
1147
|
+
inspection = file.arrayBuffer().then((buffer) => {
|
|
1148
|
+
const bytes = new Uint8Array(buffer);
|
|
1149
|
+
const type = detectFileType(bytes);
|
|
1150
|
+
return { type, dimensions: RASTER_IMAGES.has(type.mime) ? imageDimensions(bytes, type.mime) : void 0 };
|
|
1151
|
+
});
|
|
1152
|
+
inspections.set(file, inspection);
|
|
1153
|
+
}
|
|
1154
|
+
return inspection;
|
|
1155
|
+
}
|
|
1156
|
+
function clientExtension(file) {
|
|
1157
|
+
const name = file.clientName.slice(Math.max(file.clientName.lastIndexOf("/"), file.clientName.lastIndexOf("\\")) + 1);
|
|
1158
|
+
const dot = name.lastIndexOf(".");
|
|
1159
|
+
return dot === -1 ? "" : name.slice(dot + 1).trim().toLowerCase();
|
|
1160
|
+
}
|
|
1161
|
+
var PHP_EXTENSIONS = /* @__PURE__ */ new Set(["php", "php3", "php4", "php5", "php7", "php8", "phtml", "phar"]);
|
|
1162
|
+
var blocksPhp = (file, allowed) => !allowed.includes("php") && PHP_EXTENSIONS.has(clientExtension(file));
|
|
1163
|
+
var TEXT_FORMATS = /* @__PURE__ */ new Set(["txt", "text", "csv", "tsv", "json", "md"]);
|
|
1164
|
+
var RECOGNISED_EXTENSIONS = new Set(BINARY_AND_MARKUP_TYPES.flatMap(({ extensions }) => extensions));
|
|
1165
|
+
var lower = (values) => values.map((value) => value.trim().toLowerCase());
|
|
1166
|
+
function meetsMimes(type, extension, allowed) {
|
|
1167
|
+
if (type === EMPTY) return false;
|
|
1168
|
+
if (type === TEXT && allowed.has("txt")) return true;
|
|
1169
|
+
if (type !== TEXT && type.extensions.length > 0) return type.extensions.some((name) => allowed.has(name));
|
|
1170
|
+
if (!allowed.has(extension) || RECOGNISED_EXTENSIONS.has(extension)) return false;
|
|
1171
|
+
return type === TEXT || !TEXT_FORMATS.has(extension);
|
|
1172
|
+
}
|
|
1173
|
+
function allowedExtensions(ctx) {
|
|
1174
|
+
const allowed = new Set(lower(ctx.parameters));
|
|
1175
|
+
if (allowed.has("jpg") || allowed.has("jpeg")) allowed.add("jpg").add("jpeg");
|
|
1176
|
+
return allowed;
|
|
1177
|
+
}
|
|
1178
|
+
var listed2 = (ctx) => ({ values: ctx.parameters.join(", ") });
|
|
1179
|
+
var DIMENSION_KEYS = ["width", "height", "min_width", "min_height", "max_width", "max_height", "ratio"];
|
|
1180
|
+
function namedParameter(parameter) {
|
|
1181
|
+
const at = parameter.indexOf("=");
|
|
1182
|
+
return at === -1 ? [parameter, ""] : [parameter.slice(0, at), parameter.slice(at + 1)];
|
|
1183
|
+
}
|
|
1184
|
+
function parseRatio(value) {
|
|
1185
|
+
const match = /^(\d+(?:\.\d+)?|\.\d+)(?:\/(\d+))?$/.exec(value.trim());
|
|
1186
|
+
if (!match) return void 0;
|
|
1187
|
+
const ratio = Number(match[1]) / Number(match[2] ?? 1);
|
|
1188
|
+
return Number.isFinite(ratio) && ratio > 0 ? ratio : void 0;
|
|
1189
|
+
}
|
|
1190
|
+
function dimensionConstraints(parameters) {
|
|
1191
|
+
if (parameters.length === 0) throw new InvalidRuleParameterError("dimensions", "", `expected one or more of ${DIMENSION_KEYS.join(", ")}`);
|
|
1192
|
+
const constraints = {};
|
|
1193
|
+
for (const parameter of parameters) {
|
|
1194
|
+
const [key, value] = namedParameter(parameter);
|
|
1195
|
+
if (!DIMENSION_KEYS.includes(key)) {
|
|
1196
|
+
throw new InvalidRuleParameterError("dimensions", parameter, `expected one of ${DIMENSION_KEYS.join(", ")}`);
|
|
1197
|
+
}
|
|
1198
|
+
const number = key === "ratio" ? parseRatio(value) : /^\d+$/.test(value) ? Number(value) : void 0;
|
|
1199
|
+
if (number === void 0) {
|
|
1200
|
+
throw new InvalidRuleParameterError("dimensions", parameter, key === "ratio" ? "expected a ratio such as 3/2 or 1.5" : "expected a whole number of pixels");
|
|
1201
|
+
}
|
|
1202
|
+
constraints[key] = number;
|
|
1203
|
+
}
|
|
1204
|
+
return constraints;
|
|
1205
|
+
}
|
|
1206
|
+
function meetsDimensions(constraints, { width, height }) {
|
|
1207
|
+
const { width: exactWidth, height: exactHeight, min_width, max_width, min_height, max_height, ratio } = constraints;
|
|
1208
|
+
if (exactWidth !== void 0 && width !== exactWidth) return false;
|
|
1209
|
+
if (exactHeight !== void 0 && height !== exactHeight) return false;
|
|
1210
|
+
if (min_width !== void 0 && width < min_width) return false;
|
|
1211
|
+
if (max_width !== void 0 && width > max_width) return false;
|
|
1212
|
+
if (min_height !== void 0 && height < min_height) return false;
|
|
1213
|
+
if (max_height !== void 0 && height > max_height) return false;
|
|
1214
|
+
return ratio === void 0 || Math.abs(ratio - width / height) <= 1 / (Math.max((width + height) / 2, height) + 1);
|
|
1215
|
+
}
|
|
1216
|
+
var fileRules = {
|
|
1217
|
+
file: { validate: (ctx) => isUploadedFileLike(ctx.value), messageKey: () => "file" },
|
|
1218
|
+
/** A JPEG, PNG, GIF, WebP, AVIF or BMP by its bytes; SVG only as `image:allow_svg` (Laravel 12). */
|
|
1219
|
+
image: {
|
|
1220
|
+
validate: async (ctx) => {
|
|
1221
|
+
if (!isUploadedFileLike(ctx.value) || blocksPhp(ctx.value, [])) return false;
|
|
1222
|
+
const { type } = await inspect(ctx.value);
|
|
1223
|
+
return RASTER_IMAGES.has(type.mime) || type === SVG && ctx.parameters.includes("allow_svg");
|
|
1224
|
+
},
|
|
1225
|
+
messageKey: () => "image"
|
|
1226
|
+
},
|
|
1227
|
+
mimes: {
|
|
1228
|
+
validate: async (ctx) => {
|
|
1229
|
+
if (!isUploadedFileLike(ctx.value)) return false;
|
|
1230
|
+
const allowed = allowedExtensions(ctx);
|
|
1231
|
+
if (blocksPhp(ctx.value, [...allowed])) return false;
|
|
1232
|
+
return meetsMimes((await inspect(ctx.value)).type, clientExtension(ctx.value), allowed);
|
|
1233
|
+
},
|
|
1234
|
+
messageKey: () => "mimes",
|
|
1235
|
+
replacements: listed2
|
|
1236
|
+
},
|
|
1237
|
+
/**
|
|
1238
|
+
* The detected MIME type is listed, or its `type/*` wildcard is. `image/*` includes SVG (`image/svg+xml`), as in
|
|
1239
|
+
* Laravel: the rule for photos is `image`.
|
|
1240
|
+
*/
|
|
1241
|
+
mimetypes: {
|
|
1242
|
+
validate: async (ctx) => {
|
|
1243
|
+
if (!isUploadedFileLike(ctx.value)) return false;
|
|
1244
|
+
const allowed = lower(ctx.parameters);
|
|
1245
|
+
if (blocksPhp(ctx.value, allowed)) return false;
|
|
1246
|
+
const { mime } = (await inspect(ctx.value)).type;
|
|
1247
|
+
return allowed.includes(mime) || allowed.includes(`${mime.split("/")[0]}/*`);
|
|
1248
|
+
},
|
|
1249
|
+
messageKey: () => "mimetypes",
|
|
1250
|
+
replacements: listed2
|
|
1251
|
+
},
|
|
1252
|
+
/**
|
|
1253
|
+
* The pixel size read from the image's header meets every constraint: width, height, min_width, max_width,
|
|
1254
|
+
* min_height, max_height, ratio. SVG and images whose header can't be read fail.
|
|
1255
|
+
*/
|
|
1256
|
+
dimensions: {
|
|
1257
|
+
validate: async (ctx) => {
|
|
1258
|
+
const constraints = dimensionConstraints(ctx.parameters);
|
|
1259
|
+
if (!isUploadedFileLike(ctx.value)) return false;
|
|
1260
|
+
const { dimensions } = await inspect(ctx.value);
|
|
1261
|
+
return dimensions !== void 0 && meetsDimensions(constraints, dimensions);
|
|
1262
|
+
},
|
|
1263
|
+
messageKey: () => "dimensions",
|
|
1264
|
+
replacements: (ctx) => Object.fromEntries(ctx.parameters.map(namedParameter))
|
|
1265
|
+
},
|
|
1266
|
+
/** The client file name's extension is listed (Laravel 11). It checks the name only: pair it with mimes or image. */
|
|
1267
|
+
extensions: {
|
|
1268
|
+
validate: (ctx) => {
|
|
1269
|
+
if (!isUploadedFileLike(ctx.value)) return false;
|
|
1270
|
+
const allowed = lower(ctx.parameters);
|
|
1271
|
+
return !blocksPhp(ctx.value, allowed) && allowed.includes(clientExtension(ctx.value));
|
|
1272
|
+
},
|
|
1273
|
+
messageKey: () => "extensions",
|
|
1274
|
+
replacements: listed2
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1278
|
+
// rules/format.ts
|
|
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;
|
|
1282
|
+
var ALPHA_RE = /^[A-Za-z]+$/;
|
|
1283
|
+
var ALPHA_NUM_RE = /^[A-Za-z0-9]+$/;
|
|
1284
|
+
var ALPHA_DASH_RE = /^[A-Za-z0-9_-]+$/;
|
|
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(", ") });
|
|
1349
|
+
function compileRegex(raw) {
|
|
1350
|
+
const match = /^\/(.*)\/([a-z]*)$/.exec(raw);
|
|
1351
|
+
try {
|
|
1352
|
+
return match ? new RegExp(match[1], match[2]) : new RegExp(raw);
|
|
1353
|
+
} catch (error) {
|
|
1354
|
+
throw new InvalidRuleParameterError("regex", raw, error instanceof Error ? error.message : String(error));
|
|
1355
|
+
}
|
|
372
1356
|
}
|
|
373
1357
|
var formatRules = {
|
|
374
|
-
email: { validate: (ctx) => typeof ctx.value === "string" &&
|
|
375
|
-
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: {
|
|
376
1366
|
validate: (ctx) => {
|
|
377
1367
|
if (typeof ctx.value !== "string") return false;
|
|
378
1368
|
try {
|
|
379
|
-
|
|
1369
|
+
JSON.parse(ctx.value);
|
|
380
1370
|
return true;
|
|
381
1371
|
} catch {
|
|
382
1372
|
return false;
|
|
383
1373
|
}
|
|
384
1374
|
},
|
|
385
|
-
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
|
|
386
1388
|
},
|
|
387
|
-
uuid: { validate: (ctx) => typeof ctx.value === "string" && UUID_RE.test(ctx.value), messageKey: () => "uuid" },
|
|
388
1389
|
alpha: { validate: (ctx) => typeof ctx.value === "string" && ALPHA_RE.test(ctx.value), messageKey: () => "alpha" },
|
|
389
1390
|
alpha_num: {
|
|
390
1391
|
validate: (ctx) => typeof ctx.value === "string" && ALPHA_NUM_RE.test(ctx.value),
|
|
@@ -394,50 +1395,132 @@ var formatRules = {
|
|
|
394
1395
|
validate: (ctx) => typeof ctx.value === "string" && ALPHA_DASH_RE.test(ctx.value),
|
|
395
1396
|
messageKey: () => "alpha_dash"
|
|
396
1397
|
},
|
|
1398
|
+
lowercase: {
|
|
1399
|
+
validate: (ctx) => (typeof ctx.value === "string" || typeof ctx.value === "number") && String(ctx.value).toLowerCase() === String(ctx.value),
|
|
1400
|
+
messageKey: () => "lowercase"
|
|
1401
|
+
},
|
|
1402
|
+
uppercase: {
|
|
1403
|
+
validate: (ctx) => (typeof ctx.value === "string" || typeof ctx.value === "number") && String(ctx.value).toUpperCase() === String(ctx.value),
|
|
1404
|
+
messageKey: () => "uppercase"
|
|
1405
|
+
},
|
|
397
1406
|
regex: {
|
|
398
1407
|
validate: (ctx) => typeof ctx.value === "string" && compileRegex(ctx.parameters[0] ?? "").test(ctx.value),
|
|
399
1408
|
messageKey: () => "regex"
|
|
400
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). */
|
|
401
1416
|
confirmed: {
|
|
402
|
-
validate: (ctx) => ctx.value ===
|
|
1417
|
+
validate: (ctx) => ctx.value === Arr7.get(ctx.data, ctx.parameters[0] || `${ctx.attribute}_confirmation`),
|
|
403
1418
|
messageKey: () => "confirmed"
|
|
404
1419
|
},
|
|
405
1420
|
same: {
|
|
406
|
-
validate: (ctx) => ctx.value ===
|
|
1421
|
+
validate: (ctx) => ctx.value === Arr7.get(ctx.data, ctx.parameters[0] ?? ""),
|
|
407
1422
|
messageKey: () => "same",
|
|
408
|
-
replacements: (ctx) => ({ other:
|
|
1423
|
+
replacements: (ctx) => ({ other: displayName(ctx, ctx.parameters[0] ?? "") })
|
|
409
1424
|
},
|
|
410
1425
|
different: {
|
|
411
|
-
validate: (ctx) => ctx.value !==
|
|
1426
|
+
validate: (ctx) => ctx.value !== Arr7.get(ctx.data, ctx.parameters[0] ?? ""),
|
|
412
1427
|
messageKey: () => "different",
|
|
413
|
-
replacements: (ctx) => ({ other:
|
|
1428
|
+
replacements: (ctx) => ({ other: displayName(ctx, ctx.parameters[0] ?? "") })
|
|
414
1429
|
},
|
|
415
|
-
accepted: { implicit: true, validate: (ctx) => ACCEPTED_VALUES.includes(ctx.value), messageKey: () => "accepted" },
|
|
416
1430
|
digits: {
|
|
417
1431
|
validate: (ctx) => typeof ctx.value !== "object" && new RegExp(`^\\d{${ctx.parameters[0]}}$`).test(String(ctx.value)),
|
|
418
1432
|
messageKey: () => "digits",
|
|
419
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]}` })
|
|
420
1497
|
},
|
|
421
|
-
|
|
422
|
-
validate: (ctx) =>
|
|
423
|
-
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] ?? "" })
|
|
424
1502
|
},
|
|
425
|
-
|
|
426
|
-
validate: (ctx) =>
|
|
427
|
-
messageKey: () => "
|
|
428
|
-
replacements: (ctx) => ({
|
|
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] ?? "" })
|
|
429
1507
|
},
|
|
430
|
-
|
|
431
|
-
validate: (ctx) =>
|
|
432
|
-
messageKey: () => "
|
|
433
|
-
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] ?? "" })
|
|
1512
|
+
},
|
|
1513
|
+
multiple_of: {
|
|
1514
|
+
validate: (ctx) => isMultipleOf(ctx.value, ctx.parameters[0] ?? ""),
|
|
1515
|
+
messageKey: () => "multiple_of",
|
|
1516
|
+
replacements: (ctx) => ({ value: ctx.parameters[0] ?? "" })
|
|
434
1517
|
}
|
|
435
1518
|
};
|
|
436
1519
|
|
|
437
1520
|
// rules/presence.ts
|
|
438
|
-
import { Arr as
|
|
1521
|
+
import { Arr as Arr8 } from "@orieljs/support";
|
|
439
1522
|
function isFieldPresent(field, data) {
|
|
440
|
-
return
|
|
1523
|
+
return Arr8.has(data, field) && !isEmptyValue(Arr8.get(data, field));
|
|
441
1524
|
}
|
|
442
1525
|
function anyPresent(fields, data) {
|
|
443
1526
|
return fields.some((field) => isFieldPresent(field, data));
|
|
@@ -445,6 +1528,13 @@ function anyPresent(fields, data) {
|
|
|
445
1528
|
function allPresent(fields, data) {
|
|
446
1529
|
return fields.every((field) => isFieldPresent(field, data));
|
|
447
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] ?? "") });
|
|
448
1538
|
var presenceRules = {
|
|
449
1539
|
required: {
|
|
450
1540
|
implicit: true,
|
|
@@ -453,89 +1543,160 @@ var presenceRules = {
|
|
|
453
1543
|
},
|
|
454
1544
|
required_if: {
|
|
455
1545
|
implicit: true,
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const applies = values.includes(String(Arr3.get(ctx.data, other)));
|
|
459
|
-
return !applies || !isEmptyValue(ctx.value);
|
|
460
|
-
},
|
|
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),
|
|
461
1548
|
messageKey: () => "required_if",
|
|
462
|
-
replacements:
|
|
1549
|
+
replacements: otherAndValue
|
|
463
1550
|
},
|
|
464
1551
|
required_unless: {
|
|
465
1552
|
implicit: true,
|
|
466
|
-
validate: (ctx) =>
|
|
467
|
-
const [other, ...values] = ctx.parameters;
|
|
468
|
-
const applies = !values.includes(String(Arr3.get(ctx.data, other)));
|
|
469
|
-
return !applies || !isEmptyValue(ctx.value);
|
|
470
|
-
},
|
|
1553
|
+
validate: (ctx) => otherFieldMatches(ctx) || !isEmptyValue(ctx.value),
|
|
471
1554
|
messageKey: () => "required_unless",
|
|
472
|
-
replacements:
|
|
1555
|
+
replacements: otherAndValues
|
|
473
1556
|
},
|
|
474
1557
|
required_with: {
|
|
475
1558
|
implicit: true,
|
|
476
1559
|
validate: (ctx) => !anyPresent(ctx.parameters, ctx.data) || !isEmptyValue(ctx.value),
|
|
477
1560
|
messageKey: () => "required_with",
|
|
478
|
-
replacements:
|
|
1561
|
+
replacements: fieldList
|
|
479
1562
|
},
|
|
480
1563
|
required_with_all: {
|
|
481
1564
|
implicit: true,
|
|
482
1565
|
validate: (ctx) => !allPresent(ctx.parameters, ctx.data) || !isEmptyValue(ctx.value),
|
|
483
1566
|
messageKey: () => "required_with_all",
|
|
484
|
-
replacements:
|
|
1567
|
+
replacements: fieldList
|
|
485
1568
|
},
|
|
486
1569
|
required_without: {
|
|
487
1570
|
implicit: true,
|
|
488
1571
|
validate: (ctx) => ctx.parameters.every((f) => isFieldPresent(f, ctx.data)) || !isEmptyValue(ctx.value),
|
|
489
1572
|
messageKey: () => "required_without",
|
|
490
|
-
replacements:
|
|
1573
|
+
replacements: fieldList
|
|
491
1574
|
},
|
|
492
1575
|
required_without_all: {
|
|
493
1576
|
implicit: true,
|
|
494
1577
|
validate: (ctx) => anyPresent(ctx.parameters, ctx.data) || !isEmptyValue(ctx.value),
|
|
495
1578
|
messageKey: () => "required_without_all",
|
|
496
|
-
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
|
|
497
1646
|
}
|
|
498
1647
|
};
|
|
499
1648
|
|
|
500
|
-
// rules/
|
|
501
|
-
|
|
502
|
-
var
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
string: { validate: (ctx) => typeof ctx.value === "string", messageKey: () => "string" },
|
|
509
|
-
integer: {
|
|
510
|
-
validate: (ctx) => typeof ctx.value !== "object" && /^-?\d+$/.test(String(ctx.value).trim()),
|
|
511
|
-
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
|
|
512
1657
|
},
|
|
513
|
-
|
|
514
|
-
validate: (ctx) =>
|
|
515
|
-
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] ?? "") })
|
|
516
1667
|
},
|
|
517
|
-
|
|
518
|
-
|
|
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] ?? "") })
|
|
1672
|
+
},
|
|
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
|
+
}
|
|
519
1679
|
};
|
|
520
1680
|
|
|
521
1681
|
// rules/size.ts
|
|
1682
|
+
import { Arr as Arr10 } from "@orieljs/support";
|
|
522
1683
|
function hasAny(ctx, names) {
|
|
523
1684
|
return names.some((name) => ctx.ruleNames.includes(name));
|
|
524
1685
|
}
|
|
525
|
-
|
|
1686
|
+
var NUMERIC_RULES = ["numeric", "integer", "decimal"];
|
|
1687
|
+
function isPlainObject2(value) {
|
|
526
1688
|
if (typeof value !== "object" || value === null) return false;
|
|
527
1689
|
const proto = Object.getPrototypeOf(value);
|
|
528
1690
|
return proto === Object.prototype || proto === null;
|
|
529
1691
|
}
|
|
530
|
-
function measure(ctx) {
|
|
531
|
-
|
|
532
|
-
if (hasAny(ctx, ["numeric", "integer"])) {
|
|
1692
|
+
function measure(ctx, value = ctx.value, numeric = hasAny(ctx, NUMERIC_RULES)) {
|
|
1693
|
+
if (numeric) {
|
|
533
1694
|
return { type: "numeric", size: isNumeric(value) ? Number(value) : stringLength(value) };
|
|
534
1695
|
}
|
|
535
1696
|
if (Array.isArray(value)) return { type: "array", size: value.length };
|
|
536
1697
|
if (isUploadedFileLike(value)) return { type: "file", size: value.size / 1024 };
|
|
537
1698
|
if (hasAny(ctx, ["file", "image"])) return { type: "file", size: Number.NaN };
|
|
538
|
-
if (
|
|
1699
|
+
if (isPlainObject2(value)) return { type: "array", size: Object.keys(value).length };
|
|
539
1700
|
if (hasAny(ctx, ["array"])) return { type: "array", size: stringLength(value) };
|
|
540
1701
|
return { type: "string", size: stringLength(value) };
|
|
541
1702
|
}
|
|
@@ -546,6 +1707,41 @@ function stringLength(value) {
|
|
|
546
1707
|
function sizeOf(ctx) {
|
|
547
1708
|
return measure(ctx).size;
|
|
548
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
|
+
});
|
|
549
1745
|
function sizeType(ctx) {
|
|
550
1746
|
return measure(ctx).type;
|
|
551
1747
|
}
|
|
@@ -573,27 +1769,193 @@ var sizeRules = {
|
|
|
573
1769
|
messageKey: (ctx) => `size.${sizeType(ctx)}`,
|
|
574
1770
|
replacements: (ctx) => ({ size: ctx.parameters[0] ?? "" })
|
|
575
1771
|
},
|
|
576
|
-
|
|
577
|
-
|
|
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" }
|
|
578
1850
|
};
|
|
579
1851
|
|
|
580
1852
|
// registry.ts
|
|
581
1853
|
var builtinRules = {
|
|
582
1854
|
...presenceRules,
|
|
1855
|
+
...acceptanceRules,
|
|
1856
|
+
...prohibitedRules,
|
|
1857
|
+
...excludeRules,
|
|
583
1858
|
...typeRules,
|
|
1859
|
+
...arrayRules,
|
|
584
1860
|
...formatRules,
|
|
1861
|
+
...dateRules,
|
|
1862
|
+
...networkRules,
|
|
1863
|
+
...timezoneRules,
|
|
585
1864
|
...sizeRules,
|
|
1865
|
+
...numericRules,
|
|
586
1866
|
...fileRules,
|
|
587
|
-
...databaseRules
|
|
1867
|
+
...databaseRules,
|
|
1868
|
+
...authRules
|
|
1869
|
+
};
|
|
1870
|
+
|
|
1871
|
+
// rules/password.ts
|
|
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
|
+
}
|
|
1876
|
+
var PasswordRule = class _PasswordRule {
|
|
1877
|
+
#min;
|
|
1878
|
+
#max;
|
|
1879
|
+
#letters = false;
|
|
1880
|
+
#mixedCase = false;
|
|
1881
|
+
#numbers = false;
|
|
1882
|
+
#symbols = false;
|
|
1883
|
+
constructor(min = 8) {
|
|
1884
|
+
this.#min = Math.max(1, min);
|
|
1885
|
+
}
|
|
1886
|
+
/** A password rule of at least `size` characters: PasswordRule.min(12).mixedCase(). */
|
|
1887
|
+
static min(size) {
|
|
1888
|
+
return new _PasswordRule(size);
|
|
1889
|
+
}
|
|
1890
|
+
static defaults(rule) {
|
|
1891
|
+
if (rule !== void 0) {
|
|
1892
|
+
defaultRule = rule ?? void 0;
|
|
1893
|
+
return;
|
|
1894
|
+
}
|
|
1895
|
+
const resolved = typeof defaultRule === "function" ? defaultRule() : defaultRule;
|
|
1896
|
+
return resolved === void 0 ? new _PasswordRule(8) : resolved.#copy();
|
|
1897
|
+
}
|
|
1898
|
+
min(size) {
|
|
1899
|
+
this.#min = Math.max(1, size);
|
|
1900
|
+
return this;
|
|
1901
|
+
}
|
|
1902
|
+
max(size) {
|
|
1903
|
+
this.#max = size;
|
|
1904
|
+
return this;
|
|
1905
|
+
}
|
|
1906
|
+
/** At least one letter. */
|
|
1907
|
+
letters() {
|
|
1908
|
+
this.#letters = true;
|
|
1909
|
+
return this;
|
|
1910
|
+
}
|
|
1911
|
+
/** At least one uppercase and one lowercase letter. */
|
|
1912
|
+
mixedCase() {
|
|
1913
|
+
this.#mixedCase = true;
|
|
1914
|
+
return this;
|
|
1915
|
+
}
|
|
1916
|
+
/** At least one number. */
|
|
1917
|
+
numbers() {
|
|
1918
|
+
this.#numbers = true;
|
|
1919
|
+
return this;
|
|
1920
|
+
}
|
|
1921
|
+
/** At least one symbol (punctuation, a symbol or a space). */
|
|
1922
|
+
symbols() {
|
|
1923
|
+
this.#symbols = true;
|
|
1924
|
+
return this;
|
|
1925
|
+
}
|
|
1926
|
+
validate(_attribute, value, fail) {
|
|
1927
|
+
const say = (key, replacements = {}) => fail(applyReplacements(DEFAULT_MESSAGES[key], replacements));
|
|
1928
|
+
if (typeof value !== "string") return say("string");
|
|
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");
|
|
1933
|
+
if (this.#letters && !/\p{L}/u.test(value)) say("password.letters");
|
|
1934
|
+
if (this.#symbols && !/\p{Z}|\p{S}|\p{P}/u.test(value)) say("password.symbols");
|
|
1935
|
+
if (this.#numbers && !/\p{N}/u.test(value)) say("password.numbers");
|
|
1936
|
+
}
|
|
1937
|
+
#copy() {
|
|
1938
|
+
const copy = new _PasswordRule(this.#min);
|
|
1939
|
+
copy.#max = this.#max;
|
|
1940
|
+
copy.#letters = this.#letters;
|
|
1941
|
+
copy.#mixedCase = this.#mixedCase;
|
|
1942
|
+
copy.#numbers = this.#numbers;
|
|
1943
|
+
copy.#symbols = this.#symbols;
|
|
1944
|
+
return copy;
|
|
1945
|
+
}
|
|
588
1946
|
};
|
|
589
1947
|
|
|
590
1948
|
// Rule.ts
|
|
591
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
|
+
}
|
|
592
1954
|
// Declares all 4 contract parameters so a subclass's inferred type (from `Rule.in(...)` etc.) still accepts a
|
|
593
1955
|
// call with the full 4-arg ValidationRule.validate signature, as Rule.test.ts makes.
|
|
594
1956
|
async validate(attribute, value, fail, data, expandedFromWildcard = false) {
|
|
595
1957
|
if (await this.passes(attribute, value, data, expandedFromWildcard)) return;
|
|
596
|
-
fail(applyReplacements(DEFAULT_MESSAGES[this.ruleName], { attribute: defaultAttributeName(attribute) }));
|
|
1958
|
+
fail(applyReplacements(DEFAULT_MESSAGES[this.ruleName], { attribute: defaultAttributeName(attribute), values: this.messageParameters().join(", ") }));
|
|
597
1959
|
}
|
|
598
1960
|
};
|
|
599
1961
|
var InRule = class extends BuiltinRuleObject {
|
|
@@ -603,8 +1965,145 @@ var InRule = class extends BuiltinRuleObject {
|
|
|
603
1965
|
}
|
|
604
1966
|
values;
|
|
605
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
|
+
}
|
|
606
2001
|
passes(_attribute, value) {
|
|
607
|
-
|
|
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;
|
|
2015
|
+
passes(_attribute, value) {
|
|
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;
|
|
608
2107
|
}
|
|
609
2108
|
};
|
|
610
2109
|
function isModelInstance(value) {
|
|
@@ -657,14 +2156,95 @@ var ExistsRule = class extends BuiltinRuleObject {
|
|
|
657
2156
|
}
|
|
658
2157
|
};
|
|
659
2158
|
var Rule = {
|
|
2159
|
+
/** One of `values`, compared as text (as `in:a,b`); typed as those values: `Rule.in(['draft', 'publish'])`. */
|
|
660
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),
|
|
661
2183
|
unique: (table, column) => new UniqueRule(table, column),
|
|
662
|
-
exists: (table, column) => new ExistsRule(table, column)
|
|
2184
|
+
exists: (table, column) => new ExistsRule(table, column),
|
|
2185
|
+
/** Laravel's Password::defaults(): the application's password rule (at least 8 characters unless PasswordRule.defaults() changed it). */
|
|
2186
|
+
password: () => PasswordRule.defaults()
|
|
663
2187
|
};
|
|
664
2188
|
|
|
665
2189
|
// Validator.ts
|
|
666
|
-
import { Arr as
|
|
2190
|
+
import { Arr as Arr11 } from "@orieljs/support";
|
|
667
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
|
+
}
|
|
668
2248
|
function buildNested(flat) {
|
|
669
2249
|
const root = {};
|
|
670
2250
|
const created = /* @__PURE__ */ new Set([root]);
|
|
@@ -701,6 +2281,49 @@ function cloneContainer(existing) {
|
|
|
701
2281
|
}
|
|
702
2282
|
return container;
|
|
703
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
|
+
}
|
|
704
2327
|
function arrayify(node, created) {
|
|
705
2328
|
if (node === null || typeof node !== "object" || !created.has(node)) return node;
|
|
706
2329
|
const entries = Object.entries(node).map(([k, v]) => [k, arrayify(v, created)]);
|
|
@@ -726,11 +2349,18 @@ var ValidatorImpl = class {
|
|
|
726
2349
|
bag;
|
|
727
2350
|
ranPromise;
|
|
728
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();
|
|
729
2359
|
after(callback) {
|
|
730
2360
|
this.afterCallbacks.push(callback);
|
|
731
2361
|
return this;
|
|
732
2362
|
}
|
|
733
|
-
/**
|
|
2363
|
+
/** Lets an after() hook add a failure. */
|
|
734
2364
|
fail(field, message) {
|
|
735
2365
|
this.bag ??= new MessageBagImpl();
|
|
736
2366
|
this.bag.add(field, message);
|
|
@@ -747,39 +2377,143 @@ var ValidatorImpl = class {
|
|
|
747
2377
|
}
|
|
748
2378
|
async validated() {
|
|
749
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);
|
|
750
2381
|
const flat = {};
|
|
751
2382
|
for (const pattern of Object.keys(this.rules)) {
|
|
752
2383
|
for (const field of this.fieldsFor(pattern)) {
|
|
753
|
-
if (
|
|
2384
|
+
if (!this.isExcluded(field) && Arr11.has(data, field)) flat[field] = Arr11.get(data, field);
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
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;
|
|
754
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;
|
|
755
2445
|
}
|
|
756
|
-
return buildNested(flat);
|
|
757
2446
|
}
|
|
758
2447
|
fieldsFor(pattern) {
|
|
759
2448
|
return pattern.includes("*") ? expandWildcardKeys(pattern, this.data) : [pattern];
|
|
760
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
|
+
}
|
|
761
2486
|
async run() {
|
|
762
2487
|
this.bag = new MessageBagImpl();
|
|
763
|
-
|
|
764
|
-
|
|
2488
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
2489
|
+
for (const [pattern, parsed] of this.parsed()) {
|
|
765
2490
|
const names = parsed.filter((r) => r.kind === "named").map((r) => r.name);
|
|
766
2491
|
const bail = names.includes("bail");
|
|
767
2492
|
const nullable = names.includes("nullable");
|
|
768
2493
|
const sometimes = names.includes("sometimes");
|
|
769
2494
|
for (const field of this.fieldsFor(pattern)) {
|
|
770
|
-
|
|
2495
|
+
if (this.isExcluded(field)) continue;
|
|
2496
|
+
const present = Arr11.has(this.data, field);
|
|
771
2497
|
if (sometimes && !present) continue;
|
|
772
|
-
const value =
|
|
2498
|
+
const value = Arr11.get(this.data, field);
|
|
773
2499
|
const isNull = value === null || value === void 0;
|
|
774
2500
|
const isBlank = typeof value === "string" && value.trim() === "";
|
|
775
2501
|
for (const rule of parsed) {
|
|
776
2502
|
if (rule.kind === "named" && SKIPPED_MODIFIERS.has(rule.name)) continue;
|
|
777
|
-
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;
|
|
778
2512
|
if (!implicit && (!present || isBlank)) continue;
|
|
779
2513
|
if (!implicit && nullable && isNull) continue;
|
|
780
|
-
const
|
|
781
|
-
if (
|
|
782
|
-
this.bag.add(field, message);
|
|
2514
|
+
const messages = await this.runRule(rule, field, pattern, value, names);
|
|
2515
|
+
if (messages.length > 0) {
|
|
2516
|
+
for (const message of messages) this.bag.add(field, message);
|
|
783
2517
|
if (bail || implicit) break;
|
|
784
2518
|
}
|
|
785
2519
|
}
|
|
@@ -788,30 +2522,33 @@ var ValidatorImpl = class {
|
|
|
788
2522
|
for (const callback of this.afterCallbacks) await callback(this);
|
|
789
2523
|
return this.bag.isEmpty();
|
|
790
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
|
+
}
|
|
791
2528
|
async runRule(rule, field, pattern, value, ruleNames) {
|
|
792
2529
|
const expanded = pattern.includes("*");
|
|
793
2530
|
if (rule.kind === "object") {
|
|
794
2531
|
if (rule.rule instanceof BuiltinRuleObject) {
|
|
795
|
-
if (await rule.rule.passes(field, value, this.data, expanded)) return
|
|
796
|
-
const ctx2 =
|
|
797
|
-
return this.failureMessage(rule.rule.ruleName, this.registry[rule.rule.ruleName], ctx2, pattern);
|
|
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);
|
|
2534
|
+
return [this.failureMessage(rule.rule.ruleName, this.registry[rule.rule.ruleName], ctx2, pattern)];
|
|
798
2535
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
return failure;
|
|
2536
|
+
const failures = [];
|
|
2537
|
+
const attribute = this.displayName(field);
|
|
2538
|
+
await rule.rule.validate(field, value, (message) => void failures.push(applyReplacements(message, { attribute })), this.data);
|
|
2539
|
+
return failures;
|
|
804
2540
|
}
|
|
805
2541
|
const builtin = this.registry[rule.name];
|
|
806
2542
|
if (!builtin) throw new UnknownValidationRuleError(rule.name);
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
|
|
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);
|
|
2545
|
+
if (await builtin.validate(ctx)) return [];
|
|
2546
|
+
return [this.failureMessage(rule.name, builtin, ctx, pattern)];
|
|
810
2547
|
}
|
|
811
2548
|
/** Custom message (concrete field, then wildcard pattern, then rule name) or the default, with :attribute filled. */
|
|
812
2549
|
failureMessage(name, builtin, ctx, pattern) {
|
|
813
2550
|
const field = ctx.attribute;
|
|
814
|
-
const template = this.customMessages[`${field}.${name}`] ?? this.customMessages[`${pattern}.${name}`] ?? this.customMessages[name] ?? DEFAULT_MESSAGES[builtin?.messageKey(ctx) ?? name] ?? builtin?.defaultMessage ?? "The :attribute field is invalid.";
|
|
2551
|
+
const template = this.customMessages[`${field}.${name}`] ?? this.customMessages[`${pattern}.${name}`] ?? this.customMessages[name] ?? builtin?.message ?? DEFAULT_MESSAGES[builtin?.messageKey(ctx) ?? name] ?? builtin?.defaultMessage ?? "The :attribute field is invalid.";
|
|
815
2552
|
const display = this.customAttributes[field] ?? this.customAttributes[pattern] ?? defaultAttributeName(field);
|
|
816
2553
|
return applyReplacements(template, { attribute: display, ...builtin?.replacements?.(ctx) ?? {} });
|
|
817
2554
|
}
|
|
@@ -823,10 +2560,15 @@ var ValidationFactoryImpl = class {
|
|
|
823
2560
|
make(data, rules, messages = {}, attributes = {}) {
|
|
824
2561
|
return new ValidatorImpl(data, rules, messages, attributes, { ...builtinRules, ...this.extended });
|
|
825
2562
|
}
|
|
2563
|
+
/** Every rule this factory knows, built-in and extend()ed, sorted (for editors: `oriel ide:info`). */
|
|
2564
|
+
ruleNames() {
|
|
2565
|
+
return [.../* @__PURE__ */ new Set([...Object.keys(builtinRules), ...Object.keys(this.extended)])].sort();
|
|
2566
|
+
}
|
|
826
2567
|
extend(name, callback, message) {
|
|
827
2568
|
this.extended[name] = {
|
|
828
2569
|
validate: (ctx) => callback(ctx.attribute, ctx.value, ctx.parameters, ctx.data),
|
|
829
2570
|
messageKey: () => name,
|
|
2571
|
+
...message === void 0 ? {} : { message },
|
|
830
2572
|
defaultMessage: message ?? "The :attribute field is invalid."
|
|
831
2573
|
};
|
|
832
2574
|
}
|
|
@@ -852,9 +2594,11 @@ export {
|
|
|
852
2594
|
FormRequest,
|
|
853
2595
|
InvalidRuleParameterError,
|
|
854
2596
|
MessageBagImpl,
|
|
2597
|
+
PasswordRule,
|
|
855
2598
|
Rule,
|
|
856
2599
|
UnknownValidationRuleError,
|
|
857
2600
|
ValidationErrorImpl,
|
|
2601
|
+
ValidationException,
|
|
858
2602
|
ValidationFactoryImpl,
|
|
859
2603
|
ValidationServiceProvider,
|
|
860
2604
|
Validator,
|