@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,69 @@
|
|
|
1
|
+
import { CommonConstraints, validateTypeConstraint } from "../constraints/CommonConstraints"
|
|
2
|
+
import { MinMaxConstraints, validateMinMaxConstraints } from "../constraints/MinMaxConstraints"
|
|
3
|
+
import { TestConstraint, validateTestConstraint } from "../constraints/TestConstraint"
|
|
4
|
+
import { isFile, isNumber } from "../TypesUtil"
|
|
5
|
+
import { InternalValidationContext } from "../ValidationContext"
|
|
6
|
+
import { fieldValidationDecorator, Groups } from "../Metadata"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Type for a file value, which can be a File object, null, or undefined.
|
|
10
|
+
* @ignore
|
|
11
|
+
*/
|
|
12
|
+
export type FileValue = File | null | undefined
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Interface for file field constraints, combining common, min/max, and test constraints.
|
|
16
|
+
* @template Value - The type of the file value.
|
|
17
|
+
* @template Parent - The type of the parent object.
|
|
18
|
+
* @see {@link CommonConstraints}
|
|
19
|
+
* @see {@link MinMaxConstraints}
|
|
20
|
+
* @see {@link TestConstraint}
|
|
21
|
+
* @category Property Decorators
|
|
22
|
+
*/
|
|
23
|
+
export interface FileConstraints<Value extends FileValue, Parent> extends
|
|
24
|
+
CommonConstraints<Value, Parent>,
|
|
25
|
+
MinMaxConstraints<Value, number, Parent>,
|
|
26
|
+
TestConstraint<Value, Parent> {
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validates a file field against its constraints.
|
|
31
|
+
* @template Value - The type of the file value.
|
|
32
|
+
* @template Parent - The type of the parent object.
|
|
33
|
+
* @param context - The validation context.
|
|
34
|
+
* @param constraints - The file constraints to validate.
|
|
35
|
+
* @returns True if all constraints pass, false otherwise.
|
|
36
|
+
* @ignore
|
|
37
|
+
*/
|
|
38
|
+
function validateFile<Value extends FileValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: FileConstraints<Value, Parent>) {
|
|
39
|
+
return (
|
|
40
|
+
validateTypeConstraint(context, isFile, "file") &&
|
|
41
|
+
validateMinMaxConstraints(context, constraints, isNumber, (value, min) => value.size >= min, (value, max) => value.size <= max) &&
|
|
42
|
+
validateTestConstraint(context, constraints)
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Decorator for applying validation rules to a File field.
|
|
48
|
+
*
|
|
49
|
+
* Example usage:
|
|
50
|
+
* ```tsx
|
|
51
|
+
* class Person {
|
|
52
|
+
* @file({ required: true, min: [1024, "Picture size must be at least 1KB"] })
|
|
53
|
+
* profilePicture: File | null = null
|
|
54
|
+
* }
|
|
55
|
+
* const form = useForm(Person, ...)
|
|
56
|
+
*
|
|
57
|
+
* // the file decorator can also be used as a function to allow standalone validation:
|
|
58
|
+
* Yop.validate(null, file({ required: true })) // error: "Required field"
|
|
59
|
+
* ```
|
|
60
|
+
* @template Value - The type of the file value.
|
|
61
|
+
* @template Parent - The type of the parent object.
|
|
62
|
+
* @param constraints - The file 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 file<Value extends FileValue, Parent>(constraints?: FileConstraints<Value, Parent>, groups?: Groups<FileConstraints<Value, Parent>>) {
|
|
68
|
+
return fieldValidationDecorator("file", constraints ?? {}, groups, validateFile, isNumber)
|
|
69
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Constructor } from "../TypesUtil"
|
|
2
|
+
import { Yop } from "../Yop"
|
|
3
|
+
import { ArrayConstraints } from "./array"
|
|
4
|
+
import { InstanceConstraints } from "./instance"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Class decorator to register a class with a unique identifier in the Yop registry. It can be used when you need to reference
|
|
8
|
+
* classes by an identifier to prevent circular references.
|
|
9
|
+
*
|
|
10
|
+
* Example usage:
|
|
11
|
+
* ```tsx
|
|
12
|
+
* @id("Person")
|
|
13
|
+
* class Person {
|
|
14
|
+
*
|
|
15
|
+
* @instance({ of: "Person" }) // circular reference to itself using the class id
|
|
16
|
+
* friend: Person | null = null
|
|
17
|
+
*
|
|
18
|
+
* @array({ of: "Person" }) // circular reference to itself using the class id
|
|
19
|
+
* friends: Person[] | null = null
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @template Type - The type of the class instance.
|
|
24
|
+
* @template Class - The constructor type of the class.
|
|
25
|
+
* @param id - The unique identifier for the class.
|
|
26
|
+
* @returns A class decorator function that registers the class in the Yop registry.
|
|
27
|
+
* @see {@link InstanceConstraints}
|
|
28
|
+
* @see {@link ArrayConstraints}
|
|
29
|
+
* @category Class Decorators
|
|
30
|
+
*/
|
|
31
|
+
export function id<Type extends object, Class extends Constructor<Type>>(id: string) {
|
|
32
|
+
return function decorateClass(target: Class, _: ClassDecoratorContext<Class>) {
|
|
33
|
+
Yop.registerClass(id, target)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { InternalCommonConstraints } from "../constraints/CommonConstraints"
|
|
2
|
+
import { Constraint } from "../constraints/Constraint"
|
|
3
|
+
import { fieldDecorator, Groups } from "../Metadata"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Field decorator to mark a field as ignored for validation.
|
|
7
|
+
*
|
|
8
|
+
* Example usage:
|
|
9
|
+
* ```tsx
|
|
10
|
+
* class Person {
|
|
11
|
+
* @string({ required: true, match: /^[A-Za-z]+$/ })
|
|
12
|
+
* name: string | null = null
|
|
13
|
+
* }
|
|
14
|
+
* class Anonymous extends Person {
|
|
15
|
+
* @ignored() // ignore all validation constraints on `name`
|
|
16
|
+
* override name: string | null = null
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @template Parent - The type of the parent object.
|
|
21
|
+
* @param ignored - The constraint or boolean indicating if the field should be ignored (default: true).
|
|
22
|
+
* @param groups - Optional groups with their own ignored constraints.
|
|
23
|
+
* @returns A field decorator function that marks a field as ignored for validation.
|
|
24
|
+
* @category Property Decorators
|
|
25
|
+
*/
|
|
26
|
+
export function ignored<Parent>(ignored: Constraint<any, boolean, Parent> = true, groups?: Groups<Constraint<any, boolean, Parent>>) {
|
|
27
|
+
return fieldDecorator<Parent, any>(field => {
|
|
28
|
+
field.ignored = ignored
|
|
29
|
+
|
|
30
|
+
if (groups != null) {
|
|
31
|
+
field.groups ??= {}
|
|
32
|
+
for (const [name, constraint] of Object.entries(groups)) {
|
|
33
|
+
if (field.groups?.[name] != null)
|
|
34
|
+
field.groups[name].ignored = constraint
|
|
35
|
+
else
|
|
36
|
+
field.groups[name] = { ignored: constraint } as InternalCommonConstraints
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { CommonConstraints, InternalCommonConstraints, validateTypeConstraint } from "../constraints/CommonConstraints"
|
|
2
|
+
import { TestConstraint, validateTestConstraint } from "../constraints/TestConstraint"
|
|
3
|
+
import { fieldValidationDecorator, Groups, InternalClassConstraints, validateClass } from "../Metadata"
|
|
4
|
+
import { defineLazyProperty } from "../ObjectsUtil"
|
|
5
|
+
import { ClassConstructor, isObject } from "../TypesUtil"
|
|
6
|
+
import { InternalValidationContext } from "../ValidationContext"
|
|
7
|
+
import { validationSymbol, Yop } from "../Yop"
|
|
8
|
+
import { id } from "./id"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Type for values that can be validated as instances (objects, null, or undefined).
|
|
12
|
+
* @ignore
|
|
13
|
+
*/
|
|
14
|
+
export type InstanceValue = object | null | undefined
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Constraints for validating an instance of a class. Inherits common and test constraints.
|
|
19
|
+
* @template Value - The type of the instance value.
|
|
20
|
+
* @template Parent - The type of the parent object.
|
|
21
|
+
* @property of - A class constructor, a function returning a constructor, or a class id to validate the instance against.
|
|
22
|
+
* @see {@link id}
|
|
23
|
+
* @see {@link CommonConstraints}
|
|
24
|
+
* @see {@link TestConstraint}
|
|
25
|
+
* @category Property Decorators
|
|
26
|
+
*/
|
|
27
|
+
export interface InstanceConstraints<Value extends InstanceValue, Parent> extends
|
|
28
|
+
CommonConstraints<Value, Parent>,
|
|
29
|
+
TestConstraint<Value, Parent> {
|
|
30
|
+
/**
|
|
31
|
+
* A class constructor, a function returning a constructor, or a class id (see {@link id}) to validate the instance against.
|
|
32
|
+
*/
|
|
33
|
+
of: ClassConstructor<NoInfer<Value>> | (() => ClassConstructor<NoInfer<Value>>) | string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Traverses the constraints of a class instance for validation.
|
|
38
|
+
* @param context - The validation context.
|
|
39
|
+
* @param constraints - The instance constraints.
|
|
40
|
+
* @param key - The property key to traverse.
|
|
41
|
+
* @param traverseNullish - Whether to traverse nullish values.
|
|
42
|
+
* @returns A tuple of the internal constraints and the value.
|
|
43
|
+
* @ignore
|
|
44
|
+
*/
|
|
45
|
+
function traverseInstance<Value extends InstanceValue, Parent>(
|
|
46
|
+
context: InternalValidationContext<Value, Parent>,
|
|
47
|
+
constraints: InstanceConstraints<Value, Parent>,
|
|
48
|
+
key: string | number,
|
|
49
|
+
traverseNullish?: boolean
|
|
50
|
+
): readonly [InternalCommonConstraints | undefined, any] {
|
|
51
|
+
if (constraints.of == null)
|
|
52
|
+
return [undefined, undefined] as const
|
|
53
|
+
const classConstraints = (constraints.of as any)[Symbol.metadata]?.[validationSymbol] as InternalClassConstraints | undefined
|
|
54
|
+
if (classConstraints == null)
|
|
55
|
+
return [undefined, undefined] as const
|
|
56
|
+
return classConstraints.traverse!(context as InternalValidationContext<never, never>, classConstraints, key, traverseNullish)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Validates that a value is an instance of the specified class and meets all constraints.
|
|
61
|
+
* @param context - The validation context.
|
|
62
|
+
* @param constraints - The instance constraints.
|
|
63
|
+
* @returns True if valid, false otherwise.
|
|
64
|
+
* @ignore
|
|
65
|
+
*/
|
|
66
|
+
function validateInstance<Value extends InstanceValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: InstanceConstraints<Value, Parent>) {
|
|
67
|
+
if (!validateTypeConstraint(context, isObject, "object") ||
|
|
68
|
+
!validateTestConstraint(context, constraints) ||
|
|
69
|
+
constraints.of == null)
|
|
70
|
+
return false
|
|
71
|
+
|
|
72
|
+
const classConstraints = (constraints.of as any)[Symbol.metadata]?.[validationSymbol] as InternalClassConstraints | undefined
|
|
73
|
+
return classConstraints == null || validateClass(context as InternalValidationContext<{ [x: string]: any }>, classConstraints)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The kind string used to identify instance constraints.
|
|
78
|
+
* @ignore
|
|
79
|
+
*/
|
|
80
|
+
export const instanceKind = "instance"
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Decorator for validating a field value as an instance of a specified class. The `of` property must be set to a custom
|
|
84
|
+
* class constructor, not a built-in object type like String or Date.
|
|
85
|
+
*
|
|
86
|
+
* Example usage:
|
|
87
|
+
* ```tsx
|
|
88
|
+
* class Person {
|
|
89
|
+
* @instance({ of: Dog, required: true })
|
|
90
|
+
* dog: Dog | null = null
|
|
91
|
+
* }
|
|
92
|
+
* const form = useForm(Person, ...)
|
|
93
|
+
*
|
|
94
|
+
* // the instance decorator can also be used as a function to allow standalone validation:
|
|
95
|
+
* Yop.validate(new Person(), instance({ of: Person })) // error: dog is a "Required field"
|
|
96
|
+
* ```
|
|
97
|
+
* @template Value - The type of the instance value.
|
|
98
|
+
* @template Parent - The type of the parent object.
|
|
99
|
+
* @param constraints - The instance constraints.
|
|
100
|
+
* @param groups - Optional validation groups.
|
|
101
|
+
* @returns A field decorator that stores the instance constraints and validation function in the class metadata.
|
|
102
|
+
* @category Property Decorators
|
|
103
|
+
*/
|
|
104
|
+
export function instance<Value extends InstanceValue, Parent>(constraints?: InstanceConstraints<Value, Parent>, groups?: Groups<InstanceConstraints<Value, Parent>>) {
|
|
105
|
+
if (typeof constraints?.of === "string" || (typeof constraints?.of === "function" && constraints.of.prototype == null)) {
|
|
106
|
+
const of = constraints.of
|
|
107
|
+
defineLazyProperty(constraints, "of", (_this) => Yop.resolveClass(of))
|
|
108
|
+
}
|
|
109
|
+
return fieldValidationDecorator(instanceKind, constraints ?? ({} as InstanceConstraints<Value, Parent>), groups, validateInstance, undefined, traverseInstance)
|
|
110
|
+
}
|
|
@@ -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 { isNumber, isNumberArray } from "../TypesUtil"
|
|
6
|
+
import { InternalValidationContext } from "../ValidationContext"
|
|
7
|
+
import { fieldValidationDecorator, Groups } from "../Metadata"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Type for a number value, which can be a number, null, or undefined.
|
|
11
|
+
* @ignore
|
|
12
|
+
*/
|
|
13
|
+
export type NumberValue = number | null | undefined
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Interface for number field constraints, combining common, min/max, oneOf, and test constraints.
|
|
17
|
+
* @template Value - The type of the number 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 NumberConstraints<Value extends NumberValue, Parent> extends
|
|
26
|
+
CommonConstraints<Value, Parent>,
|
|
27
|
+
MinMaxConstraints<Value, number, Parent>,
|
|
28
|
+
OneOfConstraint<Value, Parent>,
|
|
29
|
+
TestConstraint<Value, Parent> {
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Validates a number field against its constraints.
|
|
34
|
+
* @template Value - The type of the number value.
|
|
35
|
+
* @template Parent - The type of the parent object.
|
|
36
|
+
* @param context - The validation context.
|
|
37
|
+
* @param constraints - The number constraints to validate.
|
|
38
|
+
* @returns True if all constraints pass, false otherwise.
|
|
39
|
+
* @ignore
|
|
40
|
+
*/
|
|
41
|
+
function validateNumber<Value extends NumberValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: NumberConstraints<Value, Parent>) {
|
|
42
|
+
return (
|
|
43
|
+
validateTypeConstraint(context, isNumber, "number") &&
|
|
44
|
+
validateMinMaxConstraints(context, constraints, isNumber, (value, min) => value >= min, (value, max) => value <= max) &&
|
|
45
|
+
validateOneOfConstraint(context, constraints, isNumberArray) &&
|
|
46
|
+
validateTestConstraint(context, constraints)
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Decorator for applying validation rules to a number field.
|
|
52
|
+
*
|
|
53
|
+
* Example usage:
|
|
54
|
+
* ```tsx
|
|
55
|
+
* class Person {
|
|
56
|
+
* @number({ required: true, min: 0 })
|
|
57
|
+
* age: number | null = null
|
|
58
|
+
* }
|
|
59
|
+
* const form = useForm(Person, ...)
|
|
60
|
+
*
|
|
61
|
+
* // the number decorator can also be used as a function to allow standalone validation:
|
|
62
|
+
* Yop.validate(-1, number({ required: true, min: 0 })) // error: "Must be greater or equal to 0"
|
|
63
|
+
* ```
|
|
64
|
+
* @template Value - The type of the number value.
|
|
65
|
+
* @template Parent - The type of the parent object.
|
|
66
|
+
* @param constraints - The number 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 number<Value extends NumberValue, Parent>(constraints?: NumberConstraints<Value, Parent>, groups?: Groups<NumberConstraints<Value, Parent>>) {
|
|
72
|
+
return fieldValidationDecorator("number", constraints ?? {}, groups, validateNumber, isNumber)
|
|
73
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { CommonConstraints, validateTypeConstraint } from "../constraints/CommonConstraints"
|
|
2
|
+
import { Constraint, Message, validateConstraint } from "../constraints/Constraint"
|
|
3
|
+
import { MinMaxConstraints, validateMinMaxConstraints } from "../constraints/MinMaxConstraints"
|
|
4
|
+
import { OneOfConstraint, validateOneOfConstraint } from "../constraints/OneOfConstraint"
|
|
5
|
+
import { TestConstraint, validateTestConstraint } from "../constraints/TestConstraint"
|
|
6
|
+
import { isNumber, isRegExp, isString, isStringArray } from "../TypesUtil"
|
|
7
|
+
import { InternalValidationContext, ValuedContext } from "../ValidationContext"
|
|
8
|
+
import { fieldValidationDecorator, Groups } from "../Metadata"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Type for a string value, which can be a string, null, or undefined.
|
|
12
|
+
* @ignore
|
|
13
|
+
*/
|
|
14
|
+
export type StringValue = string | null | undefined
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Interface for string field constraints, combining common, min/max, oneOf, test, and match constraints.
|
|
18
|
+
* @template Value - The type of the string value.
|
|
19
|
+
* @template Parent - The type of the parent object.
|
|
20
|
+
* @property match - Constraint for matching a regular expression.
|
|
21
|
+
* @see {@link CommonConstraints}
|
|
22
|
+
* @see {@link MinMaxConstraints}
|
|
23
|
+
* @see {@link OneOfConstraint}
|
|
24
|
+
* @see {@link TestConstraint}
|
|
25
|
+
* @category Property Decorators
|
|
26
|
+
*/
|
|
27
|
+
export interface StringConstraints<Value extends StringValue, Parent> extends
|
|
28
|
+
CommonConstraints<Value, Parent>,
|
|
29
|
+
MinMaxConstraints<Value, number, Parent>,
|
|
30
|
+
OneOfConstraint<Value, Parent>,
|
|
31
|
+
TestConstraint<Value, Parent> {
|
|
32
|
+
/**
|
|
33
|
+
* Constraint for matching a regular expression. The constraint value can be a RegExp, a function that returns a RegExp.
|
|
34
|
+
*/
|
|
35
|
+
match?: Constraint<NonNullable<Value>, RegExp, Parent>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validates a string field against its constraints.
|
|
40
|
+
* @template Value - The type of the string value.
|
|
41
|
+
* @template Parent - The type of the parent object.
|
|
42
|
+
* @param context - The validation context.
|
|
43
|
+
* @param constraints - The string constraints to validate.
|
|
44
|
+
* @param defaultRegexp - Optional default regular expression for matching.
|
|
45
|
+
* @param defaultMatchMessage - Optional default error message for match failures.
|
|
46
|
+
* @param type - Optional type name for error reporting.
|
|
47
|
+
* @returns True if all constraints pass, false otherwise.
|
|
48
|
+
* @ignore
|
|
49
|
+
*/
|
|
50
|
+
export function validateString<Value extends StringValue, Parent>(
|
|
51
|
+
context: InternalValidationContext<Value, Parent>,
|
|
52
|
+
constraints: StringConstraints<Value, Parent>,
|
|
53
|
+
defaultRegexp?: RegExp,
|
|
54
|
+
defaultMatchMessage?: Message<Value, Parent>,
|
|
55
|
+
type?: string
|
|
56
|
+
) {
|
|
57
|
+
return (
|
|
58
|
+
validateTypeConstraint(context, isString, type ?? "string") &&
|
|
59
|
+
validateMinMaxConstraints(context, constraints, isNumber, (value, min) => value.length >= min, (value, max) => value.length <= max) &&
|
|
60
|
+
validateConstraint(context as ValuedContext<Value, Parent>, constraints, "match", isRegExp, (value, re) => re.test(value), defaultRegexp, defaultMatchMessage) &&
|
|
61
|
+
validateOneOfConstraint(context, constraints, isStringArray) &&
|
|
62
|
+
validateTestConstraint(context, constraints)
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Decorator for applying validation rules to a string field. A required string field can be an empty string, but neither `null` nor `undefined`.
|
|
68
|
+
* To enforce non-empty strings, use the `min` constraint with a value of 1.
|
|
69
|
+
*
|
|
70
|
+
* Example usage:
|
|
71
|
+
* ```tsx
|
|
72
|
+
* class Person {
|
|
73
|
+
* @string({ required: true, min: 1 })
|
|
74
|
+
* name: string | null = null
|
|
75
|
+
* }
|
|
76
|
+
* const form = useForm(Person, ...)
|
|
77
|
+
*
|
|
78
|
+
* // the string decorator can also be used as a function to allow standalone validation:
|
|
79
|
+
* Yop.validate("", string({ required: true, min: 1 })) // error: "Minimum 1 character"
|
|
80
|
+
* ```
|
|
81
|
+
* @template Value - The type of the string value.
|
|
82
|
+
* @template Parent - The type of the parent object.
|
|
83
|
+
* @param constraints - The string constraints to apply.
|
|
84
|
+
* @param groups - Optional validation groups.
|
|
85
|
+
* @returns A field decorator function with validation.
|
|
86
|
+
* @category Property Decorators
|
|
87
|
+
*/
|
|
88
|
+
export function string<Value extends StringValue, Parent>(constraints?: StringConstraints<Value, Parent>, groups?: Groups<StringConstraints<Value, Parent>>) {
|
|
89
|
+
return fieldValidationDecorator("string", constraints ?? {}, groups, validateString, isNumber)
|
|
90
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { TestConstraintFunction } from "../constraints/TestConstraint"
|
|
2
|
+
import { initClassConstraints } from "../Metadata"
|
|
3
|
+
import { Constructor } from "../TypesUtil"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Utility type to extract the instance type from a constructor.
|
|
7
|
+
* @template Class - The constructor type.
|
|
8
|
+
* @category Class Decorators
|
|
9
|
+
*/
|
|
10
|
+
export type InstanceType<Class> = Class extends Constructor<infer Type> ? Type : never
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Class decorator to add a test constraint function to a class for validation. The test function will be called with the
|
|
14
|
+
* class instance after all field validations have passed, allowing for complex custom validation logic that depends on
|
|
15
|
+
* the entire object state.
|
|
16
|
+
*
|
|
17
|
+
* Example usage:
|
|
18
|
+
* ```tsx
|
|
19
|
+
* @test(credentials => credentials.password === credentials.confirmPassword ? true : "Passwords do not match")
|
|
20
|
+
* class Credentials {
|
|
21
|
+
* @string({ required: true, min: 8 })
|
|
22
|
+
* username: string | null = null
|
|
23
|
+
* @string({ required: true, min: 8, test: checkPasswordStrength })
|
|
24
|
+
* password: string | null = null
|
|
25
|
+
* @string({ required: true, min: 8 })
|
|
26
|
+
* confirmPassword: string | null = null
|
|
27
|
+
* }
|
|
28
|
+
* const form = useForm(Credentials, ...)
|
|
29
|
+
* ```
|
|
30
|
+
* @template Class - The constructor type of the class.
|
|
31
|
+
* @param test - The test constraint function to apply to the class instance.
|
|
32
|
+
* @returns A class decorator function that sets the test constraint.
|
|
33
|
+
* @see {@link TestConstraintFunction}
|
|
34
|
+
* @category Class Decorators
|
|
35
|
+
*/
|
|
36
|
+
export function test<Class extends Constructor>(test: TestConstraintFunction<InstanceType<Class>>) {
|
|
37
|
+
return function decorateClass(_: Class, context: ClassDecoratorContext<Class>) {
|
|
38
|
+
const classConstraints = initClassConstraints(context.metadata)
|
|
39
|
+
classConstraints.test = test
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { InternalValidationContext } from "../ValidationContext"
|
|
2
|
+
import { fieldValidationDecorator, Groups } from "../Metadata"
|
|
3
|
+
import { StringValue } from "./string"
|
|
4
|
+
import { MinMaxConstraints, validateMinMaxConstraints } from "../constraints/MinMaxConstraints"
|
|
5
|
+
import { CommonConstraints, validateTypeConstraint } from "../constraints/CommonConstraints"
|
|
6
|
+
import { isFunction, isString, isStringArray } from "../TypesUtil"
|
|
7
|
+
import { OneOfConstraint, validateOneOfConstraint } from "../constraints/OneOfConstraint"
|
|
8
|
+
import { TestConstraint, validateTestConstraint } from "../constraints/TestConstraint"
|
|
9
|
+
import { Message } from "../constraints/Constraint"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Interface for time field constraints, combining common, min/max, oneOf, and test constraints.
|
|
13
|
+
* @template Value - The type of the string value.
|
|
14
|
+
* @template Parent - The type of the parent object.
|
|
15
|
+
* @property formatError - Optional custom error message for invalid time format.
|
|
16
|
+
* @see {@link CommonConstraints}
|
|
17
|
+
* @see {@link MinMaxConstraints}
|
|
18
|
+
* @see {@link OneOfConstraint}
|
|
19
|
+
* @see {@link TestConstraint}
|
|
20
|
+
* @category Property Decorators
|
|
21
|
+
*/
|
|
22
|
+
export interface TimeConstraints<Value extends StringValue, Parent> extends
|
|
23
|
+
CommonConstraints<Value, Parent>,
|
|
24
|
+
MinMaxConstraints<Value, string, Parent>,
|
|
25
|
+
OneOfConstraint<Value, Parent>,
|
|
26
|
+
TestConstraint<Value, Parent> {
|
|
27
|
+
/**
|
|
28
|
+
* Optional custom error message for invalid time format. `formatError` can be a {@link Message} or a function that returns a {@link Message}.
|
|
29
|
+
* @see {@link timeRegex}
|
|
30
|
+
*/
|
|
31
|
+
formatError?: Message<Value, Parent>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Regular expression for validating time strings in the format HH:mm[:ss[.sss]].
|
|
36
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Date_and_time_formats#time_strings
|
|
37
|
+
* @ignore
|
|
38
|
+
*/
|
|
39
|
+
export const timeRegex = /^([01][0-9]|2[0-3]):([0-5][0-9])(?::([0-5][0-9])(?:\.([0-9]{1,3}))?)?$/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Converts a time string (HH:mm[:ss[.sss]]) to milliseconds since midnight.
|
|
43
|
+
* @param time - The time string to convert.
|
|
44
|
+
* @returns The number of milliseconds since midnight, or undefined if invalid.
|
|
45
|
+
* @ignore
|
|
46
|
+
*/
|
|
47
|
+
export function timeToMillis(time: string) {
|
|
48
|
+
const matches = timeRegex.exec(time)
|
|
49
|
+
return (
|
|
50
|
+
matches != null ?
|
|
51
|
+
(+matches[1] * 3600 * 1000) + (+matches[2] * 60 * 1000) + (+(matches[3] ?? 0) * 1000) + (+(matches[4] ?? 0)) :
|
|
52
|
+
undefined
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Maximum number of milliseconds in a day (23:59:59.999).
|
|
58
|
+
* @ignore
|
|
59
|
+
*/
|
|
60
|
+
const MAX_MILLIS = (24 * 3600 * 1000) - 1
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Validates a time field against its constraints.
|
|
64
|
+
* @template Value - The type of the string value.
|
|
65
|
+
* @template Parent - The type of the parent object.
|
|
66
|
+
* @param context - The validation context.
|
|
67
|
+
* @param constraints - The time constraints to validate.
|
|
68
|
+
* @returns True if all constraints pass, false otherwise.
|
|
69
|
+
* @ignore
|
|
70
|
+
*/
|
|
71
|
+
export function validateTime<Value extends StringValue, Parent>(context: InternalValidationContext<Value, Parent>, constraints: TimeConstraints<Value, Parent>) {
|
|
72
|
+
if (!validateTypeConstraint(context, isString, "time"))
|
|
73
|
+
return false
|
|
74
|
+
|
|
75
|
+
const millis = timeToMillis(context.value!)
|
|
76
|
+
if (millis == null) {
|
|
77
|
+
const message = isFunction(constraints.formatError) ? constraints.formatError(context) : constraints.formatError
|
|
78
|
+
return context.setStatus("match", timeRegex, message) == null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
validateMinMaxConstraints(context, constraints, isString, (_, min) => millis >= (timeToMillis(min) ?? 0), (_, max) => millis <= (timeToMillis(max) ?? MAX_MILLIS)) &&
|
|
83
|
+
validateOneOfConstraint(context, constraints, isStringArray) &&
|
|
84
|
+
validateTestConstraint(context, constraints)
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Decorator for applying validation rules to a time field. A valid time value must be a string in the format HH:mm[:ss[.sss]] (24-hour clock).
|
|
90
|
+
*
|
|
91
|
+
* Example usage:
|
|
92
|
+
* ```tsx
|
|
93
|
+
* class Person {
|
|
94
|
+
* @time({ required: true, formatError: "Invalid wake up time format", max: "18:00" })
|
|
95
|
+
* wakeUpTime: string | null = null
|
|
96
|
+
* }
|
|
97
|
+
* const form = useForm(Person, ...)
|
|
98
|
+
*
|
|
99
|
+
* // the time decorator can also be used as a function to allow standalone validation:
|
|
100
|
+
* Yop.validate("00:00", time({ min: "01:00" })) // error: "Must be after or equal to 01:00"
|
|
101
|
+
* ```
|
|
102
|
+
* @template Value - The type of the string value.
|
|
103
|
+
* @template Parent - The type of the parent object.
|
|
104
|
+
* @param constraints - The time constraints to apply.
|
|
105
|
+
* @param groups - Optional validation groups.
|
|
106
|
+
* @returns A field decorator function with validation.
|
|
107
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Date_and_time_formats#time_strings
|
|
108
|
+
* @category Property Decorators
|
|
109
|
+
*/
|
|
110
|
+
export function time<Value extends StringValue, Parent>(constraints?: TimeConstraints<Value, Parent>, groups?: Groups<TimeConstraints<Value, Parent>>) {
|
|
111
|
+
return fieldValidationDecorator("time", constraints ?? {}, groups, validateTime, isString)
|
|
112
|
+
}
|