@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/NOTICE
CHANGED
|
@@ -7,3 +7,10 @@ This product includes software developed by the Oriel project
|
|
|
7
7
|
Oriel is licensed under the Apache License, Version 2.0 (see LICENSE).
|
|
8
8
|
The Oriel name and logo are not covered by that license; see TRADEMARKS.md
|
|
9
9
|
(https://github.com/orieljs/framework/blob/main/TRADEMARKS.md).
|
|
10
|
+
|
|
11
|
+
@orieljs/support (and @orieljs/framework, which bundles it) includes transliteration data from
|
|
12
|
+
voku/portable-ascii (https://github.com/voku/portable-ascii), Copyright (C) 2019 Lars Moelleken, MIT License.
|
|
13
|
+
|
|
14
|
+
@orieljs/notifications (and @orieljs/framework, which bundles it) includes the HTML and CSS of
|
|
15
|
+
Laravel's default notification mail (laravel/framework, https://github.com/laravel/framework),
|
|
16
|
+
Copyright (c) Taylor Otwell, MIT License.
|
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @orieljs/validation
|
|
2
|
+
|
|
3
|
+
Validation for Oriel: rules for incoming data, form requests and error messages, as Laravel's validation. It is part of `@orieljs/framework`, which every Oriel app already installs, and is re-exported from it. In an app, import it from `@orieljs/framework`; `@orieljs/validation` is also published on its own.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @orieljs/framework
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts title="app/Http/Requests/StorePostRequest.ts"
|
|
10
|
+
import { FormRequest, Rule } from '@orieljs/framework'
|
|
11
|
+
|
|
12
|
+
export class StorePostRequest extends FormRequest {
|
|
13
|
+
rules() {
|
|
14
|
+
return {
|
|
15
|
+
title: 'required|string|max:255',
|
|
16
|
+
slug: ['required', 'alpha_dash', Rule.unique('posts', 'slug')],
|
|
17
|
+
body: 'required|string',
|
|
18
|
+
published_at: 'nullable|date',
|
|
19
|
+
} as const
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
In a controller, `await StorePostRequest.validate()` runs `authorize()` and `rules()` and returns the validated data, typed from the rules: write `rules()` with `as const` and no return type (it may be `async`). A failure sends the user back with the errors and the old input, or answers 422 with JSON. `await request().validate(rules)` and `Validator.make(data, rules)` validate without a class, and `Rule.unique()`, `Rule.exists()`, `Rule.in()` and `Rule.password()` build rule objects.
|
|
25
|
+
|
|
26
|
+
Documentation: https://orieljs.vercel.app/docs/validation
|
|
27
|
+
|
|
28
|
+
Status: early alpha. APIs will change.
|
|
29
|
+
|
|
30
|
+
## License
|
|
31
|
+
|
|
32
|
+
Oriel is open-source software licensed under the Apache License 2.0 (Apache-2.0; see `LICENSE` and `NOTICE`). The Oriel name and logo are trademarks; see `TRADEMARKS.md`.
|
|
33
|
+
|
|
34
|
+
Website: https://orieljs.vercel.app
|
package/TRADEMARKS.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Oriel trademark policy
|
|
2
|
+
|
|
3
|
+
The Oriel code is open source under the Apache License 2.0. The **Oriel name and logo** are not: the license covers
|
|
4
|
+
the code only (section 6 of the license says so), and the name and logo stay with the project. This page explains
|
|
5
|
+
what you can and can't do with them. The goal is simple: people should always be able to tell what is the real Oriel
|
|
6
|
+
and what isn't.
|
|
7
|
+
|
|
8
|
+
## You can, without asking
|
|
9
|
+
|
|
10
|
+
- Use, change, ship and sell the code, as the license allows.
|
|
11
|
+
- Say that your app or package is "built with Oriel", "for Oriel" or "compatible with Oriel".
|
|
12
|
+
- Name a package `oriel-<something>` or `<something>-for-oriel` when it works with Oriel, as long as it is clear that
|
|
13
|
+
it is not official (for example, don't publish it under the `@orieljs` npm scope).
|
|
14
|
+
- Use the name and logo to write about Oriel, teach it, or link to it, in articles, talks, videos and courses.
|
|
15
|
+
- Use the unmodified logo to point to the project, as it is (no recolouring, cropping or
|
|
16
|
+
distortion).
|
|
17
|
+
|
|
18
|
+
## You can't, without written permission
|
|
19
|
+
|
|
20
|
+
- Call a fork, a modified copy or a competing product "Oriel", or anything likely to be confused with it.
|
|
21
|
+
- Use the name or logo as the name, logo or domain of your own product, company or service.
|
|
22
|
+
- Suggest that the Oriel project made, endorses or supports your work when it doesn't.
|
|
23
|
+
- Sell merchandise with the name or logo.
|
|
24
|
+
|
|
25
|
+
## Forks
|
|
26
|
+
|
|
27
|
+
Forks are welcome. Please give yours a different name and logo, keep the `LICENSE` and `NOTICE` files, and say that
|
|
28
|
+
it is based on Oriel.
|
|
29
|
+
|
|
30
|
+
## Questions
|
|
31
|
+
|
|
32
|
+
Open an issue at https://github.com/orieljs/framework or email meeeet.dev@gmail.com. If you're unsure whether a use
|
|
33
|
+
is fine, ask; the answer is usually yes.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { OrielError, ValidationError, Rules, MessageBag, ValidationRule, Validator as Validator$1, ValidationFactory, RuleCallback } from '@orieljs/contracts';
|
|
1
|
+
import { OrielError, ValidationError, Rules, InferValidatedType, MessageBag, ValidationRule, TypedValidationRule, RuleTyping, InValue, UploadedFile, Validator as Validator$1, ValidationFactory, RuleCallback } from '@orieljs/contracts';
|
|
2
|
+
export { InferRuleType, InferValidatedType } from '@orieljs/contracts';
|
|
2
3
|
import * as _orieljs_support from '@orieljs/support';
|
|
3
4
|
import { ServiceProvider } from '@orieljs/support';
|
|
4
5
|
|
|
@@ -11,6 +12,16 @@ declare class ValidationErrorImpl extends Error implements ValidationError {
|
|
|
11
12
|
readonly name = "ValidationError";
|
|
12
13
|
constructor(errors: Record<string, string[]>, errorBag?: string, redirectTo?: string | undefined);
|
|
13
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Laravel's ValidationException, for failing a request by hand:
|
|
17
|
+
* `throw ValidationException.withMessages({ email: __('auth.failed') })`. Rendered like any validation error: 422 JSON,
|
|
18
|
+
* or a redirect back with the errors.
|
|
19
|
+
*/
|
|
20
|
+
declare class ValidationException extends ValidationErrorImpl {
|
|
21
|
+
/** Messages per field, each a string or a list. */
|
|
22
|
+
static withMessages(messages: Record<string, string | readonly string[]>): ValidationException;
|
|
23
|
+
constructor(errors: Record<string, string[]>, errorBag?: string, redirectTo?: string);
|
|
24
|
+
}
|
|
14
25
|
declare class AuthorizationErrorImpl extends Error implements OrielError {
|
|
15
26
|
readonly kind: "Authorization";
|
|
16
27
|
readonly status = 403;
|
|
@@ -31,34 +42,38 @@ declare class InvalidRuleParameterError extends Error implements OrielError {
|
|
|
31
42
|
constructor(rule: string, parameter: string, reason: string);
|
|
32
43
|
}
|
|
33
44
|
|
|
45
|
+
/** The data a FormRequest's validate() returns, typed from its rules(). */
|
|
46
|
+
type ValidatedData<F extends FormRequest> = InferValidatedType<Awaited<ReturnType<F['rules']>>>;
|
|
34
47
|
/**
|
|
35
48
|
* Mirrors Illuminate\Foundation\Http\FormRequest, minus dependency injection by type (Oriel resolves the current
|
|
36
49
|
* request and the ValidationFactory through inject(), never through constructor parameters — see spec §3 rule 5).
|
|
37
50
|
* A subclass only has to implement rules(); authorize()/messages()/attributes() default to Laravel's own defaults.
|
|
38
51
|
*/
|
|
39
52
|
declare abstract class FormRequest {
|
|
40
|
-
|
|
53
|
+
/** May be async, to await something first: `async rules() { const user = await request().user<User>() … }`. */
|
|
54
|
+
abstract rules(): Rules | Promise<Rules>;
|
|
41
55
|
authorize(): boolean | Promise<boolean>;
|
|
42
56
|
messages(): Record<string, string>;
|
|
43
57
|
attributes(): Record<string, string>;
|
|
44
58
|
/**
|
|
45
59
|
* `const data = await StorePostRequest.validate()` (spec §4.3). Resolves the current request and the
|
|
46
60
|
* ValidationFactory from the active container, runs authorize() first (throwing AuthorizationErrorImpl if it
|
|
47
|
-
* returns false), then validates request.all() against rules()/messages()/attributes() and returns the
|
|
48
|
-
*
|
|
61
|
+
* returns false), then validates request.all() against rules()/messages()/attributes() and returns the validated
|
|
62
|
+
* data (or throws the 422 ValidationError — see ValidatorImpl.validated()). The data is typed from rules(): write
|
|
63
|
+
* it without a return type and with `as const` (`return { title: 'required|string' } as const`), so its rule
|
|
64
|
+
* strings keep their text; a rules() typed `Rules` gives `Record<string, unknown>`.
|
|
49
65
|
*/
|
|
50
|
-
static validate<
|
|
66
|
+
static validate<F extends FormRequest>(this: new () => F): Promise<ValidatedData<F>>;
|
|
51
67
|
}
|
|
52
68
|
|
|
53
|
-
/** Humanizes a (possibly dotted) attribute key, e.g. "items.0.product_name" -> "items.0.product name". */
|
|
69
|
+
/** Humanizes a (possibly dotted) attribute key, e.g. "items.0.product_name" -> "items.0.product name" ("colors.*" stays). */
|
|
54
70
|
declare function defaultAttributeName(key: string): string;
|
|
55
71
|
declare function applyReplacements(template: string, replacements: Record<string, string>): string;
|
|
56
|
-
/** Laravel's English lang/en/validation.php, reproduced as closely as possible
|
|
72
|
+
/** Laravel's English lang/en/validation.php, reproduced as closely as possible. */
|
|
57
73
|
declare const DEFAULT_MESSAGES: Record<string, string>;
|
|
58
74
|
|
|
59
75
|
declare class MessageBagImpl implements MessageBag {
|
|
60
76
|
private readonly items;
|
|
61
|
-
/** Not part of the MessageBag contract — used internally by ValidatorImpl and by after() hooks. */
|
|
62
77
|
add(key: string, message: string): void;
|
|
63
78
|
has(key: string): boolean;
|
|
64
79
|
first(key?: string): string | undefined;
|
|
@@ -80,6 +95,17 @@ interface RuleContext {
|
|
|
80
95
|
data: Record<string, unknown>;
|
|
81
96
|
/** Every named rule attached to this field (for size-type resolution: numeric/array/file/string). */
|
|
82
97
|
ruleNames: string[];
|
|
98
|
+
/** The rule key as written, e.g. "items.*.email" (distinct reads every row of it). */
|
|
99
|
+
pattern?: string;
|
|
100
|
+
/**
|
|
101
|
+
* What the rules keep for one validation pass, such as every row a wildcard names: each pass starts with an empty
|
|
102
|
+
* one, so nothing outlives the data it was read from. Absent when a rule runs on its own.
|
|
103
|
+
*/
|
|
104
|
+
cache?: Map<string, unknown>;
|
|
105
|
+
/** The parameters of rule `name` on `field` (a concrete path), or undefined when that field has no such rule. */
|
|
106
|
+
getRule?(field: string, name: string): string[] | undefined;
|
|
107
|
+
/** How a message names `field`: its custom attribute, else the humanized key. */
|
|
108
|
+
displayName?(field: string): string;
|
|
83
109
|
}
|
|
84
110
|
interface BuiltinRule {
|
|
85
111
|
validate(ctx: RuleContext): boolean | Promise<boolean>;
|
|
@@ -89,8 +115,15 @@ interface BuiltinRule {
|
|
|
89
115
|
replacements?(ctx: RuleContext): Record<string, string>;
|
|
90
116
|
/** Used when messageKey() isn't in DEFAULT_MESSAGES (e.g. an extend()ed rule with a custom message). */
|
|
91
117
|
defaultMessage?: string;
|
|
118
|
+
/** An extend()ed rule's own message: it wins over the default lines, as Laravel's fallback messages do. */
|
|
119
|
+
message?: string;
|
|
92
120
|
/** Runs even when the value is missing, blank, or null under nullable (the required* family, and accepted); a failure stops the field. */
|
|
93
121
|
implicit?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* An exclude rule (`exclude`, `exclude_if`, …): it always runs, and when validate() returns false the field and its
|
|
124
|
+
* children are skipped from then on and left out of the validated data, with no message.
|
|
125
|
+
*/
|
|
126
|
+
excludes?: boolean;
|
|
94
127
|
}
|
|
95
128
|
type ParsedRule = {
|
|
96
129
|
kind: 'named';
|
|
@@ -100,8 +133,18 @@ type ParsedRule = {
|
|
|
100
133
|
kind: 'object';
|
|
101
134
|
rule: ValidationRule;
|
|
102
135
|
};
|
|
103
|
-
|
|
104
|
-
|
|
136
|
+
/**
|
|
137
|
+
* A rule object that stands for string rules (Rule.requiredIf(), Rule.when(), Rule.array(), …), as Laravel's
|
|
138
|
+
* ValidationRuleParser reads them: the rules expandRules() returns, given the data, take its place.
|
|
139
|
+
*/
|
|
140
|
+
interface ExpandingRule extends ValidationRule {
|
|
141
|
+
expandRules(data: Record<string, unknown>): string | ReadonlyArray<string | ValidationRule>;
|
|
142
|
+
}
|
|
143
|
+
declare function parseRuleSpec(spec: string | ReadonlyArray<string | ValidationRule>, data?: Record<string, unknown>): ParsedRule[];
|
|
144
|
+
/**
|
|
145
|
+
* Laravel's definition of "empty" for the required family (validateRequired()): null, undefined, text that is blank
|
|
146
|
+
* once trimmed, an empty array and an empty plain object (a JSON object is a PHP array). false and 0 are NOT empty.
|
|
147
|
+
*/
|
|
105
148
|
declare function isEmptyValue(value: unknown): boolean;
|
|
106
149
|
/** Duck-types Contracts/Http's UploadedFile without importing @orieljs/http. */
|
|
107
150
|
declare function isUploadedFileLike(value: unknown): value is {
|
|
@@ -114,8 +157,8 @@ declare function isUploadedFileLike(value: unknown): value is {
|
|
|
114
157
|
/**
|
|
115
158
|
* Expands a `*`-wildcard rule pattern (e.g. "items.*.name") into every concrete path present in `data`
|
|
116
159
|
* (e.g. ["items.0.name", "items.1.name"]). A pattern with no `*` is returned unchanged. A `*` segment whose
|
|
117
|
-
* container is missing, not an array and not a plain object expands to nothing for that branch
|
|
118
|
-
*
|
|
160
|
+
* container is missing, not an array and not a plain object expands to nothing for that branch, so every reported
|
|
161
|
+
* error is keyed by a concrete path, never the literal pattern. Object keys in UNSAFE_KEYS are
|
|
119
162
|
* treated as absent, so a JSON body like {"items":{"__proto__":{...}}} never yields a path through the prototype.
|
|
120
163
|
*/
|
|
121
164
|
declare function expandWildcardKeys(pattern: string, data: Record<string, unknown>): string[];
|
|
@@ -123,22 +166,162 @@ declare function expandWildcardKeys(pattern: string, data: Record<string, unknow
|
|
|
123
166
|
/** Every built-in rule, merged. Later tasks (database rules) add their own spread here. */
|
|
124
167
|
declare const builtinRules: Record<string, BuiltinRule>;
|
|
125
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Laravel's Illuminate\Validation\Rules\Password: a string of at least `min` characters (8 by default), plus the
|
|
171
|
+
* checks you add: letters(), mixedCase(), numbers(), symbols(), max(). Every failing check adds its own message.
|
|
172
|
+
* Rule.password() returns the application default (PasswordRule.defaults()). (Laravel's uncompromised() is not
|
|
173
|
+
* ported: it calls an outside service.)
|
|
174
|
+
*/
|
|
175
|
+
declare class PasswordRule implements TypedValidationRule<{
|
|
176
|
+
value: string;
|
|
177
|
+
}> {
|
|
178
|
+
#private;
|
|
179
|
+
/** A passing value is a string (for the validated data's type); never set at run time. */
|
|
180
|
+
readonly __typing?: {
|
|
181
|
+
value: string;
|
|
182
|
+
};
|
|
183
|
+
constructor(min?: number);
|
|
184
|
+
/** A password rule of at least `size` characters: PasswordRule.min(12).mixedCase(). */
|
|
185
|
+
static min(size: number): PasswordRule;
|
|
186
|
+
/**
|
|
187
|
+
* With an argument, sets the default (Laravel's Password::defaults(fn () => ...), in a provider's boot());
|
|
188
|
+
* `null` resets it. Without one, returns a copy of the default: PasswordRule.min(8) unless set.
|
|
189
|
+
*/
|
|
190
|
+
static defaults(): PasswordRule;
|
|
191
|
+
static defaults(rule: PasswordRule | (() => PasswordRule) | null): void;
|
|
192
|
+
min(size: number): this;
|
|
193
|
+
max(size: number): this;
|
|
194
|
+
/** At least one letter. */
|
|
195
|
+
letters(): this;
|
|
196
|
+
/** At least one uppercase and one lowercase letter. */
|
|
197
|
+
mixedCase(): this;
|
|
198
|
+
/** At least one number. */
|
|
199
|
+
numbers(): this;
|
|
200
|
+
/** At least one symbol (punctuation, a symbol or a space). */
|
|
201
|
+
symbols(): this;
|
|
202
|
+
validate(_attribute: string, value: unknown, fail: (message: string) => void): void;
|
|
203
|
+
}
|
|
204
|
+
|
|
126
205
|
/**
|
|
127
206
|
* Base for the fluent builders. Each one reports under a built-in rule name (`in`, `unique`, `exists`), so when it
|
|
128
207
|
* runs inside a Validator its failure goes through exactly the same lookup as the string rule: custom messages
|
|
129
208
|
* ("email.unique", "items.*.email.unique", "unique"), custom attributes, then the default text. Called directly
|
|
130
209
|
* as a plain ValidationRule, validate() fails with the default English message.
|
|
131
210
|
*/
|
|
132
|
-
declare abstract class BuiltinRuleObject implements
|
|
211
|
+
declare abstract class BuiltinRuleObject<T extends RuleTyping = Record<never, never>> implements TypedValidationRule<T> {
|
|
133
212
|
abstract readonly ruleName: string;
|
|
134
|
-
|
|
213
|
+
/** What a passing value is, for the validated data's type (`T`); never set at run time. */
|
|
214
|
+
readonly __typing?: T;
|
|
215
|
+
/** `ruleNames`: the field's named rules, when it runs inside a Validator (`Rule.in()` reads `array`). */
|
|
216
|
+
abstract passes(attribute: string, value: unknown, data: Record<string, unknown>, expandedFromWildcard?: boolean, ruleNames?: readonly string[]): boolean | Promise<boolean>;
|
|
217
|
+
/** The parameters its message shows (`:values` of Rule.doesntContain()), as the string rule's would be. */
|
|
218
|
+
messageParameters(): string[];
|
|
135
219
|
validate(attribute: string, value: unknown, fail: (message: string) => void, data: Record<string, unknown>, expandedFromWildcard?: boolean): Promise<void>;
|
|
136
220
|
}
|
|
137
|
-
|
|
221
|
+
/**
|
|
222
|
+
* What `Rule.in(values)` lets through. A list the compiler only knows as `string[]` says nothing: any value whose text
|
|
223
|
+
* is listed passes (the number 1 for '1'), so `string` would be too narrow. A rule string known only as `string` is
|
|
224
|
+
* treated the same way.
|
|
225
|
+
*/
|
|
226
|
+
type InTyping<V> = string extends V ? Record<never, never> : {
|
|
227
|
+
value: InValue<V>;
|
|
228
|
+
each: InValue<V>;
|
|
229
|
+
};
|
|
230
|
+
declare class InRule<V> extends BuiltinRuleObject<InTyping<V>> {
|
|
138
231
|
private readonly values;
|
|
139
232
|
readonly ruleName = "in";
|
|
233
|
+
constructor(values: readonly V[]);
|
|
234
|
+
passes(_attribute: string, value: unknown, _data?: Record<string, unknown>, _expanded?: boolean, ruleNames?: readonly string[]): boolean;
|
|
235
|
+
}
|
|
236
|
+
declare class NotInRule extends BuiltinRuleObject {
|
|
237
|
+
private readonly values;
|
|
238
|
+
readonly ruleName = "not_in";
|
|
140
239
|
constructor(values: readonly unknown[]);
|
|
240
|
+
passes(_attribute: string, value: unknown, _data?: Record<string, unknown>, _expanded?: boolean, ruleNames?: readonly string[]): boolean;
|
|
241
|
+
}
|
|
242
|
+
/** A TypeScript enum, or an `as const` object of string and number values. */
|
|
243
|
+
type EnumLike = Record<string, string | number>;
|
|
244
|
+
/**
|
|
245
|
+
* Laravel's Rule::enum(): one of the enum's values; for a numeric enum, its number or exactly the text of it (a form
|
|
246
|
+
* sends text: `'1'`, not `'1.0'` or `'01'`). only() and except() narrow the values.
|
|
247
|
+
*/
|
|
248
|
+
declare class EnumRule<E extends EnumLike> extends BuiltinRuleObject<{
|
|
249
|
+
value: InValue<E[keyof E]>;
|
|
250
|
+
}> {
|
|
251
|
+
#private;
|
|
252
|
+
private readonly type;
|
|
253
|
+
readonly ruleName = "enum";
|
|
254
|
+
constructor(type: E);
|
|
255
|
+
only(values: E[keyof E] | ReadonlyArray<E[keyof E]>): this;
|
|
256
|
+
except(values: E[keyof E] | ReadonlyArray<E[keyof E]>): this;
|
|
257
|
+
passes(_attribute: string, value: unknown): boolean;
|
|
258
|
+
}
|
|
259
|
+
/** Laravel's Rule::contains() and Rule::doesntContain(): every value is an item of the array, or none is. */
|
|
260
|
+
declare class ContainsRule extends BuiltinRuleObject<{
|
|
261
|
+
value: unknown[];
|
|
262
|
+
}> {
|
|
263
|
+
readonly ruleName: 'contains' | 'doesnt_contain';
|
|
264
|
+
private readonly values;
|
|
265
|
+
constructor(ruleName: 'contains' | 'doesnt_contain', values: readonly unknown[]);
|
|
141
266
|
passes(_attribute: string, value: unknown): boolean;
|
|
267
|
+
messageParameters(): string[];
|
|
268
|
+
}
|
|
269
|
+
/** Rules for the field: a rule string, a list, or a callback given the data that returns one. */
|
|
270
|
+
type RuleSet = string | ReadonlyArray<string | ValidationRule> | ((data: Record<string, unknown>) => string | ReadonlyArray<string | ValidationRule>);
|
|
271
|
+
/**
|
|
272
|
+
* Base for the builders that stand for string rules (Laravel's RequiredIf, ConditionalRules, ArrayRule, Dimensions):
|
|
273
|
+
* the Validator puts the rules expandRules() returns in their place, so they run only inside a Validator.
|
|
274
|
+
*/
|
|
275
|
+
declare abstract class ExpandingRuleObject<T extends RuleTyping = Record<never, never>> implements ExpandingRule, TypedValidationRule<T> {
|
|
276
|
+
readonly __typing?: T;
|
|
277
|
+
abstract expandRules(data: Record<string, unknown>): string | ReadonlyArray<string | ValidationRule>;
|
|
278
|
+
validate(): never;
|
|
279
|
+
}
|
|
280
|
+
/** Rule.requiredIf(), Rule.excludeIf(), Rule.prohibitedIf(): the rule when the condition holds, else nothing. */
|
|
281
|
+
declare class ConditionalRule<T extends RuleTyping = Record<never, never>> extends ExpandingRuleObject<T> {
|
|
282
|
+
private readonly rule;
|
|
283
|
+
private readonly condition;
|
|
284
|
+
constructor(rule: 'required' | 'exclude' | 'prohibited', condition: boolean | (() => boolean));
|
|
285
|
+
expandRules(): string;
|
|
286
|
+
}
|
|
287
|
+
/** Laravel's ConditionalRules (Rule.when() and Rule.unless()): one set of rules or the other, chosen from the data. */
|
|
288
|
+
declare class ConditionalRules extends ExpandingRuleObject {
|
|
289
|
+
private readonly condition;
|
|
290
|
+
private readonly rules;
|
|
291
|
+
private readonly defaultRules;
|
|
292
|
+
private readonly negate;
|
|
293
|
+
constructor(condition: boolean | ((data: Record<string, unknown>) => boolean), rules: RuleSet, defaultRules: RuleSet, negate?: boolean);
|
|
294
|
+
expandRules(data: Record<string, unknown>): string | ReadonlyArray<string | ValidationRule>;
|
|
295
|
+
}
|
|
296
|
+
/** Laravel's Rule::array(): `array`, or `array:a,b` (an object whose keys are among those named). */
|
|
297
|
+
declare class ArrayRule<K extends string> extends ExpandingRuleObject<{
|
|
298
|
+
value: [K] extends [never] ? unknown[] : {
|
|
299
|
+
[P in K]?: unknown;
|
|
300
|
+
};
|
|
301
|
+
}> {
|
|
302
|
+
private readonly keys;
|
|
303
|
+
constructor(keys: readonly K[]);
|
|
304
|
+
expandRules(): string;
|
|
305
|
+
}
|
|
306
|
+
type DimensionConstraints = Partial<Record<'width' | 'height' | 'min_width' | 'min_height' | 'max_width' | 'max_height' | 'ratio', number>>;
|
|
307
|
+
/** Laravel's Rule::dimensions(): the dimensions rule, from an object or fluently (`.maxWidth(1000).ratio(3 / 2)`). */
|
|
308
|
+
declare class DimensionsRule extends ExpandingRuleObject<{
|
|
309
|
+
value: UploadedFile;
|
|
310
|
+
}> {
|
|
311
|
+
#private;
|
|
312
|
+
constructor(constraints: DimensionConstraints);
|
|
313
|
+
width(pixels: number): this;
|
|
314
|
+
height(pixels: number): this;
|
|
315
|
+
minWidth(pixels: number): this;
|
|
316
|
+
minHeight(pixels: number): this;
|
|
317
|
+
maxWidth(pixels: number): this;
|
|
318
|
+
maxHeight(pixels: number): this;
|
|
319
|
+
/** Width over height: `ratio(3 / 2)`. */
|
|
320
|
+
ratio(ratio: number): this;
|
|
321
|
+
/** `dimensions:max_width=1000,ratio=1.5`, Laravel's __toString(). */
|
|
322
|
+
toString(): string;
|
|
323
|
+
expandRules(): string;
|
|
324
|
+
private set;
|
|
142
325
|
}
|
|
143
326
|
declare class UniqueRule extends BuiltinRuleObject {
|
|
144
327
|
private readonly table;
|
|
@@ -166,30 +349,36 @@ declare class ExistsRule extends BuiltinRuleObject {
|
|
|
166
349
|
}
|
|
167
350
|
/** Fluent rule-object builders, for when a plain string rule can't express what's needed (e.g. .ignore()). */
|
|
168
351
|
declare const Rule: {
|
|
169
|
-
in:
|
|
352
|
+
/** One of `values`, compared as text (as `in:a,b`); typed as those values: `Rule.in(['draft', 'publish'])`. */
|
|
353
|
+
in: <const V extends readonly unknown[]>(values: V) => InRule<V[number]>;
|
|
354
|
+
/** None of `values`, compared as text (as `not_in:a,b`). */
|
|
355
|
+
notIn: (values: readonly unknown[]) => NotInRule;
|
|
356
|
+
/** One of a TypeScript enum's values (or an `as const` object's): `Rule.enum(Status)`, `.only([…])`, `.except([…])`. */
|
|
357
|
+
enum: <E extends EnumLike>(type: E) => EnumRule<E>;
|
|
358
|
+
/** An array holding every one of `values`. */
|
|
359
|
+
contains: (values: readonly unknown[]) => ContainsRule;
|
|
360
|
+
/** An array holding none of `values`. */
|
|
361
|
+
doesntContain: (values: readonly unknown[]) => ContainsRule;
|
|
362
|
+
/** `required` when the condition (or the callback) holds. */
|
|
363
|
+
requiredIf: (condition: boolean | (() => boolean)) => ConditionalRule;
|
|
364
|
+
/** `exclude` when the condition (or the callback) holds; the key is optional, as `exclude_if` makes it. */
|
|
365
|
+
excludeIf: (condition: boolean | (() => boolean)) => ConditionalRule<{
|
|
366
|
+
presence: "optional";
|
|
367
|
+
}>;
|
|
368
|
+
/** `prohibited` when the condition (or the callback) holds. */
|
|
369
|
+
prohibitedIf: (condition: boolean | (() => boolean)) => ConditionalRule;
|
|
370
|
+
/** `rules` when the condition (or the callback, given the data) holds, else `defaultRules`. */
|
|
371
|
+
when: (condition: boolean | ((data: Record<string, unknown>) => boolean), rules: RuleSet, defaultRules?: RuleSet) => ConditionalRules;
|
|
372
|
+
/** `rules` unless the condition (or the callback, given the data) holds, else `defaultRules`. */
|
|
373
|
+
unless: (condition: boolean | ((data: Record<string, unknown>) => boolean), rules: RuleSet, defaultRules?: RuleSet) => ConditionalRules;
|
|
374
|
+
/** `array`, or with keys `array:name,email` (an object whose keys are among those named). */
|
|
375
|
+
array: <const K extends string = never>(keys?: readonly K[]) => ArrayRule<K>;
|
|
376
|
+
/** The dimensions rule: `Rule.dimensions().maxWidth(1000).ratio(3 / 2)`, or `Rule.dimensions({ min_width: 100 })`. */
|
|
377
|
+
dimensions: (constraints?: DimensionConstraints) => DimensionsRule;
|
|
170
378
|
unique: (table: string, column?: string) => UniqueRule;
|
|
171
379
|
exists: (table: string, column?: string) => ExistsRule;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
/** See Task 9's writeup in the plan for exactly what this does and doesn't infer. */
|
|
175
|
-
type SplitPipe<S extends string> = S extends `${infer Head}|${infer Tail}` ? Head | SplitPipe<Tail> : S;
|
|
176
|
-
type RuleName<T extends string> = T extends `${infer Name}:${string}` ? Name : T;
|
|
177
|
-
type Names<S extends string> = RuleName<SplitPipe<S>>;
|
|
178
|
-
type BaseType<S extends string> = 'array' extends Names<S> ? unknown[] : 'boolean' extends Names<S> ? boolean : 'integer' extends Names<S> ? number : 'numeric' extends Names<S> ? number : 'email' extends Names<S> ? string : 'url' extends Names<S> ? string : 'uuid' extends Names<S> ? string : 'date' extends Names<S> ? string : 'string' extends Names<S> ? string : unknown;
|
|
179
|
-
/**
|
|
180
|
-
* The inferred type for one rule string, e.g. 'nullable|integer' -> number | null. A non-literal `string` (a rules
|
|
181
|
-
* object declared without `as const`) carries no rule names to read, so it infers unknown: without this guard every
|
|
182
|
-
* `'x' extends string` check would match and the result would wrongly be `unknown[] | null`.
|
|
183
|
-
*/
|
|
184
|
-
type InferRuleType<S extends string> = string extends S ? unknown : 'nullable' extends Names<S> ? BaseType<S> | null : BaseType<S>;
|
|
185
|
-
/**
|
|
186
|
-
* Best-effort: applies InferRuleType to every string-valued rule; array-form (mixed string/object) rules -> unknown.
|
|
187
|
-
* Strips `readonly` (via `-readonly`) so a `Rules` object declared `as const` (the common way to get literal rule
|
|
188
|
-
* strings for inference — see the Task 9 type test) still produces a plain, mutable validated-data shape, not a
|
|
189
|
-
* readonly one; a mapped type otherwise carries the source's readonly modifier over by default.
|
|
190
|
-
*/
|
|
191
|
-
type InferValidatedType<R> = {
|
|
192
|
-
-readonly [K in keyof R]: R[K] extends string ? InferRuleType<R[K]> : unknown;
|
|
380
|
+
/** Laravel's Password::defaults(): the application's password rule (at least 8 characters unless PasswordRule.defaults() changed it). */
|
|
381
|
+
password: () => PasswordRule;
|
|
193
382
|
};
|
|
194
383
|
|
|
195
384
|
/**
|
|
@@ -201,7 +390,7 @@ type InferValidatedType<R> = {
|
|
|
201
390
|
* - `sometimes` skips a missing field entirely;
|
|
202
391
|
* - a field stops validating after its first failure under `bail`, or after any implicit rule fails.
|
|
203
392
|
*/
|
|
204
|
-
declare class ValidatorImpl implements Validator$1 {
|
|
393
|
+
declare class ValidatorImpl<R extends Rules = Rules> implements Validator$1<R> {
|
|
205
394
|
private readonly data;
|
|
206
395
|
private readonly rules;
|
|
207
396
|
private readonly customMessages;
|
|
@@ -211,16 +400,40 @@ declare class ValidatorImpl implements Validator$1 {
|
|
|
211
400
|
private bag;
|
|
212
401
|
private ranPromise;
|
|
213
402
|
private readonly afterCallbacks;
|
|
214
|
-
|
|
403
|
+
private parsedRules;
|
|
404
|
+
/** The fields an exclude rule took out (with their children). */
|
|
405
|
+
private readonly excluded;
|
|
406
|
+
/** The same fields as a tree of segment runs from the root, so a lookup reads the field once. */
|
|
407
|
+
private readonly excludedRuns;
|
|
408
|
+
/** What the rules keep while one pass runs (RuleContext.cache); a new pass starts with a new one. */
|
|
409
|
+
private cache;
|
|
410
|
+
constructor(data: Record<string, unknown>, rules: R, customMessages: Record<string, string>, customAttributes: Record<string, string>, registry: Record<string, BuiltinRule>, errorBagName?: string);
|
|
215
411
|
after(callback: (validator: Validator$1) => void | Promise<void>): this;
|
|
216
|
-
/**
|
|
412
|
+
/** Lets an after() hook add a failure. */
|
|
217
413
|
fail(field: string, message: string): void;
|
|
218
414
|
passes(): Promise<boolean>;
|
|
219
415
|
fails(): Promise<boolean>;
|
|
220
416
|
errors(): MessageBagImpl;
|
|
221
|
-
validated<T =
|
|
417
|
+
validated<T = InferValidatedType<R>>(): Promise<T>;
|
|
418
|
+
/** Takes the field (with its children) out of validation and of the validated data. */
|
|
419
|
+
private exclude;
|
|
420
|
+
/**
|
|
421
|
+
* Laravel's shouldBeExcluded(): the field, or one of its parents, was taken out by an exclude rule. The field's
|
|
422
|
+
* segments are followed from the root (so `user` takes out `user.name`, not `username`) and each is read once,
|
|
423
|
+
* however many fields were excluded and however many dots the field holds.
|
|
424
|
+
*/
|
|
425
|
+
private isExcluded;
|
|
222
426
|
private fieldsFor;
|
|
427
|
+
private parsed;
|
|
428
|
+
/** The rule key a concrete field falls under: itself, else the first wildcard key that matches it. */
|
|
429
|
+
private patternOf;
|
|
430
|
+
/** Laravel's getRule(): the parameters of rule `name` on `field`, or undefined when the field has no such rule. */
|
|
431
|
+
private ruleParameters;
|
|
432
|
+
/** Laravel's getDisplayableAttribute(): a custom attribute for the field or its rule key, else the humanized key. */
|
|
433
|
+
private displayName;
|
|
434
|
+
private context;
|
|
223
435
|
private run;
|
|
436
|
+
private passesExcludeRule;
|
|
224
437
|
private runRule;
|
|
225
438
|
/** Custom message (concrete field, then wildcard pattern, then rule name) or the default, with :attribute filled. */
|
|
226
439
|
private failureMessage;
|
|
@@ -228,7 +441,9 @@ declare class ValidatorImpl implements Validator$1 {
|
|
|
228
441
|
|
|
229
442
|
declare class ValidationFactoryImpl implements ValidationFactory {
|
|
230
443
|
private readonly extended;
|
|
231
|
-
make(data: Record<string, unknown>, rules:
|
|
444
|
+
make<const R extends Rules>(data: Record<string, unknown>, rules: R, messages?: Record<string, string>, attributes?: Record<string, string>): ValidatorImpl<R>;
|
|
445
|
+
/** Every rule this factory knows, built-in and extend()ed, sorted (for editors: `oriel ide:info`). */
|
|
446
|
+
ruleNames(): string[];
|
|
232
447
|
extend(name: string, callback: RuleCallback, message?: string): void;
|
|
233
448
|
}
|
|
234
449
|
|
|
@@ -239,4 +454,4 @@ declare class ValidationServiceProvider extends ServiceProvider {
|
|
|
239
454
|
register(): void;
|
|
240
455
|
}
|
|
241
456
|
|
|
242
|
-
export { AuthorizationErrorImpl, type BuiltinRule, BuiltinRuleObject, DEFAULT_MESSAGES,
|
|
457
|
+
export { AuthorizationErrorImpl, type BuiltinRule, BuiltinRuleObject, DEFAULT_MESSAGES, type ExpandingRule, FormRequest, InvalidRuleParameterError, MessageBagImpl, type ParsedRule, PasswordRule, Rule, type RuleContext, UnknownValidationRuleError, ValidationErrorImpl, ValidationException, ValidationFactoryImpl, ValidationServiceProvider, Validator, ValidatorImpl, applyReplacements, builtinRules, defaultAttributeName, expandWildcardKeys, isEmptyValue, isUploadedFileLike, parseRuleSpec };
|