reslib 1.0.0
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/README.md +298 -0
- package/build/auth/index.d.ts +2034 -0
- package/build/auth/index.js +5 -0
- package/build/auth/types.d.ts +465 -0
- package/build/auth/types.js +1 -0
- package/build/countries/countries.d.ts +1454 -0
- package/build/countries/countries.js +1 -0
- package/build/countries/index.d.ts +159 -0
- package/build/countries/index.js +5 -0
- package/build/countries/types.d.ts +65 -0
- package/build/countries/types.js +1 -0
- package/build/currency/currencies.d.ts +8 -0
- package/build/currency/currencies.js +1 -0
- package/build/currency/index.d.ts +51 -0
- package/build/currency/index.js +5 -0
- package/build/currency/session.d.ts +23 -0
- package/build/currency/session.js +5 -0
- package/build/currency/types.d.ts +1039 -0
- package/build/currency/types.js +1 -0
- package/build/currency/utils.d.ts +25 -0
- package/build/currency/utils.js +1 -0
- package/build/i18n/index.d.ts +640 -0
- package/build/i18n/index.js +5 -0
- package/build/inputFormatter/index.d.ts +396 -0
- package/build/inputFormatter/index.js +5 -0
- package/build/inputFormatter/types.d.ts +544 -0
- package/build/inputFormatter/types.js +1 -0
- package/build/logger/index.d.ts +235 -0
- package/build/logger/index.js +5 -0
- package/build/observable/index.d.ts +329 -0
- package/build/observable/index.js +1 -0
- package/build/platform/index.d.ts +32 -0
- package/build/platform/index.js +1 -0
- package/build/resources/ResourcePaginationHelper.d.ts +537 -0
- package/build/resources/ResourcePaginationHelper.js +2 -0
- package/build/resources/decorators/create.decorator.d.ts +20 -0
- package/build/resources/decorators/create.decorator.js +1 -0
- package/build/resources/decorators/index.d.ts +41 -0
- package/build/resources/decorators/index.js +1 -0
- package/build/resources/fields/index.d.ts +33 -0
- package/build/resources/fields/index.js +1 -0
- package/build/resources/filters.d.ts +62 -0
- package/build/resources/filters.js +1 -0
- package/build/resources/index.d.ts +854 -0
- package/build/resources/index.js +6 -0
- package/build/resources/types/filters.d.ts +508 -0
- package/build/resources/types/filters.js +1 -0
- package/build/resources/types/index.d.ts +4138 -0
- package/build/resources/types/index.js +1 -0
- package/build/session/index.d.ts +1474 -0
- package/build/session/index.js +1 -0
- package/build/translations/auth.en.d.ts +3 -0
- package/build/translations/auth.en.js +1 -0
- package/build/translations/countries.en.d.ts +6 -0
- package/build/translations/countries.en.js +1 -0
- package/build/translations/currencies.en.d.ts +5 -0
- package/build/translations/currencies.en.js +1 -0
- package/build/translations/date.en.d.ts +19 -0
- package/build/translations/date.en.js +1 -0
- package/build/translations/index.d.ts +1583 -0
- package/build/translations/index.js +5 -0
- package/build/translations/resources.en.d.ts +6 -0
- package/build/translations/resources.en.js +1 -0
- package/build/translations/validator.en.d.ts +104 -0
- package/build/translations/validator.en.js +5 -0
- package/build/types/date.d.ts +44 -0
- package/build/types/date.js +1 -0
- package/build/types/dictionary.d.ts +29 -0
- package/build/types/dictionary.js +1 -0
- package/build/types/i18n.d.ts +121 -0
- package/build/types/i18n.js +1 -0
- package/build/types/index.d.ts +145 -0
- package/build/types/index.js +1 -0
- package/build/utils/areEquals.d.ts +19 -0
- package/build/utils/areEquals.js +1 -0
- package/build/utils/date/dateHelper.d.ts +371 -0
- package/build/utils/date/dateHelper.js +5 -0
- package/build/utils/date/index.d.ts +212 -0
- package/build/utils/date/index.js +5 -0
- package/build/utils/date/isDateObj.d.ts +14 -0
- package/build/utils/date/isDateObj.js +1 -0
- package/build/utils/debounce.d.ts +52 -0
- package/build/utils/debounce.js +1 -0
- package/build/utils/defaultArray.d.ts +18 -0
- package/build/utils/defaultArray.js +1 -0
- package/build/utils/defaultBool.d.ts +14 -0
- package/build/utils/defaultBool.js +1 -0
- package/build/utils/defaultStr.d.ts +17 -0
- package/build/utils/defaultStr.js +1 -0
- package/build/utils/defaultVal.d.ts +18 -0
- package/build/utils/defaultVal.js +1 -0
- package/build/utils/dom/index.d.ts +65 -0
- package/build/utils/dom/index.js +1 -0
- package/build/utils/dom/isDOMElement.d.ts +11 -0
- package/build/utils/dom/isDOMElement.js +1 -0
- package/build/utils/file/index.d.ts +26 -0
- package/build/utils/file/index.js +1 -0
- package/build/utils/global.d.ts +53 -0
- package/build/utils/global.js +1 -0
- package/build/utils/image.d.ts +56 -0
- package/build/utils/image.js +1 -0
- package/build/utils/index.d.ts +39 -0
- package/build/utils/index.js +6 -0
- package/build/utils/interpolate.d.ts +105 -0
- package/build/utils/interpolate.js +1 -0
- package/build/utils/isEmail.d.ts +57 -0
- package/build/utils/isEmail.js +1 -0
- package/build/utils/isEmpty.d.ts +18 -0
- package/build/utils/isEmpty.js +1 -0
- package/build/utils/isNonNullString.d.ts +17 -0
- package/build/utils/isNonNullString.js +1 -0
- package/build/utils/isNullable.d.ts +7 -0
- package/build/utils/isNullable.js +1 -0
- package/build/utils/isNumber.d.ts +36 -0
- package/build/utils/isNumber.js +1 -0
- package/build/utils/isPrimitive.d.ts +16 -0
- package/build/utils/isPrimitive.js +1 -0
- package/build/utils/isPromise.d.ts +14 -0
- package/build/utils/isPromise.js +1 -0
- package/build/utils/isRegex.d.ts +15 -0
- package/build/utils/isRegex.js +1 -0
- package/build/utils/isTime.d.ts +18 -0
- package/build/utils/isTime.js +1 -0
- package/build/utils/json.d.ts +224 -0
- package/build/utils/json.js +1 -0
- package/build/utils/numbers.d.ts +148 -0
- package/build/utils/numbers.js +5 -0
- package/build/utils/object.d.ts +567 -0
- package/build/utils/object.js +1 -0
- package/build/utils/sort.d.ts +67 -0
- package/build/utils/sort.js +1 -0
- package/build/utils/string.d.ts +165 -0
- package/build/utils/string.js +1 -0
- package/build/utils/stringify.d.ts +23 -0
- package/build/utils/stringify.js +1 -0
- package/build/utils/uniqid.d.ts +18 -0
- package/build/utils/uniqid.js +1 -0
- package/build/utils/uri/index.d.ts +333 -0
- package/build/utils/uri/index.js +2 -0
- package/build/validator/index.d.ts +4 -0
- package/build/validator/index.js +6 -0
- package/build/validator/rules/array.d.ts +848 -0
- package/build/validator/rules/array.js +5 -0
- package/build/validator/rules/boolean.d.ts +87 -0
- package/build/validator/rules/boolean.js +5 -0
- package/build/validator/rules/date.d.ts +551 -0
- package/build/validator/rules/date.js +5 -0
- package/build/validator/rules/default.d.ts +367 -0
- package/build/validator/rules/default.js +5 -0
- package/build/validator/rules/enum.d.ts +155 -0
- package/build/validator/rules/enum.js +5 -0
- package/build/validator/rules/file.d.ts +356 -0
- package/build/validator/rules/file.js +5 -0
- package/build/validator/rules/format.d.ts +2825 -0
- package/build/validator/rules/format.js +6 -0
- package/build/validator/rules/index.d.ts +16 -0
- package/build/validator/rules/index.js +6 -0
- package/build/validator/rules/multiRules.d.ts +475 -0
- package/build/validator/rules/multiRules.js +5 -0
- package/build/validator/rules/numeric.d.ts +1135 -0
- package/build/validator/rules/numeric.js +5 -0
- package/build/validator/rules/string.d.ts +504 -0
- package/build/validator/rules/string.js +5 -0
- package/build/validator/rules/target.d.ts +137 -0
- package/build/validator/rules/target.js +5 -0
- package/build/validator/rules/utils.d.ts +1 -0
- package/build/validator/rules/utils.js +1 -0
- package/build/validator/rulesMarkers.d.ts +11 -0
- package/build/validator/rulesMarkers.js +1 -0
- package/build/validator/types.d.ts +2906 -0
- package/build/validator/types.js +1 -0
- package/build/validator/validator.d.ts +3692 -0
- package/build/validator/validator.js +5 -0
- package/lib/cjs/auth.js +1 -0
- package/lib/cjs/countries.js +1 -0
- package/lib/cjs/currency.js +1 -0
- package/lib/cjs/i18n.js +1 -0
- package/lib/cjs/inputFormatter.js +1 -0
- package/lib/cjs/logger.js +1 -0
- package/lib/cjs/observable.js +1 -0
- package/lib/cjs/platform.js +1 -0
- package/lib/cjs/resources.js +1 -0
- package/lib/cjs/session.js +1 -0
- package/lib/cjs/types.js +1 -0
- package/lib/cjs/utils.js +1 -0
- package/lib/cjs/validator.js +1 -0
- package/lib/esm/auth.mjs +1 -0
- package/lib/esm/countries.mjs +1 -0
- package/lib/esm/currency.mjs +1 -0
- package/lib/esm/i18n.mjs +1 -0
- package/lib/esm/inputFormatter.mjs +1 -0
- package/lib/esm/logger.mjs +1 -0
- package/lib/esm/observable.mjs +1 -0
- package/lib/esm/platform.mjs +1 -0
- package/lib/esm/resources.mjs +1 -0
- package/lib/esm/session.mjs +1 -0
- package/lib/esm/types.mjs +1 -0
- package/lib/esm/utils.mjs +1 -0
- package/lib/esm/validator.mjs +1 -0
- package/package.json +244 -0
|
@@ -0,0 +1,3692 @@
|
|
|
1
|
+
import { ClassConstructor, MakeOptional } from '../types';
|
|
2
|
+
import { I18n } from '../i18n';
|
|
3
|
+
import { ValidatorAsyncResult, ValidatorDefaultMultiRule, ValidatorMultiRuleFunction, ValidatorMultiRuleNames, ValidatorNestedRuleFunctionOptions, ValidatorResult, ValidatorRule, ValidatorRuleFunction, ValidatorRuleFunctionsMap, ValidatorRuleName, ValidatorRuleObject, ValidatorRuleParams, ValidatorRules, ValidatorSanitizedRuleObject, ValidatorSanitizedRules, ValidatorValidateFailure, ValidatorValidateMultiRuleOptions, ValidatorValidateOptions, ValidatorValidateResult, ValidatorValidateSuccess, ValidatorValidateTargetOptions, ValidatorValidateTargetResult } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* # Validator Class
|
|
6
|
+
*
|
|
7
|
+
* A comprehensive validation system that provides flexible and powerful validation capabilities
|
|
8
|
+
* for TypeScript/JavaScript applications. This class supports both synchronous and asynchronous
|
|
9
|
+
* validation, decorator-based validation for classes, and a rich ecosystem of validation rules.
|
|
10
|
+
*
|
|
11
|
+
* ## Key Features
|
|
12
|
+
* - **Type-Safe Validation**: Full TypeScript support with generic types
|
|
13
|
+
* - **Decorator Support**: Class property validation using decorators
|
|
14
|
+
* - **Async Validation**: Support for asynchronous validation rules
|
|
15
|
+
* - **Internationalization**: Built-in i18n support for error messages
|
|
16
|
+
* - **Extensible**: Easy to register custom validation rules
|
|
17
|
+
* - **Rule Composition**: Combine multiple validation rules
|
|
18
|
+
*
|
|
19
|
+
* ## Basic Usage
|
|
20
|
+
* ```typescript
|
|
21
|
+
* // Register a custom validation rule
|
|
22
|
+
* Validator.registerRule('CustomRule', ({ value }) => {
|
|
23
|
+
* return value > 10 || 'Value must be greater than 10';
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // Validate a single value
|
|
27
|
+
* const result = await Validator.validate({
|
|
28
|
+
* value: 15,
|
|
29
|
+
* rules: ['Required', 'CustomRule']
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* // Use with decorators
|
|
33
|
+
* class User {
|
|
34
|
+
* @IsRequired()
|
|
35
|
+
* @IsEmail()
|
|
36
|
+
* email: string;
|
|
37
|
+
*
|
|
38
|
+
* @IsRequired()
|
|
39
|
+
* @MinLength(3)
|
|
40
|
+
* name: string;
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* const userData = { email: 'user@example.com', name: 'John' };
|
|
44
|
+
* const validated = await Validator.validateTarget(User, userData);
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* ## Advanced Usage
|
|
48
|
+
* ```typescript
|
|
49
|
+
* // Complex validation with context
|
|
50
|
+
* const validationOptions = {
|
|
51
|
+
* value: userData,
|
|
52
|
+
* rules: [
|
|
53
|
+
* 'required',
|
|
54
|
+
* { minLength: [5] },
|
|
55
|
+
* async ({ value, context }) => {
|
|
56
|
+
* const exists = await checkIfUserExists(value);
|
|
57
|
+
* return !exists || 'User already exists';
|
|
58
|
+
* }
|
|
59
|
+
* ],
|
|
60
|
+
* context: { userId: 123 }
|
|
61
|
+
* };
|
|
62
|
+
*
|
|
63
|
+
* try {
|
|
64
|
+
* const result = await Validator.validate(validationOptions);
|
|
65
|
+
* console.log('Validation passed!', result);
|
|
66
|
+
* } catch (error) {
|
|
67
|
+
* console.error('Validation failed:', error.message);
|
|
68
|
+
* }
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @author Resk Framework Team
|
|
72
|
+
*
|
|
73
|
+
* @version 2.1.0
|
|
74
|
+
* @see {@link https://docs.resk.dev/validation | Validation Documentation}
|
|
75
|
+
* @public
|
|
76
|
+
*/
|
|
77
|
+
export declare class Validator {
|
|
78
|
+
private static readonly RULES_METADATA_KEY;
|
|
79
|
+
/**
|
|
80
|
+
* ## Register Validation Rule
|
|
81
|
+
*
|
|
82
|
+
* Registers a new custom validation rule that can be used throughout the application.
|
|
83
|
+
* This method provides type-safe registration of validation functions with proper
|
|
84
|
+
* error handling and validation of input parameters.
|
|
85
|
+
*
|
|
86
|
+
* ### Type Parameters
|
|
87
|
+
* - `TParams` - Array type defining the parameters the rule function accepts
|
|
88
|
+
* - `Context` - Type of the validation context object passed to the rule
|
|
89
|
+
*
|
|
90
|
+
* ### Rule Function Signature
|
|
91
|
+
* ```typescript
|
|
92
|
+
* type RuleFunction<TParams, Context> = (options: {
|
|
93
|
+
* value: any;
|
|
94
|
+
* ruleParams: TParams;
|
|
95
|
+
* context?: Context;
|
|
96
|
+
* fieldName?: string;
|
|
97
|
+
* translatedPropertyName?: string;
|
|
98
|
+
* }) => ValidatorResult
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* ### Rule Return Values
|
|
102
|
+
* - `true` - Validation passed
|
|
103
|
+
* - `false` - Validation failed (uses default error message)
|
|
104
|
+
* - `string` - Validation failed with custom error message
|
|
105
|
+
* - `ValidatorAsyncResult` - Async validation
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* // Simple synchronous rule
|
|
110
|
+
* Validator.registerRule('MinValue', ({ value, ruleParams }) => {
|
|
111
|
+
* const [minValue] = ruleParams;
|
|
112
|
+
* return value >= minValue || `Value must be at least ${minValue}`;
|
|
113
|
+
* });
|
|
114
|
+
*
|
|
115
|
+
* // Async rule with database check
|
|
116
|
+
* Validator.registerRule('UniqueEmail', async ({ value, context }) => {
|
|
117
|
+
* const exists = await database.user.findByEmail(value);
|
|
118
|
+
* return !exists || 'Email address is already taken';
|
|
119
|
+
* });
|
|
120
|
+
*
|
|
121
|
+
* // Rule with multiple parameters
|
|
122
|
+
* Validator.registerRule('Between', ({ value, ruleParams }) => {
|
|
123
|
+
* const [min, max] = ruleParams;
|
|
124
|
+
* return (value >= min && value <= max) ||
|
|
125
|
+
* `Value must be between ${min} and ${max}`;
|
|
126
|
+
* });
|
|
127
|
+
*
|
|
128
|
+
* // Rule with context
|
|
129
|
+
* Validator.registerRule('DifferentFrom', ({ value, ruleParams, context }) => {
|
|
130
|
+
* const [fieldName] = ruleParams;
|
|
131
|
+
* const otherValue = context?.data?.[fieldName];
|
|
132
|
+
* return value !== otherValue ||
|
|
133
|
+
* `Must be different from ${fieldName}`;
|
|
134
|
+
* });
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* @template TParams - Array type for rule parameters
|
|
138
|
+
* @template Context - Type for validation context
|
|
139
|
+
*
|
|
140
|
+
* @param {ValidatorOptionalOrEmptyRuleNames} ruleName - Unique identifier for the validation rule (must be non-empty string). This type constrains registration to rules with optional or empty parameters only (e.g., Required[], Email[], PhoneNumber[countryCode?]) but not rules with required parameters (e.g., MinLength[number]).
|
|
141
|
+
* @param ruleHandler - Function that performs the validation logic
|
|
142
|
+
*
|
|
143
|
+
* @throws {Error} When ruleName is not a non-empty string
|
|
144
|
+
* @throws {Error} When ruleHandler is not a function
|
|
145
|
+
*
|
|
146
|
+
*
|
|
147
|
+
* @see {@link getRule} - Find a registered rule
|
|
148
|
+
* @see {@link getRules} - Get all registered rules
|
|
149
|
+
* @public
|
|
150
|
+
*/
|
|
151
|
+
static registerRule<TParams extends ValidatorDefaultArray = ValidatorDefaultArray, Context = unknown>(ruleName: ValidatorRuleName, ruleHandler: ValidatorRuleFunction<TParams, Context>): void;
|
|
152
|
+
/**
|
|
153
|
+
* ## Get All Registered Rules
|
|
154
|
+
*
|
|
155
|
+
* Retrieves an immutable copy of all currently registered validation rules.
|
|
156
|
+
* This method provides access to the internal rules registry that contains
|
|
157
|
+
* all custom validation rules registered via {@link registerRule}.
|
|
158
|
+
*
|
|
159
|
+
* ### Rule Registry
|
|
160
|
+
* The Validator maintains a centralized registry of validation rules using
|
|
161
|
+
* TypeScript's Reflect Metadata API. This registry stores rule functions
|
|
162
|
+
* that can be used throughout the validation system.
|
|
163
|
+
*
|
|
164
|
+
* ### Return Value
|
|
165
|
+
* Returns a shallow copy of the rules registry to prevent external modification
|
|
166
|
+
* while allowing inspection of available rules. The returned object maps
|
|
167
|
+
* rule names to their corresponding validation functions.
|
|
168
|
+
*
|
|
169
|
+
* ### Rule Types
|
|
170
|
+
* The registry contains rules registered through {@link registerRule}, which
|
|
171
|
+
* are constrained by {@link ValidatorOptionalOrEmptyRuleNames} to only include
|
|
172
|
+
* rules with optional or empty parameters (e.g., Required[], Email[], PhoneNumber[countryCode?]).
|
|
173
|
+
*
|
|
174
|
+
* ### Use Cases
|
|
175
|
+
* - **Rule Discovery**: List all available validation rules for documentation
|
|
176
|
+
* - **Debugging**: Inspect the current state of the rules registry
|
|
177
|
+
* - **Testing**: Verify rule registration in unit tests
|
|
178
|
+
* - **Introspection**: Build dynamic validation UIs or rule explorers
|
|
179
|
+
* - **Rule Analysis**: Examine rule implementations programmatically
|
|
180
|
+
*
|
|
181
|
+
* ### Performance
|
|
182
|
+
* This method performs a shallow copy of the rules object, ensuring that
|
|
183
|
+
* external modifications cannot affect the internal registry while providing
|
|
184
|
+
* efficient access to rule functions.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```typescript
|
|
188
|
+
* // Get all registered rules
|
|
189
|
+
* const allRules = Validator.getRules();
|
|
190
|
+
* console.log('Available rules:', Object.keys(allRules));
|
|
191
|
+
* console.log('Total rules registered:', Object.keys(allRules).length);
|
|
192
|
+
*
|
|
193
|
+
* // Check if specific rules exist
|
|
194
|
+
* const hasEmailRule = 'Email' in Validator.getRules();
|
|
195
|
+
* const hasRequiredRule = 'Required' in Validator.getRules();
|
|
196
|
+
*
|
|
197
|
+
* // Access rule functions directly (not recommended for normal use)
|
|
198
|
+
* const rules = Validator.getRules();
|
|
199
|
+
* const emailRule = rules['Email'];
|
|
200
|
+
* if (emailRule) {
|
|
201
|
+
* // Use rule directly (bypasses normal validation pipeline)
|
|
202
|
+
* const result = await emailRule({
|
|
203
|
+
* value: 'test@example.com',
|
|
204
|
+
* ruleParams: []
|
|
205
|
+
* });
|
|
206
|
+
* }
|
|
207
|
+
*
|
|
208
|
+
* // Iterate through all rules for analysis
|
|
209
|
+
* const rulesRegistry = Validator.getRules();
|
|
210
|
+
* for (const [ruleName, ruleFunction] of Object.entries(rulesRegistry)) {
|
|
211
|
+
* console.log(`Rule: ${ruleName}, Type: ${typeof ruleFunction}`);
|
|
212
|
+
* }
|
|
213
|
+
*
|
|
214
|
+
* // Use in testing to verify rule registration
|
|
215
|
+
* describe('Rule Registration', () => {
|
|
216
|
+
* test('should register custom rules', () => {
|
|
217
|
+
* const rulesBefore = Object.keys(Validator.getRules());
|
|
218
|
+
* Validator.registerRule('CustomRule', ({ value }) => value === 'valid');
|
|
219
|
+
* const rulesAfter = Object.keys(Validator.getRules());
|
|
220
|
+
* expect(rulesAfter.length).toBe(rulesBefore.length + 1);
|
|
221
|
+
* expect(rulesAfter).toContain('CustomRule');
|
|
222
|
+
* });
|
|
223
|
+
* });
|
|
224
|
+
* ```
|
|
225
|
+
*
|
|
226
|
+
* @template Context - Optional type for validation context (passed through to rule functions)
|
|
227
|
+
*
|
|
228
|
+
* @returns A shallow copy of all registered validation rules as ValidatorRuleFunctionsMap<Context>
|
|
229
|
+
* - Keys: Rule names (ValidatorRuleName strings)
|
|
230
|
+
* - Values: Rule validation functions (ValidatorRuleFunction)
|
|
231
|
+
* - Empty object if no rules are registered
|
|
232
|
+
* - Immutable copy prevents external modification
|
|
233
|
+
*
|
|
234
|
+
* @remarks
|
|
235
|
+
* - Returns a new object each time to prevent external mutations
|
|
236
|
+
* - Rules are stored using Reflect Metadata API on the Validator class
|
|
237
|
+
* - Only includes rules registered via registerRule (not built-in decorator rules)
|
|
238
|
+
* - Rule functions maintain their original type signatures and context requirements
|
|
239
|
+
* - Useful for debugging, testing, and building rule management interfaces
|
|
240
|
+
*
|
|
241
|
+
* @see {@link registerRule} - Register a new validation rule
|
|
242
|
+
* @see {@link getRule} - Get a specific rule by name
|
|
243
|
+
* @see {@link hasRule} - Check if a rule exists (type guard)
|
|
244
|
+
* @see {@link ValidatorRuleFunctionsMap} - Type definition for the returned map
|
|
245
|
+
* @see {@link ValidatorOptionalOrEmptyRuleNames} - Type constraint for registered rules
|
|
246
|
+
* @public
|
|
247
|
+
*/
|
|
248
|
+
static getRules<Context = unknown>(): ValidatorRuleFunctionsMap<Context>;
|
|
249
|
+
/**
|
|
250
|
+
* ## Get Registered Rule
|
|
251
|
+
*
|
|
252
|
+
* Retrieves a specific validation rule function by its registered name. This method
|
|
253
|
+
* provides direct access to individual validation functions that have been registered
|
|
254
|
+
* with the Validator system through {@link registerRule}.
|
|
255
|
+
*
|
|
256
|
+
* ### Rule Retrieval
|
|
257
|
+
* This method looks up a single rule in the internal rules registry by name. It serves
|
|
258
|
+
* as a convenience method that delegates to {@link getRules} but returns only the
|
|
259
|
+
* requested rule function, or `undefined` if the rule doesn't exist.
|
|
260
|
+
*
|
|
261
|
+
* ### Registry Lookup
|
|
262
|
+
* The method performs a direct key lookup in the rules registry maintained by
|
|
263
|
+
* {@link getRules}. This registry contains only rules registered via {@link registerRule},
|
|
264
|
+
* which are constrained by {@link ValidatorOptionalOrEmptyRuleNames}.
|
|
265
|
+
*
|
|
266
|
+
* ### Return Value
|
|
267
|
+
* - Returns the validation rule function if found
|
|
268
|
+
* - Returns `undefined` if no rule with the given name exists
|
|
269
|
+
* - The returned function has the signature `ValidatorRuleFunction<TParams, Context>`
|
|
270
|
+
*
|
|
271
|
+
* ### Use Cases
|
|
272
|
+
* - **Direct Rule Access**: Get a specific rule function for programmatic use
|
|
273
|
+
* - **Rule Inspection**: Examine the implementation of a particular validation rule
|
|
274
|
+
* - **Dynamic Validation**: Access rules by name at runtime
|
|
275
|
+
* - **Rule Testing**: Verify rule registration and functionality in tests
|
|
276
|
+
* - **Custom Validation Logic**: Build custom validation workflows using existing rules
|
|
277
|
+
*
|
|
278
|
+
* ### Performance
|
|
279
|
+
* This method performs a single hash map lookup in the rules registry, making it
|
|
280
|
+
* very efficient for retrieving individual rules. The underlying registry is
|
|
281
|
+
* maintained as a shallow copy to prevent external mutations.
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```typescript
|
|
285
|
+
* // Get a specific rule function
|
|
286
|
+
* const emailRule = Validator.getRule('Email');
|
|
287
|
+
* if (emailRule) {
|
|
288
|
+
* // Use the rule directly (bypasses normal validation pipeline)
|
|
289
|
+
* const result = await emailRule({
|
|
290
|
+
* value: 'test@example.com',
|
|
291
|
+
* ruleParams: []
|
|
292
|
+
* });
|
|
293
|
+
* console.log('Direct email validation result:', result);
|
|
294
|
+
* }
|
|
295
|
+
*
|
|
296
|
+
* // Safe rule access with existence check
|
|
297
|
+
* const customRule = Validator.getRule('CustomRule');
|
|
298
|
+
* if (customRule) {
|
|
299
|
+
* console.log('CustomRule is available for use');
|
|
300
|
+
* // Rule exists and is ready to use
|
|
301
|
+
* } else {
|
|
302
|
+
* console.log('CustomRule is not registered');
|
|
303
|
+
* // Handle missing rule case
|
|
304
|
+
* }
|
|
305
|
+
*
|
|
306
|
+
* // Get rule for custom validation logic
|
|
307
|
+
* const minLengthRule = Validator.getRule('MinLength');
|
|
308
|
+
* if (minLengthRule) {
|
|
309
|
+
* const customValidator = async (value: string, minLen: number) => {
|
|
310
|
+
* const result = await minLengthRule({
|
|
311
|
+
* value,
|
|
312
|
+
* ruleParams: [minLen]
|
|
313
|
+
* });
|
|
314
|
+
* return result === true ? 'Valid' : 'Invalid';
|
|
315
|
+
* };
|
|
316
|
+
*
|
|
317
|
+
* console.log(await customValidator('hello', 3)); // 'Valid'
|
|
318
|
+
* console.log(await customValidator('hi', 5)); // 'Invalid'
|
|
319
|
+
* }
|
|
320
|
+
*
|
|
321
|
+
* // Rule inspection and analysis
|
|
322
|
+
* const allRules = Validator.getRules();
|
|
323
|
+
* for (const ruleName of Object.keys(allRules)) {
|
|
324
|
+
* const rule = Validator.getRule(ruleName);
|
|
325
|
+
* console.log(`Rule: ${ruleName}, Function: ${rule?.name || 'anonymous'}`);
|
|
326
|
+
* }
|
|
327
|
+
*
|
|
328
|
+
* // Testing rule registration
|
|
329
|
+
* describe('Rule Registration', () => {
|
|
330
|
+
* test('should register and retrieve custom rule', () => {
|
|
331
|
+
* Validator.registerRule('TestRule', ({ value }) => value === 'test');
|
|
332
|
+
* const retrievedRule = Validator.getRule('TestRule');
|
|
333
|
+
* expect(retrievedRule).toBeDefined();
|
|
334
|
+
* expect(typeof retrievedRule).toBe('function');
|
|
335
|
+
* });
|
|
336
|
+
* });
|
|
337
|
+
* ```
|
|
338
|
+
*
|
|
339
|
+
* @template Context - Optional type for validation context (passed through to rule functions)
|
|
340
|
+
*
|
|
341
|
+
* @param ruleName - The name of the validation rule to retrieve (must be a valid ValidatorRuleName)
|
|
342
|
+
*
|
|
343
|
+
* @returns The validation rule function if found, undefined otherwise
|
|
344
|
+
* - Function signature: `ValidatorRuleFunction<TParams, Context>`
|
|
345
|
+
* - Returns `undefined` if rule doesn't exist in registry
|
|
346
|
+
* - Rule function maintains its original type signatures and behavior
|
|
347
|
+
*
|
|
348
|
+
* @remarks
|
|
349
|
+
* - This method delegates to `getRules()` internally for registry access
|
|
350
|
+
* - Only returns rules registered via `registerRule()` (not built-in decorator rules)
|
|
351
|
+
* - Rules are constrained by `ValidatorOptionalOrEmptyRuleNames` type constraints
|
|
352
|
+
* - Direct rule function calls bypass the normal validation pipeline
|
|
353
|
+
* - Always check for `undefined` return value before using the rule
|
|
354
|
+
* - Useful for building custom validation logic and rule introspection
|
|
355
|
+
* - Rule functions expect the standard `ValidatorRuleFunction` parameter structure
|
|
356
|
+
*
|
|
357
|
+
* @see {@link getRules} - Get all registered rules as a map
|
|
358
|
+
* @see {@link registerRule} - Register a new validation rule
|
|
359
|
+
* @see {@link getRule} - Type-safe rule retrieval with generics
|
|
360
|
+
* @see {@link hasRule} - Check if a rule exists (type guard)
|
|
361
|
+
* @see {@link ValidatorRuleFunction} - Type definition for rule functions
|
|
362
|
+
* @see {@link ValidatorOptionalOrEmptyRuleNames} - Type constraint for registered rules
|
|
363
|
+
* @public
|
|
364
|
+
*/
|
|
365
|
+
static getRule<Context = unknown>(ruleName: ValidatorRuleName): ValidatorRuleFunction<[options?: import("../utils/index").IsEmailOptions | undefined], Context> | ValidatorRuleFunction<[options?: import("../utils/index").IsUrlOptions | undefined], Context> | ValidatorRuleFunction<[], Context> | ValidatorRuleFunction<[minLength: number], Context> | ValidatorRuleFunction<[maxLength: number], Context> | ValidatorRuleFunction<[length: number], Context> | ValidatorRuleFunction<any[], Context> | ValidatorRuleFunction<[date: string | number | Date], Context> | ValidatorRuleFunction<[date: string | number | Date], Context> | ValidatorRuleFunction<[minDate: string | number | Date, maxDate: string | number | Date], Context> | ValidatorRuleFunction<[date: string | number | Date], Context> | ValidatorRuleFunction<unknown[], Context> | ValidatorRuleFunction<[size: number], Context> | ValidatorRuleFunction<string[], Context> | ValidatorRuleFunction<[minSize: number], Context> | ValidatorRuleFunction<[countryCode?: "AF" | "AL" | "DZ" | "AS" | "AD" | "AO" | "AI" | "AG" | "AR" | "AM" | "AW" | "AU" | "AT" | "AZ" | "BS" | "BH" | "BD" | "BB" | "BY" | "BE" | "BZ" | "BJ" | "BM" | "BT" | "BO" | "BA" | "BW" | "BR" | "IO" | "VG" | "BN" | "BG" | "BF" | "BI" | "KH" | "CM" | "CA" | "CV" | "BQ" | "KY" | "CF" | "TD" | "CL" | "CN" | "CX" | "CC" | "CO" | "KM" | "CD" | "CG" | "CK" | "CR" | "CI" | "HR" | "CU" | "CW" | "CY" | "CZ" | "DK" | "DJ" | "DM" | "DO" | "EC" | "EG" | "SV" | "GQ" | "ER" | "EE" | "ET" | "FK" | "FO" | "FJ" | "FI" | "FR" | "GF" | "PF" | "GA" | "GM" | "GE" | "DE" | "GH" | "GI" | "GR" | "GL" | "GD" | "GP" | "GU" | "GT" | "GG" | "GN" | "GW" | "GY" | "HT" | "HN" | "HK" | "HU" | "IS" | "IN" | "ID" | "IR" | "IQ" | "IE" | "IM" | "IL" | "IT" | "JM" | "JP" | "JE" | "JO" | "KZ" | "KE" | "KI" | "KW" | "KG" | "LA" | "LV" | "LB" | "LS" | "LR" | "LY" | "LI" | "LT" | "LU" | "MO" | "MK" | "MG" | "MW" | "MY" | "MV" | "ML" | "MT" | "MH" | "MQ" | "MR" | "MU" | "YT" | "MX" | "FM" | "MD" | "MC" | "MN" | "ME" | "MS" | "MA" | "MZ" | "MM" | "NA" | "NR" | "NP" | "NL" | "NC" | "NZ" | "NI" | "NE" | "NG" | "NU" | "NF" | "KP" | "MP" | "NO" | "OM" | "PK" | "PW" | "PS" | "PA" | "PG" | "PY" | "PE" | "PH" | "PL" | "PT" | "PR" | "QA" | "RE" | "RO" | "RU" | "RW" | "BL" | "SH" | "KN" | "LC" | "MF" | "PM" | "VC" | "WS" | "SM" | "ST" | "SA" | "SN" | "RS" | "SC" | "SL" | "SG" | "SX" | "SK" | "SI" | "SB" | "SO" | "ZA" | "KR" | "SS" | "ES" | "LK" | "SD" | "SR" | "SJ" | "SZ" | "SE" | "CH" | "SY" | "TW" | "TJ" | "TZ" | "TH" | "TL" | "TG" | "TK" | "TO" | "TT" | "TN" | "TR" | "TM" | "TC" | "TV" | "VI" | "UG" | "UA" | "AE" | "GB" | "US" | "UY" | "UZ" | "VU" | "VA" | "VE" | "VN" | "WF" | "EH" | "YE" | "ZM" | "ZW" | "AX" | undefined], Context> | ValidatorRuleFunction<[options?: {
|
|
366
|
+
email?: import("../utils/index").IsEmailOptions;
|
|
367
|
+
phoneNumber?: {
|
|
368
|
+
countryCode?: import("../countries").CountryCode;
|
|
369
|
+
} | undefined;
|
|
370
|
+
} | undefined], Context> | ValidatorRuleFunction<[pattern: string | RegExp, options?: {
|
|
371
|
+
message?: string;
|
|
372
|
+
} | undefined], Context> | ValidatorRuleFunction<[number], Context> | ValidatorRuleFunction<[min: number, max: number], Context> | ValidatorRuleFunction<[minDecimalPlaces: number, maxDecimalPlaces?: number | undefined], Context> | ValidatorRuleFunction<[lengthOrMinLength: number, maxLength?: number | undefined], Context> | ValidatorRuleFunction<[minLength: number], Context> | ValidatorRuleFunction<[maxLength: number], Context>;
|
|
373
|
+
/**
|
|
374
|
+
* ## Check Rule Existence (Type Guard)
|
|
375
|
+
*
|
|
376
|
+
* Type guard method that checks whether a validation rule with the given name
|
|
377
|
+
* is registered in the Validator system. This method provides both existence
|
|
378
|
+
* checking and TypeScript type narrowing for rule names.
|
|
379
|
+
*
|
|
380
|
+
* ### Type Guard Behavior
|
|
381
|
+
* - **Input Validation**: First checks if the input is a non-null string
|
|
382
|
+
* - **Rule Lookup**: Uses `getRule` to check if the rule exists in the registry
|
|
383
|
+
* - **Type Narrowing**: Narrows `ruleName` to `ValidatorRuleName` if it returns true
|
|
384
|
+
*
|
|
385
|
+
* ### Use Cases
|
|
386
|
+
* - **Safe Rule Access**: Verify rule existence before using `getRule`
|
|
387
|
+
* - **Dynamic Validation**: Check rules at runtime before applying them
|
|
388
|
+
* - **Type Safety**: Enable TypeScript to narrow types based on rule existence
|
|
389
|
+
* - **Error Prevention**: Avoid undefined access when working with rule names
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```typescript
|
|
393
|
+
* // Basic existence check
|
|
394
|
+
* if (Validator.hasRule('Email')) {
|
|
395
|
+
* console.log('Email rule is available');
|
|
396
|
+
* }
|
|
397
|
+
*
|
|
398
|
+
* // Type narrowing with type guard
|
|
399
|
+
* function processRule(ruleName: string) {
|
|
400
|
+
* if (Validator.hasRule(ruleName)) {
|
|
401
|
+
* // TypeScript now knows ruleName is ValidatorRuleName
|
|
402
|
+
* const rule = Validator.getRule(ruleName); // Type safe
|
|
403
|
+
return rule;
|
|
404
|
+
* } else {
|
|
405
|
+
* console.log(`${ruleName} is not a valid rule`);
|
|
406
|
+
return null;
|
|
407
|
+
* }
|
|
408
|
+
* }
|
|
409
|
+
*
|
|
410
|
+
* // Safe rule processing
|
|
411
|
+
* const ruleNames = ['Email', 'Required', 'InvalidRule'];
|
|
412
|
+
* const validRules = ruleNames.filter(Validator.hasRule);
|
|
413
|
+
* console.log('Valid rules:', validRules); // ['Email', 'Required']
|
|
414
|
+
*
|
|
415
|
+
* // Dynamic rule application
|
|
416
|
+
* function applyRuleIfExists(value: any, ruleName: string) {
|
|
417
|
+
* if (Validator.hasRule(ruleName)) {
|
|
418
|
+
* const rule = Validator.getRule(ruleName);
|
|
419
|
+
* return rule?.({ value, ruleParams: [] });
|
|
420
|
+
* }
|
|
421
|
+
* return 'Rule not found';
|
|
422
|
+
* }
|
|
423
|
+
* ```
|
|
424
|
+
*
|
|
425
|
+
* @param ruleName - The name to check for rule existence (any type, validated internally)
|
|
426
|
+
*
|
|
427
|
+
* @returns `true` if the rule exists and ruleName is a valid ValidatorRuleName, `false` otherwise
|
|
428
|
+
*
|
|
429
|
+
*
|
|
430
|
+
* @see {@link getRule} - Get the actual rule function
|
|
431
|
+
* @see {@link getRules} - Get all registered rules
|
|
432
|
+
* @see {@link registerRule} - Register a new validation rule
|
|
433
|
+
* @public
|
|
434
|
+
*/
|
|
435
|
+
static hasRule(ruleName: any): ruleName is ValidatorRuleName;
|
|
436
|
+
private static getI18n;
|
|
437
|
+
/**
|
|
438
|
+
* ## Get Error Message Separators
|
|
439
|
+
*
|
|
440
|
+
* Retrieves the configured separators used for formatting validation error messages.
|
|
441
|
+
* This method provides centralized access to internationalized separator strings that
|
|
442
|
+
* ensure consistent error message formatting across different languages and locales.
|
|
443
|
+
*
|
|
444
|
+
* ### Purpose
|
|
445
|
+
* Error message separators are crucial for creating readable, localized error messages
|
|
446
|
+
* when multiple validation failures occur. Different languages use different punctuation
|
|
447
|
+
* conventions (commas, semicolons, periods) for joining error messages, and this method
|
|
448
|
+
* ensures proper internationalization support.
|
|
449
|
+
*
|
|
450
|
+
* ### Separator Types
|
|
451
|
+
* - **`multiple`** - Primary separator for joining multiple error messages
|
|
452
|
+
* - Default: `", "` (comma + space)
|
|
453
|
+
* - Used when combining multiple validation errors
|
|
454
|
+
* - Examples: `"Field is required, Must be email, Too short"`
|
|
455
|
+
* - **`single`** - Secondary separator for single error message formatting
|
|
456
|
+
* - Default: `", "` (comma + space)
|
|
457
|
+
* - Used for complex single error messages with multiple parts
|
|
458
|
+
* - Examples: `"Must be between 5 and 10, but received 15"`
|
|
459
|
+
*
|
|
460
|
+
* ### Internationalization Behavior
|
|
461
|
+
* - Loads separators from i18n translation key: `validator.separators`
|
|
462
|
+
* - Falls back to English defaults (`", "`) if translations unavailable
|
|
463
|
+
* - Supports custom I18n instances for testing or specialized formatting
|
|
464
|
+
* - Automatically adapts to language-specific punctuation conventions
|
|
465
|
+
*
|
|
466
|
+
* ### Use Cases
|
|
467
|
+
* - **Custom Error Builders**: Create consistent error message formatting
|
|
468
|
+
* - **Multi-Field Validation**: Join errors from multiple field validations
|
|
469
|
+
* - **Complex Rules**: Format errors from rules with multiple conditions
|
|
470
|
+
* - **UI Integration**: Ensure error messages match application locale
|
|
471
|
+
* - **Testing**: Verify error message formatting in different languages
|
|
472
|
+
*
|
|
473
|
+
* @example
|
|
474
|
+
* ```typescript
|
|
475
|
+
* // Basic separator retrieval
|
|
476
|
+
* const separators = Validator.getErrorMessageSeparators();
|
|
477
|
+
* console.log(separators); // { multiple: ", ", single: ", " }
|
|
478
|
+
*
|
|
479
|
+
* // Custom error message formatting
|
|
480
|
+
* function formatValidationErrors(fieldName: string, errors: string[]) {
|
|
481
|
+
* const seps = Validator.getErrorMessageSeparators();
|
|
482
|
+
* if (errors.length === 0) return null;
|
|
483
|
+
* if (errors.length === 1) return `${fieldName}: ${errors[0]}`;
|
|
484
|
+
* return `${fieldName}: ${errors.join(seps.multiple)}`;
|
|
485
|
+
* }
|
|
486
|
+
*
|
|
487
|
+
* const errors = ['Field is required', 'Must be email', 'Too short'];
|
|
488
|
+
* console.log(formatValidationErrors('email', errors));
|
|
489
|
+
* // Output: "email: Field is required, Must be email, Too short"
|
|
490
|
+
*
|
|
491
|
+
* // Using separators in validation result processing
|
|
492
|
+
* const result = await Validator.validate({
|
|
493
|
+
* value: '',
|
|
494
|
+
* rules: ['Required', 'Email', 'MinLength[5]'],
|
|
495
|
+
* fieldName: 'userEmail'
|
|
496
|
+
* });
|
|
497
|
+
*
|
|
498
|
+
* if (!result.success) {
|
|
499
|
+
* // This would normally be done internally, but for demonstration:
|
|
500
|
+
* const seps = Validator.getErrorMessageSeparators();
|
|
501
|
+
* const customMessage = `Validation failed: ${result.error.message}`;
|
|
502
|
+
* console.log(customMessage);
|
|
503
|
+
* }
|
|
504
|
+
*
|
|
505
|
+
* // Custom I18n instance for testing
|
|
506
|
+
* const customI18n = new I18n({
|
|
507
|
+
* validator: {
|
|
508
|
+
* separators: {
|
|
509
|
+
* multiple: '; ',
|
|
510
|
+
* single: ' - '
|
|
511
|
+
* }
|
|
512
|
+
* }
|
|
513
|
+
* });
|
|
514
|
+
*
|
|
515
|
+
* const customSeparators = Validator.getErrorMessageSeparators(customI18n);
|
|
516
|
+
* console.log(customSeparators); // { multiple: "; ", single: " - " }
|
|
517
|
+
*
|
|
518
|
+
* // Language-specific formatting example
|
|
519
|
+
* // French: "Champ requis, Doit être email, Trop court"
|
|
520
|
+
* // German: "Feld erforderlich, Muss E-Mail sein, Zu kurz"
|
|
521
|
+
* // Japanese: "フィールドは必須です、メールアドレスである必要があります、短すぎます"
|
|
522
|
+
* ```
|
|
523
|
+
*
|
|
524
|
+
* @param customI18n - Optional custom I18n instance to override default translations
|
|
525
|
+
* Useful for testing, custom formatting, or specialized locales
|
|
526
|
+
*
|
|
527
|
+
* @returns Object containing separator strings for error message formatting
|
|
528
|
+
* @returns returns.multiple - Separator string for joining multiple error messages (default: `", "`)
|
|
529
|
+
* @returns returns.single - Separator string for single error message formatting (default: `", "`)
|
|
530
|
+
*
|
|
531
|
+
* @remarks
|
|
532
|
+
* - Separators are loaded from i18n key `validator.separators` with fallback defaults
|
|
533
|
+
* - Method is used internally by `validate()` and `validateTarget()` for error formatting
|
|
534
|
+
* - Supports both built-in i18n and custom I18n instances
|
|
535
|
+
* - Thread-safe and stateless - can be called multiple times without side effects
|
|
536
|
+
* - Default separators ensure English-compatible formatting when i18n unavailable
|
|
537
|
+
*
|
|
538
|
+
* @see {@link validate} - Uses these separators for formatting validation error messages
|
|
539
|
+
* @see {@link validateTarget} - Uses these separators for multi-field validation errors
|
|
540
|
+
* @see {@link validateMultiRule} - Uses separators for OneOf/AllOf error aggregation
|
|
541
|
+
* @see {@link validateArrayOfRule} - Uses separators for array item error formatting
|
|
542
|
+
* @see {@link I18n} - Internationalization system for custom separator configuration
|
|
543
|
+
* @public
|
|
544
|
+
*/
|
|
545
|
+
static getErrorMessageSeparators(customI18n?: I18n): {
|
|
546
|
+
multiple: string;
|
|
547
|
+
single: string;
|
|
548
|
+
};
|
|
549
|
+
/**
|
|
550
|
+
* ## Parse and Validate Rules
|
|
551
|
+
*
|
|
552
|
+
* Core rule normalization method that converts diverse validation rule formats into a standardized,
|
|
553
|
+
* executable representation while identifying and isolating invalid rules. This method serves as
|
|
554
|
+
* the critical preprocessing step that enables flexible rule input while ensuring type safety
|
|
555
|
+
* and consistent validation behavior across the entire validator system.
|
|
556
|
+
*
|
|
557
|
+
* ### Purpose
|
|
558
|
+
* The `parseAndValidateRules` method bridges the gap between user-friendly rule specification
|
|
559
|
+
* and the internal validation engine. It accepts rules in multiple formats (strings, objects, functions)
|
|
560
|
+
* and transforms them into a uniform structure that the validation pipeline can process efficiently.
|
|
561
|
+
* Invalid rules are separated out for error reporting rather than causing validation failures.
|
|
562
|
+
*
|
|
563
|
+
* ### Supported Input Formats
|
|
564
|
+
*
|
|
565
|
+
* #### 1. Function Rules (Direct Validation Functions)
|
|
566
|
+
* ```typescript
|
|
567
|
+
* // Synchronous function rule
|
|
568
|
+
* const positiveRule = ({ value }) => value > 0 || 'Must be positive';
|
|
569
|
+
*
|
|
570
|
+
* // Asynchronous function rule with context
|
|
571
|
+
* const asyncRule = async ({ value, context }) => {
|
|
572
|
+
* const result = await someAsyncCheck(value, context);
|
|
573
|
+
* return result || 'Async validation failed';
|
|
574
|
+
* };
|
|
575
|
+
*
|
|
576
|
+
* // Function rules with custom error messages
|
|
577
|
+
* const customRule = ({ value }) => {
|
|
578
|
+
* if (!value) return 'Value is required';
|
|
579
|
+
* if (value.length < 3) return 'Must be at least 3 characters';
|
|
580
|
+
* return true; // Valid
|
|
581
|
+
* };
|
|
582
|
+
* ```
|
|
583
|
+
*
|
|
584
|
+
* #### 2. String Rules (Bracket Notation for Parameters)
|
|
585
|
+
* ```typescript
|
|
586
|
+
* // Simple rules without parameters
|
|
587
|
+
* 'Required' // Basic required field check
|
|
588
|
+
* 'Email' // Email format validation
|
|
589
|
+
* 'IsNumber' // Type checking
|
|
590
|
+
*
|
|
591
|
+
* // Rules with single parameter
|
|
592
|
+
* 'MinLength[5]' // Minimum length validation
|
|
593
|
+
* 'MaxLength[100]' // Maximum length validation
|
|
594
|
+
* 'GreaterThan[0]' // Numeric comparison
|
|
595
|
+
*
|
|
596
|
+
* // Rules with multiple parameters
|
|
597
|
+
* 'Between[10,20]' // Range validation (inclusive)
|
|
598
|
+
* 'InArray["option1","option2","option3"]' // Value enumeration
|
|
599
|
+
* 'Matches[/^[A-Z]{2}\d{6}$/]' // Regex pattern matching
|
|
600
|
+
* ```
|
|
601
|
+
*
|
|
602
|
+
* #### 3. Object Rules (Structured Parameter Passing)
|
|
603
|
+
* ```typescript
|
|
604
|
+
* // Rules without parameters
|
|
605
|
+
* { Required: [] } // Explicit empty parameter array
|
|
606
|
+
* { Email: undefined } // Undefined parameters (treated as [])
|
|
607
|
+
*
|
|
608
|
+
* // Rules with single parameter
|
|
609
|
+
* { MinLength: [5] } // Array with one element
|
|
610
|
+
* { MaxLength: [100] } // Array with one element
|
|
611
|
+
*
|
|
612
|
+
* // Rules with multiple parameters
|
|
613
|
+
* { Between: [10, 20] } // Range validation
|
|
614
|
+
* { InArray: ['admin', 'user', 'guest'] } // Multiple allowed values
|
|
615
|
+
* { CustomRule: ['param1', 'param2', 42] } // Mixed parameter types
|
|
616
|
+
* ```
|
|
617
|
+
*
|
|
618
|
+
* ### Processing Logic
|
|
619
|
+
* The method follows a systematic approach to rule processing:
|
|
620
|
+
*
|
|
621
|
+
* 1. **Input Validation**: Accepts `undefined`, empty arrays, or arrays of mixed rule types
|
|
622
|
+
* 2. **Function Detection**: Direct function rules are preserved unchanged in the output
|
|
623
|
+
* 3. **String Parsing**: Bracket syntax is parsed to extract rule names and parameters
|
|
624
|
+
* 4. **Object Processing**: Object notation is converted to standardized rule objects
|
|
625
|
+
* 5. **Rule Registry Lookup**: Each parsed rule is validated against the registered rules map
|
|
626
|
+
* 6. **Error Isolation**: Invalid/unregistered rules are collected separately for reporting
|
|
627
|
+
* 7. **Type Safety**: All output maintains TypeScript type safety with proper generics
|
|
628
|
+
*
|
|
629
|
+
* ### Output Structure
|
|
630
|
+
* The method returns a structured result object with two key properties:
|
|
631
|
+
*
|
|
632
|
+
* #### `sanitizedRules` - Successfully Processed Rules
|
|
633
|
+
* Array of standardized rule objects with consistent structure:
|
|
634
|
+
* ```typescript
|
|
635
|
+
* interface SanitizedRule {
|
|
636
|
+
* ruleName: ValidatorRuleName; // Extracted rule name
|
|
637
|
+
* params: any[]; // Parameter array (empty for no params)
|
|
638
|
+
* ruleFunction: ValidatorRuleFunction; // Actual validation function
|
|
639
|
+
* rawRuleName: string; // Original input string/object
|
|
640
|
+
* }
|
|
641
|
+
* ```
|
|
642
|
+
*
|
|
643
|
+
* #### `invalidRules` - Unprocessable Rules
|
|
644
|
+
* Array of rules that couldn't be processed (maintains original input types):
|
|
645
|
+
* - Unregistered rule names
|
|
646
|
+
* - Malformed string syntax
|
|
647
|
+
* - Invalid object structures
|
|
648
|
+
* - Rules that failed registry lookup
|
|
649
|
+
*
|
|
650
|
+
* ### Error Handling Strategy
|
|
651
|
+
* - **Graceful Degradation**: Invalid rules don't break the entire validation process
|
|
652
|
+
* - **Error Reporting**: Invalid rules are collected for user feedback
|
|
653
|
+
* - **Type Preservation**: Original rule formats are maintained in invalidRules array
|
|
654
|
+
* - **Validation Continuation**: Valid rules proceed through the validation pipeline
|
|
655
|
+
*
|
|
656
|
+
* @example
|
|
657
|
+
* ```typescript
|
|
658
|
+
* // Mixed rule formats with validation
|
|
659
|
+
* const mixedRules = [
|
|
660
|
+
* 'Required', // String rule
|
|
661
|
+
* 'MinLength[3]', // Parameterized string rule
|
|
662
|
+
* { MaxLength: [50] }, // Object rule
|
|
663
|
+
* ({ value }) => value.includes('@') || 'Must contain @', // Function rule
|
|
664
|
+
* 'InvalidRule', // Will be reported as invalid
|
|
665
|
+
* { NonExistentRule: ['param'] } // Will be reported as invalid
|
|
666
|
+
* ];
|
|
667
|
+
*
|
|
668
|
+
* const { sanitizedRules, invalidRules } = Validator.parseAndValidateRules(mixedRules);
|
|
669
|
+
*
|
|
670
|
+
* console.log('Successfully parsed rules:', sanitizedRules.length); // 4
|
|
671
|
+
* console.log('Invalid rules found:', invalidRules.length); // 2
|
|
672
|
+
* console.log('Invalid rules:', invalidRules);
|
|
673
|
+
* // Output: ['InvalidRule', { NonExistentRule: ['param'] }]
|
|
674
|
+
*
|
|
675
|
+
* // Each sanitized rule has consistent structure
|
|
676
|
+
* sanitizedRules.forEach(rule => {
|
|
677
|
+
* console.log(`Rule: ${rule.ruleName}, Params: ${rule.params.length}, Raw: ${rule.rawRuleName}`);
|
|
678
|
+
* });
|
|
679
|
+
*
|
|
680
|
+
* // Empty or undefined input handling
|
|
681
|
+
* const { sanitizedRules: emptyRules } = Validator.parseAndValidateRules();
|
|
682
|
+
* console.log('Empty input rules:', emptyRules.length); // 0
|
|
683
|
+
*
|
|
684
|
+
* const { sanitizedRules: undefinedRules } = Validator.parseAndValidateRules(undefined);
|
|
685
|
+
* console.log('Undefined input rules:', undefinedRules.length); // 0
|
|
686
|
+
*
|
|
687
|
+
* // Complex validation scenarios
|
|
688
|
+
* const complexRules = [
|
|
689
|
+
* 'Between[1,100]', // Numeric range
|
|
690
|
+
* { InArray: ['admin', 'user'] }, // Value enumeration
|
|
691
|
+
* ({ value, context }) => { // Context-aware function
|
|
692
|
+
* if (context?.userType === 'admin') return true;
|
|
693
|
+
* return value !== 'admin' || 'Admin access required';
|
|
694
|
+
* }
|
|
695
|
+
* ];
|
|
696
|
+
*
|
|
697
|
+
* const result = Validator.parseAndValidateRules(complexRules);
|
|
698
|
+
* console.log('Complex rules processed:', result.sanitizedRules.length); // 3
|
|
699
|
+
* console.log('No invalid rules:', result.invalidRules.length); // 0
|
|
700
|
+
*
|
|
701
|
+
* // Error handling in validation pipeline
|
|
702
|
+
* const validationRules = ['Required', 'Email', 'UnknownRule'];
|
|
703
|
+
* const { sanitizedRules: validRules, invalidRules: errors } = Validator.parseAndValidateRules(validationRules);
|
|
704
|
+
*
|
|
705
|
+
* if (errors.length > 0) {
|
|
706
|
+
* console.warn('Some validation rules are invalid:', errors);
|
|
707
|
+
* // Could log to monitoring system or throw custom error
|
|
708
|
+
* }
|
|
709
|
+
*
|
|
710
|
+
* // Proceed with valid rules only
|
|
711
|
+
* const validationResult = await Validator.validate({
|
|
712
|
+
* value: 'test@example.com',
|
|
713
|
+
* rules: validRules.map(rule => rule.ruleFunction) // Extract functions for validation
|
|
714
|
+
* });
|
|
715
|
+
* ```
|
|
716
|
+
*
|
|
717
|
+
* @template Context - Optional validation context type passed to rule functions
|
|
718
|
+
*
|
|
719
|
+
* @param inputRules - Array of validation rules in various formats, or undefined
|
|
720
|
+
* Supports strings, objects, and functions in any combination
|
|
721
|
+
* Undefined or empty arrays are handled gracefully
|
|
722
|
+
*
|
|
723
|
+
* @returns Structured result object containing processed rules and errors
|
|
724
|
+
* @returns returns.sanitizedRules - Array of successfully parsed rule objects with standardized structure
|
|
725
|
+
* @returns returns.invalidRules - Array of rules that couldn't be processed (maintains original format)
|
|
726
|
+
*
|
|
727
|
+
* @remarks
|
|
728
|
+
* - This method is the primary entry point for rule preprocessing in the validation pipeline
|
|
729
|
+
* - Called internally by `validate()` and `validateTarget()` before rule execution
|
|
730
|
+
* - Invalid rules are isolated rather than causing validation failures for better error handling
|
|
731
|
+
* - Supports all rule formats: functions, bracket strings, and parameter objects
|
|
732
|
+
* - Maintains type safety through TypeScript generics for context propagation
|
|
733
|
+
* - Rule registry lookup ensures only registered rules are accepted
|
|
734
|
+
* - Empty input (undefined/null) returns empty arrays without errors
|
|
735
|
+
* - Processing is synchronous and performant for large rule sets
|
|
736
|
+
* - Invalid rules preserve original input format for accurate error reporting
|
|
737
|
+
*
|
|
738
|
+
* @see {@link parseStringRule} - Internal method for parsing bracket notation strings
|
|
739
|
+
* @see {@link parseObjectRule} - Internal method for processing object notation rules
|
|
740
|
+
* @see {@link validate} - Main validation method that uses this preprocessing
|
|
741
|
+
* @see {@link validateTarget} - Class-based validation that uses this preprocessing
|
|
742
|
+
* @see {@link registerRule} - Method for registering custom rules in the system
|
|
743
|
+
* @see {@link getRules} - Method to retrieve all registered rules
|
|
744
|
+
* @see {@link ValidatorSanitizedRules} - Type definition for processed rules
|
|
745
|
+
* @public
|
|
746
|
+
*/
|
|
747
|
+
static parseAndValidateRules<Context = unknown>(inputRules?: ValidatorValidateOptions<ValidatorDefaultArray, Context>['rules']): {
|
|
748
|
+
sanitizedRules: ValidatorSanitizedRules<Context>;
|
|
749
|
+
invalidRules: ValidatorRules<Context>[];
|
|
750
|
+
};
|
|
751
|
+
/**
|
|
752
|
+
* ## Parse String-Based Validation Rules
|
|
753
|
+
*
|
|
754
|
+
* Internal helper method that parses string-format validation rules into standardized
|
|
755
|
+
* rule objects. Currently handles simple rule names without parameter parsing.
|
|
756
|
+
* This method is part of the rule preprocessing pipeline that converts various
|
|
757
|
+
* rule formats into a consistent internal representation.
|
|
758
|
+
*
|
|
759
|
+
* ### Current Supported String Formats
|
|
760
|
+
* - `"ruleName"` - Simple rule without parameters (e.g., `"Required"`, `"Email"`)
|
|
761
|
+
*
|
|
762
|
+
* ### Future Parameter Support (Currently Commented Out)
|
|
763
|
+
* The method includes commented code for bracket notation parameter parsing:
|
|
764
|
+
* - `"ruleName[param]"` - Rule with single parameter (planned)
|
|
765
|
+
* - `"ruleName[param1,param2,param3]"` - Rule with multiple parameters (planned)
|
|
766
|
+
*
|
|
767
|
+
* ### Processing Logic
|
|
768
|
+
* 1. **Input Validation**: Accepts any value, converts to trimmed string
|
|
769
|
+
* 2. **Rule Lookup**: Searches registered rules map using the string as rule name
|
|
770
|
+
* 3. **Function Retrieval**: Extracts the validation function if rule exists
|
|
771
|
+
* 4. **Object Construction**: Creates standardized rule object with metadata
|
|
772
|
+
* 5. **Error Handling**: Returns null if rule is not found in registry
|
|
773
|
+
*
|
|
774
|
+
* ### Parameter Handling
|
|
775
|
+
* - Currently no parameter parsing is performed
|
|
776
|
+
- All parameters must be provided via object notation: `{ RuleName: [params] }`
|
|
777
|
+
- Bracket notation parsing is reserved for future implementation
|
|
778
|
+
*
|
|
779
|
+
* ### Return Value Structure
|
|
780
|
+
* When successful, returns a complete rule object:
|
|
781
|
+
* ```typescript
|
|
782
|
+
* {
|
|
783
|
+
* ruleName: "Required", // The rule identifier
|
|
784
|
+
* params: [], // Empty array (no parsing yet)
|
|
785
|
+
* ruleFunction: Function, // The actual validation function
|
|
786
|
+
* rawRuleName: "Required" // Original input string
|
|
787
|
+
* }
|
|
788
|
+
* ```
|
|
789
|
+
*
|
|
790
|
+
* ### Error Cases
|
|
791
|
+
* - **Unknown Rule**: Returns `null` if rule name not found in registry
|
|
792
|
+
* - **Invalid Input**: Any input is accepted (converted to string)
|
|
793
|
+
* - **Empty String**: Empty/whitespace strings return `null`
|
|
794
|
+
*
|
|
795
|
+
* ### Examples
|
|
796
|
+
*
|
|
797
|
+
* #### Basic Rule Parsing
|
|
798
|
+
* ```typescript
|
|
799
|
+
* // Simple rule lookup
|
|
800
|
+
* const rule = Validator.parseStringRule("Required", registeredRules);
|
|
801
|
+
* // Returns: { ruleName: "Required", params: [], ruleFunction: fn, rawRuleName: "Required" }
|
|
802
|
+
*
|
|
803
|
+
* const emailRule = Validator.parseStringRule("Email", registeredRules);
|
|
804
|
+
* // Returns: { ruleName: "Email", params: [], ruleFunction: fn, rawRuleName: "Email" }
|
|
805
|
+
* ```
|
|
806
|
+
*
|
|
807
|
+
* #### Unknown Rule Handling
|
|
808
|
+
* ```typescript
|
|
809
|
+
* const unknownRule = Validator.parseStringRule("UnknownRule", registeredRules);
|
|
810
|
+
* // Returns: null (rule not found)
|
|
811
|
+
* ```
|
|
812
|
+
*
|
|
813
|
+
* #### Current Limitations
|
|
814
|
+
* ```typescript
|
|
815
|
+
* // These currently don't parse parameters (bracket notation commented out)
|
|
816
|
+
* const minLengthRule = Validator.parseStringRule("MinLength[5]", registeredRules);
|
|
817
|
+
* // Returns: null (looks for rule named "MinLength[5]")
|
|
818
|
+
*
|
|
819
|
+
* // Use object notation instead for parameters
|
|
820
|
+
* const objectRule = { MinLength: [5] };
|
|
821
|
+
* const parsedObject = Validator.parseObjectRule(objectRule, registeredRules);
|
|
822
|
+
* // Returns: [{ ruleName: "MinLength", params: [5], ruleFunction: fn, rawRuleName: "MinLength" }]
|
|
823
|
+
* ```
|
|
824
|
+
*
|
|
825
|
+
* #### Integration with Validation Pipeline
|
|
826
|
+
* ```typescript
|
|
827
|
+
* // Used internally by parseAndValidateRules
|
|
828
|
+
* const mixedRules = ["Required", "Email", { MinLength: [3] }];
|
|
829
|
+
* const { sanitizedRules, invalidRules } = Validator.parseAndValidateRules(mixedRules);
|
|
830
|
+
*
|
|
831
|
+
* // String rules are processed by this method
|
|
832
|
+
* // Object rules are processed by parseObjectRule
|
|
833
|
+
* // Function rules are used directly
|
|
834
|
+
* ```
|
|
835
|
+
*
|
|
836
|
+
* ### Performance Characteristics
|
|
837
|
+
* - **Fast Lookup**: O(1) hash map lookup in registered rules
|
|
838
|
+
* - **Minimal Processing**: Only string trimming and function lookup
|
|
839
|
+
* - **Memory Efficient**: Creates minimal rule objects
|
|
840
|
+
* - **Synchronous**: No async operations or I/O
|
|
841
|
+
*
|
|
842
|
+
* ### Future Enhancements
|
|
843
|
+
* - Implement bracket notation parameter parsing
|
|
844
|
+
* - Support nested parameter structures
|
|
845
|
+
* - Add parameter type validation
|
|
846
|
+
* - Support quoted parameters with spaces
|
|
847
|
+
*
|
|
848
|
+
* @template Context - Optional validation context type for rule functions
|
|
849
|
+
*
|
|
850
|
+
* @param ruleString - The string representation of the rule to parse
|
|
851
|
+
* Currently only supports simple rule names without parameters
|
|
852
|
+
* @param registeredRules - Map of all currently registered validation rules
|
|
853
|
+
* Used to lookup the validation function by rule name
|
|
854
|
+
*
|
|
855
|
+
* @returns ValidatorSanitizedRuleObject with standardized structure, or null if rule not found
|
|
856
|
+
* @returns returns.ruleName - The rule identifier (same as input string)
|
|
857
|
+
* @returns returns.params - Empty array (parameter parsing not implemented)
|
|
858
|
+
* @returns returns.ruleFunction - The actual validation function from registry
|
|
859
|
+
* @returns returns.rawRuleName - The original unparsed rule string
|
|
860
|
+
*
|
|
861
|
+
* @throws {Never} This method never throws errors; returns null for invalid rules
|
|
862
|
+
*
|
|
863
|
+
* @remarks
|
|
864
|
+
* - This is an internal method used by `parseAndValidateRules`
|
|
865
|
+
* - Parameter parsing via bracket notation is planned but currently commented out
|
|
866
|
+
* - For rules with parameters, use object notation: `{ RuleName: [param1, param2] }`
|
|
867
|
+
* - Rule registry lookup ensures only registered rules are accepted
|
|
868
|
+
* - Maintains type safety through TypeScript generics for context propagation
|
|
869
|
+
* - Processing is synchronous and performant for large rule sets
|
|
870
|
+
*
|
|
871
|
+
* @see {@link parseAndValidateRules} - Public method that orchestrates rule parsing
|
|
872
|
+
* @see {@link parseObjectRule} - Handles object notation rules with parameters
|
|
873
|
+
* @see {@link registerRule} - Method for registering rules in the system
|
|
874
|
+
* @see {@link getRules} - Method to retrieve all registered rules
|
|
875
|
+
* @see {@link ValidatorSanitizedRuleObject} - Type definition for parsed rules
|
|
876
|
+
*/
|
|
877
|
+
static parseStringRule<Context = unknown>(ruleString: string, registeredRules: ValidatorRuleFunctionsMap<Context>): any;
|
|
878
|
+
static parseFunctionRule<Context = unknown>(rule: ValidatorRuleFunction<ValidatorDefaultArray, Context>): ValidatorSanitizedRuleObject<ValidatorDefaultArray, Context>;
|
|
879
|
+
/**
|
|
880
|
+
* ## Parse Object Rule
|
|
881
|
+
*
|
|
882
|
+
* Parses object notation validation rules into standardized rule objects. This method handles
|
|
883
|
+
* rules specified as key-value pairs where the key is the rule name and the value is an array
|
|
884
|
+
* of parameters. Object notation allows for more complex rule configurations with explicit
|
|
885
|
+
* parameter passing.
|
|
886
|
+
*
|
|
887
|
+
* ### Object Rule Format
|
|
888
|
+
* Object rules are specified as JavaScript objects where:
|
|
889
|
+
* - **Keys**: Rule names (strings) that correspond to registered validation functions
|
|
890
|
+
* - **Values**: Arrays of parameters to pass to the validation rule function
|
|
891
|
+
*
|
|
892
|
+
* ### Processing Logic
|
|
893
|
+
* 1. **Input Validation**: Ensures `rulesObject` is a valid object, returns empty array otherwise
|
|
894
|
+
* 2. **Rule Iteration**: Iterates through each property in the rules object
|
|
895
|
+
* 3. **Rule Lookup**: Checks if each rule name exists in the registered rules map
|
|
896
|
+
* 4. **Parameter Extraction**: Retrieves the parameter array for each valid rule
|
|
897
|
+
* 5. **Rule Construction**: Creates sanitized rule objects with function references and parameters
|
|
898
|
+
* 6. **Result Aggregation**: Collects all valid rules into a result array
|
|
899
|
+
*
|
|
900
|
+
* ### Parameter Requirements
|
|
901
|
+
* - Rule parameters must be arrays (e.g., `[5]` for MinLength, `[10, 100]` for range rules)
|
|
902
|
+
* - Non-array parameters are ignored (future enhancement may support other formats)
|
|
903
|
+
* - Invalid rule names (not registered) are silently skipped
|
|
904
|
+
*
|
|
905
|
+
* ### Examples
|
|
906
|
+
*
|
|
907
|
+
* #### Basic Object Rules
|
|
908
|
+
* ```typescript
|
|
909
|
+
* const rulesObject = {
|
|
910
|
+
* Required: [], // No parameters
|
|
911
|
+
* MinLength: [5], // Single parameter
|
|
912
|
+
* MaxLength: [50], // Single parameter
|
|
913
|
+
* Pattern: ['^[A-Z]+$'], // String parameter
|
|
914
|
+
* };
|
|
915
|
+
*
|
|
916
|
+
* const parsedRules = Validator.parseObjectRule(rulesObject, registeredRules);
|
|
917
|
+
* // Returns array of sanitized rule objects with function references
|
|
918
|
+
* ```
|
|
919
|
+
*
|
|
920
|
+
* #### Complex Parameter Rules
|
|
921
|
+
* ```typescript
|
|
922
|
+
* const complexRules = {
|
|
923
|
+
* Range: [1, 100], // Multiple numeric parameters
|
|
924
|
+
* CustomPattern: ['^[0-9]{3}-[0-9]{3}$'], // Regex pattern
|
|
925
|
+
* Enum: [['active', 'inactive', 'pending']], // Array parameter
|
|
926
|
+
* };
|
|
927
|
+
* ```
|
|
928
|
+
*
|
|
929
|
+
* #### Mixed with String Rules
|
|
930
|
+
* ```typescript
|
|
931
|
+
* // Object rules are typically used alongside string rules
|
|
932
|
+
* const allRules = [
|
|
933
|
+
* "Required", // String rule
|
|
934
|
+
* "Email", // String rule
|
|
935
|
+
* { MinLength: [3] }, // Object rule
|
|
936
|
+
* { MaxLength: [100] }, // Object rule
|
|
937
|
+
* ];
|
|
938
|
+
*
|
|
939
|
+
* const result = Validator.parseAndValidateRules(allRules);
|
|
940
|
+
* ```
|
|
941
|
+
*
|
|
942
|
+
* #### Integration with Validation Pipeline
|
|
943
|
+
* ```typescript
|
|
944
|
+
* // Object rules are processed during rule parsing phase
|
|
945
|
+
* const inputRules = [
|
|
946
|
+
* "Required",
|
|
947
|
+
* { MinLength: [5], MaxLength: [50] },
|
|
948
|
+
* "Email"
|
|
949
|
+
* ];
|
|
950
|
+
*
|
|
951
|
+
* const { sanitizedRules } = Validator.parseAndValidateRules(inputRules);
|
|
952
|
+
* // sanitizedRules contains both string and object rule objects
|
|
953
|
+
* ```
|
|
954
|
+
*
|
|
955
|
+
* ### Error Handling
|
|
956
|
+
* - **Invalid Input**: Returns empty array if `rulesObject` is not an object
|
|
957
|
+
* - **Missing Rules**: Unregistered rule names are ignored (not added to result)
|
|
958
|
+
* - **Invalid Parameters**: Non-array parameters are ignored
|
|
959
|
+
* - **No Errors Thrown**: Method is robust and never throws exceptions
|
|
960
|
+
*
|
|
961
|
+
* ### Performance Characteristics
|
|
962
|
+
* - **Linear Time**: O(n) where n is the number of properties in rules object
|
|
963
|
+
* - **Memory Efficient**: Creates minimal objects with function references
|
|
964
|
+
* - **Registry Lookup**: Fast hash map lookup for rule function existence
|
|
965
|
+
* - **Parameter Validation**: Lightweight array type checking
|
|
966
|
+
*
|
|
967
|
+
* ### Future Enhancements
|
|
968
|
+
* - Support for non-array parameter formats (single values, objects)
|
|
969
|
+
* - Parameter type validation against rule function signatures
|
|
970
|
+
* - Rule dependency resolution and ordering
|
|
971
|
+
* - Enhanced error reporting for invalid configurations
|
|
972
|
+
*
|
|
973
|
+
* @template Context - Optional type for validation context passed to rule functions
|
|
974
|
+
*
|
|
975
|
+
* @param rulesObject - Object containing rule names as keys and parameter arrays as values
|
|
976
|
+
* @param registeredRules - Map of registered validation rule functions for lookup
|
|
977
|
+
*
|
|
978
|
+
* @returns Array of sanitized rule objects with function references and parameters
|
|
979
|
+
* - Empty array if input is invalid or no valid rules found
|
|
980
|
+
* - Each object contains: ruleName, ruleFunction, params, rawRuleName
|
|
981
|
+
*
|
|
982
|
+
* @throws {Never} This method never throws errors; invalid inputs return empty arrays
|
|
983
|
+
*
|
|
984
|
+
* @remarks
|
|
985
|
+
* - This method complements `parseStringRule` for different rule input formats
|
|
986
|
+
* - Object rules enable complex parameter passing not possible with string notation
|
|
987
|
+
* - Rules are validated against the registry to ensure only registered functions are used
|
|
988
|
+
* - Parameter arrays are passed directly to rule functions without modification
|
|
989
|
+
* - The method is part of the rule preprocessing pipeline in `parseAndValidateRules`
|
|
990
|
+
*
|
|
991
|
+
* @see {@link parseStringRule} - Handles string notation rules without parameters
|
|
992
|
+
* @see {@link parseAndValidateRules} - Main rule parsing method that uses this function
|
|
993
|
+
* @see {@link registerRule} - How rules are registered in the rule functions map
|
|
994
|
+
* @see {@link ValidatorRuleObject} - Type definition for object rule format
|
|
995
|
+
* @see {@link ValidatorSanitizedRuleObject} - Type definition for parsed rule objects
|
|
996
|
+
*
|
|
997
|
+
* @public
|
|
998
|
+
* @static
|
|
999
|
+
*/
|
|
1000
|
+
static parseObjectRule<Context = unknown>(rulesObject: ValidatorRuleObject, registeredRules: ValidatorRuleFunctionsMap<Context>): ValidatorSanitizedRuleObject<ValidatorDefaultArray, Context>[];
|
|
1001
|
+
static isSanitizedRuleObject<TRuleParams extends ValidatorDefaultArray = ValidatorDefaultArray, Context = unknown>(rule: any): rule is ValidatorSanitizedRuleObject<TRuleParams, Context>;
|
|
1002
|
+
/**
|
|
1003
|
+
* ## Validate - Core Single-Value Validation Engine
|
|
1004
|
+
*
|
|
1005
|
+
* Executes comprehensive validation on a single value against an array of validation rules.
|
|
1006
|
+
* This method implements the core validation pipeline with sequential rule execution,
|
|
1007
|
+
* multi-rule delegation, and sophisticated error handling for complex validation scenarios.
|
|
1008
|
+
*
|
|
1009
|
+
* ### Validation Pipeline Overview
|
|
1010
|
+
* 1. **Rule Parsing**: Validates and sanitizes input rules using `parseAndValidateRules`
|
|
1011
|
+
* 2. **Invalid Rule Handling**: Returns failure result for any invalid rules
|
|
1012
|
+
* 3. **Nullable Skip Check**: Skips validation if value meets nullable rule conditions
|
|
1013
|
+
* 4. **Sequential Execution**: Processes rules one-by-one using Promise-based `next()` function
|
|
1014
|
+
* 5. **Multi-Rule Detection**: Delegates to specialized handlers for OneOf/AllOf/ArrayOf/ValidateNested
|
|
1015
|
+
* 6. **Result Processing**: Handles boolean, string, and Error results with proper error creation
|
|
1016
|
+
* 7. **Error Aggregation**: Returns discriminated union result (success/failure)
|
|
1017
|
+
*
|
|
1018
|
+
* ### Rule Execution Strategy
|
|
1019
|
+
* - **Sequential Processing**: Rules execute one after another, not in parallel
|
|
1020
|
+
* - **Early Exit**: Validation stops on first rule failure (fail-fast behavior)
|
|
1021
|
+
* - **Rule Context**: Each rule receives i18n context, field names, and validation options
|
|
1022
|
+
* - **Parameter Extraction**: Automatically extracts rule names and parameters from various formats
|
|
1023
|
+
*
|
|
1024
|
+
* ### Multi-Rule Support
|
|
1025
|
+
* The method automatically detects and delegates to specialized validators:
|
|
1026
|
+
* - **OneOf/AllOf**: Uses symbol markers to identify logical combination rules
|
|
1027
|
+
* - **ArrayOf**: Validates arrays where each item must satisfy sub-rules
|
|
1028
|
+
* - **ValidateNested**: Delegates to class-based validation for nested objects
|
|
1029
|
+
*
|
|
1030
|
+
* ### Error Handling Architecture
|
|
1031
|
+
* - **Boolean Results**: `false` → creates validation error with i18n message
|
|
1032
|
+
* - **String Results**: Direct error messages (validated for non-null strings)
|
|
1033
|
+
* - **Error Objects**: Stringifies thrown errors and creates validation errors
|
|
1034
|
+
* - **Invalid Messages**: Falls back to i18n for null/undefined string results
|
|
1035
|
+
*
|
|
1036
|
+
* ### Nullable Rule Behavior
|
|
1037
|
+
* Skips remaining validation when nullable conditions are met:
|
|
1038
|
+
* - **Empty**: Skips if value is empty string `""`
|
|
1039
|
+
* - **Nullable**: Skips if value is `null` or `undefined`
|
|
1040
|
+
* - **Optional**: Skips if value is `undefined` only
|
|
1041
|
+
*
|
|
1042
|
+
* ### Rule Format Support
|
|
1043
|
+
* Accepts rules in multiple formats with automatic parameter extraction:
|
|
1044
|
+
* - **String Rules**: `"Email"`, `"Required"`, `"MinLength[5]"`
|
|
1045
|
+
* - **Function Rules**: Direct validator functions with optional parameters
|
|
1046
|
+
* - **Object Rules**: `{ ruleFunction, params, ruleName }` structured objects
|
|
1047
|
+
*
|
|
1048
|
+
* ### Context Propagation
|
|
1049
|
+
* Builds comprehensive context for each rule execution:
|
|
1050
|
+
* - **i18n Options**: Translation keys, field names, rule information
|
|
1051
|
+
* - **Validation Data**: Current value, rule parameters, context object
|
|
1052
|
+
* - **Field Metadata**: Property names, translated names, data references
|
|
1053
|
+
*
|
|
1054
|
+
* #### Basic Usage Examples
|
|
1055
|
+
* ```typescript
|
|
1056
|
+
* // Simple string validation
|
|
1057
|
+
* const result1 = await Validator.validate({
|
|
1058
|
+
* value: "test@example.com",
|
|
1059
|
+
* rules: ["Required", "Email"],
|
|
1060
|
+
* });
|
|
1061
|
+
* // result1.success === true
|
|
1062
|
+
*
|
|
1063
|
+
* // Numeric validation with parameters
|
|
1064
|
+
* const result2 = await Validator.validate({
|
|
1065
|
+
* value: 25,
|
|
1066
|
+
* rules: ["Required", { Min: [18] }, { Max: [65] }],
|
|
1067
|
+
* });
|
|
1068
|
+
* // result2.success === true
|
|
1069
|
+
*
|
|
1070
|
+
* // Function-based validation
|
|
1071
|
+
* const result3 = await Validator.validate({
|
|
1072
|
+
* value: "custom",
|
|
1073
|
+
* rules: [({ value }) => value.startsWith("prefix") || "Must start with prefix"],
|
|
1074
|
+
* });
|
|
1075
|
+
* // result3.success === true
|
|
1076
|
+
* ```
|
|
1077
|
+
*
|
|
1078
|
+
* #### Nullable Rule Examples
|
|
1079
|
+
* ```typescript
|
|
1080
|
+
* // Empty allows skipping other rules
|
|
1081
|
+
* const result1 = await Validator.validate({
|
|
1082
|
+
* value: "",
|
|
1083
|
+
* rules: ["Empty", "Email"],
|
|
1084
|
+
* });
|
|
1085
|
+
* // result1.success === true (skips Email check)
|
|
1086
|
+
*
|
|
1087
|
+
* // Nullable allows null/undefined
|
|
1088
|
+
* const result2 = await Validator.validate({
|
|
1089
|
+
* value: null,
|
|
1090
|
+
* rules: ["Nullable", "IsNumber"],
|
|
1091
|
+
* });
|
|
1092
|
+
* // result2.success === true (skips IsNumber check)
|
|
1093
|
+
*
|
|
1094
|
+
* // Optional allows undefined only
|
|
1095
|
+
* const result3 = await Validator.validate({
|
|
1096
|
+
* value: undefined,
|
|
1097
|
+
* rules: ["Optional", "MinLength[5]"],
|
|
1098
|
+
* });
|
|
1099
|
+
* // result3.success === true (skips MinLength check)
|
|
1100
|
+
* ```
|
|
1101
|
+
*
|
|
1102
|
+
* #### Multi-Rule Examples
|
|
1103
|
+
* ```typescript
|
|
1104
|
+
* // OneOf: email OR phone required
|
|
1105
|
+
* const result1 = await Validator.validate({
|
|
1106
|
+
* value: "user@example.com",
|
|
1107
|
+
* rules: [Validator.oneOf(["Email", "PhoneNumber"])],
|
|
1108
|
+
* });
|
|
1109
|
+
* // result1.success === true
|
|
1110
|
+
*
|
|
1111
|
+
* // ArrayOf: validate array items
|
|
1112
|
+
* const result2 = await Validator.validate({
|
|
1113
|
+
* value: ["a@b.com", "c@d.com"],
|
|
1114
|
+
* rules: [Validator.arrayOf(["Email"])],
|
|
1115
|
+
* });
|
|
1116
|
+
* // result2.success === true
|
|
1117
|
+
*
|
|
1118
|
+
* // AllOf: must satisfy all conditions
|
|
1119
|
+
* const result3 = await Validator.validate({
|
|
1120
|
+
* value: "hello",
|
|
1121
|
+
* rules: [Validator.allOf(["String", { MinLength: [3] }])],
|
|
1122
|
+
* });
|
|
1123
|
+
* // result3.success === true
|
|
1124
|
+
* ```
|
|
1125
|
+
*
|
|
1126
|
+
* #### Type Guards for Result Narrowing
|
|
1127
|
+
* ```typescript
|
|
1128
|
+
* const result = await Validator.validate({
|
|
1129
|
+
* value: "test",
|
|
1130
|
+
* rules: ["Required"],
|
|
1131
|
+
* });
|
|
1132
|
+
*
|
|
1133
|
+
* // Using type guards
|
|
1134
|
+
* if (Validator.isSuccess(result)) {
|
|
1135
|
+
* // TypeScript knows result.success === true
|
|
1136
|
+
* console.log("Valid value:", result.value);
|
|
1137
|
+
* } else if (Validator.isFailure(result)) {
|
|
1138
|
+
* // TypeScript knows result.success === false
|
|
1139
|
+
* console.error("Error:", result.error.message);
|
|
1140
|
+
* }
|
|
1141
|
+
* ```
|
|
1142
|
+
*
|
|
1143
|
+
* ### Technical Implementation Details
|
|
1144
|
+
* - **Promise-Based Execution**: Uses recursive `next()` function for sequential processing
|
|
1145
|
+
* - **Symbol-Based Detection**: Identifies multi-rules using internal symbol markers
|
|
1146
|
+
* - **Context Building**: Constructs i18n and validation context for each rule
|
|
1147
|
+
* - **Error Creation**: Uses `createValidationError` for consistent error objects
|
|
1148
|
+
* - **Duration Tracking**: Measures validation execution time from start to finish
|
|
1149
|
+
*
|
|
1150
|
+
* @template Context - Optional type for the validation context object
|
|
1151
|
+
*
|
|
1152
|
+
* @param options - Validation options (MakeOptional<
|
|
1153
|
+
ValidatorValidateOptions<ValidatorDefaultArray, Context>,
|
|
1154
|
+
"i18n"
|
|
1155
|
+
>)
|
|
1156
|
+
* @param options.value - The value to validate (required)
|
|
1157
|
+
* @param options.rules - Array of validation rules to apply
|
|
1158
|
+
* @param options.context - Optional context object passed to rule functions
|
|
1159
|
+
* @param options.data - Optional data object for rule context
|
|
1160
|
+
* @param options.fieldName - Optional field identifier for error messages
|
|
1161
|
+
* @param options.propertyName - Optional property identifier for error messages
|
|
1162
|
+
* @param options.translatedPropertyName - Optional translated property name
|
|
1163
|
+
* @param options.message - Optional custom error message prefix
|
|
1164
|
+
*
|
|
1165
|
+
* @returns Promise resolving to ValidatorValidateResult<Context>
|
|
1166
|
+
* - Success: object with success=true, value, validatedAt, duration
|
|
1167
|
+
* - Failure: object with success=false, error, failedAt, duration
|
|
1168
|
+
*
|
|
1169
|
+
* @throws {Never} This method never throws. All errors are returned in the result object.
|
|
1170
|
+
*
|
|
1171
|
+
*
|
|
1172
|
+
* @see {@link validateTarget} - For class-based validation using decorators
|
|
1173
|
+
* @see {@link registerRule} - To register custom validation rules
|
|
1174
|
+
* @see {@link parseAndValidateRules} - Internal rule parsing and validation
|
|
1175
|
+
* @see {@link shouldSkipValidation} - Nullable rule checking logic
|
|
1176
|
+
* @see {@link validateMultiRule} - OneOf/AllOf rule implementation
|
|
1177
|
+
* @see {@link validateArrayOfRule} - ArrayOf rule implementation
|
|
1178
|
+
* @see {@link validateNestedRule} - ValidateNested rule implementation
|
|
1179
|
+
* @see {@link ValidatorValidateResult} - Result type documentation
|
|
1180
|
+
* @see {@link ValidatorValidationError} - Error details type
|
|
1181
|
+
*
|
|
1182
|
+
* @public
|
|
1183
|
+
* @async
|
|
1184
|
+
*/
|
|
1185
|
+
static validate<Context = unknown>({ rules, ...extra }: MakeOptional<ValidatorValidateOptions<ValidatorDefaultArray, Context>, 'i18n' | 'ruleParams'>): Promise<ValidatorValidateResult<Context>>;
|
|
1186
|
+
/**
|
|
1187
|
+
* ## Should Skip Validation
|
|
1188
|
+
*
|
|
1189
|
+
* Determines whether validation should be skipped based on the presence of nullable rules
|
|
1190
|
+
* and the current value. This method checks if the value meets the conditions for
|
|
1191
|
+
* skipping validation when nullable rules like Empty, Nullable, or Optional are present
|
|
1192
|
+
* in the rules array.
|
|
1193
|
+
*
|
|
1194
|
+
* ### Nullable Rules and Conditions
|
|
1195
|
+
* - **Empty**: Skips validation if value is an empty string ""
|
|
1196
|
+
* - **Nullable**: Skips validation if value is null or undefined
|
|
1197
|
+
* - **Optional**: Skips validation if value is undefined
|
|
1198
|
+
*
|
|
1199
|
+
* ### Logic
|
|
1200
|
+
* 1. Only checks when the value is considered "empty" (using isEmpty utility)
|
|
1201
|
+
* 2. Iterates through the rules array to find matching nullable rule names
|
|
1202
|
+
* 3. Supports both string rules ("Empty") and object rules ({ Empty: [] })
|
|
1203
|
+
* 4. Returns true if any matching nullable rule condition is met
|
|
1204
|
+
* 5. Function rules are ignored in this check
|
|
1205
|
+
*
|
|
1206
|
+
* @param options - The options object containing value and rules
|
|
1207
|
+
* @param options.value - The value to check for nullable conditions
|
|
1208
|
+
* @param options.rules - The array of validation rules to inspect for nullable rules
|
|
1209
|
+
*
|
|
1210
|
+
* @returns `true` if validation should be skipped due to nullable conditions, `false` otherwise
|
|
1211
|
+
*
|
|
1212
|
+
*
|
|
1213
|
+
* @see {@link validate} - Uses this method to conditionally skip validation
|
|
1214
|
+
* @see {@link validateTarget} - Also uses this method for class-based validation
|
|
1215
|
+
* @public
|
|
1216
|
+
*/
|
|
1217
|
+
static shouldSkipValidation<Context = unknown>({ value, rules, }: {
|
|
1218
|
+
rules: Array<ValidatorRuleName> | ValidatorSanitizedRules<Context>;
|
|
1219
|
+
value: unknown;
|
|
1220
|
+
}): boolean;
|
|
1221
|
+
/**
|
|
1222
|
+
* ## Validate OneOf Rule
|
|
1223
|
+
*
|
|
1224
|
+
* Wrapper that applies OR logic across multiple sub-rules. Delegates to
|
|
1225
|
+
* {@link validateMultiRule} with `"OneOf"`. Succeeds on the first passing
|
|
1226
|
+
* sub-rule (early exit). If all sub-rules fail, returns a single error string
|
|
1227
|
+
* aggregating each sub-rule’s message joined by `; `.
|
|
1228
|
+
*
|
|
1229
|
+
* @template Context - Optional type for validation context
|
|
1230
|
+
* @template RulesFunctions - Array of sub-rules to evaluate
|
|
1231
|
+
* @param options - Multi-rule validation options
|
|
1232
|
+
* @returns `ValidatorResult` (`ValidatorAsyncResult`)
|
|
1233
|
+
* @example
|
|
1234
|
+
* const res = await Validator.validateOneOfRule({
|
|
1235
|
+
* value: "user@example.com",
|
|
1236
|
+
* ruleParams: ["Email", "PhoneNumber"],
|
|
1237
|
+
* });
|
|
1238
|
+
* // res === true when any sub-rule succeeds
|
|
1239
|
+
*
|
|
1240
|
+
* @see {@link validateMultiRule}
|
|
1241
|
+
*/
|
|
1242
|
+
static validateOneOfRule<Context = unknown, RulesFunctions extends ValidatorDefaultMultiRule<Context> = ValidatorDefaultMultiRule<Context>>(options: ValidatorValidateMultiRuleOptions<Context, RulesFunctions>): ValidatorResult;
|
|
1243
|
+
/**
|
|
1244
|
+
* ## Validate AllOf Rule
|
|
1245
|
+
*
|
|
1246
|
+
* Wrapper that applies AND logic across multiple sub-rules. Delegates to
|
|
1247
|
+
* {@link validateMultiRule} with `"AllOf"`. Succeeds only if all sub-rules
|
|
1248
|
+
* pass. When any sub-rule fails, returns a single aggregated error string
|
|
1249
|
+
* joining messages with `; `.
|
|
1250
|
+
*
|
|
1251
|
+
* @template Context - Optional type for validation context
|
|
1252
|
+
* @template RulesFunctions - Array of sub-rules to evaluate
|
|
1253
|
+
* @param options - Multi-rule validation options
|
|
1254
|
+
* @returns `ValidatorResult` (`ValidatorAsyncResult`)
|
|
1255
|
+
* @example
|
|
1256
|
+
* const res = await Validator.validateAllOfRule({
|
|
1257
|
+
* value: "hello",
|
|
1258
|
+
* ruleParams: ["String", { MinLength: [5] }],
|
|
1259
|
+
* });
|
|
1260
|
+
* // res === true only if all sub-rules succeed
|
|
1261
|
+
*
|
|
1262
|
+
* @see {@link validateMultiRule}
|
|
1263
|
+
*/
|
|
1264
|
+
static validateAllOfRule<Context = unknown, RulesFunctions extends ValidatorDefaultMultiRule<Context> = ValidatorDefaultMultiRule<Context>>(options: ValidatorValidateMultiRuleOptions<Context, RulesFunctions>): ValidatorResult;
|
|
1265
|
+
/**
|
|
1266
|
+
* ## Validate ArrayOf Rule
|
|
1267
|
+
*
|
|
1268
|
+
* Validates that a value is an array and that each item in the array
|
|
1269
|
+
* satisfies all of the provided sub-rules (AND logic per item).
|
|
1270
|
+
*
|
|
1271
|
+
* - Ensures `value` is an array; otherwise returns the localized `array` error.
|
|
1272
|
+
* - Applies {@link validateMultiRule} with `"AllOf"` to each item using the provided `ruleParams`.
|
|
1273
|
+
* - Aggregates failing item messages; returns `true` when all items pass.
|
|
1274
|
+
* - When any items fail, returns a localized summary using `failedForNItems`
|
|
1275
|
+
* followed by concatenated item error messages.
|
|
1276
|
+
*
|
|
1277
|
+
* @template Context - Optional type for validation context
|
|
1278
|
+
* @template RulesFunctions - Array of sub-rules applied to each item
|
|
1279
|
+
* @param options - Multi-rule validation options
|
|
1280
|
+
* @returns `ValidatorResult` (`ValidatorAsyncResult`) - `true` if all items pass; otherwise an aggregated error string
|
|
1281
|
+
* @example
|
|
1282
|
+
* const res = await Validator.validateArrayOfRule({
|
|
1283
|
+
* value: ["user@example.com", "admin@example.com"],
|
|
1284
|
+
* ruleParams: ["Email"],
|
|
1285
|
+
* });
|
|
1286
|
+
* // res === true when every item is a valid email
|
|
1287
|
+
*
|
|
1288
|
+
*/
|
|
1289
|
+
static validateArrayOfRule<Context = unknown, RulesFunctions extends ValidatorDefaultMultiRule<Context> = ValidatorDefaultMultiRule<Context>>(options: ValidatorValidateMultiRuleOptions<Context, RulesFunctions>): ValidatorAsyncResult;
|
|
1290
|
+
static getI18nTranslateOptions<Context = unknown>({ fieldName, propertyName, fieldLabel, translatedPropertyName, context, ...rest }: Partial<ValidatorValidateOptions<ValidatorDefaultArray, Context>>): Partial<ValidatorValidateOptions<ValidatorDefaultArray, Context>>;
|
|
1291
|
+
/**
|
|
1292
|
+
* ## Validate Nested Rule (Core Nested Validation Executor)
|
|
1293
|
+
*
|
|
1294
|
+
* Internal rule function that validates a nested object against a class constructor with
|
|
1295
|
+
* validation decorators. This method is the workhorse for nested class validation, delegating
|
|
1296
|
+
* to {@link validateTarget} for the actual multi-field validation logic.
|
|
1297
|
+
*
|
|
1298
|
+
* ### Purpose
|
|
1299
|
+
* This method implements the core logic for the `ValidateNested` rule, enabling validation of
|
|
1300
|
+
* complex hierarchical object structures where a property value must itself be validated against
|
|
1301
|
+
* a decorated class schema. It acts as the bridge between single-value rule validation and
|
|
1302
|
+
* multi-field class-based validation.
|
|
1303
|
+
*
|
|
1304
|
+
* ### Validation Flow
|
|
1305
|
+
* 1. **Parameter Extraction**: Extracts the target class constructor from `ruleParams[0]`
|
|
1306
|
+
* 2. **Validation**: Calls `validateTarget()` to validate the nested object against the class
|
|
1307
|
+
* 3. **Error Aggregation**: Collects nested validation errors with property path information
|
|
1308
|
+
* 4. **Result Formatting**: Returns either `true` (success) or error message string (failure)
|
|
1309
|
+
*
|
|
1310
|
+
* ### Error Handling Strategy
|
|
1311
|
+
* - **Missing Class Constructor**: Returns invalidRule error if no target class provided
|
|
1312
|
+
* - **Invalid Data Type**: Returns validateNested error if data is not an object
|
|
1313
|
+
* - **Nested Validation Failures**: Aggregates all nested field errors with property names in format:
|
|
1314
|
+
* `"[propertyName]: error message; [propertyName]: error message"`
|
|
1315
|
+
* - **Successful Validation**: Returns `true` without modification
|
|
1316
|
+
*
|
|
1317
|
+
* ### Type Parameters
|
|
1318
|
+
* - `Target` - Class constructor extending ClassConstructor with validation decorators
|
|
1319
|
+
* - `Context` - Optional validation context type passed through nested validations
|
|
1320
|
+
*
|
|
1321
|
+
* ### Return Values
|
|
1322
|
+
* - `true` - Nested object validation succeeded
|
|
1323
|
+
* - `string` - Validation failed; returns i18n-formatted error message with nested error details
|
|
1324
|
+
*
|
|
1325
|
+
* ### Usage Context
|
|
1326
|
+
* This method is primarily used as:
|
|
1327
|
+
* - The internal handler for the `validateNested` factory function
|
|
1328
|
+
* - A sub-rule within multi-rule validators (OneOf, AllOf)
|
|
1329
|
+
* - Direct validator for nested object properties in class-based validation
|
|
1330
|
+
*
|
|
1331
|
+
* ### Example
|
|
1332
|
+
* ```typescript
|
|
1333
|
+
* class Address {
|
|
1334
|
+
* @IsRequired()
|
|
1335
|
+
* @MinLength(5)
|
|
1336
|
+
* street: string;
|
|
1337
|
+
*
|
|
1338
|
+
* @IsRequired()
|
|
1339
|
+
* @IsPostalCode
|
|
1340
|
+
* postalCode: string;
|
|
1341
|
+
* }
|
|
1342
|
+
*
|
|
1343
|
+
* class User {
|
|
1344
|
+
* @IsRequired()
|
|
1345
|
+
* name: string;
|
|
1346
|
+
*
|
|
1347
|
+
* @ValidateNested(Address)
|
|
1348
|
+
* address: Address;
|
|
1349
|
+
* }
|
|
1350
|
+
*
|
|
1351
|
+
* // When validating a User instance with an Address property,
|
|
1352
|
+
* // validateNestedRule is called to validate the address against the Address class
|
|
1353
|
+
* const result = await Validator.validateTarget(User, {
|
|
1354
|
+
* data : {
|
|
1355
|
+
* name: "John",
|
|
1356
|
+
* address: { street: "123 Main St", postalCode: "12345" }
|
|
1357
|
+
* }});
|
|
1358
|
+
* ```
|
|
1359
|
+
*
|
|
1360
|
+
* ### Key Features
|
|
1361
|
+
* - **DRY Principle**: Reuses existing `validateTarget` logic to avoid code duplication
|
|
1362
|
+
* - **Error Context**: Preserves field hierarchy information in error messages
|
|
1363
|
+
* - **i18n Integration**: Uses translation system for localized error messages
|
|
1364
|
+
* - **Context Propagation**: Passes validation context through to nested validators
|
|
1365
|
+
* - **Timing Tracking**: Maintains duration tracking across nested validations
|
|
1366
|
+
*
|
|
1367
|
+
* @template Target - Class constructor type (must extend ClassConstructor)
|
|
1368
|
+
* @template Context - Optional validation context type
|
|
1369
|
+
*
|
|
1370
|
+
* @param options - Validation rule function options (ValidatorNestedRuleFunctionOptions<Target, Context>)
|
|
1371
|
+
* @param options.ruleParams - Array containing the nested class constructor at index [0]
|
|
1372
|
+
* @param options.value - The nested object value to validate (extracted to data property)
|
|
1373
|
+
* @param options.data - The nested object data to validate against the target class
|
|
1374
|
+
* @param options.context - Optional validation context passed to all validation rules
|
|
1375
|
+
* @param options.fieldName - Optional field identifier for error messages
|
|
1376
|
+
* @param options.propertyName - Optional property identifier for error messages
|
|
1377
|
+
* @param options.translatedPropertyName - Optional i18n property name for error messages
|
|
1378
|
+
* @param options.startTime - Optional timestamp for duration tracking
|
|
1379
|
+
* @param options.i18n - Optional i18n instance for error message translation
|
|
1380
|
+
*
|
|
1381
|
+
* @returns {ValidatorAsyncResult}
|
|
1382
|
+
* - Resolves to `true` if nested object validation succeeds
|
|
1383
|
+
* - Resolves to error message string if validation fails
|
|
1384
|
+
* - Never rejects; all errors are returned as resolution values
|
|
1385
|
+
* - Error messages include nested field paths: `"[fieldName]: error; [fieldName]: error"`
|
|
1386
|
+
*
|
|
1387
|
+
* @throws {Never} This method never throws errors; all failures are returned as strings
|
|
1388
|
+
*
|
|
1389
|
+
* @remarks
|
|
1390
|
+
* - This is an internal method primarily used by the `validateNested` factory
|
|
1391
|
+
* - Accepts ValidatorNestedRuleFunctionOptions which omits validateTarget's i18n parameter
|
|
1392
|
+
* - Delegates directly to validateTarget(target, options) maintaining all context
|
|
1393
|
+
* - Nested validation errors include property names for clear error tracing
|
|
1394
|
+
* - The method integrates seamlessly with the multi-rule validation system
|
|
1395
|
+
* - Supports recursive nesting of arbitrarily deep object structures
|
|
1396
|
+
* - Performance: Delegates to validateTarget which validates fields in parallel
|
|
1397
|
+
* - Error aggregation uses nested field paths for hierarchical clarity
|
|
1398
|
+
*
|
|
1399
|
+
*
|
|
1400
|
+
* @see {@link validateNested} - Factory function that creates rule functions using this method
|
|
1401
|
+
* @see {@link validateTarget} - The underlying class-based validation method (accepts options with data)
|
|
1402
|
+
* @see {@link ValidateNested} - Decorator that uses this method via the factory
|
|
1403
|
+
* @see {@link ValidatorNestedRuleFunctionOptions} - Options interface for this method
|
|
1404
|
+
* @see {@link buildMultiRuleDecorator} - Decorator builder for complex multi-rule scenarios
|
|
1405
|
+
* @internal
|
|
1406
|
+
* @async
|
|
1407
|
+
*/
|
|
1408
|
+
static validateNestedRule<Target extends ClassConstructor = ClassConstructor, Context = unknown>({ ruleParams, ...options }: ValidatorNestedRuleFunctionOptions<Target, Context>): ValidatorAsyncResult;
|
|
1409
|
+
/**
|
|
1410
|
+
* ## Validate Multi-Rule (OneOf / AllOf)
|
|
1411
|
+
*
|
|
1412
|
+
* Evaluates multiple sub-rules against a single value using either OR logic (`OneOf`) or
|
|
1413
|
+
* AND logic (`AllOf`). Each sub-rule is validated in sequence via {@link Validator.validate},
|
|
1414
|
+
* with early exit on success for `OneOf` and full aggregation of errors for `AllOf`.
|
|
1415
|
+
*
|
|
1416
|
+
* ### Behavior
|
|
1417
|
+
* - `OneOf`: Returns `true` as soon as any sub-rule succeeds (early exit). If all sub-rules fail,
|
|
1418
|
+
* returns a concatenated error message string summarizing each failure.
|
|
1419
|
+
* - `AllOf`: Requires every sub-rule to succeed. If any sub-rule fails, returns a concatenated
|
|
1420
|
+
* error message string summarizing all failures; otherwise returns `true`.
|
|
1421
|
+
* - Empty `ruleParams`: If no sub-rules are provided, returns `true`.
|
|
1422
|
+
*
|
|
1423
|
+
* ### Execution Notes
|
|
1424
|
+
* - Sub-rules are evaluated sequentially (not in parallel) to allow early exit optimization for `OneOf`.
|
|
1425
|
+
* - Error messages from failed sub-rules are collected and joined using `; ` as a separator.
|
|
1426
|
+
* - Internationalization: Uses `i18n` (if provided) to prefix the aggregated error message
|
|
1427
|
+
* with the localized rule label (`validator.OneOf` or `validator.AllOf`).
|
|
1428
|
+
* - Timing: Initializes `startTime` when absent to enable duration tracking downstream.
|
|
1429
|
+
*
|
|
1430
|
+
* @template Context - Optional type for the validation context object
|
|
1431
|
+
* @template RulesFunctions - Array type of sub-rules; each sub-rule can be a named rule,
|
|
1432
|
+
* parameterized rule object, or a rule function
|
|
1433
|
+
*
|
|
1434
|
+
* @param ruleName - Multi-rule mode to apply: `"OneOf"` or `"AllOf"`
|
|
1435
|
+
* @param options - Validation options extending {@link ValidatorValidateMultiRuleOptions}
|
|
1436
|
+
* @param options.value - The value to validate against the sub-rules
|
|
1437
|
+
* @param options.ruleParams - Array of sub-rules to evaluate (functions or named/object rules)
|
|
1438
|
+
* @param options.context - Optional context passed through to each sub-rule
|
|
1439
|
+
* @param options.data - Optional auxiliary data passed through to each sub-rule
|
|
1440
|
+
* @param options.startTime - Optional start timestamp used for duration tracking
|
|
1441
|
+
* @param options.fieldName - Optional field identifier used in error construction
|
|
1442
|
+
* @param options.propertyName - Optional property identifier used in error construction
|
|
1443
|
+
* @param options.translatedPropertyName - Optional localized property name for error messages
|
|
1444
|
+
* @param options.i18n - Optional i18n instance used to localize the error label
|
|
1445
|
+
*
|
|
1446
|
+
* @returns ValidatorResult
|
|
1447
|
+
* - `true` when validation succeeds (any sub-rule for `OneOf`, all sub-rules for `AllOf`)
|
|
1448
|
+
* - `string` containing aggregated error messages when validation fails
|
|
1449
|
+
*
|
|
1450
|
+
* @example
|
|
1451
|
+
* // OneOf: either email or phone must be valid
|
|
1452
|
+
* const resultOneOf = await Validator.validateOneOfRule({
|
|
1453
|
+
* value: "user@example.com",
|
|
1454
|
+
* ruleParams: ["Email", "PhoneNumber"],
|
|
1455
|
+
* });
|
|
1456
|
+
* // resultOneOf === true
|
|
1457
|
+
*
|
|
1458
|
+
* @example
|
|
1459
|
+
* // AllOf: must be a string and minimum length 5
|
|
1460
|
+
* const resultAllOf = await Validator.validateAllOfRule({
|
|
1461
|
+
* value: "hello",
|
|
1462
|
+
* ruleParams: ["String", { MinLength: [5] }],
|
|
1463
|
+
* });
|
|
1464
|
+
* // resultAllOf === true
|
|
1465
|
+
*
|
|
1466
|
+
*
|
|
1467
|
+
* @see {@link validateOneOfRule} - Convenience wrapper applying `OneOf` logic
|
|
1468
|
+
* @see {@link validateAllOfRule} - Convenience wrapper applying `AllOf` logic
|
|
1469
|
+
* @see {@link oneOf} - Factory to build a reusable `OneOf` rule function
|
|
1470
|
+
* @see {@link allOf} - Factory to build a reusable `AllOf` rule function
|
|
1471
|
+
* @see {@link validate} - Underlying validator used for each sub-rule
|
|
1472
|
+
* @public
|
|
1473
|
+
* @async
|
|
1474
|
+
*/
|
|
1475
|
+
static validateMultiRule<Context = unknown, RulesFunctions extends ValidatorDefaultMultiRule<Context> = ValidatorDefaultMultiRule<Context>>(ruleName: ValidatorMultiRuleNames, { value, ruleParams, startTime, ...extra }: ValidatorValidateMultiRuleOptions<Context, RulesFunctions>): Promise<string | true>;
|
|
1476
|
+
/**
|
|
1477
|
+
* ## Create OneOf Validation Rule
|
|
1478
|
+
*
|
|
1479
|
+
* Factory method that creates a OneOf validation rule function. This method provides
|
|
1480
|
+
* a programmatic way to create validation rules that implement OR logic, where
|
|
1481
|
+
* validation succeeds if at least one of the specified sub-rules passes.
|
|
1482
|
+
*
|
|
1483
|
+
* ### OneOf Validation Concept
|
|
1484
|
+
* OneOf validation allows flexible validation scenarios where multiple validation
|
|
1485
|
+
* paths are acceptable. Instead of requiring all rules to pass (AND logic),
|
|
1486
|
+
* OneOf requires only one rule to pass (OR logic), making it ideal for:
|
|
1487
|
+
* - Alternative input formats (email OR phone number)
|
|
1488
|
+
* - Flexible validation requirements
|
|
1489
|
+
* - Multiple acceptable validation criteria
|
|
1490
|
+
*
|
|
1491
|
+
* ### Method Behavior
|
|
1492
|
+
* This factory method returns a validation rule function that can be used directly
|
|
1493
|
+
* in validation calls or registered as a named rule. The returned function delegates
|
|
1494
|
+
* to `validateOneOfRule` for the actual validation logic.
|
|
1495
|
+
*
|
|
1496
|
+
* ### Parallel Execution
|
|
1497
|
+
* When the returned rule function is executed, all sub-rules are validated in parallel
|
|
1498
|
+
* using `Promise.all()` for optimal performance. The method returns immediately upon
|
|
1499
|
+
* the first successful validation, avoiding unnecessary processing of remaining rules.
|
|
1500
|
+
*
|
|
1501
|
+
* ### Error Aggregation
|
|
1502
|
+
* When all sub-rules fail, error messages are collected and joined with semicolons
|
|
1503
|
+
* to provide comprehensive feedback about all validation failures.
|
|
1504
|
+
*
|
|
1505
|
+
* ### Examples
|
|
1506
|
+
*
|
|
1507
|
+
* #### Basic OneOf Rule Creation
|
|
1508
|
+
* ```typescript
|
|
1509
|
+
* // Create a OneOf rule that accepts either email or phone number
|
|
1510
|
+
* const contactRule = Validator.oneOf(['Email', 'PhoneNumber']);
|
|
1511
|
+
*
|
|
1512
|
+
* // Use the rule directly
|
|
1513
|
+
* const result = await contactRule({
|
|
1514
|
+
* value: 'user@example.com',
|
|
1515
|
+
* ruleParams: ['Email', 'PhoneNumber'],
|
|
1516
|
+
* fieldName: 'contact'
|
|
1517
|
+
* });
|
|
1518
|
+
*
|
|
1519
|
+
* if (result === true) {
|
|
1520
|
+
* console.log('Contact validation passed');
|
|
1521
|
+
* } else {
|
|
1522
|
+
* console.log('Contact validation failed:', result);
|
|
1523
|
+
* }
|
|
1524
|
+
* ```
|
|
1525
|
+
*
|
|
1526
|
+
* #### Complex OneOf with Mixed Rule Types
|
|
1527
|
+
* ```typescript
|
|
1528
|
+
* // Create a rule that accepts UUID, custom format, or admin format
|
|
1529
|
+
* const identifierRule = Validator.oneOf([
|
|
1530
|
+
* 'UUID', // Built-in UUID validation
|
|
1531
|
+
* { MinLength: [5] }, // Object rule with parameters
|
|
1532
|
+
* ({ value }) => value.startsWith('ADMIN-') // Custom function rule
|
|
1533
|
+
* ]);
|
|
1534
|
+
*
|
|
1535
|
+
* const result = await identifierRule({
|
|
1536
|
+
* value: '550e8400-e29b-41d4-a716-446655440000',
|
|
1537
|
+
* ruleParams: ['UUID', { MinLength: [5] }, ({ value }) => value.startsWith('ADMIN-')],
|
|
1538
|
+
* fieldName: 'identifier'
|
|
1539
|
+
* });
|
|
1540
|
+
* ```
|
|
1541
|
+
*
|
|
1542
|
+
* #### Registering as Named Rule
|
|
1543
|
+
* ```typescript
|
|
1544
|
+
* // Create and register a reusable OneOf rule
|
|
1545
|
+
* const contactValidator = Validator.oneOf(['Email', 'PhoneNumber']);
|
|
1546
|
+
* Validator.registerRule('Contact', contactValidator);
|
|
1547
|
+
*
|
|
1548
|
+
* // Now use it in validation
|
|
1549
|
+
* const result = await Validator.validate({
|
|
1550
|
+
* value: '+1234567890',
|
|
1551
|
+
* rules: ['Contact']
|
|
1552
|
+
* });
|
|
1553
|
+
* ```
|
|
1554
|
+
*
|
|
1555
|
+
* #### Context-Aware OneOf Rules
|
|
1556
|
+
* ```typescript
|
|
1557
|
+
* interface UserContext {
|
|
1558
|
+
* userType: 'admin' | 'user';
|
|
1559
|
+
* permissions: string[];
|
|
1560
|
+
* }
|
|
1561
|
+
*
|
|
1562
|
+
* const flexibleIdRule = Validator.oneOf<UserContext>([
|
|
1563
|
+
* 'UUID',
|
|
1564
|
+
* ({ value, context }) => {
|
|
1565
|
+
* if (context?.userType === 'admin') {
|
|
1566
|
+
* return value.startsWith('ADMIN-') || 'Admin IDs must start with ADMIN-';
|
|
1567
|
+
* }
|
|
1568
|
+
* return false; // Skip for non-admins
|
|
1569
|
+
* }
|
|
1570
|
+
* ]);
|
|
1571
|
+
*
|
|
1572
|
+
* const result = await flexibleIdRule({
|
|
1573
|
+
* value: 'ADMIN-12345',
|
|
1574
|
+
* ruleParams: ['UUID', 'customValidationFunction'],
|
|
1575
|
+
* context: { userType: 'admin', permissions: ['manage'] },
|
|
1576
|
+
* fieldName: 'identifier'
|
|
1577
|
+
* });
|
|
1578
|
+
* ```
|
|
1579
|
+
*
|
|
1580
|
+
* #### Error Aggregation Example
|
|
1581
|
+
* ```typescript
|
|
1582
|
+
* // When all rules fail, errors are aggregated
|
|
1583
|
+
* const strictRule = Validator.oneOf(['Email', 'PhoneNumber', { MinLength: [10] }]);
|
|
1584
|
+
*
|
|
1585
|
+
* const result = await strictRule({
|
|
1586
|
+
* value: 'invalid', // Fails all rules
|
|
1587
|
+
* ruleParams: ['Email', 'PhoneNumber', { MinLength: [10] }],
|
|
1588
|
+
* fieldName: 'contact'
|
|
1589
|
+
* });
|
|
1590
|
+
*
|
|
1591
|
+
* // result will be: "Invalid email format; Invalid phone number; Must be at least 10 characters"
|
|
1592
|
+
* ```
|
|
1593
|
+
*
|
|
1594
|
+
* ### Performance Characteristics
|
|
1595
|
+
* - **Parallel Execution**: All rules execute simultaneously
|
|
1596
|
+
* - **Early Success**: Returns immediately on first success
|
|
1597
|
+
* - **Full Error Collection**: Waits for all failures before rejecting
|
|
1598
|
+
* - **Memory Efficient**: No unnecessary rule processing after success
|
|
1599
|
+
*
|
|
1600
|
+
* ### Internationalization Support
|
|
1601
|
+
* Error messages are automatically translated using the provided i18n instance.
|
|
1602
|
+
* Custom error messages can be provided through rule functions.
|
|
1603
|
+
*
|
|
1604
|
+
* @template Context - Type of the validation context object passed to rules
|
|
1605
|
+
* @template RulesFunctions - Array type defining the structure of validation rules
|
|
1606
|
+
*
|
|
1607
|
+
* @param ruleParams - Array of sub-rules to validate against (required)
|
|
1608
|
+
* Can include strings, objects, or functions
|
|
1609
|
+
*
|
|
1610
|
+
* @returns Validation rule function that implements OneOf logic
|
|
1611
|
+
* Returns `true` if validation passes, error message string if fails
|
|
1612
|
+
*
|
|
1613
|
+
* @throws {string} Aggregated error messages when all sub-rules fail
|
|
1614
|
+
*
|
|
1615
|
+
* @remarks
|
|
1616
|
+
* - Rules are executed in parallel for optimal performance
|
|
1617
|
+
* - Method returns immediately upon first successful validation
|
|
1618
|
+
* - Error messages from failed rules are joined with semicolons
|
|
1619
|
+
* - Empty ruleParams array results in immediate failure
|
|
1620
|
+
* - Supports both built-in rules and custom validation functions
|
|
1621
|
+
* - Context is passed through to all sub-rule validations
|
|
1622
|
+
*
|
|
1623
|
+
*
|
|
1624
|
+
* @see {@link validateOneOfRule} - The underlying validation method
|
|
1625
|
+
* @see {@link buildMultiRuleDecorator} - Creates decorators using this method
|
|
1626
|
+
* @see {@link registerRule} - Register the returned function as a named rule
|
|
1627
|
+
* @public
|
|
1628
|
+
*/
|
|
1629
|
+
static oneOf<Context = unknown>(ruleParams: ValidatorDefaultMultiRule<Context>): ValidatorRuleFunction<ValidatorDefaultArray, Context>;
|
|
1630
|
+
/**
|
|
1631
|
+
* ## Create AllOf Validation Rule
|
|
1632
|
+
*
|
|
1633
|
+
* Factory that returns a rule function implementing AND logic across multiple
|
|
1634
|
+
* sub-rules. The returned function delegates to {@link validateAllOfRule} and
|
|
1635
|
+
* succeeds only when every sub-rule passes; otherwise it returns a single
|
|
1636
|
+
* aggregated error string (messages joined with `; `).
|
|
1637
|
+
*
|
|
1638
|
+
* @template Context - Optional type for validation context
|
|
1639
|
+
* @template RulesFunctions - Array of sub-rules to combine
|
|
1640
|
+
* @param ruleParams - Array of sub-rules evaluated with AND logic
|
|
1641
|
+
* @returns `ValidatorRuleFunction` to use in `Validator.validate` or decorators
|
|
1642
|
+
* @example
|
|
1643
|
+
* const strongStringRule = Validator.allOf(["String", { MinLength: [5] }]);
|
|
1644
|
+
* const res = await strongStringRule({ value: "hello" });
|
|
1645
|
+
* // res === true
|
|
1646
|
+
*
|
|
1647
|
+
* @see {@link validateAllOfRule}
|
|
1648
|
+
* @see {@link buildMultiRuleDecorator}
|
|
1649
|
+
* @see {@link registerRule}
|
|
1650
|
+
* @public
|
|
1651
|
+
*/
|
|
1652
|
+
static allOf<Context = unknown>(ruleParams: ValidatorDefaultMultiRule<Context>): ValidatorRuleFunction<ValidatorDefaultArray, Context>;
|
|
1653
|
+
/**
|
|
1654
|
+
* ## Create ArrayOf Validation Rule
|
|
1655
|
+
*
|
|
1656
|
+
* Factory that returns a rule function applying AND logic across provided sub-rules
|
|
1657
|
+
* to every item of an array value. Delegates to {@link validateArrayOfRule}.
|
|
1658
|
+
*
|
|
1659
|
+
* @template Context - Optional type for validation context
|
|
1660
|
+
* @template RulesFunctions - Array of sub-rules applied to each item
|
|
1661
|
+
* @param ruleParams - Sub-rules to apply to each array item
|
|
1662
|
+
* @returns `ValidatorRuleFunction` usable in `Validator.validate` or decorators
|
|
1663
|
+
* @example
|
|
1664
|
+
* const emails = Validator.arrayOf(["Email"]);
|
|
1665
|
+
* const res = await emails({ value: ["a@b.com", "c@d.com"] }); // true
|
|
1666
|
+
*
|
|
1667
|
+
*/
|
|
1668
|
+
static arrayOf<Context = unknown>(ruleParams: ValidatorDefaultMultiRule<Context>): ValidatorRuleFunction<ValidatorDefaultArray, Context>;
|
|
1669
|
+
/**
|
|
1670
|
+
* ## Create Nested Validation Rule Factory
|
|
1671
|
+
*
|
|
1672
|
+
* Factory function that creates a validation rule function for validating nested objects
|
|
1673
|
+
* against a decorated class schema. This method follows the factory pattern established by
|
|
1674
|
+
* {@link oneOf}, {@link allOf}, and {@link arrayOf} for consistency across complex validation rules.
|
|
1675
|
+
*
|
|
1676
|
+
* ### Purpose
|
|
1677
|
+
* The `validateNested` factory enables validation of hierarchical data structures by creating
|
|
1678
|
+
* a reusable rule function that validates object properties against class-based validation schemas.
|
|
1679
|
+
* It bridges the gap between single-value rules and multi-field class validation.
|
|
1680
|
+
*
|
|
1681
|
+
* ### How It Works
|
|
1682
|
+
* 1. **Factory Invocation**: Called with a class constructor `[target]` parameter
|
|
1683
|
+
* 2. **Returns Rule Function**: Returns a rule function that validates nested objects
|
|
1684
|
+
* 3. **Rule Execution**: When the rule is executed, it delegates to `validateNestedRule`
|
|
1685
|
+
* 4. **Result**: Returns `true` on success or error message string on failure
|
|
1686
|
+
*
|
|
1687
|
+
* ### Factory Pattern Consistency
|
|
1688
|
+
* Like `oneOf`, `allOf`, and `arrayOf`, this factory:
|
|
1689
|
+
* - Accepts rule parameters during factory creation
|
|
1690
|
+
* - Returns an `ValidatorRuleFunction` for use in validators
|
|
1691
|
+
* - Supports generic typing for Context
|
|
1692
|
+
* - Follows the nested function closure pattern
|
|
1693
|
+
* - Can be registered as a named rule via `Validator.registerRule()`
|
|
1694
|
+
*
|
|
1695
|
+
* ### Key Characteristics
|
|
1696
|
+
* - **Lazy Evaluation**: Rule parameters are captured at factory creation time
|
|
1697
|
+
* - **Composability**: Can be combined with other rules using OneOf, AllOf, ArrayOf
|
|
1698
|
+
* - **Type Safety**: Full TypeScript support with generic Target class type
|
|
1699
|
+
* - **Contextual Validation**: Supports optional validation context propagation
|
|
1700
|
+
* - **i18n Support**: Automatically uses i18n system for error messages
|
|
1701
|
+
*
|
|
1702
|
+
* ### Usage Patterns
|
|
1703
|
+
*
|
|
1704
|
+
* #### Direct Factory Usage
|
|
1705
|
+
* ```typescript
|
|
1706
|
+
* class Address {
|
|
1707
|
+
* @IsRequired()
|
|
1708
|
+
* street: string;
|
|
1709
|
+
*
|
|
1710
|
+
* @IsRequired()
|
|
1711
|
+
* postalCode: string;
|
|
1712
|
+
* }
|
|
1713
|
+
*
|
|
1714
|
+
* class UserForm {
|
|
1715
|
+
* @IsRequired()
|
|
1716
|
+
* @IsEmail()
|
|
1717
|
+
* email: string;
|
|
1718
|
+
*
|
|
1719
|
+
* @ValidateNested(Address)
|
|
1720
|
+
* address: Address;
|
|
1721
|
+
* }
|
|
1722
|
+
*
|
|
1723
|
+
* // Validator.validateNested is called internally by the @ValidateNested decorator
|
|
1724
|
+
* ```
|
|
1725
|
+
*
|
|
1726
|
+
* #### Programmatic Rule Creation
|
|
1727
|
+
* ```typescript
|
|
1728
|
+
* const nestedAddressRule = Validator.validateNested([Address]);
|
|
1729
|
+
*
|
|
1730
|
+
* const result = await nestedAddressRule({
|
|
1731
|
+
* data: { street: "123 Main St", postalCode: "12345" },
|
|
1732
|
+
* fieldName: "address"
|
|
1733
|
+
* });
|
|
1734
|
+
* ```
|
|
1735
|
+
*
|
|
1736
|
+
* #### Registration as Named Rule
|
|
1737
|
+
* ```typescript
|
|
1738
|
+
* const addressValidator = Validator.validateNested([Address]);
|
|
1739
|
+
* Validator.registerRule('AddressValidator', addressValidator);
|
|
1740
|
+
*
|
|
1741
|
+
* // Now can be used by name in validation rules
|
|
1742
|
+
* class User {
|
|
1743
|
+
* @ValidateNested(Address)
|
|
1744
|
+
* address: Address;
|
|
1745
|
+
* }
|
|
1746
|
+
* ```
|
|
1747
|
+
*
|
|
1748
|
+
* #### Composition with MultiRule Validators
|
|
1749
|
+
* ```typescript
|
|
1750
|
+
* // Using ValidateNested within OneOf (e.g., either Address or simplified Location)
|
|
1751
|
+
* class Location {
|
|
1752
|
+
* @IsRequired()
|
|
1753
|
+
* coordinates: string; // e.g., "40.7128,-74.0060"
|
|
1754
|
+
* }
|
|
1755
|
+
*
|
|
1756
|
+
* const addressOrLocation = Validator.oneOf([
|
|
1757
|
+
* Validator.validateNested(Address),
|
|
1758
|
+
* Validator.validateNested(Location)
|
|
1759
|
+
* ]);
|
|
1760
|
+
* ```
|
|
1761
|
+
*
|
|
1762
|
+
* #### Nested Validation with Context
|
|
1763
|
+
* ```typescript
|
|
1764
|
+
* interface UserContext {
|
|
1765
|
+
* userId: number;
|
|
1766
|
+
* isAdmin: boolean;
|
|
1767
|
+
* }
|
|
1768
|
+
*
|
|
1769
|
+
* class AdminProfile {
|
|
1770
|
+
* @IsRequired()
|
|
1771
|
+
* role: string;
|
|
1772
|
+
*
|
|
1773
|
+
* @IsRequired()
|
|
1774
|
+
* permissions: string[];
|
|
1775
|
+
* }
|
|
1776
|
+
*
|
|
1777
|
+
* // When context is provided during validation
|
|
1778
|
+
* const result = await Validator.validateTarget(User, {
|
|
1779
|
+
* data: userData,
|
|
1780
|
+
* context: { userId: 1, isAdmin: true }
|
|
1781
|
+
* });
|
|
1782
|
+
* ```
|
|
1783
|
+
*
|
|
1784
|
+
* ### Type Parameters
|
|
1785
|
+
* - `Target` - Class constructor type (extends ClassConstructor) that the nested object must satisfy
|
|
1786
|
+
* - `Context` - Optional validation context type passed through nested validations
|
|
1787
|
+
*
|
|
1788
|
+
* ### Parameters
|
|
1789
|
+
* @param Target - The nested class constructor.
|
|
1790
|
+
* Must be a class decorated with validation rules.
|
|
1791
|
+
*
|
|
1792
|
+
* ### Returns
|
|
1793
|
+
* `ValidatorRuleFunction<[target: Target], Context>` - A rule function that:
|
|
1794
|
+
* - Accepts validation options with nested object data (ValidatorValidateOptions)
|
|
1795
|
+
* - Delegates to `validateNestedRule` for actual validation
|
|
1796
|
+
* - Returns `true` on successful nested object validation
|
|
1797
|
+
* - Returns error message string if any nested field validation fails
|
|
1798
|
+
* - Propagates context and i18n through nested validations
|
|
1799
|
+
*
|
|
1800
|
+
* ### Implementation Details
|
|
1801
|
+
* The returned rule function:
|
|
1802
|
+
* 1. Extracts the data property from validation options
|
|
1803
|
+
* 2. Creates a shallow copy of the data using `Object.assign`
|
|
1804
|
+
* 3. Calls `validateNestedRule` with the combined parameters
|
|
1805
|
+
* 4. Properly types the data as `ValidatorValidateTargetData<Target>`
|
|
1806
|
+
* 5. Delegates to validateTarget via validateNestedRule which expects options.data
|
|
1807
|
+
*
|
|
1808
|
+
* ### Error Message Format
|
|
1809
|
+
* When nested validation fails, error messages include field-level details:
|
|
1810
|
+
* ```
|
|
1811
|
+
* "Validation failed: [fieldName]: error message; [fieldName]: error message"
|
|
1812
|
+
* ```
|
|
1813
|
+
*
|
|
1814
|
+
* ### Performance Characteristics
|
|
1815
|
+
* - **Lazy Evaluation**: Parameters are captured but not executed until rule runs
|
|
1816
|
+
* - **Efficient Nesting**: Reuses validateTarget's parallel field validation
|
|
1817
|
+
* - **Memory Efficient**: Shallow copy of data prevents unnecessary object duplication
|
|
1818
|
+
* - **Async Optimized**: Properly awaits nested async validation rules
|
|
1819
|
+
*
|
|
1820
|
+
* ### Internationalization
|
|
1821
|
+
* - Error messages are automatically localized using the i18n system
|
|
1822
|
+
* - Supports translation keys: `validator.validateNested`, `validator.invalidRule`
|
|
1823
|
+
* - Property names can be translated based on i18n configuration
|
|
1824
|
+
*
|
|
1825
|
+
* ### Integration with Decorator System
|
|
1826
|
+
* This factory is the foundation for the `@ValidateNested` decorator:
|
|
1827
|
+
* ```typescript
|
|
1828
|
+
* const ValidateNested = buildMultiRuleDecorator(function ValidateNested(options) {
|
|
1829
|
+
* return { validateNested: Validator.validateNested(options.nested) };
|
|
1830
|
+
* });
|
|
1831
|
+
* ```
|
|
1832
|
+
*
|
|
1833
|
+
* @template Target - Class constructor for the nested object schema
|
|
1834
|
+
* @template Context - Optional context type for validations
|
|
1835
|
+
*
|
|
1836
|
+
* @param ruleParams - Tuple `[target]` where target is the class constructor
|
|
1837
|
+
*
|
|
1838
|
+
* @returns ValidatorRuleFunction - Rule function for nested object validation
|
|
1839
|
+
* - Accepts options with nested object data
|
|
1840
|
+
* - Returns true on success, error string on failure
|
|
1841
|
+
* - Supports context propagation
|
|
1842
|
+
* - Integrates with validation pipeline
|
|
1843
|
+
*
|
|
1844
|
+
* @throws {Never} The returned rule function never throws; errors are returned as strings
|
|
1845
|
+
*
|
|
1846
|
+
* @remarks
|
|
1847
|
+
* - This factory closely parallels the `oneOf`, `allOf`, and `arrayOf` factory pattern
|
|
1848
|
+
* - The class constructor in ruleParams must have validation decorators
|
|
1849
|
+
* - Nested validation errors include property names for traceability
|
|
1850
|
+
* - Supports recursive nesting (nested class can have ValidateNested properties)
|
|
1851
|
+
* - Data is shallow-copied to prevent external modifications
|
|
1852
|
+
* - Fully compatible with TypeScript's strict type checking
|
|
1853
|
+
*
|
|
1854
|
+
* @example
|
|
1855
|
+
* ```typescript
|
|
1856
|
+
* // Simple nested validation
|
|
1857
|
+
* class Contact {
|
|
1858
|
+
* @IsRequired() @IsEmail() email: string;
|
|
1859
|
+
* @IsRequired() @IsPhoneNumber phone: string;
|
|
1860
|
+
* }
|
|
1861
|
+
*
|
|
1862
|
+
* class Person {
|
|
1863
|
+
* @IsRequired() @MinLength(2) name: string;
|
|
1864
|
+
* @ValidateNested([Contact]) contact: Contact;
|
|
1865
|
+
* }
|
|
1866
|
+
*
|
|
1867
|
+
* const person = {
|
|
1868
|
+
* name: "Alice",
|
|
1869
|
+
* contact: {
|
|
1870
|
+
* email: "alice@example.com",
|
|
1871
|
+
* phone: "+1234567890"
|
|
1872
|
+
* }
|
|
1873
|
+
* };
|
|
1874
|
+
*
|
|
1875
|
+
* const result = await Validator.validateTarget(Person, {data:person});
|
|
1876
|
+
* if (result.success) {
|
|
1877
|
+
* console.log("Valid person with contact", result.data);
|
|
1878
|
+
* } else {
|
|
1879
|
+
* console.error("Validation errors", result.errors);
|
|
1880
|
+
* }
|
|
1881
|
+
* ```
|
|
1882
|
+
*
|
|
1883
|
+
*
|
|
1884
|
+
* @see {@link validateNestedRule} - The underlying validation executor that delegates to validateTarget
|
|
1885
|
+
* @see {@link ValidateNested} - Decorator using this factory
|
|
1886
|
+
* @see {@link validateTarget} - Multi-field class validation (signature: validateTarget<T, Context>(target, options))
|
|
1887
|
+
* @see {@link ValidatorValidateOptions} - Validation options interface for rule functions
|
|
1888
|
+
* @see {@link ValidatorValidateTargetOptions} - Target validation options interface
|
|
1889
|
+
* @see {@link oneOf} - Similar factory for OneOf rule creation
|
|
1890
|
+
* @see {@link allOf} - Similar factory for AllOf rule creation
|
|
1891
|
+
* @see {@link arrayOf} - Similar factory for ArrayOf rule creation
|
|
1892
|
+
* @see {@link buildMultiRuleDecorator} - Decorator builder for complex rules
|
|
1893
|
+
* @public
|
|
1894
|
+
*/
|
|
1895
|
+
static validateNested<Target extends ClassConstructor<unknown> = ClassConstructor<unknown>, Context = unknown>(target: Target): ValidatorRuleFunction<Array<unknown>, Context>;
|
|
1896
|
+
static isSuccess<Context = unknown>(result: ValidatorValidateResult<Context>): result is ValidatorValidateSuccess<Context>;
|
|
1897
|
+
static isFailure<Context = unknown>(result: any): result is ValidatorValidateFailure<Context>;
|
|
1898
|
+
/**
|
|
1899
|
+
* ## Validate Target - Class-Based Validation
|
|
1900
|
+
*
|
|
1901
|
+
* Performs validation on all decorated properties of a class instance using decorator-based rules.
|
|
1902
|
+
* This method supports complex, multi-field validation with field-level error accumulation.
|
|
1903
|
+
*
|
|
1904
|
+
* ### Key Features
|
|
1905
|
+
* - **Decorator Support**: Uses @IsEmail(), @IsRequired(), @MinLength, etc. decorators
|
|
1906
|
+
* - **Multi-FieldMeta Validation**: Validates all decorated properties in parallel
|
|
1907
|
+
* - **Error Accumulation**: Collects all field validation errors into a single result
|
|
1908
|
+
* - **FieldMeta Mapping**: Maps validated data back to original structure with proper types
|
|
1909
|
+
* - **Internationalization**: Supports translated property names and error messages
|
|
1910
|
+
* - **Custom Error Formatting**: Allows custom error message builders per field
|
|
1911
|
+
* - **Async Rules**: Supports both sync and async validation rules for each field
|
|
1912
|
+
* - **Type Safe**: Full TypeScript support with generic typing for class instances
|
|
1913
|
+
*
|
|
1914
|
+
* ### Return Type: ValidatorValidateTargetResult
|
|
1915
|
+
* Returns a discriminated union that can be narrowed:
|
|
1916
|
+
* ```typescript
|
|
1917
|
+
* type ValidatorValidateTargetResult<T> =
|
|
1918
|
+
* | ValidatorValidateTargetSuccess<T> // success: true
|
|
1919
|
+
* | ValidatorValidateTargetFailure<T> // success: false
|
|
1920
|
+
* ```
|
|
1921
|
+
*
|
|
1922
|
+
* #### Success Result (success: true)
|
|
1923
|
+
* - `success`: true
|
|
1924
|
+
* - `data`: Validated object data matching the class structure
|
|
1925
|
+
* - `value`: undefined for target validation
|
|
1926
|
+
* - `validatedAt`: ISO timestamp when validation completed
|
|
1927
|
+
* - `duration`: Milliseconds elapsed during validation
|
|
1928
|
+
* - `status`: "success"
|
|
1929
|
+
* - `context`: Optional validation context of type Context
|
|
1930
|
+
*
|
|
1931
|
+
* #### Failure Result (success: false)
|
|
1932
|
+
* - `success`: false
|
|
1933
|
+
* - `data`: undefined for target failures
|
|
1934
|
+
* - `errors`: Array of ValidatorValidationError objects, one per failed field
|
|
1935
|
+
* - `failureCount`: Number of fields that failed validation
|
|
1936
|
+
* - `message`: Summary message (e.g., "Validation failed for 3 fields")
|
|
1937
|
+
* - `failedAt`: ISO timestamp when validation failed
|
|
1938
|
+
* - `duration`: Milliseconds elapsed before failure
|
|
1939
|
+
* - `status`: "error"
|
|
1940
|
+
*
|
|
1941
|
+
* ### Supported Decorators
|
|
1942
|
+
* - `@IsRequired()` / `@IsNullable` / `@IsEmpty()` / `@IsOptional()` - Conditional rules
|
|
1943
|
+
* - `@IsEmail()` / `@IsUrl` / `@IsPhoneNumber()` - Format validators
|
|
1944
|
+
* - `@MinLength(3)` / `@MaxLength(50)` - Length validators
|
|
1945
|
+
* - `@IsNumber()` / `@IsNonNullString()` - Type validators
|
|
1946
|
+
* - `@ Length[n]` - Exact length validator
|
|
1947
|
+
* - Custom decorators created with `Validator.buildPropertyDecorator()`
|
|
1948
|
+
*
|
|
1949
|
+
* ### Nullable Rule Behavior
|
|
1950
|
+
* - **@IsEmpty()**: Skips remaining rules if value is empty string ""
|
|
1951
|
+
* - **@IsNullable**: Skips remaining rules if value is null or undefined
|
|
1952
|
+
* - **@IsOptional()**: Skips remaining rules if value is undefined only
|
|
1953
|
+
* - **Skip if Absent**: @IsOptional() fields can be omitted from data entirely
|
|
1954
|
+
*
|
|
1955
|
+
* ### Examples
|
|
1956
|
+
*
|
|
1957
|
+
* #### Basic Class Validation
|
|
1958
|
+
* ```typescript
|
|
1959
|
+
* class UserForm {
|
|
1960
|
+
* @IsRequired()
|
|
1961
|
+
* @IsEmail()
|
|
1962
|
+
* email: string;
|
|
1963
|
+
*
|
|
1964
|
+
* @IsRequired()
|
|
1965
|
+
* @MinLength(3)
|
|
1966
|
+
* @MaxLength(50)
|
|
1967
|
+
* name: string;
|
|
1968
|
+
*
|
|
1969
|
+
* @IsNullable
|
|
1970
|
+
* @IsNumber()
|
|
1971
|
+
* age?: number;
|
|
1972
|
+
* }
|
|
1973
|
+
*
|
|
1974
|
+
* const result = await Validator.validateTarget(UserForm, {
|
|
1975
|
+
* email: "user@example.com",
|
|
1976
|
+
* name: "John Doe",
|
|
1977
|
+
* age: null,
|
|
1978
|
+
* });
|
|
1979
|
+
*
|
|
1980
|
+
* if (result.success) {
|
|
1981
|
+
* console.log("Form is valid:", result.data);
|
|
1982
|
+
* } else {
|
|
1983
|
+
* result.errors.forEach(error => {
|
|
1984
|
+
* console.error(`${error.propertyName}: ${error.message}`);
|
|
1985
|
+
* });
|
|
1986
|
+
* }
|
|
1987
|
+
* ```
|
|
1988
|
+
*
|
|
1989
|
+
* #### Complex Multi-FieldMeta Validation
|
|
1990
|
+
* ```typescript
|
|
1991
|
+
* class ProductForm {
|
|
1992
|
+
* @IsRequired()
|
|
1993
|
+
* @MinLength(3)
|
|
1994
|
+
* title: string;
|
|
1995
|
+
*
|
|
1996
|
+
* @IsRequired()
|
|
1997
|
+
* @IsNumber()
|
|
1998
|
+
* @NumberGT(0)
|
|
1999
|
+
* price: number;
|
|
2000
|
+
*
|
|
2001
|
+
* @IsEmpty() // Product description can be empty
|
|
2002
|
+
* @MaxLength(1000)
|
|
2003
|
+
* description?: string;
|
|
2004
|
+
*
|
|
2005
|
+
* @IsOptional() // Can be omitted entirely
|
|
2006
|
+
* @IsUrl
|
|
2007
|
+
* imageUrl?: string;
|
|
2008
|
+
* }
|
|
2009
|
+
*
|
|
2010
|
+
* const result = await Validator.validateTarget(ProductForm, {
|
|
2011
|
+
* data : {
|
|
2012
|
+
* title: "Awesome Product",
|
|
2013
|
+
* price: 29.99,
|
|
2014
|
+
* description: "",
|
|
2015
|
+
* // imageUrl omitted (valid with @IsOptional())
|
|
2016
|
+
* }
|
|
2017
|
+
* });
|
|
2018
|
+
* ```
|
|
2019
|
+
*
|
|
2020
|
+
* #### Custom Error Message Building
|
|
2021
|
+
* ```typescript
|
|
2022
|
+
* const result = await Validator.validateTarget(UserForm, data, {
|
|
2023
|
+
* errorMessageBuilder: (translatedPropertyName, error, options) => {
|
|
2024
|
+
* // Custom format: "FieldMeta Name (validation rule): error message"
|
|
2025
|
+
* return `${translatedPropertyName} (${options.ruleName}): ${error}`;
|
|
2026
|
+
* }
|
|
2027
|
+
* });
|
|
2028
|
+
* ```
|
|
2029
|
+
*
|
|
2030
|
+
* #### Validation with Context
|
|
2031
|
+
* ```typescript
|
|
2032
|
+
* interface AuthContext {
|
|
2033
|
+
* userId: number;
|
|
2034
|
+
* isAdmin: boolean;
|
|
2035
|
+
* }
|
|
2036
|
+
*
|
|
2037
|
+
* class AdminAction {
|
|
2038
|
+
* @IsRequired()
|
|
2039
|
+
* action: string;
|
|
2040
|
+
*
|
|
2041
|
+
* @IsRequired()
|
|
2042
|
+
* targetId: number;
|
|
2043
|
+
* }
|
|
2044
|
+
*
|
|
2045
|
+
* const result = await Validator.validateTarget<typeof AdminAction, AuthContext>(
|
|
2046
|
+
* AdminAction,
|
|
2047
|
+
* { action: "delete", targetId: 123 },
|
|
2048
|
+
* { context: { userId: 1, isAdmin: true } }
|
|
2049
|
+
* );
|
|
2050
|
+
* ```
|
|
2051
|
+
*
|
|
2052
|
+
* #### Error Handling
|
|
2053
|
+
* ```typescript
|
|
2054
|
+
* const result = await Validator.validateTarget(UserForm, {data:userData});
|
|
2055
|
+
*
|
|
2056
|
+
* if (!result.success) {
|
|
2057
|
+
* // Access failure details
|
|
2058
|
+
* console.log(`${result.failureCount} fields failed validation`);
|
|
2059
|
+
* console.log(result.message); // "Validation failed for 2 fields"
|
|
2060
|
+
*
|
|
2061
|
+
* result.errors.forEach(error => {
|
|
2062
|
+
* console.error({
|
|
2063
|
+
* field: error.propertyName,
|
|
2064
|
+
* message: error.message,
|
|
2065
|
+
* rule: error.ruleName,
|
|
2066
|
+
* value: error.value,
|
|
2067
|
+
* });
|
|
2068
|
+
* });
|
|
2069
|
+
* }
|
|
2070
|
+
* ```
|
|
2071
|
+
*
|
|
2072
|
+
* #### Type Guards
|
|
2073
|
+
* ```typescript
|
|
2074
|
+
* const result = await Validator.validateTarget(UserForm, {data});
|
|
2075
|
+
*
|
|
2076
|
+
* if (result.success) {
|
|
2077
|
+
* // result.data is properly typed
|
|
2078
|
+
* const validatedUser: Partial<UserForm> = result.data;
|
|
2079
|
+
* } else {
|
|
2080
|
+
* // result.errors is available
|
|
2081
|
+
* const errorCount = result.errors.length;
|
|
2082
|
+
* }
|
|
2083
|
+
* ```
|
|
2084
|
+
*
|
|
2085
|
+
* ### Signature
|
|
2086
|
+
* ```typescript
|
|
2087
|
+
* static async validateTarget<
|
|
2088
|
+
* Target extends ClassConstructor = ClassConstructor,
|
|
2089
|
+
* Context = unknown,
|
|
2090
|
+
* >(
|
|
2091
|
+
* target: Target,
|
|
2092
|
+
* options: Omit<ValidatorValidateTargetOptions<Target, Context>, "i18n"> & {
|
|
2093
|
+
* i18n?: I18n;
|
|
2094
|
+
* }
|
|
2095
|
+
* ): Promise<ValidatorValidateTargetResult<Context>>
|
|
2096
|
+
* ```
|
|
2097
|
+
*
|
|
2098
|
+
* ### Method Parameters
|
|
2099
|
+
* The method accepts two parameters:
|
|
2100
|
+
* 1. `target` - Class constructor decorated with validation rules
|
|
2101
|
+
* 2. `options` - Configuration object containing validation data and settings
|
|
2102
|
+
*
|
|
2103
|
+
* ### Options Structure
|
|
2104
|
+
* The `options` parameter extends `ValidatorValidateTargetOptions` and includes:
|
|
2105
|
+
* - `data`: Object containing property values to validate (can be partial)
|
|
2106
|
+
* - `context`: Optional context object passed to all validation rules
|
|
2107
|
+
* - `errorMessageBuilder`: Optional custom error message formatter function
|
|
2108
|
+
* - Signature: `(translatedPropertyName: string, error: string, options?: any) => string`
|
|
2109
|
+
* - Default: `(name, error) => \"[${name}] : ${error}\"`
|
|
2110
|
+
* - `i18n`: Optional I18n instance (merged with default i18n if not provided)
|
|
2111
|
+
* - Other properties from ValidatorValidateTargetOptions
|
|
2112
|
+
*
|
|
2113
|
+
* ### Usage Examples
|
|
2114
|
+
* ```typescript
|
|
2115
|
+
* // Simple validation with data object
|
|
2116
|
+
* const result = await Validator.validateTarget(UserForm, {
|
|
2117
|
+
* data: { email: \"test@example.com\", name: \"John\" },
|
|
2118
|
+
* context: { userId: 123 }
|
|
2119
|
+
* });
|
|
2120
|
+
*
|
|
2121
|
+
* // With custom error formatting
|
|
2122
|
+
* const result = await Validator.validateTarget(UserForm, {
|
|
2123
|
+
* data: { email: \"test@example.com\", name: \"John\" },
|
|
2124
|
+
* errorMessageBuilder: (name, error) => `FieldMeta ${name}: ${error}`
|
|
2125
|
+
* });
|
|
2126
|
+
* ```
|
|
2127
|
+
*
|
|
2128
|
+
* @template Target - Class constructor type (extends ClassConstructor)\n * @template Context - Optional type for validation context passed to rules
|
|
2129
|
+
*
|
|
2130
|
+
* @param target - Class constructor decorated with validation decorators (e.g., UserForm)
|
|
2131
|
+
* @param options - Validation options object\n * Extended from ValidatorValidateTargetOptions with optional i18n property\n * Type: Omit<ValidatorValidateTargetOptions<Target, Context>, \"i18n\"> & { i18n?: I18n }
|
|
2132
|
+
* @param options.data - Object containing property values to validate (can be partial, required)
|
|
2133
|
+
* @param options.context - Optional context object passed to all validation rules
|
|
2134
|
+
* @param options.errorMessageBuilder - Optional custom error message formatter function
|
|
2135
|
+
* @param options.i18n - Optional i18n instance for localization
|
|
2136
|
+
*
|
|
2137
|
+
* @returns Promise<ValidatorValidateTargetResult<Context>>
|
|
2138
|
+
* - **Success**: object with success=true, data (validated object), validatedAt, duration, status=\"success\"
|
|
2139
|
+
* - **Failure**: object with success=false, errors (array), failureCount, message, failedAt, duration, status=\"error\"
|
|
2140
|
+
* - Never throws; all errors are returned in the result object
|
|
2141
|
+
*
|
|
2142
|
+
* @throws {Never} This method never throws. All errors are returned in the result object.
|
|
2143
|
+
*
|
|
2144
|
+
* @remarks
|
|
2145
|
+
* - Validation is performed in parallel for all decorated fields using Promise.all()
|
|
2146
|
+
* - Fields decorated with @IsOptional() can be omitted entirely from input data\n * - Nullable/Empty rules prevent other rules from executing for that field
|
|
2147
|
+
* - Property names are translated using i18n if available (via i18n.translateTarget method)
|
|
2148
|
+
* - Errors include field-specific information: propertyName, translatedPropertyName, message, ruleName, value
|
|
2149
|
+
* - Custom errorMessageBuilder allows field-level error message customization
|
|
2150
|
+
* - Context is propagated through to all field validation rules
|
|
2151
|
+
* - Supports nested validation through @ValidateNested rule and validateNested factory
|
|
2152
|
+
* - Error messages use default format: \"[translatedPropertyName] : error\" unless custom builder provided
|
|
2153
|
+
* - Integrates with the multi-rule system (OneOf, AllOf, ArrayOf) for field validation
|
|
2154
|
+
*
|
|
2155
|
+
*
|
|
2156
|
+
* @see {@link validate} - For single-value validation without class schema
|
|
2157
|
+
* @see {@link validateNestedRule} - Internal rule handler that delegates to validateTarget
|
|
2158
|
+
* @see {@link validateNested} - Factory creating nested validation rule functions
|
|
2159
|
+
* @see {@link buildPropertyDecorator} - To create custom validation decorators
|
|
2160
|
+
* @see {@link registerRule} - To register custom validation rules\n * @see {@link ValidatorValidateTargetResult} - Result type documentation
|
|
2161
|
+
* @see {@link ValidatorValidationError} - Error details type
|
|
2162
|
+
* @see {@link ValidatorValidateTargetOptions} - Full options interface
|
|
2163
|
+
*
|
|
2164
|
+
* @public
|
|
2165
|
+
* @async
|
|
2166
|
+
*/
|
|
2167
|
+
static validateTarget<Target extends ClassConstructor = ClassConstructor, Context = unknown>(target: Target, options: Omit<ValidatorValidateTargetOptions<Target, Context>, 'i18n' | 'ruleParams'> & {
|
|
2168
|
+
i18n?: I18n;
|
|
2169
|
+
}): Promise<ValidatorValidateTargetResult<Context>>;
|
|
2170
|
+
/**
|
|
2171
|
+
* ## Extract Validation Rules from Class
|
|
2172
|
+
*
|
|
2173
|
+
* Retrieves all validation rules that have been applied to a class through property
|
|
2174
|
+
* decorators. This method introspects the class metadata to extract the complete
|
|
2175
|
+
* validation schema defined by decorators.
|
|
2176
|
+
*
|
|
2177
|
+
* ### Metadata Introspection
|
|
2178
|
+
* This method uses reflection to access metadata that was stored when validation
|
|
2179
|
+
* decorators were applied to class properties. It provides a programmatic way to
|
|
2180
|
+
* inspect the validation schema of any decorated class.
|
|
2181
|
+
*
|
|
2182
|
+
* ### Use Cases
|
|
2183
|
+
* - **Schema Inspection**: Understand what validation rules apply to a class
|
|
2184
|
+
* - **Dynamic Validation**: Build validation logic based on class structure
|
|
2185
|
+
* - **Documentation**: Generate validation documentation from code
|
|
2186
|
+
* - **Testing**: Verify that proper decorators are applied
|
|
2187
|
+
*
|
|
2188
|
+
* @example
|
|
2189
|
+
* ```typescript
|
|
2190
|
+
* class User {
|
|
2191
|
+
* @IsRequired()
|
|
2192
|
+
* @IsEmail()
|
|
2193
|
+
* email: string;
|
|
2194
|
+
*
|
|
2195
|
+
* @IsRequired()
|
|
2196
|
+
* @MinLength(3)
|
|
2197
|
+
* @MaxLength(50)
|
|
2198
|
+
* name: string;
|
|
2199
|
+
*
|
|
2200
|
+
* @IsOptional()
|
|
2201
|
+
* @IsNumber()
|
|
2202
|
+
* age?: number;
|
|
2203
|
+
* }
|
|
2204
|
+
*
|
|
2205
|
+
* // Extract validation rules
|
|
2206
|
+
* const rules = Validator.getTargetRules(User);
|
|
2207
|
+
* console.log(rules);
|
|
2208
|
+
* // Output:
|
|
2209
|
+
* // {
|
|
2210
|
+
* // email: ['required', 'email'],
|
|
2211
|
+
* // name: ['required', 'minLength', 'maxLength'],
|
|
2212
|
+
* // age: ['number'] // IsOptional doesn't add a rule
|
|
2213
|
+
* // }
|
|
2214
|
+
*
|
|
2215
|
+
* // Check if a property has specific rules
|
|
2216
|
+
* const emailRules = rules.email;
|
|
2217
|
+
* const hasEmailValidation = emailRules.includes('email');
|
|
2218
|
+
*
|
|
2219
|
+
* // Programmatic rule inspection
|
|
2220
|
+
* function analyzeClass(targetClass: any) {
|
|
2221
|
+
* const rules = Validator.getTargetRules(targetClass);
|
|
2222
|
+
* const analysis = {
|
|
2223
|
+
* totalProperties: Object.keys(rules).length,
|
|
2224
|
+
* requiredProperties: [],
|
|
2225
|
+
* optionalProperties: []
|
|
2226
|
+
* };
|
|
2227
|
+
*
|
|
2228
|
+
* for (const [property, propertyRules] of Object.entries(rules)) {
|
|
2229
|
+
* if (propertyRules.includes('required')) {
|
|
2230
|
+
* analysis.requiredProperties.push(property);
|
|
2231
|
+
* } else {
|
|
2232
|
+
* analysis.optionalProperties.push(property);
|
|
2233
|
+
* }
|
|
2234
|
+
* }
|
|
2235
|
+
*
|
|
2236
|
+
* return analysis;
|
|
2237
|
+
* }
|
|
2238
|
+
* ```
|
|
2239
|
+
*
|
|
2240
|
+
* @template T - The class constructor type to extract rules from
|
|
2241
|
+
*
|
|
2242
|
+
* @param target - Class constructor with validation decorators
|
|
2243
|
+
*
|
|
2244
|
+
* @returns Record mapping property names to their validation rules
|
|
2245
|
+
*
|
|
2246
|
+
*
|
|
2247
|
+
* @see {@link validateTarget} - Uses this method to get validation rules
|
|
2248
|
+
* @see {@link buildPropertyDecorator} - How rules are attached to properties
|
|
2249
|
+
* @public
|
|
2250
|
+
*/
|
|
2251
|
+
static getTargetRules<T extends ClassConstructor>(target: T): Record<keyof InstanceType<T>, ValidatorRule[]>;
|
|
2252
|
+
/**
|
|
2253
|
+
* ## Get Target Validation Options
|
|
2254
|
+
*
|
|
2255
|
+
* Retrieves validation options that have been configured for a specific class
|
|
2256
|
+
* through the `@ValidationTargetOptions` decorator. These options control how
|
|
2257
|
+
* validation behaves when `validateTarget` is called on the class.
|
|
2258
|
+
*
|
|
2259
|
+
* ### Configuration Options
|
|
2260
|
+
* Options can include custom error message builders, validation contexts,
|
|
2261
|
+
* and other class-level validation behaviors that should be applied consistently
|
|
2262
|
+
* whenever the class is validated.
|
|
2263
|
+
*
|
|
2264
|
+
* @example
|
|
2265
|
+
* ```typescript
|
|
2266
|
+
* // Class with custom validation options
|
|
2267
|
+
* @ValidationTargetOptions({
|
|
2268
|
+
* errorMessageBuilder: (translatedName, error) => {
|
|
2269
|
+
* return `❌ ${translatedName}: ${error}`;
|
|
2270
|
+
* }
|
|
2271
|
+
* })
|
|
2272
|
+
* class CustomUser {
|
|
2273
|
+
* @IsRequired()
|
|
2274
|
+
* @IsEmail()
|
|
2275
|
+
* email: string;
|
|
2276
|
+
* }
|
|
2277
|
+
*
|
|
2278
|
+
* // Get the configured options
|
|
2279
|
+
* const options = Validator.getValidateTargetOptions(CustomUser);
|
|
2280
|
+
* console.log(typeof options.errorMessageBuilder); // 'function'
|
|
2281
|
+
*
|
|
2282
|
+
* // These options will be automatically used when validating
|
|
2283
|
+
* const result = await Validator.validateTarget(CustomUser, userData);
|
|
2284
|
+
* // Error messages will use the custom format
|
|
2285
|
+
* ```
|
|
2286
|
+
*
|
|
2287
|
+
* @template T - The class constructor type to get options for
|
|
2288
|
+
*
|
|
2289
|
+
* @param target - Class constructor that may have validation options
|
|
2290
|
+
*
|
|
2291
|
+
* @returns Validation options object, or empty object if none configured
|
|
2292
|
+
*
|
|
2293
|
+
*
|
|
2294
|
+
* @see {@link validateTarget} - Uses these options during validation
|
|
2295
|
+
* @see {@link ValidationTargetOptions} - Decorator to set these options
|
|
2296
|
+
* @public
|
|
2297
|
+
*/
|
|
2298
|
+
static getValidateTargetOptions<T extends ClassConstructor>(target: T): ValidatorValidateTargetOptions<T, any>;
|
|
2299
|
+
/**
|
|
2300
|
+
* ## Build Rule Decorator Factory
|
|
2301
|
+
*
|
|
2302
|
+
* **Core Method**: Creates parameterized decorator factories for validation rules.
|
|
2303
|
+
*
|
|
2304
|
+
* This is the primary factory method for building reusable validation decorators
|
|
2305
|
+
* that can accept configuration parameters. It transforms simple validation functions
|
|
2306
|
+
* into TypeScript decorators with full type safety, parameter validation, and
|
|
2307
|
+
* metadata management.
|
|
2308
|
+
*
|
|
2309
|
+
* ### Architecture Overview
|
|
2310
|
+
*
|
|
2311
|
+
* The decorator factory pattern implemented here enables clean, type-safe validation
|
|
2312
|
+
* decorators while maintaining runtime flexibility. The method creates a three-level
|
|
2313
|
+
* function chain:
|
|
2314
|
+
*
|
|
2315
|
+
* 1. **Factory Function** (`buildRuleDecorator(ruleFunction)`)
|
|
2316
|
+
* - Takes a validation rule function
|
|
2317
|
+
* - Returns a parameter-accepting function
|
|
2318
|
+
*
|
|
2319
|
+
* 2. **Parameter Function** (`factory(ruleParameters)`)
|
|
2320
|
+
* - Accepts rule parameters (conditionally optional)
|
|
2321
|
+
* - Returns a property decorator
|
|
2322
|
+
*
|
|
2323
|
+
* 3. **Property Decorator** (`@Decorator(parameters)`)
|
|
2324
|
+
* - Attaches validation metadata to class properties
|
|
2325
|
+
* - Executed at class definition time
|
|
2326
|
+
*
|
|
2327
|
+
* ### Type System Deep Dive
|
|
2328
|
+
*
|
|
2329
|
+
* #### Parameter Handling
|
|
2330
|
+
* The method uses rest parameters (`...ruleParameters: TRuleParams`) to accept
|
|
2331
|
+
* variable numbers of arguments. This allows decorators to be called with zero
|
|
2332
|
+
* or more parameters:
|
|
2333
|
+
*
|
|
2334
|
+
* ```typescript
|
|
2335
|
+
* @MinLength(5) // Single parameter
|
|
2336
|
+
* @Range(0, 100) // Multiple parameters
|
|
2337
|
+
* @IsRequired() // No parameters (empty rest args)
|
|
2338
|
+
* ```
|
|
2339
|
+
*
|
|
2340
|
+
* #### Rule Name Registration
|
|
2341
|
+
* The optional `ruleName` parameter enables automatic rule registration for rules
|
|
2342
|
+
* that can be called without parameters. This is restricted to rules where
|
|
2343
|
+
* `ValidatorTupleAllowsEmpty<TRuleParams>` is true, ensuring type safety.
|
|
2344
|
+
*
|
|
2345
|
+
* ### Implementation Flow
|
|
2346
|
+
*
|
|
2347
|
+
* #### Phase 1: Rule Registration (Optional)
|
|
2348
|
+
* ```typescript
|
|
2349
|
+
* if (isNonNullString(ruleName)) {
|
|
2350
|
+
* Validator.registerRule(ruleName, ruleFunction);
|
|
2351
|
+
* }
|
|
2352
|
+
* ```
|
|
2353
|
+
* - Registers the rule function under the provided name
|
|
2354
|
+
* - Enables string-based rule references in validation configurations
|
|
2355
|
+
* - Only available for rules that support empty parameter calls
|
|
2356
|
+
*
|
|
2357
|
+
* #### Phase 2: Decorator Factory Creation
|
|
2358
|
+
* ```typescript
|
|
2359
|
+
* return function (...ruleParameters: TRuleParams) {
|
|
2360
|
+
* const finalRuleParameters = ruleParameters;
|
|
2361
|
+
* ```
|
|
2362
|
+
* - Returns a function accepting rest parameters
|
|
2363
|
+
* - Captures the exact parameters passed to the decorator
|
|
2364
|
+
* - No default value assignment - parameters are used as-is
|
|
2365
|
+
* ```typescript
|
|
2366
|
+
* const enhancedValidatorFunction = function(validationOptions) {
|
|
2367
|
+
* const enhancedOptions = Object.assign({}, validationOptions);
|
|
2368
|
+
* enhancedOptions.ruleParams = Validator.normalizeRuleParams(finalRuleParameters);
|
|
2369
|
+
* return ruleFunction(enhancedOptions);
|
|
2370
|
+
* };
|
|
2371
|
+
* ```
|
|
2372
|
+
* - Creates wrapper function that injects normalized parameters
|
|
2373
|
+
* - Preserves original validation options structure
|
|
2374
|
+
* - Maintains function identity for debugging
|
|
2375
|
+
*
|
|
2376
|
+
* #### Phase 3: Symbol Marker Preservation
|
|
2377
|
+
* ```typescript
|
|
2378
|
+
* if (hasRuleMarker(ruleFunction, VALIDATOR_NESTED_RULE_MARKER)) {
|
|
2379
|
+
* (enhancedValidatorFunction as any)[VALIDATOR_NESTED_RULE_MARKER] = true;
|
|
2380
|
+
* (enhancedValidatorFunction as any)[VALIDATOR_NESTED_RULE_PARAMS] = normalizedParams;
|
|
2381
|
+
* }
|
|
2382
|
+
* ```
|
|
2383
|
+
* - Preserves special markers for nested validation rules
|
|
2384
|
+
* - Stores parameters for inspection by advanced validation features
|
|
2385
|
+
* - Essential for `ValidateNested` and similar target-based rules
|
|
2386
|
+
*
|
|
2387
|
+
* #### Phase 4: Decorator Creation
|
|
2388
|
+
* ```typescript
|
|
2389
|
+
* return Validator.buildPropertyDecorator<TRuleParams, Context>(enhancedValidatorFunction);
|
|
2390
|
+
* ```
|
|
2391
|
+
* - Delegates to lower-level decorator creation
|
|
2392
|
+
* - Attaches validation metadata to class properties
|
|
2393
|
+
* - Enables rule accumulation and execution
|
|
2394
|
+
*
|
|
2395
|
+
* ### Rule Function Interface
|
|
2396
|
+
*
|
|
2397
|
+
* Rule functions receive a comprehensive validation context:
|
|
2398
|
+
*
|
|
2399
|
+
* ```typescript
|
|
2400
|
+
* interface ValidationOptions<TRuleParams, Context> {
|
|
2401
|
+
* value: any; // The property value being validated
|
|
2402
|
+
* ruleParams: TRuleParams; // Normalized parameter array
|
|
2403
|
+
* context?: Context; // Optional validation context
|
|
2404
|
+
* fieldName: string; // Raw property name
|
|
2405
|
+
* translatedPropertyName: string; // Localized property name
|
|
2406
|
+
* i18n?: II18nService; // Internationalization service
|
|
2407
|
+
* // ... additional validation metadata
|
|
2408
|
+
* }
|
|
2409
|
+
* ```
|
|
2410
|
+
*
|
|
2411
|
+
* **Return Values:**
|
|
2412
|
+
* - `string | false | undefined`: Validation error message or falsy for success
|
|
2413
|
+
* - `Promise<string | false | undefined>`: For async validation rules
|
|
2414
|
+
* - `ValidationResult`: Structured validation result object
|
|
2415
|
+
*
|
|
2416
|
+
* ### Usage Patterns
|
|
2417
|
+
*
|
|
2418
|
+
* #### Basic Parameterized Rules
|
|
2419
|
+
* ```typescript
|
|
2420
|
+
* // Rule function with single parameter
|
|
2421
|
+
* const minLengthRule = ({ value, ruleParams }: ValidationOptions) => {
|
|
2422
|
+
* const [minLength] = ruleParams;
|
|
2423
|
+
* return value.length >= minLength || `Must be at least ${minLength} characters`;
|
|
2424
|
+
* };
|
|
2425
|
+
*
|
|
2426
|
+
* // Create decorator factory
|
|
2427
|
+
* const MinLength = Validator.buildRuleDecorator(minLengthRule);
|
|
2428
|
+
*
|
|
2429
|
+
* // Usage with different parameters
|
|
2430
|
+
* class User {
|
|
2431
|
+
* @MinLength(3) // Requires 3+ characters
|
|
2432
|
+
* username: string;
|
|
2433
|
+
*
|
|
2434
|
+
* @MinLength(8) // Requires 8+ characters
|
|
2435
|
+
* password: string;
|
|
2436
|
+
* }
|
|
2437
|
+
* ```
|
|
2438
|
+
*
|
|
2439
|
+
* #### Multi-Parameter Rules
|
|
2440
|
+
* ```typescript
|
|
2441
|
+
* const rangeRule = ({ value, ruleParams }) => {
|
|
2442
|
+
* const [min, max] = ruleParams;
|
|
2443
|
+
* if (typeof value !== 'number') return 'Must be a number';
|
|
2444
|
+
* return (value >= min && value <= max) || `Must be between ${min} and ${max}`;
|
|
2445
|
+
* };
|
|
2446
|
+
*
|
|
2447
|
+
* const InRange = Validator.buildRuleDecorator(rangeRule);
|
|
2448
|
+
*
|
|
2449
|
+
* class Product {
|
|
2450
|
+
* @InRange(0, 100) // Percentage: 0-100
|
|
2451
|
+
* discount: number;
|
|
2452
|
+
*
|
|
2453
|
+
* @InRange(-90, 90) // Latitude: -90 to 90
|
|
2454
|
+
* latitude: number;
|
|
2455
|
+
* }
|
|
2456
|
+
* ```
|
|
2457
|
+
*
|
|
2458
|
+
* #### Context-Aware Rules
|
|
2459
|
+
* ```typescript
|
|
2460
|
+
* interface SecurityContext {
|
|
2461
|
+
* userRole: 'admin' | 'user' | 'guest';
|
|
2462
|
+
* permissions: string[];
|
|
2463
|
+
* organizationId: string;
|
|
2464
|
+
* }
|
|
2465
|
+
*
|
|
2466
|
+
* const requiresPermissionRule = ({ value, ruleParams, context }) => {
|
|
2467
|
+
* const [requiredPermission] = ruleParams;
|
|
2468
|
+
* const securityContext = context as SecurityContext;
|
|
2469
|
+
*
|
|
2470
|
+
* if (!securityContext) return 'Security context required';
|
|
2471
|
+
* return securityContext.permissions.includes(requiredPermission) ||
|
|
2472
|
+
* `Requires '${requiredPermission}' permission`;
|
|
2473
|
+
* };
|
|
2474
|
+
*
|
|
2475
|
+
* const RequiresPermission = Validator.buildRuleDecorator<SecurityContext>(requiresPermissionRule);
|
|
2476
|
+
*
|
|
2477
|
+
* class SecureResource {
|
|
2478
|
+
* @RequiresPermission('read')
|
|
2479
|
+
* publicData: string;
|
|
2480
|
+
*
|
|
2481
|
+
* @RequiresPermission('admin')
|
|
2482
|
+
* adminData: string;
|
|
2483
|
+
* }
|
|
2484
|
+
* ```
|
|
2485
|
+
*
|
|
2486
|
+
* #### Optional Parameter Rules
|
|
2487
|
+
* ```typescript
|
|
2488
|
+
* // Rule that works with or without parameters
|
|
2489
|
+
* const patternRule = ({ value, ruleParams }) => {
|
|
2490
|
+
* const [regex, flags] = ruleParams;
|
|
2491
|
+
*
|
|
2492
|
+
* // Default email pattern if no parameters provided
|
|
2493
|
+
* const defaultRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2494
|
+
* const pattern = regex || defaultRegex;
|
|
2495
|
+
* const regexFlags = flags || 'i';
|
|
2496
|
+
*
|
|
2497
|
+
* const testRegex = new RegExp(pattern, regexFlags);
|
|
2498
|
+
* return testRegex.test(value) || 'Invalid format';
|
|
2499
|
+
* };
|
|
2500
|
+
*
|
|
2501
|
+
* const MatchesPattern = Validator.buildRuleDecorator(patternRule);
|
|
2502
|
+
*
|
|
2503
|
+
* class Data {
|
|
2504
|
+
* @MatchesPattern() // Uses default email pattern
|
|
2505
|
+
* email: string;
|
|
2506
|
+
*
|
|
2507
|
+
* @MatchesPattern(/^\d{3}-\d{2}-\d{4}$/) // SSN pattern
|
|
2508
|
+
* ssn: string;
|
|
2509
|
+
* }
|
|
2510
|
+
* ```
|
|
2511
|
+
*
|
|
2512
|
+
* #### Async Validation Rules
|
|
2513
|
+
* ```typescript
|
|
2514
|
+
* const uniqueEmailRule = async ({ value, context }) => {
|
|
2515
|
+
* if (!value) return true; // Skip if empty
|
|
2516
|
+
*
|
|
2517
|
+
* const db = context?.database;
|
|
2518
|
+
* if (!db) return 'Database context required for uniqueness validation';
|
|
2519
|
+
*
|
|
2520
|
+
* try {
|
|
2521
|
+
* const existing = await db.query(
|
|
2522
|
+
* `SELECT 1 FROM users WHERE email = $1 LIMIT 1`,
|
|
2523
|
+
* [value]
|
|
2524
|
+
* );
|
|
2525
|
+
*
|
|
2526
|
+
* return !existing.rows.length || 'Email address already registered';
|
|
2527
|
+
* } catch (error) {
|
|
2528
|
+
* return 'Unable to validate email uniqueness';
|
|
2529
|
+
* }
|
|
2530
|
+
* };
|
|
2531
|
+
*
|
|
2532
|
+
* const IsUniqueEmail = Validator.buildRuleDecorator(uniqueEmailRule);
|
|
2533
|
+
*
|
|
2534
|
+
* class Registration {
|
|
2535
|
+
* @IsRequired()
|
|
2536
|
+
* @IsEmail()
|
|
2537
|
+
* @IsUniqueEmail() // Empty parameters since no config needed
|
|
2538
|
+
* email: string;
|
|
2539
|
+
* }
|
|
2540
|
+
* ```
|
|
2541
|
+
*
|
|
2542
|
+
* ### Advanced Features
|
|
2543
|
+
*
|
|
2544
|
+
* #### Custom Validation Results
|
|
2545
|
+
* ```typescript
|
|
2546
|
+
* const complexRule = ({ value, ruleParams, fieldName }) => {
|
|
2547
|
+
* const [config] = ruleParams;
|
|
2548
|
+
*
|
|
2549
|
+
* // Return structured validation result
|
|
2550
|
+
* return {
|
|
2551
|
+
* success: false,
|
|
2552
|
+
* error: {
|
|
2553
|
+
* message: `Custom validation failed for ${fieldName}`,
|
|
2554
|
+
* code: 'CUSTOM_VALIDATION_ERROR',
|
|
2555
|
+
* details: { config, value }
|
|
2556
|
+
* }
|
|
2557
|
+
* };
|
|
2558
|
+
* };
|
|
2559
|
+
* ```
|
|
2560
|
+
*
|
|
2561
|
+
* #### Rule Composition
|
|
2562
|
+
* ```typescript
|
|
2563
|
+
* const notEmptyRule = ({ value }) =>
|
|
2564
|
+
* value != null && value !== '' || 'Field cannot be empty';
|
|
2565
|
+
*
|
|
2566
|
+
* const emailRule = ({ value }) =>
|
|
2567
|
+
* /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) || 'Invalid email format';
|
|
2568
|
+
*
|
|
2569
|
+
* // Combine rules in a single decorator
|
|
2570
|
+
* const IsRequiredEmail = Validator.buildRuleDecorator(({ value }) => {
|
|
2571
|
+
* const notEmpty = notEmptyRule({ value });
|
|
2572
|
+
* if (notEmpty !== true) return notEmpty;
|
|
2573
|
+
*
|
|
2574
|
+
* return emailRule({ value });
|
|
2575
|
+
* });
|
|
2576
|
+
*
|
|
2577
|
+
* class Contact {
|
|
2578
|
+
* @IsRequiredEmail() // Combines required + email validation
|
|
2579
|
+
* primaryEmail: string;
|
|
2580
|
+
* }
|
|
2581
|
+
* ```
|
|
2582
|
+
*
|
|
2583
|
+
* ### Performance Considerations
|
|
2584
|
+
*
|
|
2585
|
+
* #### Memory Efficiency
|
|
2586
|
+
* - Factory functions are created once per rule type, not per usage
|
|
2587
|
+
* - Parameter normalization happens at decoration time, not validation time
|
|
2588
|
+
* - Rule functions are reused across multiple property applications
|
|
2589
|
+
*
|
|
2590
|
+
* #### Runtime Performance
|
|
2591
|
+
* - Validation execution is deferred until `validateTarget()` is called
|
|
2592
|
+
* - Parameter binding happens during decoration, not validation
|
|
2593
|
+
* - No reflection overhead during actual validation
|
|
2594
|
+
*
|
|
2595
|
+
* #### Bundle Size Impact
|
|
2596
|
+
* - Rule functions are tree-shakeable when not used
|
|
2597
|
+
* - Decorator factories have minimal runtime footprint
|
|
2598
|
+
* - Type-only parameters don't affect bundle size
|
|
2599
|
+
*
|
|
2600
|
+
* ### Error Handling & Debugging
|
|
2601
|
+
*
|
|
2602
|
+
* #### Common Issues
|
|
2603
|
+
* ```typescript
|
|
2604
|
+
* // ❌ Wrong: Single value instead of array
|
|
2605
|
+
* @MinLength(5) // TypeScript error: expected array
|
|
2606
|
+
*
|
|
2607
|
+
* // ✅ Correct: Array parameter
|
|
2608
|
+
* @MinLength(5)
|
|
2609
|
+
* ```
|
|
2610
|
+
*
|
|
2611
|
+
* ```typescript
|
|
2612
|
+
* // ❌ Wrong: Missing parameters for required rule
|
|
2613
|
+
* const RequiredRule = ({ value }) => !!value || 'Required';
|
|
2614
|
+
* const IsRequired = Validator.buildRuleDecorator(RequiredRule);
|
|
2615
|
+
* @IsRequired() // Runtime error: undefined parameters
|
|
2616
|
+
*
|
|
2617
|
+
* ```
|
|
2618
|
+
*
|
|
2619
|
+
* #### Debugging Tips
|
|
2620
|
+
* - Use `console.log(ruleParams)` in rule functions to inspect parameters
|
|
2621
|
+
* - Check `hasRuleMarker()` for nested rule identification
|
|
2622
|
+
* - Validate parameter types at rule function entry
|
|
2623
|
+
* - Use meaningful error messages for easier debugging
|
|
2624
|
+
*
|
|
2625
|
+
* ### Integration with Validation System
|
|
2626
|
+
*
|
|
2627
|
+
* #### Rule Accumulation
|
|
2628
|
+
* Multiple decorators on the same property are accumulated:
|
|
2629
|
+
* ```typescript
|
|
2630
|
+
* class RobustField {
|
|
2631
|
+
* @IsRequired() // Rule 1: Required check
|
|
2632
|
+
* @MinLength(3) // Rule 2: Length check
|
|
2633
|
+
* @IsAlphanumeric() // Rule 3: Content check
|
|
2634
|
+
* username: string;
|
|
2635
|
+
* }
|
|
2636
|
+
* ```
|
|
2637
|
+
*
|
|
2638
|
+
* #### Validation Execution Order
|
|
2639
|
+
* - Rules execute in decoration order (top to bottom)
|
|
2640
|
+
* - First failing rule stops validation (unless configured otherwise)
|
|
2641
|
+
* - All rules receive the same validation context
|
|
2642
|
+
*
|
|
2643
|
+
* #### Context Propagation
|
|
2644
|
+
* ```typescript
|
|
2645
|
+
* const result = await Validator.validateTarget(UserClass, {
|
|
2646
|
+
* data: userData,
|
|
2647
|
+
* context: { userId: 123, permissions: ['read'] },
|
|
2648
|
+
* errorMessageBuilder: (field, error) => `${field}: ${error}`
|
|
2649
|
+
* });
|
|
2650
|
+
* ```
|
|
2651
|
+
*
|
|
2652
|
+
* ### Migration Guide
|
|
2653
|
+
*
|
|
2654
|
+
* #### From Manual Decorators
|
|
2655
|
+
* ```typescript
|
|
2656
|
+
* // Before: Manual decorator creation
|
|
2657
|
+
* function MinLength(minLength: number) {
|
|
2658
|
+
* return function(target: any, propertyKey: string) {
|
|
2659
|
+
* // Manual metadata attachment...
|
|
2660
|
+
* };
|
|
2661
|
+
* }
|
|
2662
|
+
*
|
|
2663
|
+
* // After: Using buildRuleDecorator
|
|
2664
|
+
* const minLengthRule = ({ value, ruleParams }) => {
|
|
2665
|
+
* const [min] = ruleParams;
|
|
2666
|
+
* return value.length >= min || `Too short`;
|
|
2667
|
+
* };
|
|
2668
|
+
* const MinLength = Validator.buildRuleDecorator(minLengthRule);
|
|
2669
|
+
* ```
|
|
2670
|
+
*
|
|
2671
|
+
* #### From Class-Based Validators
|
|
2672
|
+
* ```typescript
|
|
2673
|
+
* // Before: Class-based approach
|
|
2674
|
+
* class MinLengthValidator {
|
|
2675
|
+
* constructor(private minLength: number) {}
|
|
2676
|
+
* validate(value: any): string | null {
|
|
2677
|
+
* return value.length >= this.minLength ? null : 'Too short';
|
|
2678
|
+
* }
|
|
2679
|
+
* }
|
|
2680
|
+
*
|
|
2681
|
+
* // After: Function-based with buildRuleDecorator
|
|
2682
|
+
* const MinLength = Validator.buildRuleDecorator(
|
|
2683
|
+
* ({ value, ruleParams }) => {
|
|
2684
|
+
* const [min] = ruleParams;
|
|
2685
|
+
* return value.length >= min || 'Too short';
|
|
2686
|
+
* }
|
|
2687
|
+
* );
|
|
2688
|
+
* ```
|
|
2689
|
+
*
|
|
2690
|
+
* ### Best Practices
|
|
2691
|
+
*
|
|
2692
|
+
* #### Rule Function Design
|
|
2693
|
+
* - Keep rule functions pure and testable
|
|
2694
|
+
* - Use descriptive error messages
|
|
2695
|
+
* - Handle edge cases (null, undefined, empty strings)
|
|
2696
|
+
* - Validate parameter types at function entry
|
|
2697
|
+
* - Return consistent result types
|
|
2698
|
+
*
|
|
2699
|
+
* #### Parameter Design
|
|
2700
|
+
* - Use array parameters for consistency
|
|
2701
|
+
* - Provide sensible defaults for optional parameters
|
|
2702
|
+
* - Document parameter order and types
|
|
2703
|
+
* - Consider parameter validation in rule functions
|
|
2704
|
+
*
|
|
2705
|
+
* #### Performance Optimization
|
|
2706
|
+
* - Avoid expensive operations in frequently-used rules
|
|
2707
|
+
* - Use async rules only when necessary
|
|
2708
|
+
* - Cache expensive computations when possible
|
|
2709
|
+
* - Profile validation performance in production
|
|
2710
|
+
*
|
|
2711
|
+
* #### Type Safety
|
|
2712
|
+
* - Leverage TypeScript's type system fully
|
|
2713
|
+
* - Use strict null checks in rule functions
|
|
2714
|
+
* - Provide accurate type annotations
|
|
2715
|
+
* - Test with strict TypeScript settings
|
|
2716
|
+
*
|
|
2717
|
+
* ### Troubleshooting
|
|
2718
|
+
*
|
|
2719
|
+
* #### "Property 'ruleParams' does not exist"
|
|
2720
|
+
* - Ensure rule function accepts `ValidationOptions` parameter
|
|
2721
|
+
* - Check that parameters are destructured correctly
|
|
2722
|
+
*
|
|
2723
|
+
* #### "Cannot read property '0' of undefined"
|
|
2724
|
+
* - Rule expects parameters but none provided
|
|
2725
|
+
* - Use empty call `()` for rules that don't need parameters
|
|
2726
|
+
* - Check that decorator is called with correct syntax
|
|
2727
|
+
*
|
|
2728
|
+
* #### "Type 'string' is not assignable to type 'TRuleParams'"
|
|
2729
|
+
* - Parameters must be arrays, not single values
|
|
2730
|
+
* - Wrap single parameters: `[value]` instead of `value`
|
|
2731
|
+
*
|
|
2732
|
+
* #### Async rule not working
|
|
2733
|
+
* - Ensure `validateTarget()` is called with `await`
|
|
2734
|
+
* - Check that validation context includes required services
|
|
2735
|
+
* - Verify async rule returns Promise<string | false | undefined>
|
|
2736
|
+
*
|
|
2737
|
+
* ### Related Methods
|
|
2738
|
+
*
|
|
2739
|
+
* - {@link buildTargetRuleDecorator} - Specialized for nested class validation
|
|
2740
|
+
* - {@link buildPropertyDecorator} - Low-level decorator creation
|
|
2741
|
+
* - {@link buildMultiRuleDecorator} - For rules with multiple validation functions
|
|
2742
|
+
*
|
|
2743
|
+
* ### See Also
|
|
2744
|
+
*
|
|
2745
|
+
* - {@link ValidatorTupleAllowsEmpty} - Type for conditional parameter optionality
|
|
2746
|
+
* - {@link ValidatorRuleFunction} - Rule function interface
|
|
2747
|
+
* - {@link validateTarget} - Main validation execution method
|
|
2748
|
+
* - {@link ValidationOptions} - Complete validation context interface
|
|
2749
|
+
*
|
|
2750
|
+
* @template TRuleParams - Tuple type defining the exact parameter structure for the rule.
|
|
2751
|
+
* Must extend `ValidatorRuleParams` (array of unknown). Examples:
|
|
2752
|
+
* - `[min: number]` - Single numeric parameter
|
|
2753
|
+
* - `[min: number, max: number]` - Two numeric parameters
|
|
2754
|
+
* - `[pattern: RegExp, flags?: string]` - Regex with optional flags
|
|
2755
|
+
* - `[]` - No parameters required
|
|
2756
|
+
*
|
|
2757
|
+
* @template Context - Type of the validation context object passed through the validation chain.
|
|
2758
|
+
* Defaults to `unknown` if not specified. Common patterns:
|
|
2759
|
+
* - `SecurityContext` - User permissions and roles
|
|
2760
|
+
* - `DatabaseContext` - Database connections and transactions
|
|
2761
|
+
* - `LocalizationContext` - I18n and locale information
|
|
2762
|
+
* - `{ userId: string; permissions: string[] }` - Inline context type
|
|
2763
|
+
*
|
|
2764
|
+
* @param ruleFunction - The validation rule function to be wrapped in a decorator factory.
|
|
2765
|
+
* @param ruleName - Optional rule name for automatic registration with `Validator.registerRule()`.
|
|
2766
|
+
* This parameter is typed as `ValidatorOptionalOrEmptyRuleNames`, which is a union of rule names
|
|
2767
|
+
* where `ValidatorTupleAllowsEmpty<TRuleParams>` extends `true`. This includes rules like:
|
|
2768
|
+
* - `"Required"` - No parameters needed
|
|
2769
|
+
* - `"Email"` - No parameters needed
|
|
2770
|
+
* - `"PhoneNumber"` - Optional country code parameter
|
|
2771
|
+
* - `"Nullable"`, `"Optional"`, `"Empty"` - No parameters needed
|
|
2772
|
+
*
|
|
2773
|
+
* When provided, the rule function will be automatically registered under this name,
|
|
2774
|
+
* enabling programmatic rule discovery and string-based rule references in validation configurations.
|
|
2775
|
+
* This is particularly useful for built-in validation rules that need to be accessible by name
|
|
2776
|
+
* without requiring function references. Rules with required parameters cannot be registered
|
|
2777
|
+
* this way since they must always be called with arguments (e.g., `MinLength[5]`, `MaxLength[10]`).
|
|
2778
|
+
* This function receives normalized validation options and returns validation results.
|
|
2779
|
+
* Function signature: `(options: ValidationOptions<TRuleParams, Context>) => ValidationResult`
|
|
2780
|
+
*
|
|
2781
|
+
* @param symbolMarker - (Internal use only) A unique symbol used to mark the rule function for special handling.
|
|
2782
|
+
* This parameter is not intended for public use and should generally be omitted.
|
|
2783
|
+
* It is used internally to preserve symbol markers through function wrapping,
|
|
2784
|
+
* enabling reliable identification of the rule internally even in minified code.
|
|
2785
|
+
* @returns A decorator factory function that accepts rule parameters as rest parameters and returns a property decorator.
|
|
2786
|
+
* The signature is: `(...ruleParameters: TRuleParams) => PropertyDecorator`
|
|
2787
|
+
*
|
|
2788
|
+
* @example
|
|
2789
|
+
* ```typescript
|
|
2790
|
+
* // Basic usage with required parameters
|
|
2791
|
+
* const minLengthRule = ({ value, ruleParams }) => {
|
|
2792
|
+
* const [minLength] = ruleParams;
|
|
2793
|
+
* return value.length >= minLength || `Minimum ${minLength} characters required`;
|
|
2794
|
+
* };
|
|
2795
|
+
*
|
|
2796
|
+
* const MinLength = Validator.buildRuleDecorator(minLengthRule);
|
|
2797
|
+
*
|
|
2798
|
+
* class User {
|
|
2799
|
+
* @MinLength(8)
|
|
2800
|
+
* password: string;
|
|
2801
|
+
* }
|
|
2802
|
+
* ```
|
|
2803
|
+
*
|
|
2804
|
+
* @example
|
|
2805
|
+
* ```typescript
|
|
2806
|
+
* // Advanced usage with context and custom types
|
|
2807
|
+
* interface DatabaseContext {
|
|
2808
|
+
* db: DatabaseConnection;
|
|
2809
|
+
* transaction: Transaction;
|
|
2810
|
+
* }
|
|
2811
|
+
*
|
|
2812
|
+
* const uniqueRule = async ({ value, ruleParams, context }) => {
|
|
2813
|
+
* const [table, column] = ruleParams;
|
|
2814
|
+
* const db = (context as DatabaseContext).db;
|
|
2815
|
+
*
|
|
2816
|
+
* const exists = await db.query(
|
|
2817
|
+
* `SELECT 1 FROM ${table} WHERE ${column} = $1 LIMIT 1`,
|
|
2818
|
+
* [value]
|
|
2819
|
+
* );
|
|
2820
|
+
*
|
|
2821
|
+
* return !exists.rows.length || `${column} must be unique`;
|
|
2822
|
+
* };
|
|
2823
|
+
*
|
|
2824
|
+
* const IsUnique = Validator.buildRuleDecorator<DatabaseContext>(uniqueRule);
|
|
2825
|
+
*
|
|
2826
|
+
* class User {
|
|
2827
|
+
* @IsUnique('users', 'email')
|
|
2828
|
+
* email: string;
|
|
2829
|
+
* }
|
|
2830
|
+
* ```
|
|
2831
|
+
*
|
|
2832
|
+
* @since 1.0.0
|
|
2833
|
+
* @public
|
|
2834
|
+
* @category Decorator Factories
|
|
2835
|
+
* @see {@link buildTargetRuleDecorator}
|
|
2836
|
+
* @see {@link ValidatorRuleFunction}
|
|
2837
|
+
* @see {@link ValidatorTupleAllowsEmpty}
|
|
2838
|
+
*/
|
|
2839
|
+
static buildRuleDecorator<TRuleParams extends ValidatorRuleParams = ValidatorRuleParams, Context = unknown>(ruleFunction: ValidatorRuleFunction<TRuleParams, Context>, ruleName?: ValidatorRuleName, symbolMarker?: symbol): (...ruleParameters: TRuleParams) => PropertyDecorator;
|
|
2840
|
+
private static _prepareRuleDecorator;
|
|
2841
|
+
private static _buildRuleDecorator;
|
|
2842
|
+
/**
|
|
2843
|
+
* ## Build Target Rule Decorator Factory
|
|
2844
|
+
*
|
|
2845
|
+
* Creates a specialized decorator factory for validation rules that target nested class
|
|
2846
|
+
* objects. This method wraps buildRuleDecorator with type specialization for target-based
|
|
2847
|
+
* rules like @ValidateNested.
|
|
2848
|
+
*
|
|
2849
|
+
* ### Purpose
|
|
2850
|
+
* Target rule decorators validate properties by delegating to another class's validation
|
|
2851
|
+
* schema. The most common example is @ValidateNested, which validates nested objects
|
|
2852
|
+
* against a separate decorated class.
|
|
2853
|
+
*
|
|
2854
|
+
* ### How It Works
|
|
2855
|
+
* 1. Takes a rule function specialized for target parameters: [TargetClass]
|
|
2856
|
+
* 2. Wraps it using buildRuleDecorator to create a decorator factory
|
|
2857
|
+
* 3. Returns a decorator factory that accepts the target class constructor
|
|
2858
|
+
* 4. When the decorator is applied to a property, it triggers target-based validation
|
|
2859
|
+
* 5. The rule function receives the target class constructor in ruleParams[0]
|
|
2860
|
+
*
|
|
2861
|
+
* ### Type Parameters
|
|
2862
|
+
* - Target: The nested class constructor type (defaults to ClassConstructor)
|
|
2863
|
+
* - Must be a valid TypeScript class constructor
|
|
2864
|
+
* - Can have any validation decorators
|
|
2865
|
+
* - Example: Address, Contact, Location
|
|
2866
|
+
*
|
|
2867
|
+
* - Context: Type of the validation context passed through validation layers
|
|
2868
|
+
* - Optional, defaults to unknown
|
|
2869
|
+
* - Available to the rule function for context-aware validation
|
|
2870
|
+
* - Example: { userId: 123, permissions: [...] }
|
|
2871
|
+
*
|
|
2872
|
+
* ### Rule Function Interface
|
|
2873
|
+
* The rule function receives:
|
|
2874
|
+
* ```typescript
|
|
2875
|
+
* {
|
|
2876
|
+
* value: any; // The property value being validated
|
|
2877
|
+
* ruleParams: [TargetClass]; // Single-element array with target constructor
|
|
2878
|
+
* context?: Context; // Validation context (if provided)
|
|
2879
|
+
* fieldName: string; // Property name
|
|
2880
|
+
* translatedPropertyName: string; // Localized property name
|
|
2881
|
+
* i18n?: II18nService; // i18n service for error messages
|
|
2882
|
+
* }
|
|
2883
|
+
* ```
|
|
2884
|
+
*
|
|
2885
|
+
* @example
|
|
2886
|
+
* ```typescript
|
|
2887
|
+
* // Create a target-based validation rule
|
|
2888
|
+
* const validateNestedRule = ({ value, ruleParams, context }) => {
|
|
2889
|
+
* const [TargetClass] = ruleParams;
|
|
2890
|
+
* // Validate value against TargetClass schema
|
|
2891
|
+
* return Validator.validateTarget(TargetClass, {
|
|
2892
|
+
* data: value,
|
|
2893
|
+
* context: context
|
|
2894
|
+
* });
|
|
2895
|
+
* };
|
|
2896
|
+
*
|
|
2897
|
+
* // Create a target rule decorator
|
|
2898
|
+
* const ValidateNested = Validator.buildTargetRuleDecorator(validateNestedRule);
|
|
2899
|
+
*
|
|
2900
|
+
* // Use the decorator with a target class
|
|
2901
|
+
* class Address {
|
|
2902
|
+
* @IsRequired()
|
|
2903
|
+
* street: string;
|
|
2904
|
+
*
|
|
2905
|
+
* @IsRequired()
|
|
2906
|
+
* city: string;
|
|
2907
|
+
* }
|
|
2908
|
+
*
|
|
2909
|
+
* class User {
|
|
2910
|
+
* @IsRequired()
|
|
2911
|
+
* name: string;
|
|
2912
|
+
*
|
|
2913
|
+
* @ValidateNested(Address)
|
|
2914
|
+
* address: Address;
|
|
2915
|
+
* }
|
|
2916
|
+
* ```
|
|
2917
|
+
*
|
|
2918
|
+
* ### Advanced Usage with Validation Options
|
|
2919
|
+
* ```typescript
|
|
2920
|
+
* class Coordinates {
|
|
2921
|
+
* @IsRequired()
|
|
2922
|
+
* @IsNumber()
|
|
2923
|
+
* latitude: number;
|
|
2924
|
+
*
|
|
2925
|
+
* @IsRequired()
|
|
2926
|
+
* @IsNumber()
|
|
2927
|
+
* longitude: number;
|
|
2928
|
+
* }
|
|
2929
|
+
*
|
|
2930
|
+
* class Location {
|
|
2931
|
+
* @IsRequired()
|
|
2932
|
+
* name: string;
|
|
2933
|
+
*
|
|
2934
|
+
* @ValidateNested([Coordinates])
|
|
2935
|
+
* coordinates: Coordinates;
|
|
2936
|
+
* }
|
|
2937
|
+
*
|
|
2938
|
+
* class Event {
|
|
2939
|
+
* @IsRequired()
|
|
2940
|
+
* name: string;
|
|
2941
|
+
*
|
|
2942
|
+
* @ValidateNested([Location])
|
|
2943
|
+
* location: Location;
|
|
2944
|
+
* }
|
|
2945
|
+
*
|
|
2946
|
+
* // Validate with context
|
|
2947
|
+
* const result = await Validator.validateTarget(Event, {
|
|
2948
|
+
* data: eventData,
|
|
2949
|
+
* context: { userId: 123, permissions: ['edit'] },
|
|
2950
|
+
* errorMessageBuilder: (fieldName, error) => `${fieldName}: ${error}`
|
|
2951
|
+
* });
|
|
2952
|
+
* ```
|
|
2953
|
+
*
|
|
2954
|
+
* ### Comparison with buildRuleDecorator
|
|
2955
|
+
* While both create decorator factories, they differ in specialization:
|
|
2956
|
+
*
|
|
2957
|
+
* **buildRuleDecorator (General Purpose):**
|
|
2958
|
+
* - Accepts any array type as ruleParams
|
|
2959
|
+
* - Used for property-level validation
|
|
2960
|
+
* - Examples: @MinLength([5]), @IsEmail()([]), @Pattern([/regex/])
|
|
2961
|
+
* - Rule params can be any values
|
|
2962
|
+
*
|
|
2963
|
+
* **buildTargetRuleDecorator (Specialized):**
|
|
2964
|
+
* - Accepts specifically [TargetClass] as ruleParams
|
|
2965
|
+
* - Used for class-level nested validation
|
|
2966
|
+
* - Examples: @ValidateNested(Address), custom target validators
|
|
2967
|
+
* - Rule params must contain a class constructor
|
|
2968
|
+
*
|
|
2969
|
+
* ### Implementation Details
|
|
2970
|
+
* This method is a thin wrapper around buildRuleDecorator that:
|
|
2971
|
+
* - Specializes the TRuleParams to [target: Target]
|
|
2972
|
+
* - Maintains type safety for target-based rules
|
|
2973
|
+
* - Delegates all decorator factory logic to buildRuleDecorator
|
|
2974
|
+
* - Reduces code duplication while providing specialized typing
|
|
2975
|
+
*
|
|
2976
|
+
* ### Performance Characteristics
|
|
2977
|
+
* - No additional overhead vs buildRuleDecorator
|
|
2978
|
+
* - Wrapper instantiation happens at decoration time, not at import
|
|
2979
|
+
* - Actual validation is performed lazily during validateTarget() calls
|
|
2980
|
+
* - Multiple properties with same target class share no cached state
|
|
2981
|
+
*
|
|
2982
|
+
* ### Error Handling
|
|
2983
|
+
* When the target rule function returns errors:
|
|
2984
|
+
* - Single errors are wrapped in the field name context
|
|
2985
|
+
* - Nested errors include full path information
|
|
2986
|
+
* - Multi-level nesting creates hierarchical error paths
|
|
2987
|
+
* - Error messages can be customized via errorMessageBuilder
|
|
2988
|
+
*
|
|
2989
|
+
* ### Integration with Validation System
|
|
2990
|
+
* - Works seamlessly with property decorators (buildPropertyDecorator)
|
|
2991
|
+
* - Compatible with multi-rule decorators (buildMultiRuleDecorator)
|
|
2992
|
+
* - Participates in parallel validation of all class properties
|
|
2993
|
+
* - Context is propagated through nested validation layers
|
|
2994
|
+
* - I18n support for error messages
|
|
2995
|
+
*
|
|
2996
|
+
* @template Target - Class constructor type for the target/nested class
|
|
2997
|
+
* - Extends ClassConstructor (default generic class constructor)
|
|
2998
|
+
* - Must be a class decorated with validation rules
|
|
2999
|
+
* - Example types: typeof Address, typeof Contact, typeof Location
|
|
3000
|
+
*
|
|
3001
|
+
* @template Context - Type of context object passed through validation
|
|
3002
|
+
* - Defaults to unknown if not specified
|
|
3003
|
+
* - Used for context-aware validation decisions
|
|
3004
|
+
* - Can include permissions, user info, environmental data, etc.
|
|
3005
|
+
*
|
|
3006
|
+
* @param ruleFunction - The target validation rule function to wrap
|
|
3007
|
+
* - Must accept ruleParams in format [TargetClass]
|
|
3008
|
+
* - Called by the decorator with target class as first param element
|
|
3009
|
+
* - Should return validation result or error message
|
|
3010
|
+
* - Can be synchronous or asynchronous
|
|
3011
|
+
*
|
|
3012
|
+
* @param symbolMarker - (Internal use only) Unique symbol for marking the rule function
|
|
3013
|
+
*
|
|
3014
|
+
* @returns Decorator factory function that:
|
|
3015
|
+
* - Accepts the target class constructor
|
|
3016
|
+
* - Returns a property decorator
|
|
3017
|
+
* - Attaches target-based validation to class properties
|
|
3018
|
+
* - Works with class validation via validateTarget()
|
|
3019
|
+
*
|
|
3020
|
+
*
|
|
3021
|
+
* @see {@link buildRuleDecorator} - General-purpose decorator factory
|
|
3022
|
+
* @see {@link buildPropertyDecorator} - Low-level decorator creation
|
|
3023
|
+
* @see {@link ValidateNested} - Example target rule decorator
|
|
3024
|
+
* @see {@link validateNestedRule} - Example target validation rule
|
|
3025
|
+
* @see {@link validateTarget} - Parent validation method using target rules
|
|
3026
|
+
* @public
|
|
3027
|
+
*/
|
|
3028
|
+
static buildTargetRuleDecorator<Target extends ClassConstructor = ClassConstructor, Context = unknown>(ruleFunction: ValidatorRuleFunction<[target: Target], Context>, symbolMarker?: symbol): (target: Target) => PropertyDecorator;
|
|
3029
|
+
/**
|
|
3030
|
+
* ## Build Multi-Rule Decorator Factory
|
|
3031
|
+
*
|
|
3032
|
+
* Creates a specialized decorator factory for validation rules that operate on multiple
|
|
3033
|
+
* sub-rules simultaneously. This method wraps {@link buildRuleDecorator} with type
|
|
3034
|
+
* specialization for multi-rule validation functions like OneOf, AllOf, and ArrayOf.
|
|
3035
|
+
*
|
|
3036
|
+
* ### Purpose
|
|
3037
|
+
* Multi-rule decorators enable complex validation logic that combines multiple validation
|
|
3038
|
+
* rules with logical operators (AND/OR). This factory creates decorators that can apply
|
|
3039
|
+
* OneOf (at least one rule must pass), AllOf (all rules must pass), or ArrayOf (validate
|
|
3040
|
+
* arrays where each item must satisfy all sub-rules) validation patterns.
|
|
3041
|
+
*
|
|
3042
|
+
* ### How It Works
|
|
3043
|
+
* 1. Takes a multi-rule validation function that accepts an array of sub-rules as parameters
|
|
3044
|
+
* 2. Wraps it using {@link buildRuleDecorator} to create a decorator factory
|
|
3045
|
+
* 3. Returns a decorator factory that accepts an array of sub-rules as decorator parameters
|
|
3046
|
+
* 4. When the decorator is applied to a property, it triggers multi-rule validation
|
|
3047
|
+
* 5. The rule function receives the sub-rules array in `ruleParams[0]`
|
|
3048
|
+
*
|
|
3049
|
+
* ### Type Parameters
|
|
3050
|
+
* - **Context**: Type of the validation context passed through validation layers
|
|
3051
|
+
* - Optional, defaults to `unknown`
|
|
3052
|
+
* - Available to all sub-rules for context-aware validation
|
|
3053
|
+
* - Example: `{ userId: 123, permissions: ['read', 'write'] }`
|
|
3054
|
+
*
|
|
3055
|
+
* - **RulesFunctions**: Array type of validation rules accepted as parameters
|
|
3056
|
+
* - Constrained by {@link ValidatorDefaultMultiRule} for type safety
|
|
3057
|
+
* - Must be an array of validation rule functions or rule specifications
|
|
3058
|
+
* - Example: `[() => value > 0, 'Required', { MinLength: [3] }]`
|
|
3059
|
+
*
|
|
3060
|
+
* ### Rule Function Interface
|
|
3061
|
+
* The multi-rule function receives:
|
|
3062
|
+
* ```typescript
|
|
3063
|
+
* {
|
|
3064
|
+
* value: any; // The property value being validated
|
|
3065
|
+
* ruleParams: RulesFunctions; // Array of sub-rules to evaluate
|
|
3066
|
+
* context?: Context; // Validation context (if provided)
|
|
3067
|
+
* fieldName: string; // Property name
|
|
3068
|
+
* translatedPropertyName: string; // Localized property name
|
|
3069
|
+
* i18n?: II18nService; // i18n service for error messages
|
|
3070
|
+
* }
|
|
3071
|
+
* ```
|
|
3072
|
+
*
|
|
3073
|
+
* ### Multi-Rule Patterns
|
|
3074
|
+
*
|
|
3075
|
+
* #### OneOf Validation (OR Logic)
|
|
3076
|
+
* ```typescript
|
|
3077
|
+
* const validateOneOf = ({ value, ruleParams }) => {
|
|
3078
|
+
* const subRules = ruleParams;
|
|
3079
|
+
* // Return true if any sub-rule passes
|
|
3080
|
+
* for (const subRule of subRules) {
|
|
3081
|
+
* const result = await Validator.validate({ value, rules: [subRule] });
|
|
3082
|
+
* if (result.success) return true;
|
|
3083
|
+
* }
|
|
3084
|
+
* return "None of the rules passed validation";
|
|
3085
|
+
* };
|
|
3086
|
+
*
|
|
3087
|
+
* const OneOf = Validator.buildMultiRuleDecorator(validateOneOf);
|
|
3088
|
+
*
|
|
3089
|
+
* class Contact {
|
|
3090
|
+
* @OneOf(['Email', 'PhoneNumber']) // Must be valid email OR phone
|
|
3091
|
+
* contactInfo: string;
|
|
3092
|
+
* }
|
|
3093
|
+
* ```
|
|
3094
|
+
*
|
|
3095
|
+
* #### AllOf Validation (AND Logic)
|
|
3096
|
+
* ```typescript
|
|
3097
|
+
* const validateAllOf = ({ value, ruleParams }) => {
|
|
3098
|
+
* const subRules = ruleParams;
|
|
3099
|
+
* // Return true only if all sub-rules pass
|
|
3100
|
+
* for (const subRule of subRules) {
|
|
3101
|
+
* const result = await Validator.validate({ value, rules: [subRule] });
|
|
3102
|
+
* if (!result.success) return result.error.message;
|
|
3103
|
+
* }
|
|
3104
|
+
* return true;
|
|
3105
|
+
* };
|
|
3106
|
+
*
|
|
3107
|
+
* const AllOf = Validator.buildMultiRuleDecorator(validateAllOf);
|
|
3108
|
+
*
|
|
3109
|
+
* class Password {
|
|
3110
|
+
* @AllOf(['Required', { MinLength: [8] }, { MaxLength: [128] }])
|
|
3111
|
+
* password: string; // Must satisfy ALL conditions
|
|
3112
|
+
* }
|
|
3113
|
+
* ```
|
|
3114
|
+
*
|
|
3115
|
+
* #### ArrayOf Validation
|
|
3116
|
+
* ```typescript
|
|
3117
|
+
* const validateArrayOf = async ({ value, ruleParams }) => {
|
|
3118
|
+
* if (!Array.isArray(value)) return "Must be an array";
|
|
3119
|
+
* const subRules = ruleParams;
|
|
3120
|
+
*
|
|
3121
|
+
* for (let i = 0; i < value.length; i++) {
|
|
3122
|
+
* const item = value[i];
|
|
3123
|
+
* for (const subRule of subRules) {
|
|
3124
|
+
* const result = await Validator.validate({ value: item, rules: [subRule] });
|
|
3125
|
+
* if (!result.success) return `Item ${i}: ${result.error.message}`;
|
|
3126
|
+
* }
|
|
3127
|
+
* }
|
|
3128
|
+
* return true;
|
|
3129
|
+
* };
|
|
3130
|
+
*
|
|
3131
|
+
* const ArrayOf = Validator.buildMultiRuleDecorator(validateArrayOf);
|
|
3132
|
+
*
|
|
3133
|
+
* class UserList {
|
|
3134
|
+
* @ArrayOf(['Email']) // Each item must be a valid email
|
|
3135
|
+
* emails: string[];
|
|
3136
|
+
* }
|
|
3137
|
+
* ```
|
|
3138
|
+
*
|
|
3139
|
+
* ### Advanced Usage with Context
|
|
3140
|
+
* ```typescript
|
|
3141
|
+
* interface ValidationContext {
|
|
3142
|
+
* userRole: 'admin' | 'user';
|
|
3143
|
+
* strictMode: boolean;
|
|
3144
|
+
* }
|
|
3145
|
+
*
|
|
3146
|
+
* const validateConditional = ({ value, ruleParams, context }) => {
|
|
3147
|
+
* const subRules = ruleParams;
|
|
3148
|
+
* const { userRole, strictMode } = context as ValidationContext;
|
|
3149
|
+
*
|
|
3150
|
+
* // Apply different validation based on context
|
|
3151
|
+
* if (userRole === 'admin' && strictMode) {
|
|
3152
|
+
* return validateAllOf([{ value, ruleParams: subRules, context }]);
|
|
3153
|
+
* }
|
|
3154
|
+
* return validateOneOf([{ value, ruleParams: subRules, context }]);
|
|
3155
|
+
* };
|
|
3156
|
+
*
|
|
3157
|
+
* const Conditional = Validator.buildMultiRuleDecorator<ValidationContext>(validateConditional);
|
|
3158
|
+
*
|
|
3159
|
+
* class FlexibleField {
|
|
3160
|
+
* @Conditional(['Required', { MinLength: [10] }])
|
|
3161
|
+
* flexibleValue: string; // Validation depends on user context
|
|
3162
|
+
* }
|
|
3163
|
+
* ```
|
|
3164
|
+
*
|
|
3165
|
+
* ### Comparison with buildRuleDecorator
|
|
3166
|
+
* While both create decorator factories, they differ in parameter handling:
|
|
3167
|
+
*
|
|
3168
|
+
* **buildRuleDecorator (Single Rule Focus):**
|
|
3169
|
+
* - Accepts flexible parameter arrays for single validation rules
|
|
3170
|
+
* - Used for property-level validation with fixed parameters
|
|
3171
|
+
* - Examples: `@MinLength([5])`, `@IsEmail([])`, `@Pattern([/regex/])`
|
|
3172
|
+
* - Rule parameters are typically fixed values
|
|
3173
|
+
*
|
|
3174
|
+
* **buildMultiRuleDecorator (Multiple Rules Focus):**
|
|
3175
|
+
* - Accepts arrays of validation rules as parameters
|
|
3176
|
+
* - Used for combining multiple validation rules with logic
|
|
3177
|
+
* - Examples: `@OneOf(['Email', 'Phone'])`, `@AllOf(['Required', 'MinLength'])`
|
|
3178
|
+
* - Rule parameters are themselves validation rules
|
|
3179
|
+
*
|
|
3180
|
+
* ### Implementation Details
|
|
3181
|
+
* This method is a thin wrapper around {@link buildRuleDecorator} that:
|
|
3182
|
+
* - Specializes the `TRuleParams` to `RulesFunctions` array type
|
|
3183
|
+
* - Maintains type safety for multi-rule validation patterns
|
|
3184
|
+
* - Delegates all decorator factory logic to `buildRuleDecorator`
|
|
3185
|
+
* - Reduces code duplication while providing specialized typing
|
|
3186
|
+
* - Enables complex validation logic through rule composition
|
|
3187
|
+
*
|
|
3188
|
+
* ### Performance Characteristics
|
|
3189
|
+
* - **No additional overhead** vs `buildRuleDecorator`
|
|
3190
|
+
* - **Wrapper instantiation** happens at decoration time, not import
|
|
3191
|
+
* - **Actual validation** is performed lazily during `validate()` calls
|
|
3192
|
+
* - **Rule evaluation** depends on the specific multi-rule logic (sequential/parallel)
|
|
3193
|
+
* - **Memory efficient** - no additional state stored beyond rule metadata
|
|
3194
|
+
*
|
|
3195
|
+
* ### Error Handling
|
|
3196
|
+
* When multi-rule validation fails:
|
|
3197
|
+
* - **OneOf failures**: Aggregates all sub-rule error messages with separators
|
|
3198
|
+
* - **AllOf failures**: Returns the first failing sub-rule's error message
|
|
3199
|
+
* - **ArrayOf failures**: Includes item indices in error messages
|
|
3200
|
+
* - **Invalid sub-rules**: Treated as validation failures with appropriate messages
|
|
3201
|
+
* - **Context errors**: Propagated through the validation chain
|
|
3202
|
+
*
|
|
3203
|
+
* ### Integration with Validation System
|
|
3204
|
+
* - **Works seamlessly** with property decorators and class validation
|
|
3205
|
+
* - **Compatible with** target rule decorators for nested validation
|
|
3206
|
+
* - **Participates in** parallel validation of all class properties
|
|
3207
|
+
* - **Context propagation** through nested validation layers
|
|
3208
|
+
* - **i18n support** for localized error messages in sub-rules
|
|
3209
|
+
* - **Symbol markers** used internally for rule type identification
|
|
3210
|
+
*
|
|
3211
|
+
* ### Type Parameters
|
|
3212
|
+
* - **Context**: Type of context object passed through validation
|
|
3213
|
+
* - Defaults to `unknown` if not specified
|
|
3214
|
+
* - Used for context-aware validation decisions
|
|
3215
|
+
* - Can include user permissions, environmental data, etc.
|
|
3216
|
+
*
|
|
3217
|
+
* - **RulesFunctions**: Array type of validation rules
|
|
3218
|
+
* - Constrained by `ValidatorDefaultMultiRule<Context>`
|
|
3219
|
+
* - Must be an array of valid validation rule specifications
|
|
3220
|
+
* - Enables type-safe multi-rule validation
|
|
3221
|
+
*
|
|
3222
|
+
* @param ruleFunction - The multi-rule validation function to wrap
|
|
3223
|
+
* - Must accept `ruleParams` as an array of validation rules
|
|
3224
|
+
* - Called by the decorator with sub-rules as first parameter
|
|
3225
|
+
* - Should implement the desired multi-rule logic (OneOf/AllOf/ArrayOf)
|
|
3226
|
+
* - Can be synchronous or asynchronous
|
|
3227
|
+
* - Returns validation result or error message
|
|
3228
|
+
*
|
|
3229
|
+
* @param symbolMarker - (Internal use only) A unique symbol used to mark the rule function for special handling.
|
|
3230
|
+
*
|
|
3231
|
+
* @returns Decorator factory function that:
|
|
3232
|
+
* - Accepts an array of validation rules as parameters
|
|
3233
|
+
* - Returns a property decorator when called
|
|
3234
|
+
* - Attaches multi-rule validation to class properties
|
|
3235
|
+
* - Works with class validation via `validateTarget()`
|
|
3236
|
+
* - Enables complex validation logic through rule composition
|
|
3237
|
+
*
|
|
3238
|
+
* @example
|
|
3239
|
+
* ```typescript
|
|
3240
|
+
* // Create a OneOf validation rule
|
|
3241
|
+
* const validateOneOf = async ({ value, ruleParams }) => {
|
|
3242
|
+
* const subRules = ruleParams;
|
|
3243
|
+
* for (const subRule of subRules) {
|
|
3244
|
+
* const result = await Validator.validate({ value, rules: [subRule] });
|
|
3245
|
+
* if (result.success) return true;
|
|
3246
|
+
* }
|
|
3247
|
+
* return "Value must satisfy at least one of the specified rules";
|
|
3248
|
+
* };
|
|
3249
|
+
*
|
|
3250
|
+
* // Create the decorator factory
|
|
3251
|
+
* const OneOf = Validator.buildMultiRuleDecorator(validateOneOf);
|
|
3252
|
+
*
|
|
3253
|
+
* // Use the decorator
|
|
3254
|
+
* class Contact {
|
|
3255
|
+
* @OneOf(['Email', 'PhoneNumber'])
|
|
3256
|
+
* primaryContact: string;
|
|
3257
|
+
*
|
|
3258
|
+
* @OneOf(['Required', { MinLength: [10] }])
|
|
3259
|
+
* secondaryContact: string;
|
|
3260
|
+
* }
|
|
3261
|
+
*
|
|
3262
|
+
* // Validate with context
|
|
3263
|
+
* const result = await Validator.validateTarget(Contact, {
|
|
3264
|
+
* data: { primaryContact: "user@example.com", secondaryContact: "" },
|
|
3265
|
+
* context: { allowEmptySecondary: true }
|
|
3266
|
+
* });
|
|
3267
|
+
* ```
|
|
3268
|
+
*
|
|
3269
|
+
* @see {@link buildRuleDecorator} - General-purpose decorator factory
|
|
3270
|
+
* @see {@link buildTargetRuleDecorator} - For nested class validation
|
|
3271
|
+
* @see {@link buildPropertyDecorator} - Low-level decorator creation
|
|
3272
|
+
* @see {@link OneOf} - Example OneOf multi-rule decorator
|
|
3273
|
+
* @see {@link AllOf} - Example AllOf multi-rule decorator
|
|
3274
|
+
* @see {@link ArrayOf} - Example ArrayOf multi-rule decorator
|
|
3275
|
+
* @see {@link validateMultiRule} - Core multi-rule validation logic
|
|
3276
|
+
* @see {@link ValidatorDefaultMultiRule} - Type constraint for rule arrays
|
|
3277
|
+
* @see {@link ValidatorMultiRuleFunction} - Multi-rule function type
|
|
3278
|
+
* @public
|
|
3279
|
+
*/
|
|
3280
|
+
static buildMultiRuleDecorator<Context = unknown, RulesFunctions extends ValidatorDefaultMultiRule<Context> = ValidatorDefaultMultiRule<Context>>(ruleFunction: ValidatorMultiRuleFunction<Context, RulesFunctions>, symbolMarker?: symbol): (ruleParameters: RulesFunctions) => PropertyDecorator;
|
|
3281
|
+
/**
|
|
3282
|
+
* ## Build Property Decorator Factory
|
|
3283
|
+
*
|
|
3284
|
+
* Creates a low-level property decorator that attaches validation rules directly to class properties
|
|
3285
|
+
* using TypeScript metadata reflection. This method provides the foundation for all validation
|
|
3286
|
+
* decorators in the system, enabling rule accumulation and metadata storage on class properties.
|
|
3287
|
+
*
|
|
3288
|
+
* ### Purpose
|
|
3289
|
+
* This is the most fundamental decorator factory in the validation system. It creates property
|
|
3290
|
+
* decorators that store validation rules as metadata on class properties, enabling the
|
|
3291
|
+
* {@link validateTarget} method to discover and execute validation rules during class-based
|
|
3292
|
+
* validation. Unlike higher-level factories, this method works directly with rule objects
|
|
3293
|
+
* and metadata storage.
|
|
3294
|
+
*
|
|
3295
|
+
* ### How It Works
|
|
3296
|
+
* 1. Takes a validation rule or array of validation rules as input
|
|
3297
|
+
* 2. Uses the imported `buildPropertyDecorator` helper to create a property decorator
|
|
3298
|
+
* 3. Stores rules under the `VALIDATOR_TARGET_RULES_METADATA_KEY` symbol
|
|
3299
|
+
* 4. When applied to a property, accumulates rules with existing rules on the same property
|
|
3300
|
+
* 5. Enables rule discovery during `validateTarget()` execution
|
|
3301
|
+
*
|
|
3302
|
+
* ### Rule Accumulation Logic
|
|
3303
|
+
* The decorator accumulates rules rather than replacing them:
|
|
3304
|
+
* ```typescript
|
|
3305
|
+
* // Multiple decorators on the same property accumulate rules
|
|
3306
|
+
* class Example {
|
|
3307
|
+
* @IsRequired() // Adds ["Required"] rule
|
|
3308
|
+
* @MinLength([5]) // Adds [{ MinLength: [5] }] rule
|
|
3309
|
+
* @MaxLength([100]) // Adds [{ MaxLength: [100] }] rule
|
|
3310
|
+
* name: string; // Final rules: ["Required", { MinLength: [5] }, { MaxLength: [100] }]
|
|
3311
|
+
* }
|
|
3312
|
+
* ```
|
|
3313
|
+
*
|
|
3314
|
+
* ### Type Parameters
|
|
3315
|
+
* - **TRuleParams**: Array type for rule parameters (default: `ValidatorRuleParams`)
|
|
3316
|
+
* - Defines the parameter structure for validation rules
|
|
3317
|
+
* - Must extend `ValidatorRuleParams` for type safety
|
|
3318
|
+
* - Example: `[minLength: number]` for MinLength rule
|
|
3319
|
+
*
|
|
3320
|
+
* - **Context**: Type of the validation context (default: `unknown`)
|
|
3321
|
+
* - Optional context passed to rule functions
|
|
3322
|
+
* - Enables context-aware validation logic
|
|
3323
|
+
* - Example: `{ userId: 123, permissions: ['read'] }`
|
|
3324
|
+
*
|
|
3325
|
+
* ### Rule Input Types
|
|
3326
|
+
* Accepts validation rules in two formats:
|
|
3327
|
+
* - **Single Rule**: `ValidatorRule<TRuleParams, Context>`
|
|
3328
|
+
* - **Rule Array**: `ValidatorRule<TRuleParams, Context>[]`
|
|
3329
|
+
*
|
|
3330
|
+
* ### Metadata Storage
|
|
3331
|
+
* Rules are stored using TypeScript's Reflect Metadata API:
|
|
3332
|
+
* ```typescript
|
|
3333
|
+
* // Metadata structure on class constructor
|
|
3334
|
+
* {
|
|
3335
|
+
* [VALIDATOR_TARGET_RULES_METADATA_KEY]: {
|
|
3336
|
+
* propertyName: [rule1, rule2, rule3, ...],
|
|
3337
|
+
* anotherProperty: [ruleA, ruleB, ...],
|
|
3338
|
+
* ...
|
|
3339
|
+
* }
|
|
3340
|
+
* }
|
|
3341
|
+
* ```
|
|
3342
|
+
*
|
|
3343
|
+
* ### Usage Examples
|
|
3344
|
+
*
|
|
3345
|
+
* #### Basic Property Decoration
|
|
3346
|
+
* ```typescript
|
|
3347
|
+
* // Create a simple required decorator
|
|
3348
|
+
* const IsRequired = Validator.buildPropertyDecorator("Required");
|
|
3349
|
+
*
|
|
3350
|
+
* class User {
|
|
3351
|
+
* @IsRequired
|
|
3352
|
+
* name: string; // Property now has ["Required"] rule attached
|
|
3353
|
+
* }
|
|
3354
|
+
* ```
|
|
3355
|
+
*
|
|
3356
|
+
* #### Parameterized Rule Decoration
|
|
3357
|
+
* ```typescript
|
|
3358
|
+
* // Create a min length decorator
|
|
3359
|
+
* const MinLength = Validator.buildPropertyDecorator({ MinLength: [5] });
|
|
3360
|
+
*
|
|
3361
|
+
* class Product {
|
|
3362
|
+
* @MinLength
|
|
3363
|
+
* name: string; // Property has [{ MinLength: [5] }] rule attached
|
|
3364
|
+
* }
|
|
3365
|
+
* ```
|
|
3366
|
+
*
|
|
3367
|
+
* #### Multiple Rules on One Property
|
|
3368
|
+
* ```typescript
|
|
3369
|
+
* class Contact {
|
|
3370
|
+
* @Validator.buildPropertyDecorator("Required")
|
|
3371
|
+
* @Validator.buildPropertyDecorator({ MinLength: [3] })
|
|
3372
|
+
* @Validator.buildPropertyDecorator({ MaxLength: [50] })
|
|
3373
|
+
* name: string; // Accumulates all three rules
|
|
3374
|
+
* }
|
|
3375
|
+
* ```
|
|
3376
|
+
*
|
|
3377
|
+
* #### Array Rule Input
|
|
3378
|
+
* ```typescript
|
|
3379
|
+
* // Attach multiple rules at once
|
|
3380
|
+
* const NameRules = Validator.buildPropertyDecorator([
|
|
3381
|
+
* "Required",
|
|
3382
|
+
* { MinLength: [2] },
|
|
3383
|
+
* { MaxLength: [100] }
|
|
3384
|
+
* ]);
|
|
3385
|
+
*
|
|
3386
|
+
* class Person {
|
|
3387
|
+
* @NameRules
|
|
3388
|
+
* fullName: string; // All rules attached in single decorator
|
|
3389
|
+
* }
|
|
3390
|
+
* ```
|
|
3391
|
+
*
|
|
3392
|
+
* ### Advanced Usage with Custom Rules
|
|
3393
|
+
* ```typescript
|
|
3394
|
+
* // Custom validation function
|
|
3395
|
+
* const customRule = ({ value }) => value.startsWith('prefix_') || 'Must start with prefix_';
|
|
3396
|
+
*
|
|
3397
|
+
* const Prefixed = Validator.buildPropertyDecorator(customRule);
|
|
3398
|
+
*
|
|
3399
|
+
* class Config {
|
|
3400
|
+
* @Prefixed
|
|
3401
|
+
* apiKey: string; // Uses custom validation function
|
|
3402
|
+
* }
|
|
3403
|
+
* ```
|
|
3404
|
+
*
|
|
3405
|
+
* ### Context-Aware Rules
|
|
3406
|
+
* ```typescript
|
|
3407
|
+
* interface UserContext {
|
|
3408
|
+
* isAdmin: boolean;
|
|
3409
|
+
* department: string;
|
|
3410
|
+
* }
|
|
3411
|
+
*
|
|
3412
|
+
* const contextRule = ({ value, context }) => {
|
|
3413
|
+
* const userCtx = context as UserContext;
|
|
3414
|
+
* if (userCtx.isAdmin) return true; // Admins bypass validation
|
|
3415
|
+
* return value.length >= 5 || 'Non-admin users need longer values';
|
|
3416
|
+
* };
|
|
3417
|
+
*
|
|
3418
|
+
* const AdminBypass = Validator.buildPropertyDecorator<UserContext>(contextRule);
|
|
3419
|
+
*
|
|
3420
|
+
* class Document {
|
|
3421
|
+
* @AdminBypass
|
|
3422
|
+
* title: string; // Validation depends on user context
|
|
3423
|
+
* }
|
|
3424
|
+
* ```
|
|
3425
|
+
*
|
|
3426
|
+
* ### Comparison with Higher-Level Factories
|
|
3427
|
+
*
|
|
3428
|
+
* **buildPropertyDecorator (Low-Level):**
|
|
3429
|
+
* - Works directly with rule objects and metadata
|
|
3430
|
+
* - Requires manual rule specification
|
|
3431
|
+
* - Maximum flexibility and control
|
|
3432
|
+
* - Used internally by other decorator factories
|
|
3433
|
+
* - Example: `buildPropertyDecorator("Required")`
|
|
3434
|
+
*
|
|
3435
|
+
* **buildRuleDecorator (Mid-Level):**
|
|
3436
|
+
* - Creates decorator factories with parameter handling
|
|
3437
|
+
* - Supports rest parameters and rule wrapping
|
|
3438
|
+
* - Used for single validation rules with parameters
|
|
3439
|
+
* - Example: `buildRuleDecorator(ruleFunc)` returns factory for `@MinLength([5])`
|
|
3440
|
+
*
|
|
3441
|
+
* **buildMultiRuleDecorator (High-Level):**
|
|
3442
|
+
* - Creates decorators for multi-rule validation
|
|
3443
|
+
* - Handles OneOf/AllOf/ArrayOf patterns
|
|
3444
|
+
* - Used for complex validation logic
|
|
3445
|
+
* - Example: `buildMultiRuleDecorator(ruleFunc)` returns factory for `@OneOf(['Email', 'Phone'])`
|
|
3446
|
+
*
|
|
3447
|
+
* ### Implementation Details
|
|
3448
|
+
* This method uses the imported `buildPropertyDecorator` helper:
|
|
3449
|
+
* ```typescript
|
|
3450
|
+
* return buildPropertyDecorator<ValidatorRule<TRuleParams, Context>[]>(
|
|
3451
|
+
* VALIDATOR_TARGET_RULES_METADATA_KEY, // Metadata key for storage
|
|
3452
|
+
* (oldRules) => { // Accumulation function
|
|
3453
|
+
* return [
|
|
3454
|
+
* ...(Array.isArray(oldRules) ? oldRules : []), // Preserve existing
|
|
3455
|
+
* ...(Array.isArray(rule) ? rule : [rule]), // Add new rules
|
|
3456
|
+
* ];
|
|
3457
|
+
* }
|
|
3458
|
+
* );
|
|
3459
|
+
* ```
|
|
3460
|
+
*
|
|
3461
|
+
* ### Performance Characteristics
|
|
3462
|
+
* - **Decoration time**: Minimal overhead (metadata storage only)
|
|
3463
|
+
* - **Runtime impact**: No performance cost during decoration
|
|
3464
|
+
* - **Validation time**: Rules discovered efficiently via metadata lookup
|
|
3465
|
+
* - **Memory usage**: Stores rule references (not rule execution)
|
|
3466
|
+
* - **Accumulation**: Efficient array concatenation for multiple decorators
|
|
3467
|
+
*
|
|
3468
|
+
* ### Error Handling
|
|
3469
|
+
* - **Invalid rules**: Stored as-is (validation errors occur during `validateTarget`)
|
|
3470
|
+
* - **Metadata failures**: Falls back gracefully if Reflect Metadata unavailable
|
|
3471
|
+
* - **Type mismatches**: TypeScript prevents invalid rule types at compile time
|
|
3472
|
+
* - **Runtime errors**: Handled during validation, not decoration
|
|
3473
|
+
*
|
|
3474
|
+
* ### Integration with Validation System
|
|
3475
|
+
* - **Metadata discovery**: Rules found by `getDecoratedProperties()` during validation
|
|
3476
|
+
* - **Parallel execution**: All property rules validated simultaneously in `validateTarget()`
|
|
3477
|
+
* - **Error aggregation**: Property-level errors collected with field names
|
|
3478
|
+
* - **Context propagation**: Validation context passed to all rule functions
|
|
3479
|
+
* - **i18n support**: Error messages localized through validation system
|
|
3480
|
+
*
|
|
3481
|
+
* ### Type Parameters
|
|
3482
|
+
* - **TRuleParams**: Parameter array type for validation rules
|
|
3483
|
+
* - Defaults to `ValidatorRuleParams` for maximum compatibility
|
|
3484
|
+
* - Constrains rule parameter structures for type safety
|
|
3485
|
+
* - Example: `[number]` for single numeric parameter
|
|
3486
|
+
*
|
|
3487
|
+
* - **Context**: Validation context type
|
|
3488
|
+
* - Defaults to `unknown` for flexibility
|
|
3489
|
+
* - Passed to rule functions for context-aware validation
|
|
3490
|
+
* - Example: `{ user: User, permissions: string[] }`
|
|
3491
|
+
*
|
|
3492
|
+
* @param rule - Validation rule(s) to attach to properties
|
|
3493
|
+
* - Can be a single `ValidatorRule<TRuleParams, Context>`
|
|
3494
|
+
* - Or an array of `ValidatorRule<TRuleParams, Context>[]`
|
|
3495
|
+
* - Rules are accumulated when multiple decorators applied
|
|
3496
|
+
* - Invalid rules stored but cause validation errors later
|
|
3497
|
+
*
|
|
3498
|
+
* @returns PropertyDecorator function that:
|
|
3499
|
+
* - Attaches the specified rule(s) to class properties
|
|
3500
|
+
* - Accumulates rules when multiple decorators applied
|
|
3501
|
+
* - Stores rules as metadata for validation discovery
|
|
3502
|
+
* - Works with `validateTarget()` for class validation
|
|
3503
|
+
* - Enables type-safe property-level validation
|
|
3504
|
+
*
|
|
3505
|
+
* @example
|
|
3506
|
+
* ```typescript
|
|
3507
|
+
* // Create basic validation decorators
|
|
3508
|
+
* const IsRequired = Validator.buildPropertyDecorator("Required");
|
|
3509
|
+
* const IsEmail = Validator.buildPropertyDecorator("Email");
|
|
3510
|
+
* const MinLength5 = Validator.buildPropertyDecorator({ MinLength: [5] });
|
|
3511
|
+
*
|
|
3512
|
+
* // Use in class definitions
|
|
3513
|
+
* class User {
|
|
3514
|
+
* @IsRequired
|
|
3515
|
+
* @MinLength5
|
|
3516
|
+
* name: string;
|
|
3517
|
+
*
|
|
3518
|
+
* @IsEmail()
|
|
3519
|
+
* email: string;
|
|
3520
|
+
*
|
|
3521
|
+
* @Validator.buildPropertyDecorator([
|
|
3522
|
+
* "Required",
|
|
3523
|
+
* { MinLength: [8] },
|
|
3524
|
+
* { MaxLength: [128] }
|
|
3525
|
+
* ])
|
|
3526
|
+
* password: string;
|
|
3527
|
+
* }
|
|
3528
|
+
*
|
|
3529
|
+
* // Validate the class
|
|
3530
|
+
* const result = await Validator.validateTarget(User, {
|
|
3531
|
+
* data: {
|
|
3532
|
+
* name: "John",
|
|
3533
|
+
* email: "john@example.com",
|
|
3534
|
+
* password: "secure123"
|
|
3535
|
+
* }
|
|
3536
|
+
* });
|
|
3537
|
+
*
|
|
3538
|
+
* console.log(result.success); // true if all validations pass
|
|
3539
|
+
* ```
|
|
3540
|
+
*
|
|
3541
|
+
* @see {@link buildRuleDecorator} - Higher-level decorator factory with parameter handling
|
|
3542
|
+
* @see {@link buildMultiRuleDecorator} - For multi-rule validation patterns
|
|
3543
|
+
* @see {@link buildTargetRuleDecorator} - For nested class validation
|
|
3544
|
+
* @see {@link validateTarget} - Class validation method that uses these decorators
|
|
3545
|
+
* @see {@link getDecoratedProperties} - Metadata discovery for validation
|
|
3546
|
+
* @see {@link ValidatorRule} - Rule type attached by this decorator
|
|
3547
|
+
* @see {@link VALIDATOR_TARGET_RULES_METADATA_KEY} - Metadata key for rule storage
|
|
3548
|
+
* @public
|
|
3549
|
+
*/
|
|
3550
|
+
static buildPropertyDecorator<TRuleParams extends ValidatorRuleParams = ValidatorRuleParams, Context = unknown>(rule: BuildPropertyDecorator<TRuleParams, Context> | BuildPropertyDecorator<TRuleParams, Context>[]): PropertyDecorator;
|
|
3551
|
+
}
|
|
3552
|
+
/**
|
|
3553
|
+
* ## ValidationTargetOptions Class Decorator
|
|
3554
|
+
*
|
|
3555
|
+
* Class decorator that configures validation behavior for a target class.
|
|
3556
|
+
* This decorator allows you to set class-level validation options that will
|
|
3557
|
+
* be automatically applied whenever `validateTarget` is called on the class.
|
|
3558
|
+
*
|
|
3559
|
+
* ### Configuration Options
|
|
3560
|
+
* - **errorMessageBuilder**: Custom function to format validation error messages
|
|
3561
|
+
* - **context**: Default validation context for all validations
|
|
3562
|
+
* - **stopOnFirstError**: Whether to stop validation at first error (future feature)
|
|
3563
|
+
* - **locale**: Specific locale for error messages (future feature)
|
|
3564
|
+
*
|
|
3565
|
+
* ### Use Cases
|
|
3566
|
+
* - **Consistent Error Formatting**: Apply uniform error message styling across a class
|
|
3567
|
+
* - **Context Injection**: Provide default context for validation rules
|
|
3568
|
+
* - **Custom Validation Behavior**: Override default validation behavior per class
|
|
3569
|
+
*
|
|
3570
|
+
* @example
|
|
3571
|
+
* ```typescript
|
|
3572
|
+
* // Basic usage with custom error formatting
|
|
3573
|
+
* @ValidationTargetOptions({
|
|
3574
|
+
* errorMessageBuilder: (fieldName, error) => {
|
|
3575
|
+
* return `🚫 ${fieldName.toUpperCase()}: ${error}`;
|
|
3576
|
+
* // {
|
|
3577
|
+
* // email: ['Required', 'Email'],
|
|
3578
|
+
* // name: ['Required', 'MinLength', 'MaxLength'],
|
|
3579
|
+
* // age: ['Number'] // IsOptional doesn't add a rule
|
|
3580
|
+
* // }
|
|
3581
|
+
*
|
|
3582
|
+
* // Check if a property has specific rules
|
|
3583
|
+
* const emailRules = rules.email;
|
|
3584
|
+
* const hasEmailValidation = emailRules.includes('Email');
|
|
3585
|
+
* name: string;
|
|
3586
|
+
* }
|
|
3587
|
+
*
|
|
3588
|
+
* // When validation fails, errors will be formatted as:
|
|
3589
|
+
* // "🚫 EMAIL: Invalid email format"
|
|
3590
|
+
* // "🚫 NAME: Must be at least 3 characters"
|
|
3591
|
+
*
|
|
3592
|
+
* // Advanced usage with context and detailed formatting
|
|
3593
|
+
* @ValidationTargetOptions({
|
|
3594
|
+
* errorMessageBuilder: (translatedName, error, builderOptions) => {
|
|
3595
|
+
* const { propertyName, ruleName, separators } = builderOptions;
|
|
3596
|
+
*
|
|
3597
|
+
* // Custom formatting based on rule type
|
|
3598
|
+
* if (ruleName === 'required') {
|
|
3599
|
+
* return `❗ ${translatedName} is mandatory`;
|
|
3600
|
+
* }
|
|
3601
|
+
*
|
|
3602
|
+
* if (ruleName === 'email') {
|
|
3603
|
+
* return `📧 Please enter a valid email for ${translatedName}`;
|
|
3604
|
+
* }
|
|
3605
|
+
*
|
|
3606
|
+
* return `⚠️ ${translatedName}: ${error}`;
|
|
3607
|
+
* }
|
|
3608
|
+
* })
|
|
3609
|
+
* class DetailedUser {
|
|
3610
|
+
* @IsRequired()
|
|
3611
|
+
* @IsEmail()
|
|
3612
|
+
* email: string;
|
|
3613
|
+
*
|
|
3614
|
+
* @IsRequired()
|
|
3615
|
+
* name: string;
|
|
3616
|
+
* }
|
|
3617
|
+
* ```
|
|
3618
|
+
*
|
|
3619
|
+
* ### Context-Aware Validation
|
|
3620
|
+
* ```typescript
|
|
3621
|
+
* interface UserValidationContext {
|
|
3622
|
+
* isAdmin: boolean;
|
|
3623
|
+
* permissions: string[];
|
|
3624
|
+
* organizationId: string;
|
|
3625
|
+
* }
|
|
3626
|
+
*
|
|
3627
|
+
* @ValidationTargetOptions({
|
|
3628
|
+
* errorMessageBuilder: (fieldName, error, { context }) => {
|
|
3629
|
+
* const userContext = context as UserValidationContext;
|
|
3630
|
+
* if (userContext?.isAdmin) {
|
|
3631
|
+
* return `[ADMIN] ${fieldName}: ${error}`;
|
|
3632
|
+
* }
|
|
3633
|
+
* return `${fieldName}: ${error}`;
|
|
3634
|
+
* }
|
|
3635
|
+
* })
|
|
3636
|
+
* class AdminUser {
|
|
3637
|
+
* @IsRequired()
|
|
3638
|
+
* @IsEmail()
|
|
3639
|
+
* email: string;
|
|
3640
|
+
*
|
|
3641
|
+
* @CustomRule([
|
|
3642
|
+
* ({ value, context }) => {
|
|
3643
|
+
* const { isAdmin, permissions } = context as UserValidationContext;
|
|
3644
|
+
* return isAdmin && permissions.includes('manage-users') ||
|
|
3645
|
+
* 'Admin privileges required';
|
|
3646
|
+
* }
|
|
3647
|
+
* ])
|
|
3648
|
+
* adminAction: string;
|
|
3649
|
+
* }
|
|
3650
|
+
* ```
|
|
3651
|
+
*
|
|
3652
|
+
* ### Internationalization Support
|
|
3653
|
+
* ```typescript
|
|
3654
|
+
* @ValidationTargetOptions({
|
|
3655
|
+
* errorMessageBuilder: (translatedName, error, { data }) => {
|
|
3656
|
+
* // Use translated property names and localized error formatting
|
|
3657
|
+
* const locale = data.preferredLocale || 'en';
|
|
3658
|
+
*
|
|
3659
|
+
* switch (locale) {
|
|
3660
|
+
* case 'fr':
|
|
3661
|
+
* return `❌ ${translatedName} : ${error}`;
|
|
3662
|
+
* case 'es':
|
|
3663
|
+
* return `❌ ${translatedName}: ${error}`;
|
|
3664
|
+
* default:
|
|
3665
|
+
* return `❌ ${translatedName}: ${error}`;
|
|
3666
|
+
* }
|
|
3667
|
+
* }
|
|
3668
|
+
* })
|
|
3669
|
+
* class InternationalUser {
|
|
3670
|
+
* @IsRequired()
|
|
3671
|
+
* @IsEmail()
|
|
3672
|
+
* email: string;
|
|
3673
|
+
*
|
|
3674
|
+
* preferredLocale?: string;
|
|
3675
|
+
* }
|
|
3676
|
+
* ```
|
|
3677
|
+
*
|
|
3678
|
+
* @param validationOptions - Configuration object for validation behavior
|
|
3679
|
+
* @param validationOptions.errorMessageBuilder - Custom error message formatting function
|
|
3680
|
+
*
|
|
3681
|
+
* @returns Class decorator function that applies the validation configuration
|
|
3682
|
+
*
|
|
3683
|
+
*
|
|
3684
|
+
* @see {@link validateTarget} - Method that uses these options
|
|
3685
|
+
* @see {@link getValidateTargetOptions} - Retrieves configured options
|
|
3686
|
+
* @decorator
|
|
3687
|
+
* @public
|
|
3688
|
+
*/
|
|
3689
|
+
export declare function ValidationTargetOptions(validationOptions: ValidatorValidateTargetOptions<any, any>): ClassDecorator;
|
|
3690
|
+
type ValidatorDefaultArray = Array<unknown>;
|
|
3691
|
+
type BuildPropertyDecorator<TRuleParams extends ValidatorRuleParams = ValidatorRuleParams, Context = unknown> = ValidatorRule<TRuleParams, Context> | ValidatorSanitizedRuleObject<TRuleParams, Context>;
|
|
3692
|
+
export {};
|