@resk/core
Version:
An innovative TypeScript framework that empowers developers to build applications with a fully decorator-based architecture for efficient resource management. By combining the power of decorators with a resource-oriented design, DecorRes enhances code cla
196 lines (195 loc) • 7.15 kB
TypeScript
/**
* ## OneOf Validation Decorator
*
* A powerful validation decorator that implements "OneOf" logic, where validation succeeds
* if at least one of the provided sub-rules validates successfully. This decorator enables
* flexible validation scenarios where multiple validation paths are acceptable.
*
* ### OneOf Validation Concept
* Unlike traditional AND validation (where all rules must pass), OneOf validation uses
* OR logic - validation succeeds when any single sub-rule passes. This is ideal for
* scenarios where data can be valid in multiple different formats or meet different criteria.
*
* ### Key Features
* - **Flexible Validation**: Accept values that satisfy any one of several validation criteria
* - **Parallel Execution**: Sub-rules are validated concurrently for optimal performance
* - **Short-Circuit Success**: Returns immediately when the first sub-rule passes
* - **Error Aggregation**: Combines error messages from all failed sub-rules when all fail
* - **Type Safe**: Full TypeScript support with generic context typing
* - **Decorator Pattern**: Easy to apply to class properties using the `@OneOf()` syntax
*
* ### Common Use Cases
* - **Alternative Contact Methods**: Accept either email OR phone number
* - **Multiple ID Formats**: Allow UUID, custom ID format, or database-generated ID
* - **Flexible Input Types**: Accept string OR number for certain fields
* - **Conditional Business Rules**: Different validation rules based on context
* - **Format Alternatives**: Accept data in JSON, XML, or custom format
*
* ### Validation Behavior
* - **Success Condition**: At least one sub-rule must return a successful validation result
* - **Failure Condition**: All sub-rules fail, aggregated errors are returned
* - **Empty Rules**: If no sub-rules are provided, validation fails with "invalidRule" error
* - **Rule Processing**: Each sub-rule is validated using the standard `Validator.validate` method
*
* @example
* ```typescript
* import { OneOf } from '@resk/core/validator';
*
* class User {
* // Accept either a valid email OR a valid phone number
* @OneOf(["Email", "PhoneNumber"])
* contact: string;
*
* // Accept either a UUID OR a custom ID format
* @OneOf([
* "UUID",
* ({ value }) => value.startsWith('CUSTOM-') || 'Must start with CUSTOM-'
* ])
* identifier: string;
*
* // Accept either a string name OR a number ID
* @OneOf([
* "IsNonNullString",
* "IsNumber"
* ])
* flexibleId: string | number;
* }
*
* // Validation examples
* const user1 = new User();
* user1.contact = "user@example.com"; // ✅ Passes (Email rule)
*
* const user2 = new User();
* user2.contact = "+1234567890"; // ✅ Passes (PhoneNumber rule)
*
* const user3 = new User();
* user3.contact = "invalid-input"; // ❌ Fails (both Email and PhoneNumber fail)
* // Error: "Invalid email format; Invalid phone number format"
* ```
*
* ### Advanced Usage with Context
* ```typescript
* interface ValidationContext {
* userType: 'admin' | 'user';
* permissions: string[];
* }
*
* class Entity {
* @OneOf([
* "Email",
* "UUID",
* ({ value, context }) => {
* // Custom rule that depends on context
* const ctx = context as ValidationContext;
* if (ctx?.userType === 'admin') {
* return value.startsWith('ADMIN-') || 'Admin IDs must start with ADMIN-';
* }
* return false; // Skip this rule for non-admins
* }
* ])
* identifier: string;
* }
*
* // Context-aware validation
* const adminEntity = new Entity();
* adminEntity.identifier = "ADMIN-123"; // ✅ Passes (custom rule for admin)
*
* const userEntity = new Entity();
* userEntity.identifier = "user@example.com"; // ✅ Passes (Email rule)
* ```
*
* ### Complex Rule Combinations
* ```typescript
* class Product {
* // Accept either a valid URL OR a relative path starting with '/'
* @OneOf([
* "IsUrl",
* ({ value }) => value.startsWith('/') || 'Path must start with /'
* ])
* imagePath: string;
*
* // Accept either a standard email OR an internal company email
* @OneOf([
* "Email",
* ({ value }) => value.endsWith('@company.com') || 'Must be company email'
* ])
* contactEmail: string;
* }
* ```
*
* ### Error Handling
* ```typescript
* class FlexibleForm {
* @OneOf(["Email", "PhoneNumber", "UUID"])
* identifier: string;
* }
*
* const form = new FlexibleForm();
* form.identifier = "invalid"; // ❌ All rules fail
*
* // When validation fails, you get aggregated error messages:
* // "Invalid email format; Invalid phone number format; Invalid UUID format"
*
* // Use with validateTarget for comprehensive error reporting
* const result = await Validator.validateTarget(FlexibleForm, {
* identifier: "invalid"
* });
*
* if (!result.success) {
* console.log(result.errors[0].message);
* // Output: "Invalid email format; Invalid phone number format; Invalid UUID format"
* }
* ```
*
* ### Integration with Other Decorators
* ```typescript
* class ComprehensiveUser {
* @IsRequired // Must be present
* @OneOf([ // And must satisfy at least one of these
* "Email",
* "PhoneNumber"
* ])
* contact: string;
*
* @IsOptional // Can be omitted
* @OneOf([ // But if present, must satisfy one of these
* "IsUrl",
* ({ value }) => value.startsWith('file://') || 'Must be file:// URL'
* ])
* avatarUrl?: string;
* }
* ```
*
* @template Context - Optional type for the validation context object
*
* @param rules - Array of validation rules where at least one must pass for validation to succeed
* @param rules - Each rule can be a string (rule name), object (rule with parameters), or function (custom validation)
*
* @returns Property decorator that applies OneOf validation logic to class properties
*
* @throws {string} When all sub-rules fail, throws aggregated error messages joined with semicolons
* @throws {string} When no sub-rules are provided, throws "invalidRule" error
*
* @since 1.35.0
* @see {@link Validator.validateOneOfRule} - The underlying validation method
* @see {@link Validator.buildMultiRuleDecorator} - Factory method that creates this decorator
* @see {@link Validator.validateTarget} - For class-based validation using decorators
* @see {@link IValidatorValidateMultiRuleOptions} - Type definition for validation options
*
* @public
* @decorator
*/
export declare const OneOf: (ruleParameters: import("..").IValidatorDefaultMultiRule<unknown>) => PropertyDecorator;
export declare const AllOf: (ruleParameters: import("..").IValidatorDefaultMultiRule<unknown>) => PropertyDecorator;
/**
* ## ArrayOf Validation Decorator
*
* Ensures a property value is an array and that every item satisfies all provided
* sub-rules (AND logic per item). Delegates to `Validator.validateArrayOfRule`.
*
* @example
* class Model {
* @ArrayOf(["Email"]) emails!: string[];
* }
*/
export declare const ArrayOf: (ruleParameters: import("..").IValidatorDefaultMultiRule<unknown>) => PropertyDecorator;