@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
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Constraint, validateConstraint } from "./Constraint"
|
|
2
|
+
import { InternalValidationContext, ValuedContext } from "../ValidationContext"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Interface for min and max constraints on a value.
|
|
6
|
+
* @template Value - The type of the value being validated.
|
|
7
|
+
* @template MinMax - The type for min/max values.
|
|
8
|
+
* @template Parent - The type of the parent object.
|
|
9
|
+
* @property min - Minimum constraint for the value, if any.
|
|
10
|
+
* @property max - Maximum constraint for the value, if any.
|
|
11
|
+
* @property isMinMaxType - Type guard for min/max values.
|
|
12
|
+
* @category Shared Constraints
|
|
13
|
+
*/
|
|
14
|
+
export interface MinMaxConstraints<Value, MinMax, Parent = unknown> {
|
|
15
|
+
/**
|
|
16
|
+
* Minimum constraint for the value. The value must be greater than or equal to this constraint.
|
|
17
|
+
*/
|
|
18
|
+
min?: Constraint<NonNullable<Value>, MinMax | null | undefined, Parent>
|
|
19
|
+
/**
|
|
20
|
+
* Maximum constraint for the value. The value must be less than or equal to this constraint.
|
|
21
|
+
*/
|
|
22
|
+
max?: Constraint<NonNullable<Value>, MinMax | null | undefined, Parent>
|
|
23
|
+
/**
|
|
24
|
+
* Optional type guard function to determine if a value is a valid min/max constraint. This can be used to ensure
|
|
25
|
+
* that the constraint values are of the expected type.
|
|
26
|
+
*/
|
|
27
|
+
isMinMaxType?: (value: any) => value is MinMax
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Validates min and max constraints for a value.
|
|
32
|
+
* @template Value - The type of the value being validated.
|
|
33
|
+
* @template MinMax - The type for min/max values.
|
|
34
|
+
* @template Parent - The type of the parent object.
|
|
35
|
+
* @param context - The validation context.
|
|
36
|
+
* @param constraints - The min/max constraints to validate.
|
|
37
|
+
* @param isConstraintValue - Type guard for min/max values.
|
|
38
|
+
* @param validateMin - Function to validate the min constraint.
|
|
39
|
+
* @param validateMax - Function to validate the max constraint.
|
|
40
|
+
* @returns True if both min and max constraints pass, false otherwise.
|
|
41
|
+
* @ignore
|
|
42
|
+
*/
|
|
43
|
+
export function validateMinMaxConstraints<Value, MinMax, Parent>(
|
|
44
|
+
context: InternalValidationContext<Value, Parent>,
|
|
45
|
+
constraints: MinMaxConstraints<Value, MinMax, Parent>,
|
|
46
|
+
isConstraintValue: (value: any) => value is MinMax,
|
|
47
|
+
validateMin: (value: NonNullable<Value>, min: NonNullable<MinMax>) => boolean,
|
|
48
|
+
validateMax: (value: NonNullable<Value>, max: NonNullable<MinMax>) => boolean) {
|
|
49
|
+
return (
|
|
50
|
+
validateConstraint(context as ValuedContext<Value, Parent>, constraints, "min", isConstraintValue, validateMin) &&
|
|
51
|
+
validateConstraint(context as ValuedContext<Value, Parent>, constraints, "max", isConstraintValue, validateMax)
|
|
52
|
+
)
|
|
53
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Constraint, validateConstraint } from "./Constraint"
|
|
2
|
+
import { InternalValidationContext, ValuedContext } from "../ValidationContext"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Interface for a constraint that checks if a value is one of a set of allowed values.
|
|
6
|
+
* @template Value - The type of the value being validated.
|
|
7
|
+
* @template Parent - The type of the parent object.
|
|
8
|
+
* @property oneOf - Constraint for allowed values, if any.
|
|
9
|
+
* @category Shared Constraints
|
|
10
|
+
*/
|
|
11
|
+
export interface OneOfConstraint<Value, Parent = unknown> {
|
|
12
|
+
oneOf?: Constraint<NonNullable<Value>, NoInfer<NonNullable<Value>>[], Parent>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Validates the oneOf constraint for a value.
|
|
17
|
+
* @template Value - The type of the value being validated.
|
|
18
|
+
* @template OneOfType - The type for the allowed values array.
|
|
19
|
+
* @template Parent - The type of the parent object.
|
|
20
|
+
* @param context - The validation context.
|
|
21
|
+
* @param constraints - The oneOf constraint to validate.
|
|
22
|
+
* @param isConstraintValue - Type guard for the allowed values array.
|
|
23
|
+
* @param equals - Optional equality function for comparing values.
|
|
24
|
+
* @returns True if the value is one of the allowed values, false otherwise.
|
|
25
|
+
* @ignore
|
|
26
|
+
*/
|
|
27
|
+
export function validateOneOfConstraint<Value, OneOfType extends NoInfer<NonNullable<Value>>[], Parent>(
|
|
28
|
+
context: InternalValidationContext<Value, Parent>,
|
|
29
|
+
constraints: OneOfConstraint<Value, Parent>,
|
|
30
|
+
isConstraintValue: (value: any) => value is OneOfType,
|
|
31
|
+
equals?: (value1: NonNullable<Value>, value2: NonNullable<Value>) => boolean
|
|
32
|
+
) {
|
|
33
|
+
return validateConstraint(
|
|
34
|
+
context as ValuedContext<Value, Parent>,
|
|
35
|
+
constraints,
|
|
36
|
+
"oneOf",
|
|
37
|
+
isConstraintValue,
|
|
38
|
+
(value, array) => equals == null ? array.includes(value) : array.some((item) => equals(value, item))
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { InternalValidationContext, Level, ValidationContext, ValidationStatus } from "../ValidationContext"
|
|
2
|
+
import { ConstraintFunction, ConstraintMessage } from "./Constraint"
|
|
3
|
+
import { isFunction, isObject } from "../TypesUtil"
|
|
4
|
+
import { joinPath } from "../ObjectsUtil"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Type for test constraint messages, which can be a simple message, a tuple of message and level, a boolean, or undefined.
|
|
8
|
+
* @category Shared Constraints
|
|
9
|
+
*/
|
|
10
|
+
export type TestConstraintMessage = ConstraintMessage | readonly [ConstraintMessage, Level] | boolean | undefined
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Type for a test constraint function, which can return a test constraint message or be undefined.
|
|
14
|
+
* @template Value - The type of the value being validated.
|
|
15
|
+
* @template Parent - The type of the parent object.
|
|
16
|
+
* @category Shared Constraints
|
|
17
|
+
*/
|
|
18
|
+
export type TestConstraintFunction<Value, Parent = unknown> = ConstraintFunction<NonNullable<Value>, TestConstraintMessage, Parent>
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Interface for an asynchronous test constraint.
|
|
22
|
+
* @template Value - The type of the value being validated.
|
|
23
|
+
* @template Parent - The type of the parent object.
|
|
24
|
+
* @property promise - Function returning a promise for the constraint message.
|
|
25
|
+
* @property pendingMessage - Message to show while pending.
|
|
26
|
+
* @property unavailableMessage - Message to show if async service is unavailable.
|
|
27
|
+
* @property dependencies - Function to get dependencies for revalidation.
|
|
28
|
+
* @property revalidate - Function to determine if revalidation is needed.
|
|
29
|
+
* @category Shared Constraints
|
|
30
|
+
*/
|
|
31
|
+
export interface AsyncTestConstraint<Value, Parent = unknown> {
|
|
32
|
+
promise: (context: ValidationContext<NonNullable<Value>, Parent>) => Promise<TestConstraintMessage>
|
|
33
|
+
pendingMessage?: ConstraintMessage
|
|
34
|
+
unavailableMessage?: ConstraintMessage
|
|
35
|
+
dependencies?: (context: InternalValidationContext<Value, Parent>) => any[],
|
|
36
|
+
revalidate?: (context: InternalValidationContext<Value, Parent>, previous: any[], current: any[], status: ValidationStatus | undefined) => boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Interface for a test constraint, which can be sync, async, or both.
|
|
41
|
+
* @template Value - The type of the value being validated.
|
|
42
|
+
* @template Parent - The type of the parent object.
|
|
43
|
+
* @property test - The test constraint function or async constraint.
|
|
44
|
+
* @category Shared Constraints
|
|
45
|
+
*/
|
|
46
|
+
export interface TestConstraint<Value, Parent = unknown> {
|
|
47
|
+
test?: TestConstraintFunction<Value, Parent> | AsyncTestConstraint<Value, Parent> | readonly [TestConstraintFunction<Value, Parent>, AsyncTestConstraint<Value, Parent>]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const defaultGetDependencies = (_context: InternalValidationContext<unknown>) => []
|
|
51
|
+
const defaultShouldRevalidate = (_context: InternalValidationContext<unknown>, previous: any[], current: any[], status: ValidationStatus | undefined) =>
|
|
52
|
+
status?.level !== "unavailable" && current.some((v, i) => v !== previous[i])
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validates a test constraint for a value, handling sync and async cases.
|
|
56
|
+
* @template Value - The type of the value being validated.
|
|
57
|
+
* @template Parent - The type of the parent object.
|
|
58
|
+
* @param context - The validation context.
|
|
59
|
+
* @param testConstraint - The test constraint to validate.
|
|
60
|
+
* @returns True if the constraint passes, false otherwise.
|
|
61
|
+
* @ignore
|
|
62
|
+
*/
|
|
63
|
+
export function validateTestConstraint<Value, Parent>(
|
|
64
|
+
context: InternalValidationContext<Value, Parent>,
|
|
65
|
+
testConstraint: TestConstraint<Value, Parent>
|
|
66
|
+
) {
|
|
67
|
+
if (context.groups == null)
|
|
68
|
+
return testConstraint.test == null || _validateTestConstraint(context, testConstraint)
|
|
69
|
+
|
|
70
|
+
const groups = Array.isArray(context.groups) ? context.groups : [context.groups]
|
|
71
|
+
for (const group of groups) {
|
|
72
|
+
const test = (group == null ? testConstraint.test : (testConstraint as any).groups?.[group]?.test)
|
|
73
|
+
if (test != null && !_validateTestConstraint(context, { test }))
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
return true
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Internal helper to validate a test constraint (sync and async).
|
|
81
|
+
* @ignore
|
|
82
|
+
*/
|
|
83
|
+
function _validateTestConstraint<Value, Parent>(
|
|
84
|
+
context: InternalValidationContext<Value, Parent>,
|
|
85
|
+
testConstraint: TestConstraint<Value, Parent>
|
|
86
|
+
) {
|
|
87
|
+
const test = testConstraint.test!
|
|
88
|
+
|
|
89
|
+
const syncTest = (isFunction(test) ? test : Array.isArray(test) ? test[0] : undefined) as TestConstraintFunction<Value, Parent> | undefined
|
|
90
|
+
if (syncTest != null && !_validateTestConstraintFunction(context as InternalValidationContext<NonNullable<Value>, Parent>, syncTest))
|
|
91
|
+
return false
|
|
92
|
+
|
|
93
|
+
const asyncTest = (isObject(test) ? test : Array.isArray(test) ? test[1] : undefined) as AsyncTestConstraint<Value, Parent> | undefined
|
|
94
|
+
return asyncTest == null || _validateAsyncTestConstraint(context as InternalValidationContext<NonNullable<Value>, Parent>, asyncTest)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Internal helper to validate a synchronous test constraint function.
|
|
99
|
+
* @ignore
|
|
100
|
+
*/
|
|
101
|
+
function _validateTestConstraintFunction<Value, Parent>(
|
|
102
|
+
context: InternalValidationContext<NonNullable<Value>, Parent>,
|
|
103
|
+
test: TestConstraintFunction<Value, Parent>
|
|
104
|
+
) {
|
|
105
|
+
let constraint: any = undefined
|
|
106
|
+
let message: any = undefined
|
|
107
|
+
let level: Level | undefined = undefined
|
|
108
|
+
|
|
109
|
+
constraint = test(context)
|
|
110
|
+
|
|
111
|
+
if (Array.isArray(constraint)) {
|
|
112
|
+
const [maybeConstraint, maybeMessage, maybeLevel] = constraint
|
|
113
|
+
constraint = maybeConstraint
|
|
114
|
+
message = maybeMessage
|
|
115
|
+
level = maybeLevel ?? undefined
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (constraint == null || constraint === true)
|
|
119
|
+
return true
|
|
120
|
+
return context.setStatus("test", false, typeof constraint === "string" ? constraint : message, level ?? "error") == null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Internal helper to validate an asynchronous test constraint.
|
|
125
|
+
* @ignore
|
|
126
|
+
*/
|
|
127
|
+
function _validateAsyncTestConstraint<Value, Parent>(
|
|
128
|
+
context: InternalValidationContext<NonNullable<Value>, Parent>,
|
|
129
|
+
test: AsyncTestConstraint<Value, Parent>
|
|
130
|
+
) {
|
|
131
|
+
if (context.settings?.skipAsync)
|
|
132
|
+
return true
|
|
133
|
+
|
|
134
|
+
const getDependencies = test.dependencies ?? defaultGetDependencies
|
|
135
|
+
const dependencies = [context.value].concat(getDependencies(context))
|
|
136
|
+
|
|
137
|
+
const path = joinPath(context.path)
|
|
138
|
+
let asyncStatus = context.yop.asyncStatuses.get(path)
|
|
139
|
+
if (asyncStatus != null) {
|
|
140
|
+
const previousDependencies = asyncStatus.dependencies
|
|
141
|
+
asyncStatus.dependencies = dependencies
|
|
142
|
+
const shouldRevalidate = test.revalidate ?? defaultShouldRevalidate
|
|
143
|
+
if (!shouldRevalidate(context, previousDependencies, dependencies, asyncStatus.status)) {
|
|
144
|
+
if (asyncStatus.status != null) {
|
|
145
|
+
context.statuses.set(path, asyncStatus.status)
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
return true
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
asyncStatus = { dependencies }
|
|
153
|
+
|
|
154
|
+
const promise = test.promise(context)
|
|
155
|
+
.then(message => {
|
|
156
|
+
if (message == null || message === true)
|
|
157
|
+
asyncStatus.status = undefined
|
|
158
|
+
else if (message === false)
|
|
159
|
+
asyncStatus.status = context.createStatus("test", false)
|
|
160
|
+
else if (!Array.isArray(message))
|
|
161
|
+
asyncStatus.status = context.createStatus("test", false, message as ConstraintMessage)
|
|
162
|
+
else {
|
|
163
|
+
const [maybeMessage, maybeLevel] = message as readonly [ConstraintMessage, Level]
|
|
164
|
+
asyncStatus.status = context.createStatus("test", false, maybeMessage, maybeLevel ?? "error")
|
|
165
|
+
}
|
|
166
|
+
return asyncStatus.status
|
|
167
|
+
})
|
|
168
|
+
.catch(error => {
|
|
169
|
+
asyncStatus.status = context.createStatus("test", false, error != null ? String(error) : test.unavailableMessage, "unavailable")
|
|
170
|
+
return Promise.resolve(asyncStatus.status)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
asyncStatus.status = context.setStatus("test", promise, test.pendingMessage, "pending")
|
|
174
|
+
context.yop.asyncStatuses.set(path, asyncStatus)
|
|
175
|
+
return false
|
|
176
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { CommonConstraints, InternalCommonConstraints, validateTypeConstraint } from "../constraints/CommonConstraints"
|
|
2
|
+
import { MinMaxConstraints, validateMinMaxConstraints } from "../constraints/MinMaxConstraints"
|
|
3
|
+
import { TestConstraint, validateTestConstraint } from "../constraints/TestConstraint"
|
|
4
|
+
import { fieldValidationDecorator, getValidationDecoratorKind, Groups, InternalClassConstraints } from "../Metadata"
|
|
5
|
+
import { defineLazyProperty } from "../ObjectsUtil"
|
|
6
|
+
import { ArrayElementType, Constructor, isNumber } from "../TypesUtil"
|
|
7
|
+
import { InternalValidationContext } from "../ValidationContext"
|
|
8
|
+
import { validationSymbol, Yop } from "../Yop"
|
|
9
|
+
import { id } from "./id"
|
|
10
|
+
import { string } from "./string"
|
|
11
|
+
import { number } from "./number"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Type for an array value, which can be an array, null, or undefined.
|
|
15
|
+
* @ignore
|
|
16
|
+
*/
|
|
17
|
+
export type ArrayValue = any[] | null | undefined
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Interface for array field constraints, combining common, min/max, test, and element type constraints.
|
|
21
|
+
* @template Value - The type of the array value.
|
|
22
|
+
* @template Parent - The type of the parent object.
|
|
23
|
+
* @see {@link id}
|
|
24
|
+
* @see {@link CommonConstraints}
|
|
25
|
+
* @see {@link MinMaxConstraints}
|
|
26
|
+
* @see {@link TestConstraint}
|
|
27
|
+
* @category Property Decorators
|
|
28
|
+
*/
|
|
29
|
+
export interface ArrayConstraints<Value extends ArrayValue, Parent> extends
|
|
30
|
+
CommonConstraints<Value, Parent>,
|
|
31
|
+
MinMaxConstraints<Value, number, Parent>,
|
|
32
|
+
TestConstraint<Value, Parent> {
|
|
33
|
+
/**
|
|
34
|
+
* A class constructor, a function returning a constructor, a validation decorator, or a class id (see {@link id}) to
|
|
35
|
+
* validate each array element against.
|
|
36
|
+
* @see {@link array}
|
|
37
|
+
*/
|
|
38
|
+
of: (
|
|
39
|
+
Constructor<ArrayElementType<Value>> |
|
|
40
|
+
(() => Constructor<ArrayElementType<Value>>) |
|
|
41
|
+
((_: any, context: ClassFieldDecoratorContext<Value, ArrayElementType<Value>>) => void) |
|
|
42
|
+
string
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Traverses an array field to retrieve element constraints and value at a given index or property.
|
|
48
|
+
* @template Value - The type of the array value.
|
|
49
|
+
* @template Parent - The type of the parent object.
|
|
50
|
+
* @param context - The validation context for the array.
|
|
51
|
+
* @param constraints - The array constraints.
|
|
52
|
+
* @param propertyOrIndex - The property name or array index to traverse.
|
|
53
|
+
* @param traverseNullish - If true, traverses only if value is not nullish; otherwise, returns undefined for nullish values.
|
|
54
|
+
* @returns A tuple of the element constraints (if any) and the value at the given index/property.
|
|
55
|
+
* @ignore
|
|
56
|
+
*/
|
|
57
|
+
function traverseArray<Value extends ArrayValue, Parent>(
|
|
58
|
+
context: InternalValidationContext<Value, Parent>,
|
|
59
|
+
constraints: ArrayConstraints<Value, Parent>,
|
|
60
|
+
propertyOrIndex: string | number,
|
|
61
|
+
traverseNullish?: boolean
|
|
62
|
+
): readonly [InternalCommonConstraints | undefined, any] {
|
|
63
|
+
if (traverseNullish ? context.value != null && (!Array.isArray(context.value) || typeof propertyOrIndex !== "number") : context.value == null)
|
|
64
|
+
return [undefined, undefined]
|
|
65
|
+
const of = constraints.of as any
|
|
66
|
+
const elementConstraints = of?.[Symbol.metadata]?.[validationSymbol]
|
|
67
|
+
return [elementConstraints, context.value?.[propertyOrIndex as number]]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Validates an array field against its constraints, including type, min/max length, and element validation.
|
|
72
|
+
* @template Value - The type of the array value.
|
|
73
|
+
* @template Parent - The type of the parent object.
|
|
74
|
+
* @param context - The validation context for the array.
|
|
75
|
+
* @param constraints - The array constraints to validate.
|
|
76
|
+
* @returns True if all constraints pass, false otherwise.
|
|
77
|
+
* @ignore
|
|
78
|
+
*/
|
|
79
|
+
function validateArray<Value extends ArrayValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: ArrayConstraints<Value, Parent>) {
|
|
80
|
+
if (!validateTypeConstraint(context, Array.isArray, "array") ||
|
|
81
|
+
!validateMinMaxConstraints(context, constraints, isNumber, (value, min) => value.length >= min, (value, max) => value.length <= max) ||
|
|
82
|
+
constraints.of == null)
|
|
83
|
+
return false
|
|
84
|
+
|
|
85
|
+
let valid = true
|
|
86
|
+
|
|
87
|
+
const elementConstraints = (constraints.of as any)[Symbol.metadata]?.[validationSymbol] as InternalCommonConstraints | undefined
|
|
88
|
+
if (elementConstraints != null) {
|
|
89
|
+
for (const [index, element] of context.value!.entries()) {
|
|
90
|
+
const elementContext = context.createChildContext({
|
|
91
|
+
kind: elementConstraints.kind,
|
|
92
|
+
value: element,
|
|
93
|
+
key: index,
|
|
94
|
+
})
|
|
95
|
+
valid = elementConstraints.validate(elementContext, elementConstraints) && valid
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return valid && validateTestConstraint(context, constraints)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Constant representing the kind of array validation decorator.
|
|
104
|
+
* @ignore
|
|
105
|
+
*/
|
|
106
|
+
export const arrayKind = "array"
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Decorator for validating a field value as an array of a specified element type. The `of` property can be set to a custom
|
|
110
|
+
* class constructor, or a validation decorator such as {@link string}, {@link number}, etc. Using a validation decorator allows
|
|
111
|
+
* applying constraints to each array element.
|
|
112
|
+
*
|
|
113
|
+
* Example usage:
|
|
114
|
+
* ```tsx
|
|
115
|
+
* class Person {
|
|
116
|
+
*
|
|
117
|
+
* // the array itself must not be null, but its elements may be null
|
|
118
|
+
* @array({ of: Dog, required: true })
|
|
119
|
+
* dogs: Dog[] | null = null
|
|
120
|
+
*
|
|
121
|
+
* // the array must not be null, and all its elements must also be non-null
|
|
122
|
+
* @array({ of: instance({ of: Cat, required: true }), required: true })
|
|
123
|
+
* cats: Cat[] | null = null
|
|
124
|
+
*
|
|
125
|
+
* // a non-empty array of strings with at least 5 characters each
|
|
126
|
+
* @array({ of: string({ required: true, min: 5 }), required: true, min: 1 })
|
|
127
|
+
* names: string[] | null = null
|
|
128
|
+
* }
|
|
129
|
+
* const form = useForm(Person, ...)
|
|
130
|
+
*
|
|
131
|
+
* // the array decorator can also be used as a function to allow standalone validation:
|
|
132
|
+
* Yop.validate([], array({ of: Dog, min: 1 })) // error: "At least 1 element"
|
|
133
|
+
* ```
|
|
134
|
+
*
|
|
135
|
+
* @template Value - The type of the array value.
|
|
136
|
+
* @template Parent - The type of the parent object.
|
|
137
|
+
* @param constraints - The array constraints to apply.
|
|
138
|
+
* @param groups - Optional validation groups.
|
|
139
|
+
* @returns A field decorator function with validation.
|
|
140
|
+
* @category Property Decorators
|
|
141
|
+
*/
|
|
142
|
+
export function array<Value extends ArrayValue, Parent>(constraints?: ArrayConstraints<Value, Parent>, groups?: Groups<ArrayConstraints<Value, Parent>>) {
|
|
143
|
+
if (getValidationDecoratorKind(constraints?.of) != null) {
|
|
144
|
+
const of = constraints!.of as ((_: any, context: Partial<ClassFieldDecoratorContext<Value, ArrayElementType<Value>>>) => void)
|
|
145
|
+
defineLazyProperty(constraints, "of", (_this) => {
|
|
146
|
+
const metadata = { [validationSymbol]: {} as InternalClassConstraints }
|
|
147
|
+
of(null, { metadata, name: "of" })
|
|
148
|
+
return { [Symbol.metadata]: { [validationSymbol]: metadata[validationSymbol]!.fields!.of }}
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
else if (typeof constraints?.of === "string" || (typeof constraints?.of === "function" && constraints.of.prototype == null)) {
|
|
152
|
+
const of = constraints!.of
|
|
153
|
+
defineLazyProperty(constraints, "of", (_this) => Yop.resolveClass(of))
|
|
154
|
+
}
|
|
155
|
+
return fieldValidationDecorator(arrayKind, constraints ?? ({} as ArrayConstraints<Value, Parent>), groups, validateArray, isNumber, traverseArray)
|
|
156
|
+
}
|
|
157
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { CommonConstraints, validateTypeConstraint } from "../constraints/CommonConstraints"
|
|
2
|
+
import { OneOfConstraint, validateOneOfConstraint } from "../constraints/OneOfConstraint"
|
|
3
|
+
import { TestConstraint, validateTestConstraint } from "../constraints/TestConstraint"
|
|
4
|
+
import { isBoolean, isBooleanArray } from "../TypesUtil"
|
|
5
|
+
import { InternalValidationContext } from "../ValidationContext"
|
|
6
|
+
import { fieldValidationDecorator, Groups } from "../Metadata"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Type for a boolean value, which can be true, false, null, or undefined.
|
|
10
|
+
* @ignore
|
|
11
|
+
*/
|
|
12
|
+
export type BooleanValue = boolean | null | undefined
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Interface for boolean field constraints, combining common, oneOf, and test constraints.
|
|
16
|
+
* @template Value - The type of the boolean value.
|
|
17
|
+
* @template Parent - The type of the parent object.
|
|
18
|
+
* @see {@link CommonConstraints}
|
|
19
|
+
* @see {@link OneOfConstraint}
|
|
20
|
+
* @see {@link TestConstraint}
|
|
21
|
+
* @category Property Decorators
|
|
22
|
+
*/
|
|
23
|
+
export interface BooleanConstraints<Value extends BooleanValue, Parent> extends
|
|
24
|
+
CommonConstraints<Value, Parent>,
|
|
25
|
+
OneOfConstraint<Value, Parent>,
|
|
26
|
+
TestConstraint<Value, Parent> {
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validates a boolean field against its constraints.
|
|
31
|
+
* @template Value - The type of the boolean value.
|
|
32
|
+
* @template Parent - The type of the parent object.
|
|
33
|
+
* @param context - The validation context.
|
|
34
|
+
* @param constraints - The boolean constraints to validate.
|
|
35
|
+
* @returns True if all constraints pass, false otherwise.
|
|
36
|
+
* @ignore
|
|
37
|
+
*/
|
|
38
|
+
function validateBoolean<Value extends BooleanValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: BooleanConstraints<Value, Parent>) {
|
|
39
|
+
return (
|
|
40
|
+
validateTypeConstraint(context, isBoolean, "boolean") &&
|
|
41
|
+
validateOneOfConstraint(context, constraints, isBooleanArray) &&
|
|
42
|
+
validateTestConstraint(context, constraints)
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Decorator for applying validation rules to a boolean field.
|
|
48
|
+
*
|
|
49
|
+
* Example usage:
|
|
50
|
+
* ```tsx
|
|
51
|
+
* class Person {
|
|
52
|
+
* @boolean({ required: true, oneOf: [[true], "Must be an adult"] })
|
|
53
|
+
* adult: boolean | null = null
|
|
54
|
+
* }
|
|
55
|
+
* const form = useForm(Person, ...)
|
|
56
|
+
*
|
|
57
|
+
* // the boolean decorator can also be used as a function to allow standalone validation:
|
|
58
|
+
* Yop.validate(false, boolean({ oneOf: [true] })) // error: "Must be one of: true"
|
|
59
|
+
* ```
|
|
60
|
+
* @template Value - The type of the boolean value.
|
|
61
|
+
* @template Parent - The type of the parent object.
|
|
62
|
+
* @param constraints - The boolean constraints to apply.
|
|
63
|
+
* @param groups - Optional validation groups.
|
|
64
|
+
* @returns A field decorator function with validation.
|
|
65
|
+
* @category Property Decorators
|
|
66
|
+
*/
|
|
67
|
+
export function boolean<Value extends BooleanValue, Parent>(constraints?: BooleanConstraints<Value, Parent>, groups?: Groups<BooleanConstraints<Value, Parent>>) {
|
|
68
|
+
return fieldValidationDecorator("boolean", constraints ?? {}, groups, validateBoolean)
|
|
69
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { CommonConstraints, validateTypeConstraint } from "../constraints/CommonConstraints"
|
|
2
|
+
import { MinMaxConstraints, validateMinMaxConstraints } from "../constraints/MinMaxConstraints"
|
|
3
|
+
import { OneOfConstraint, validateOneOfConstraint } from "../constraints/OneOfConstraint"
|
|
4
|
+
import { TestConstraint, validateTestConstraint } from "../constraints/TestConstraint"
|
|
5
|
+
import { isDate, isDateArray } from "../TypesUtil"
|
|
6
|
+
import { InternalValidationContext } from "../ValidationContext"
|
|
7
|
+
import { fieldValidationDecorator, Groups } from "../Metadata"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Type for a date value, which can be a Date object, null, or undefined.
|
|
11
|
+
* @ignore
|
|
12
|
+
*/
|
|
13
|
+
export type DateValue = Date | null | undefined
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Interface for date field constraints, combining common, min/max, oneOf, and test constraints.
|
|
17
|
+
* @template Value - The type of the date value.
|
|
18
|
+
* @template Parent - The type of the parent object.
|
|
19
|
+
* @see {@link CommonConstraints}
|
|
20
|
+
* @see {@link MinMaxConstraints}
|
|
21
|
+
* @see {@link OneOfConstraint}
|
|
22
|
+
* @see {@link TestConstraint}
|
|
23
|
+
* @category Property Decorators
|
|
24
|
+
*/
|
|
25
|
+
export interface DateConstraints<Value extends DateValue, Parent> extends
|
|
26
|
+
CommonConstraints<Value, Parent>,
|
|
27
|
+
MinMaxConstraints<Value, Date, Parent>,
|
|
28
|
+
OneOfConstraint<Value, Parent>,
|
|
29
|
+
TestConstraint<Value, Parent> {
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Validates a date field against its constraints.
|
|
34
|
+
* @template Value - The type of the date value.
|
|
35
|
+
* @template Parent - The type of the parent object.
|
|
36
|
+
* @param context - The validation context.
|
|
37
|
+
* @param constraints - The date constraints to validate.
|
|
38
|
+
* @returns True if all constraints pass, false otherwise.
|
|
39
|
+
* @ignore
|
|
40
|
+
*/
|
|
41
|
+
function validateDate<Value extends DateValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: DateConstraints<Value, Parent>) {
|
|
42
|
+
return (
|
|
43
|
+
validateTypeConstraint(context, isDate, "date") &&
|
|
44
|
+
validateMinMaxConstraints(context, constraints, isDate, (value, min) => value >= min, (value, max) => value <= max) &&
|
|
45
|
+
validateOneOfConstraint(context, constraints, isDateArray, (date1, date2) => date1.getTime() === date2.getTime()) &&
|
|
46
|
+
validateTestConstraint(context, constraints)
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Decorator for applying validation rules to a Date field.
|
|
52
|
+
*
|
|
53
|
+
* Example usage:
|
|
54
|
+
* ```tsx
|
|
55
|
+
* class Person {
|
|
56
|
+
* @date({ required: true, min: new Date("1900-01-01") })
|
|
57
|
+
* dateOfBirth: Date | null = null
|
|
58
|
+
* }
|
|
59
|
+
* const form = useForm(Person, ...)
|
|
60
|
+
*
|
|
61
|
+
* // the date decorator can also be used as a function to allow standalone validation:
|
|
62
|
+
* Yop.validate(null, date({ required: true })) // error: "Required field"
|
|
63
|
+
* ```
|
|
64
|
+
* @template Value - The type of the date value.
|
|
65
|
+
* @template Parent - The type of the parent object.
|
|
66
|
+
* @param constraints - The date constraints to apply.
|
|
67
|
+
* @param groups - Optional validation groups.
|
|
68
|
+
* @returns A field decorator function with validation.
|
|
69
|
+
* @category Property Decorators
|
|
70
|
+
*/
|
|
71
|
+
export function date<Value extends DateValue, Parent>(constraints?: DateConstraints<Value, Parent>, groups?: Groups<DateConstraints<Value, Parent>>) {
|
|
72
|
+
return fieldValidationDecorator("date", constraints ?? {}, groups, validateDate, isDate)
|
|
73
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Message } from "../constraints/Constraint"
|
|
2
|
+
import { InternalValidationContext } from "../ValidationContext"
|
|
3
|
+
import { fieldValidationDecorator, Groups } from "../Metadata"
|
|
4
|
+
import { StringConstraints, StringValue, validateString } from "./string"
|
|
5
|
+
import { isNumber } from "../TypesUtil"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Interface for email field constraints, extending string constraints but omitting 'match'.
|
|
9
|
+
* @template Value - The type of the string value.
|
|
10
|
+
* @template Parent - The type of the parent object.
|
|
11
|
+
* @property formatError - Custom error message for invalid email format.
|
|
12
|
+
* @see {@link StringConstraints}
|
|
13
|
+
* @category Property Decorators
|
|
14
|
+
*/
|
|
15
|
+
export interface EmailConstraints<Value extends StringValue, Parent> extends
|
|
16
|
+
Omit<StringConstraints<Value, Parent>, "match"> {
|
|
17
|
+
/**
|
|
18
|
+
* Custom error message for invalid email format. `formatError` can be a {@link Message} or a function that returns a {@link Message}.
|
|
19
|
+
* @see {@link emailRegex}
|
|
20
|
+
*/
|
|
21
|
+
formatError?: Message<Value, Parent>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Regular expression for validating email addresses (RFC 5322 compliant).
|
|
26
|
+
* @ignore
|
|
27
|
+
*/
|
|
28
|
+
export const emailRegex = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Validates an email field against its constraints and the email regex.
|
|
32
|
+
* @template Value - The type of the string value.
|
|
33
|
+
* @template Parent - The type of the parent object.
|
|
34
|
+
* @param context - The validation context.
|
|
35
|
+
* @param constraints - The email constraints to validate.
|
|
36
|
+
* @returns True if the value is a valid email, false otherwise.
|
|
37
|
+
* @ignore
|
|
38
|
+
*/
|
|
39
|
+
export function validateEmail<Value extends StringValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: EmailConstraints<Value, Parent>) {
|
|
40
|
+
return validateString(context, constraints, emailRegex, constraints.formatError, "email")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Decorator for applying validation rules to an email field. Emails are validated against a standard email regex pattern (RFC 5322 compliant).
|
|
45
|
+
*
|
|
46
|
+
* Example usage:
|
|
47
|
+
* ```tsx
|
|
48
|
+
* class Person {
|
|
49
|
+
* @email({ required: true, formatError: "Invalid email address" })
|
|
50
|
+
* email: string | null = null
|
|
51
|
+
* }
|
|
52
|
+
* const form = useForm(Person, ...)
|
|
53
|
+
*
|
|
54
|
+
* // the email decorator can also be used as a function to allow standalone validation:
|
|
55
|
+
* Yop.validate(null, email({ required: true })) // error: "Required field"
|
|
56
|
+
* ```
|
|
57
|
+
* @template Value - The type of the string value.
|
|
58
|
+
* @template Parent - The type of the parent object.
|
|
59
|
+
* @param constraints - The email constraints to apply.
|
|
60
|
+
* @param groups - Optional validation groups.
|
|
61
|
+
* @returns A field decorator function with validation.
|
|
62
|
+
* @category Property Decorators
|
|
63
|
+
*/
|
|
64
|
+
export function email<Value extends StringValue, Parent>(constraints?: EmailConstraints<Value, Parent>, groups?: Groups<EmailConstraints<Value, Parent>>) {
|
|
65
|
+
return fieldValidationDecorator("email", constraints ?? {}, groups, validateEmail, isNumber)
|
|
66
|
+
}
|