-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathvalidate.ts
More file actions
152 lines (139 loc) · 4.45 KB
/
validate.ts
File metadata and controls
152 lines (139 loc) · 4.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/**
* @module
* Helper function to generate validator, decorator and validate objects.
*/
// deno-lint-ignore-file no-explicit-any
import { Constructor, ValidateFunction, ValidateFunctionOptions, ValidateInfo, ValidateSymbol, Validator } from './types.ts';
import { createErrorMessage, formatErrors, ValidationError } from './errors.ts';
/**
* Will be replace by constraint in error message at runtime
*/
export const constraintKey: string = `$constraint`;
/**
* Creates a property decorator that attaches validation logic to a class property.
*
* @param validatorFunction - The function that performs the validation.
* @param validatorOption - Options to be passed to the validator function.
* @returns A property decorator function.
*/
export const createDecorator = (
validatorFunction: ValidateFunction,
validatorOption: ValidateFunctionOptions,
): PropertyDecorator => {
// deno-lint-ignore ban-types
return (target: Object, propertyKey: PropertyKey) => {
if (typeof propertyKey === 'string') {
if (
Object.getOwnPropertyDescriptor(target, ValidateSymbol) === undefined
) {
Object.defineProperty(target, ValidateSymbol, {
enumerable: false,
value: {},
});
}
const info: ValidateInfo = Object.getOwnPropertyDescriptor(
target,
ValidateSymbol,
)?.value;
const propInfo = info[propertyKey];
const validator: Validator = {
behavior: validatorFunction,
options: validatorOption,
};
if (propInfo) {
propInfo.push(validator);
} else {
info[propertyKey] = [validator];
}
}
};
};
/**
* Validates an object against the validators defined in the provided class.
*
* @template T - A type that extends the Constructor type.
* @param obj - The object to be validated.
* @param Class - The class containing the validation rules.
* @returns An array of validation errors, if any.
*/
export const validateObject = <T extends Constructor>(
obj: any,
Class: T,
): ValidationError[] => {
const validators = Object.getOwnPropertyDescriptor(
Class.prototype,
ValidateSymbol,
)
?.value as ValidateInfo | undefined;
const emptyInstance = Reflect.construct(Class, []);
obj = { ...emptyInstance, ...obj };
if (validators === undefined) {
return [];
}
const errors: any[] = [];
Object.getOwnPropertyNames(obj).forEach((propertyName) => {
const propertyValidators = validators[propertyName];
if (propertyValidators) {
for (const validator of propertyValidators.reverse()) {
try {
const passValidation = validator.behavior(obj[propertyName]);
if (!passValidation) {
errors.push({
property: propertyName,
errorMessage: createErrorMessage(validator.options),
constraints: validator.options?.constraints,
});
}
} catch (err) {
if (Array.isArray(err)) {
const formattedErrors = formatErrors(err, propertyName);
errors.push(...formattedErrors);
} else {
errors.push({
property: propertyName,
errorMessage: createErrorMessage(validator.options),
constraints: validator.options?.constraints,
});
}
}
}
}
});
return errors;
};
/**
* Creates a type validator function for a specified type name.
*
* @param typeName - The name of the type to validate against (e.g., 'string', 'number').
* @returns A validator function that checks if a property is of the specified type.
*
* @example
* const isString = createTypeValidator('string');
* console.log(isString('hello')); // true
* console.log(isString(123)); // false
*/
export function createTypeValidator(typeName: string): () => PropertyDecorator {
return createValidator(() => {
// deno-lint-ignore valid-typeof
return (prop) => typeof prop === typeName;
}, `Property must be a ${typeName}`);
}
/**
* Creates a validator decorator function.
*
* @template T - A tuple type representing the arguments for the validator function.
* @param validatorFunction - A function that takes arguments of type `T` and returns a `ValidateFunction`.
* @param errorMessage - A string representing the error message to be used if validation fails.
* @returns A function that takes arguments of type `T` and returns a decorator created by `createDecorator`.
*/
export function createValidator<T extends Array<any>>(
validatorFunction: (...args: T) => ValidateFunction,
errorMessage: string,
): (...args: T) => PropertyDecorator {
return (...args: T) => {
return createDecorator(validatorFunction(...args), {
errorMessage,
constraints: [...args],
});
};
}