@4riders/reform 3.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +266 -0
- package/dist/index.d.ts +2715 -0
- package/dist/index.es.js +1715 -0
- package/dist/index.es.js.map +1 -0
- package/package.json +70 -0
- package/src/index.ts +90 -0
- package/src/reform/ArrayHelper.ts +164 -0
- package/src/reform/Form.tsx +81 -0
- package/src/reform/FormManager.ts +494 -0
- package/src/reform/Reform.ts +15 -0
- package/src/reform/components/BaseCheckboxField.tsx +72 -0
- package/src/reform/components/BaseDateField.tsx +84 -0
- package/src/reform/components/BaseRadioField.tsx +72 -0
- package/src/reform/components/BaseSelectField.tsx +103 -0
- package/src/reform/components/BaseTextAreaField.tsx +87 -0
- package/src/reform/components/BaseTextField.tsx +135 -0
- package/src/reform/components/InputHTMLProps.tsx +89 -0
- package/src/reform/observers/observer.ts +131 -0
- package/src/reform/observers/observerPath.ts +327 -0
- package/src/reform/observers/useObservers.ts +232 -0
- package/src/reform/useForm.ts +246 -0
- package/src/reform/useFormContext.tsx +37 -0
- package/src/reform/useFormField.ts +75 -0
- package/src/reform/useRender.ts +12 -0
- package/src/yop/MessageProvider.ts +204 -0
- package/src/yop/Metadata.ts +304 -0
- package/src/yop/ObjectsUtil.ts +811 -0
- package/src/yop/TypesUtil.ts +148 -0
- package/src/yop/ValidationContext.ts +207 -0
- package/src/yop/Yop.ts +430 -0
- package/src/yop/constraints/CommonConstraints.ts +124 -0
- package/src/yop/constraints/Constraint.ts +135 -0
- package/src/yop/constraints/MinMaxConstraints.ts +53 -0
- package/src/yop/constraints/OneOfConstraint.ts +40 -0
- package/src/yop/constraints/TestConstraint.ts +176 -0
- package/src/yop/decorators/array.ts +157 -0
- package/src/yop/decorators/boolean.ts +69 -0
- package/src/yop/decorators/date.ts +73 -0
- package/src/yop/decorators/email.ts +66 -0
- package/src/yop/decorators/file.ts +69 -0
- package/src/yop/decorators/id.ts +35 -0
- package/src/yop/decorators/ignored.ts +40 -0
- package/src/yop/decorators/instance.ts +110 -0
- package/src/yop/decorators/number.ts +73 -0
- package/src/yop/decorators/string.ts +90 -0
- package/src/yop/decorators/test.ts +41 -0
- package/src/yop/decorators/time.ts +112 -0
package/src/yop/Yop.ts
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { CommonConstraints } from "./constraints/CommonConstraints"
|
|
2
|
+
import { validateConstraint } from "./constraints/Constraint"
|
|
3
|
+
import { MinMaxConstraints, validateMinMaxConstraints } from "./constraints/MinMaxConstraints"
|
|
4
|
+
import { MessageProvider, messageProvider_en_US, messageProvider_fr_FR } from "./MessageProvider"
|
|
5
|
+
import { ClassFieldDecorator, getMetadataFromDecorator } from "./Metadata"
|
|
6
|
+
import { joinPath, Path, splitPath } from "./ObjectsUtil"
|
|
7
|
+
import { Constructor, isBoolean } from "./TypesUtil"
|
|
8
|
+
import { Group, InternalValidationContext, ValidationStatus } from "./ValidationContext"
|
|
9
|
+
import { FormManager } from "../reform/FormManager"
|
|
10
|
+
|
|
11
|
+
(Symbol as any).metadata ??= Symbol.for("Symbol.metadata")
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Symbol used to store validation metadata on class fields. This symbol is used as a key to attach validation constraints and related
|
|
15
|
+
* information to class properties.
|
|
16
|
+
* @ignore
|
|
17
|
+
*/
|
|
18
|
+
export const validationSymbol = Symbol('YopValidation')
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Type for async validation status, including dependencies.
|
|
22
|
+
* @category Validation Management
|
|
23
|
+
*/
|
|
24
|
+
export type AsyncValidationStatus = {
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The current validation status, which can be "pending", "unavailable", or a regular validation status.
|
|
28
|
+
*/
|
|
29
|
+
status?: ValidationStatus | undefined
|
|
30
|
+
/**
|
|
31
|
+
* The dependencies for the asynchronous validation.
|
|
32
|
+
*/
|
|
33
|
+
dependencies: any[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Type for resolved constraints, including required, min, and max.
|
|
38
|
+
* @template MinMax - The type for min/max values.
|
|
39
|
+
* @category Validation Management
|
|
40
|
+
*/
|
|
41
|
+
export type ResolvedConstraints<MinMax = unknown> = {
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whether the field is required.
|
|
45
|
+
*/
|
|
46
|
+
required: boolean
|
|
47
|
+
/**
|
|
48
|
+
* The minimum value or length for the field, if applicable.
|
|
49
|
+
*/
|
|
50
|
+
min?: MinMax
|
|
51
|
+
/**
|
|
52
|
+
* The maximum value or length for the field, if applicable.
|
|
53
|
+
*/
|
|
54
|
+
max?: MinMax
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Type for resolved constraints with an editable field metadata. *Warning*: the `fieldMetadata` property is mutable and should be used with
|
|
59
|
+
* caution, as modifying it can lead to unexpected behavior in validation logic. It is intended for advanced use cases where access to the
|
|
60
|
+
* original constraints is necessary, but it should not be modified during normal validation operations.
|
|
61
|
+
* @template MinMax - The type for min/max values.
|
|
62
|
+
* @category Validation Management
|
|
63
|
+
*/
|
|
64
|
+
export type UnsafeResolvedConstraints<MinMax = unknown> = ResolvedConstraints<MinMax> &{
|
|
65
|
+
fieldMetadata?: CommonConstraints<any, any> | undefined
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Base interface of a form manager, extended by {@link FormManager}, representing form state and operations.
|
|
70
|
+
* @category Validation Management
|
|
71
|
+
*/
|
|
72
|
+
export interface ValidationForm {
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Whether the form has been submitted at least once, even if the submission was unsuccessful.
|
|
76
|
+
*/
|
|
77
|
+
readonly submitted: boolean
|
|
78
|
+
/**
|
|
79
|
+
* Whether the form is currently being submitted.
|
|
80
|
+
*/
|
|
81
|
+
readonly submitting: boolean
|
|
82
|
+
/**
|
|
83
|
+
* Map of validation statuses for each field.
|
|
84
|
+
*/
|
|
85
|
+
readonly statuses: Map<string, ValidationStatus>
|
|
86
|
+
/**
|
|
87
|
+
* List of all errors in the form: this includes all validation statuses with a severity level of "error".
|
|
88
|
+
*/
|
|
89
|
+
readonly errors: ValidationStatus[]
|
|
90
|
+
/**
|
|
91
|
+
* A map that can be used to store arbitrary data related to the form.
|
|
92
|
+
*/
|
|
93
|
+
readonly store: Map<string, any>
|
|
94
|
+
/**
|
|
95
|
+
* The HTML form element associated with this validation form.
|
|
96
|
+
*/
|
|
97
|
+
readonly htmlForm?: HTMLFormElement
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Retrieves the value of a field by its path.
|
|
101
|
+
* @template T - The expected type of the field value.
|
|
102
|
+
* @param path - The path to the field.
|
|
103
|
+
* @returns The value of the field, or undefined if not found.
|
|
104
|
+
*/
|
|
105
|
+
getValue<T = any>(path: string | Path): T | undefined
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Checks if a field has been touched.
|
|
109
|
+
* @param path - The path to the field.
|
|
110
|
+
* @returns True if the field has been touched, false otherwise.
|
|
111
|
+
*/
|
|
112
|
+
isTouched(path?: string | Path): boolean
|
|
113
|
+
/**
|
|
114
|
+
* Marks a field as touched.
|
|
115
|
+
* @param path - The path to the field.
|
|
116
|
+
*/
|
|
117
|
+
touch(path?: string | Path): void
|
|
118
|
+
/**
|
|
119
|
+
* Marks a field as untouched.
|
|
120
|
+
* @param path - The path to the field.
|
|
121
|
+
*/
|
|
122
|
+
untouch(path?: string | Path): void
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Checks if a field is dirty (has been modified). If no path is provided, checks if any field in the entire form is dirty.
|
|
126
|
+
* @param path - The path to the field.
|
|
127
|
+
* @param ignoredPath - Optional path to ignore during the check.
|
|
128
|
+
* @returns True if the field is dirty, false otherwise.
|
|
129
|
+
*/
|
|
130
|
+
isDirty(path?: string | Path, ignoredPath?: string | Path): boolean
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Interface for validation settings, including path, groups, and options.
|
|
135
|
+
* @category Validation Management
|
|
136
|
+
*/
|
|
137
|
+
export interface ValidationSettings {
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The path to the field or object being validated. This can be a string with dot notation (e.g., "address.street") or
|
|
141
|
+
* an array of path segments (e.g., ["address", "street"]). If omitted, the validation will be performed on the root value.
|
|
142
|
+
*/
|
|
143
|
+
path?: string | Path
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Validation groups to apply during validation. This can be a single group or an array of groups. If specified, only the
|
|
147
|
+
* constraints associated with at least one of the active groups will be validated.
|
|
148
|
+
*/
|
|
149
|
+
groups?: Group
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Function to determine if a path should be ignored during validation.
|
|
153
|
+
*/
|
|
154
|
+
ignore?: (path: Path) => boolean
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Whether to skip asynchronous validation.
|
|
158
|
+
*/
|
|
159
|
+
skipAsync?: boolean
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The validation form associated with this validation context. This form is set by the form manager when performing form-level validation
|
|
163
|
+
* and can be used to access form state and operations during validation.
|
|
164
|
+
*/
|
|
165
|
+
form?: ValidationForm
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Interface for settings used when resolving constraints at a specific path.
|
|
170
|
+
* @ignore
|
|
171
|
+
*/
|
|
172
|
+
export interface ConstraintsAtSettings extends ValidationSettings {
|
|
173
|
+
unsafeMetadata?: boolean
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Main class for validation logic, constraint resolution, and message provider management.
|
|
178
|
+
* @category Validation Management
|
|
179
|
+
*/
|
|
180
|
+
export class Yop {
|
|
181
|
+
|
|
182
|
+
private static defaultInstance?: Yop
|
|
183
|
+
private static classIds = new Map<string, Constructor<unknown>>()
|
|
184
|
+
|
|
185
|
+
private static messageProviders = new Map<string, MessageProvider>()
|
|
186
|
+
static {
|
|
187
|
+
this.registerMessageProvider(messageProvider_en_US)
|
|
188
|
+
this.registerMessageProvider(messageProvider_fr_FR)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private locale: string = Yop.defaultInstance?.locale ?? "en-US"
|
|
192
|
+
|
|
193
|
+
private _store = new Map<string, any>()
|
|
194
|
+
private _asyncStatuses = new Map<string, AsyncValidationStatus>()
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Gets the store map, which can be used to store arbitrary data related to validation operations. This allows sharing data across different
|
|
198
|
+
* validation contexts and operations.
|
|
199
|
+
*/
|
|
200
|
+
get store() {
|
|
201
|
+
return this._store
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Gets the map of asynchronous validation statuses, where each key is a path and the value is the corresponding asynchronous validation status.
|
|
206
|
+
*/
|
|
207
|
+
get asyncStatuses() {
|
|
208
|
+
return this._asyncStatuses
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Registers a class constructor with a given ID.
|
|
213
|
+
* @param id The ID to associate with the class constructor.
|
|
214
|
+
* @param constructor The class constructor to register.
|
|
215
|
+
* @ignore
|
|
216
|
+
*/
|
|
217
|
+
static registerClass(id: string, constructor: Constructor<unknown>) {
|
|
218
|
+
Yop.classIds.set(id, constructor)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Resolves a class constructor by its ID.
|
|
223
|
+
* @param id The ID of the class or a function reference.
|
|
224
|
+
* @param silent If true, suppresses error messages for unregistered classes.
|
|
225
|
+
* @returns The resolved class constructor, or undefined if not found.
|
|
226
|
+
* @ignore
|
|
227
|
+
*/
|
|
228
|
+
static resolveClass<T>(id: unknown, silent = false): Constructor<T> | undefined {
|
|
229
|
+
if (typeof id === "string") {
|
|
230
|
+
const resolved = Yop.classIds.get(id)
|
|
231
|
+
if (resolved == null && silent === false)
|
|
232
|
+
console.error(`Class "${ id }" unregistered in Yop. Did you forget to add a @id("${ id }") decorator to the class?`)
|
|
233
|
+
return resolved as Constructor<T>
|
|
234
|
+
}
|
|
235
|
+
if (typeof id === "function" && id.prototype == null)
|
|
236
|
+
return id() as Constructor<T>
|
|
237
|
+
return id as Constructor<T> | undefined
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private contextAt(decorator: ClassFieldDecorator<any>, value: any, settings: ValidationSettings, traverseNullish = false) {
|
|
241
|
+
let constraints = getMetadataFromDecorator(decorator)
|
|
242
|
+
if (constraints == null)
|
|
243
|
+
return undefined
|
|
244
|
+
|
|
245
|
+
const segments = typeof settings.path === "string" ? splitPath(settings.path) : (settings.path ?? [])
|
|
246
|
+
if (segments == null)
|
|
247
|
+
return undefined
|
|
248
|
+
|
|
249
|
+
let context = new InternalValidationContext<any>({
|
|
250
|
+
yop: this,
|
|
251
|
+
kind: constraints.kind,
|
|
252
|
+
value,
|
|
253
|
+
settings: settings,
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
for (const segment of segments) {
|
|
257
|
+
[constraints, value] = constraints.traverse?.(context, constraints, segment, traverseNullish) ?? [,]
|
|
258
|
+
if (constraints == null)
|
|
259
|
+
return undefined
|
|
260
|
+
context = context.createChildContext({ kind: constraints.kind, value, key: segment })
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return [context, constraints] as const
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Retrieves the constraints for a given class field decorator and value, using the provided settings.
|
|
268
|
+
* @param decorator The class field decorator defining the constraints.
|
|
269
|
+
* @param value The value to be validated against the constraints.
|
|
270
|
+
* @param settings Optional settings for constraint resolution, including path and unsafe metadata flag.
|
|
271
|
+
* @returns The resolved constraints, or undefined if no constraints are found.
|
|
272
|
+
*/
|
|
273
|
+
constraintsAt<MinMax = unknown>(decorator: ClassFieldDecorator<any>, value: any, settings?: ConstraintsAtSettings): ResolvedConstraints<MinMax> | undefined {
|
|
274
|
+
const [context, constraints] = this.contextAt(decorator, value, settings ?? {}, true) ?? []
|
|
275
|
+
|
|
276
|
+
if (context != null && constraints != null) {
|
|
277
|
+
const resolvedContraints: UnsafeResolvedConstraints<MinMax> = { required: false }
|
|
278
|
+
validateConstraint(context, constraints, "required", isBoolean, (_, constraint) => { resolvedContraints.required = constraint; return true })
|
|
279
|
+
const isMinMaxType = (constraints as MinMaxConstraints<any, any>).isMinMaxType
|
|
280
|
+
if (isMinMaxType != null) {
|
|
281
|
+
validateMinMaxConstraints(
|
|
282
|
+
context,
|
|
283
|
+
constraints as MinMaxConstraints<unknown, unknown>,
|
|
284
|
+
isMinMaxType,
|
|
285
|
+
(_, min) => { resolvedContraints.min = min; return true },
|
|
286
|
+
(_, max) => { resolvedContraints.max = max; return true }
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
if (settings?.unsafeMetadata)
|
|
290
|
+
resolvedContraints.fieldMetadata = constraints
|
|
291
|
+
return resolvedContraints
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return undefined
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Static version of the constraintsAt method, which initializes the Yop instance and retrieves the constraints for a given class field decorator and value.
|
|
298
|
+
* @param decorator The class field decorator defining the constraints.
|
|
299
|
+
* @param value The value to be validated against the constraints.
|
|
300
|
+
* @param settings Optional settings for constraint resolution, including path and unsafe metadata flag.
|
|
301
|
+
* @returns The resolved constraints, or undefined if no constraints are found.
|
|
302
|
+
*/
|
|
303
|
+
static constraintsAt<Value>(decorator: ClassFieldDecorator<Value>, value: any, settings?: ConstraintsAtSettings) {
|
|
304
|
+
return Yop.init().constraintsAt(decorator, value, settings)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Retrieves the asynchronous validation status for a given path. The path can be a string or an array of path segments.
|
|
309
|
+
* @param path The path for which to retrieve the asynchronous validation status.
|
|
310
|
+
* @returns The asynchronous validation status, or undefined if not found.
|
|
311
|
+
*/
|
|
312
|
+
getAsyncStatus(path: string | Path) {
|
|
313
|
+
return this.asyncStatuses.get(typeof path === "string" ? path : joinPath(path))?.status
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Validates a value against the constraints defined by a class field decorator, using the provided validation settings. This method performs
|
|
318
|
+
* the validation logic and returns the validation context, which includes the results of the validation operation. The context contains
|
|
319
|
+
* information about the value, its path, any validation statuses.
|
|
320
|
+
* @param value The value to be validated.
|
|
321
|
+
* @param decorator The decorator defining the validation constraints.
|
|
322
|
+
* @param settings The validation settings, including the path and other options.
|
|
323
|
+
* @returns The validation context after performing the validation.
|
|
324
|
+
* @ignore
|
|
325
|
+
*/
|
|
326
|
+
rawValidate<Value>(value: any, decorator: ClassFieldDecorator<Value>, settings: ValidationSettings = { path: [] }) {
|
|
327
|
+
const [context, constraints] = this.contextAt(decorator, value, settings) ?? []
|
|
328
|
+
if (context != null && constraints != null)
|
|
329
|
+
constraints.validate(context, constraints)
|
|
330
|
+
return context
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Validates a value against the constraints defined by a class field decorator, using the provided validation settings. This method performs
|
|
335
|
+
* the validation logic and returns the validation statuses array.
|
|
336
|
+
* @param value The value to be validated.
|
|
337
|
+
* @param decorator The decorator defining the validation constraints.
|
|
338
|
+
* @param settings The validation settings, including the path and other options.
|
|
339
|
+
* @returns An array of validation statuses after performing the validation.
|
|
340
|
+
*/
|
|
341
|
+
validate<Value>(value: any, decorator: ClassFieldDecorator<Value>, settings: ValidationSettings = { path: [] }) {
|
|
342
|
+
const context = this.rawValidate(value, decorator, settings)
|
|
343
|
+
return context != null ? Array.from(context.statuses.values()) : []
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Static version of the validate method, which initializes the Yop instance and performs validation on the provided value using the specified
|
|
347
|
+
* decorator and settings.
|
|
348
|
+
* @param value The value to be validated.
|
|
349
|
+
* @param decorator The decorator defining the validation constraints.
|
|
350
|
+
* @param settings The validation settings, including the path and other options.
|
|
351
|
+
* @returns An array of validation statuses after performing the validation.
|
|
352
|
+
* @see {@link Yop#validate}
|
|
353
|
+
*/
|
|
354
|
+
static validate<Value>(value: any, decorator: ClassFieldDecorator<Value>, settings?: ValidationSettings) {
|
|
355
|
+
return Yop.init().validate(value, decorator, settings)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Registers a message provider for a specific locale. The message provider is used to retrieve localized validation messages
|
|
360
|
+
* based on the current locale setting.
|
|
361
|
+
* @param provider The message provider to register.
|
|
362
|
+
*/
|
|
363
|
+
static registerMessageProvider(provider: MessageProvider) {
|
|
364
|
+
try {
|
|
365
|
+
const locale = Intl.getCanonicalLocales(provider.locale)[0]
|
|
366
|
+
Yop.messageProviders.set(locale, provider)
|
|
367
|
+
}
|
|
368
|
+
catch (e) {
|
|
369
|
+
console.error(`Invalid locale "${ provider.locale }" in message provider. Ignoring.`, e)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Gets the current locale used for message resolution. This locale determines which message provider is used to retrieve validation messages.
|
|
375
|
+
* @returns The current locale as a string.
|
|
376
|
+
*/
|
|
377
|
+
getLocale() {
|
|
378
|
+
return this.locale
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Static version of the getLocale method, which initializes the Yop instance and retrieves the current locale.
|
|
382
|
+
* @returns The current locale as a string.
|
|
383
|
+
* @see {@link Yop#getLocale}
|
|
384
|
+
*/
|
|
385
|
+
static getLocale() {
|
|
386
|
+
return Yop.init().locale
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Sets the current locale used for message resolution. This locale determines which message provider is used to retrieve validation messages.
|
|
391
|
+
* @param locale The locale to set. This should be a valid BCP 47 language tag (e.g., "en-US", "fr-FR").
|
|
392
|
+
*/
|
|
393
|
+
setLocale(locale: string) {
|
|
394
|
+
try {
|
|
395
|
+
locale = Intl.getCanonicalLocales(locale)[0]
|
|
396
|
+
if (Yop.messageProviders.has(locale))
|
|
397
|
+
this.locale = locale
|
|
398
|
+
else
|
|
399
|
+
console.error(`No message provider for locale "${ locale }". Ignoring`)
|
|
400
|
+
}
|
|
401
|
+
catch (e) {
|
|
402
|
+
console.error(`Invalid locale "${ locale }". Ignoring.`, e)
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Static version of the setLocale method, which initializes the Yop instance and sets the current locale.
|
|
407
|
+
* @param locale The locale to set. This should be a valid BCP 47 language tag (e.g., "en-US", "fr-FR").
|
|
408
|
+
* @see {@link Yop#setLocale}
|
|
409
|
+
*/
|
|
410
|
+
static setLocale(locale: string) {
|
|
411
|
+
Yop.init().setLocale(locale)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Gets the message provider for the current locale. This message provider is used to retrieve localized validation messages based on the current locale setting.
|
|
416
|
+
*/
|
|
417
|
+
get messageProvider() {
|
|
418
|
+
return Yop.messageProviders.get(this.locale)!
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Initializes the Yop instance if it hasn't been initialized yet and returns the default instance.
|
|
423
|
+
* @returns The default Yop instance.
|
|
424
|
+
*/
|
|
425
|
+
static init(): Yop {
|
|
426
|
+
if (Yop.defaultInstance == null)
|
|
427
|
+
Yop.defaultInstance = new Yop()
|
|
428
|
+
return Yop.defaultInstance
|
|
429
|
+
}
|
|
430
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Constraint, validateConstraint } from "./Constraint"
|
|
2
|
+
import { isBoolean } from "../TypesUtil"
|
|
3
|
+
import { InternalValidationContext } from "../ValidationContext"
|
|
4
|
+
import { Groups } from "../Metadata"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Common validation constraints for a value, used in decorators and validation logic.
|
|
8
|
+
* @template Value - The type of the value being validated.
|
|
9
|
+
* @template Parent - The type of the parent object.
|
|
10
|
+
* @category Shared Constraints
|
|
11
|
+
*/
|
|
12
|
+
export interface CommonConstraints<Value, Parent = unknown> {
|
|
13
|
+
/**
|
|
14
|
+
* If `true`, the validation of the decorated element is skipped.
|
|
15
|
+
*/
|
|
16
|
+
ignored?: Constraint<Value | null | undefined, boolean, Parent>
|
|
17
|
+
/**
|
|
18
|
+
* If `true`, the property must be present in the parent object (ie: `"prop" in obj` is true).
|
|
19
|
+
*/
|
|
20
|
+
exists?: Constraint<Value | null | undefined, boolean, Parent>
|
|
21
|
+
/**
|
|
22
|
+
* If `true`, the value must not be `undefined`.
|
|
23
|
+
*/
|
|
24
|
+
defined?: Constraint<Value | null | undefined, boolean, Parent>
|
|
25
|
+
/**
|
|
26
|
+
* If `true`, the value must not be `null`.
|
|
27
|
+
*/
|
|
28
|
+
notnull?: Constraint<Value | null | undefined, boolean, Parent>
|
|
29
|
+
/**
|
|
30
|
+
* If `true`, the value must not be `undefined` or `null`.
|
|
31
|
+
*/
|
|
32
|
+
required?: Constraint<Value | null | undefined, boolean, Parent>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Extracts the value type from a CommonConstraints type.
|
|
37
|
+
* @ignore
|
|
38
|
+
*/
|
|
39
|
+
export type ContraintsValue<Contraints> = Contraints extends CommonConstraints<infer Value, infer _Parent> ? Value : never
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Extracts the parent type from a CommonConstraints type.
|
|
43
|
+
* @ignore
|
|
44
|
+
*/
|
|
45
|
+
export type ContraintsParent<Contraints> = Contraints extends CommonConstraints<infer _Value, infer Parent> ? Parent : never
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Type for a validation function for a set of constraints.
|
|
49
|
+
* @ignore
|
|
50
|
+
*/
|
|
51
|
+
export type Validator<Constraints, Value = ContraintsValue<Constraints>, Parent = ContraintsParent<Constraints>> =
|
|
52
|
+
(context: InternalValidationContext<Value, Parent>, constraints: Constraints) => boolean
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Type for a function that traverses nested constraints and values.
|
|
56
|
+
* @ignore
|
|
57
|
+
*/
|
|
58
|
+
export type Traverser<Constraints, Value = ContraintsValue<Constraints>, Parent = ContraintsParent<Constraints>> =
|
|
59
|
+
((context: InternalValidationContext<Value, Parent>, constraints: Constraints, propertyOrIndex: string | number, traverseNullish?: boolean) =>
|
|
60
|
+
readonly [InternalCommonConstraints | undefined, InternalValidationContext<unknown>])
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Used internally.
|
|
64
|
+
* @ignore
|
|
65
|
+
*/
|
|
66
|
+
export interface InternalConstraints {
|
|
67
|
+
/**
|
|
68
|
+
* The kind of the decorated value (eg: `string`, `number`, etc.)
|
|
69
|
+
*/
|
|
70
|
+
kind: string
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The method that validates the decorated value.
|
|
74
|
+
*/
|
|
75
|
+
validate: Validator<this>
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The method that returns the constraints and value of a nested field.
|
|
79
|
+
*/
|
|
80
|
+
traverse?: Traverser<this>
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Validation groups with specific constraints. If specified, the given constraints will only be validated if at least one
|
|
84
|
+
* of the groups is active in the validation context.
|
|
85
|
+
*/
|
|
86
|
+
groups?: Groups<this>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Internal constraints that include both common and internal constraint logic.
|
|
92
|
+
* @ignore
|
|
93
|
+
*/
|
|
94
|
+
export interface InternalCommonConstraints extends CommonConstraints<any, any>, InternalConstraints {
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Validates the common constraints (defined, notnull, required) for a value.
|
|
99
|
+
* @template Value - The type of the value being validated.
|
|
100
|
+
* @template Parent - The type of the parent object.
|
|
101
|
+
* @param context - The validation context.
|
|
102
|
+
* @param constraints - The constraints to validate.
|
|
103
|
+
* @returns True if all constraints pass, false otherwise.
|
|
104
|
+
* @ignore
|
|
105
|
+
*/
|
|
106
|
+
export function validateCommonConstraints<Value, Parent>(context: InternalValidationContext<Value, Parent>, constraints: CommonConstraints<Value, Parent>) {
|
|
107
|
+
return (
|
|
108
|
+
validateConstraint(context, constraints, "defined", isBoolean, (value, constraint) => constraint !== true || value !== undefined) &&
|
|
109
|
+
validateConstraint(context, constraints, "notnull", isBoolean, (value, constraint) => constraint !== true || value !== null) &&
|
|
110
|
+
validateConstraint(context, constraints, "required", isBoolean, (value, constraint) => constraint !== true || value != null)
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Validates the type of a value using a provided type check function.
|
|
116
|
+
* @param context - The validation context.
|
|
117
|
+
* @param checkType - Function to check the value's type.
|
|
118
|
+
* @param expectedType - The expected type as a string.
|
|
119
|
+
* @returns True if the value matches the expected type, false otherwise.
|
|
120
|
+
* @ignore
|
|
121
|
+
*/
|
|
122
|
+
export function validateTypeConstraint(context: InternalValidationContext<any>, checkType: (value: any) => boolean, expectedType: string) {
|
|
123
|
+
return checkType(context.value) || context.setStatus("type", expectedType) == null
|
|
124
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { isFunction } from "../TypesUtil"
|
|
2
|
+
import { InternalValidationContext, Level, ValidationContext } from "../ValidationContext"
|
|
3
|
+
import { JSX } from "react"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Type representing a constraint message, which can be a direct string or JSX element.
|
|
7
|
+
* @category Shared Constraints
|
|
8
|
+
*/
|
|
9
|
+
export type ConstraintMessage = string | JSX.Element
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Type representing a constraint value, which can be a direct value or a tuple containing the value, an optional message, and an optional level.
|
|
13
|
+
* This allows for flexible constraint definitions that can include custom error messages and severity levels.
|
|
14
|
+
* @template ConstraintType - The type of the constraint value.
|
|
15
|
+
* @category Shared Constraints
|
|
16
|
+
*/
|
|
17
|
+
export type ConstraintValue<ConstraintType> = ConstraintType | readonly [ConstraintType, ConstraintMessage, Level?]
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Type representing a constraint function that returns a constraint value.
|
|
21
|
+
* @template Value - The type of the value being validated.
|
|
22
|
+
* @template ConstraintType - The type of the constraint value.
|
|
23
|
+
* @template Parent - The type of the parent object.
|
|
24
|
+
* @category Shared Constraints
|
|
25
|
+
*/
|
|
26
|
+
export type ConstraintFunction<Value, ConstraintType, Parent = unknown> = ((context: ValidationContext<Value, Parent>) => ConstraintValue<ConstraintType> | undefined)
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A constraint can be defined as a direct value, a tuple with value, message and level, or a function returning
|
|
30
|
+
* either of those.
|
|
31
|
+
* @category Shared Constraints
|
|
32
|
+
*/
|
|
33
|
+
export type Constraint<Value, ConstraintType, Parent = unknown> =
|
|
34
|
+
ConstraintValue<ConstraintType> |
|
|
35
|
+
ConstraintFunction<Value, ConstraintType, Parent>
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A validation message can be defined as a direct string/JSX element or a function returning a message.
|
|
39
|
+
* @category Shared Constraints
|
|
40
|
+
*/
|
|
41
|
+
export type Message<Value, Parent> = ConstraintMessage | ((context: ValidationContext<Value, Parent>) => ConstraintMessage)
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Validates a constraint for a given context and constraint definition.
|
|
45
|
+
* Handles group-based constraints and default values/messages.
|
|
46
|
+
*
|
|
47
|
+
* @template Value - The type of the value being validated.
|
|
48
|
+
* @template ConstraintType - The type of the constraint value.
|
|
49
|
+
* @template Parent - The type of the parent object.
|
|
50
|
+
* @template Constraints - The type of the constraints object.
|
|
51
|
+
* @param context - The validation context.
|
|
52
|
+
* @param constraints - The constraints object.
|
|
53
|
+
* @param name - The name of the constraint to validate.
|
|
54
|
+
* @param isConstraintType - Type guard for the constraint value.
|
|
55
|
+
* @param validate - Function to validate the value against the constraint.
|
|
56
|
+
* @param defaultConstraint - Optional default constraint value.
|
|
57
|
+
* @param defaultMessage - Optional default error message.
|
|
58
|
+
* @param setStatus - Whether to set the status on failure (default: true).
|
|
59
|
+
* @returns True if the constraint passes, false otherwise.
|
|
60
|
+
* @ignore
|
|
61
|
+
*/
|
|
62
|
+
export function validateConstraint<Value, ConstraintType, Parent, Constraints = { [name: string]: Constraint<Value, ConstraintType, Parent> }>(
|
|
63
|
+
context: InternalValidationContext<Value, Parent>,
|
|
64
|
+
constraints: Constraints,
|
|
65
|
+
name: keyof Constraints,
|
|
66
|
+
isConstraintType: (value: any) => value is ConstraintType,
|
|
67
|
+
validate: (value: Value, constraintValue: NonNullable<ConstraintType>) => boolean,
|
|
68
|
+
defaultConstraint?: ConstraintType,
|
|
69
|
+
defaultMessage?: Message<Value, Parent>,
|
|
70
|
+
setStatus = true
|
|
71
|
+
) {
|
|
72
|
+
if (context.groups == null) {
|
|
73
|
+
const constraint = constraints[name] as Constraint<Value, ConstraintType, Parent> | undefined
|
|
74
|
+
return _validateConstraint(context, constraint, isConstraintType, validate, name as string, defaultConstraint, defaultMessage, setStatus)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const groups = Array.isArray(context.groups) ? context.groups : [context.groups]
|
|
78
|
+
for (const group of groups) {
|
|
79
|
+
const constraint = (group == null ? constraints[name] : (constraints as any).groups?.[group]?.[name]) as Constraint<Value, ConstraintType, Parent> | undefined
|
|
80
|
+
if (!_validateConstraint(context, constraint, isConstraintType, validate, name as string, defaultConstraint, defaultMessage, setStatus))
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
return true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Internal helper to validate a single constraint value, handling tuple and function forms.
|
|
88
|
+
* @ignore
|
|
89
|
+
*/
|
|
90
|
+
function _validateConstraint<Value, ConstraintType, Parent>(
|
|
91
|
+
context: InternalValidationContext<Value, Parent>,
|
|
92
|
+
constraint: Constraint<Value, ConstraintType, Parent> | undefined,
|
|
93
|
+
isConstraintType: (value: any) => value is ConstraintType,
|
|
94
|
+
validate: (value: Value, constraintValue: NonNullable<ConstraintType>) => boolean,
|
|
95
|
+
errorCode: string,
|
|
96
|
+
defaultConstraint?: ConstraintType,
|
|
97
|
+
defaultMessage?: Message<Value, Parent>,
|
|
98
|
+
setStatus = true
|
|
99
|
+
) {
|
|
100
|
+
if (constraint == null && defaultConstraint == null)
|
|
101
|
+
return true
|
|
102
|
+
|
|
103
|
+
let message: ConstraintMessage | undefined = undefined
|
|
104
|
+
let level: Level = "error"
|
|
105
|
+
|
|
106
|
+
if (isFunction(constraint))
|
|
107
|
+
constraint = (constraint as ConstraintFunction<Value, ConstraintType>)(context)
|
|
108
|
+
|
|
109
|
+
if (constraint != null && !isConstraintType(constraint)) {
|
|
110
|
+
if (Array.isArray(constraint)) {
|
|
111
|
+
const [maybeConstraint, maybeMessage, maybeLevel, _maybeGroup] = constraint
|
|
112
|
+
if (maybeConstraint == null || isConstraintType(maybeConstraint)) {
|
|
113
|
+
constraint = maybeConstraint
|
|
114
|
+
message = maybeMessage
|
|
115
|
+
level = (maybeLevel as unknown as Level) ?? "error"
|
|
116
|
+
}
|
|
117
|
+
else
|
|
118
|
+
constraint = undefined
|
|
119
|
+
}
|
|
120
|
+
else
|
|
121
|
+
constraint = undefined
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (constraint == null && defaultConstraint != null)
|
|
125
|
+
constraint = defaultConstraint
|
|
126
|
+
|
|
127
|
+
if (message == null && defaultMessage != null)
|
|
128
|
+
message = isFunction(defaultMessage) ? defaultMessage(context) : defaultMessage
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
constraint == null ||
|
|
132
|
+
validate(context.value as Value, constraint as NonNullable<ConstraintType>) ||
|
|
133
|
+
(setStatus === true && context.setStatus(errorCode, constraint, message, level) == null)
|
|
134
|
+
)
|
|
135
|
+
}
|