Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | 12x 12x 12x 12x 12x 12x 12x 42x 42x 42x 42x 22x 22x 42x 42x 42x 42x 42x 42x 42x 42x 42x 42x 42x 42x 22x 22x 42x 42x 42x 42x 42x 42x 12x 42x 42x 42x 42x 42x 42x | /**
* @module module:lib/engine/ValidatorFactory
* @description Factory for generating Angular ValidatorFn from Decaf validation metadata.
* @summary ValidatorFactory maps validation keys defined by the Decaf validation system
* into Angular ValidatorFn instances. It supports type-based resolution and comparison
* validators and provides helpers to create proxies for nested control validation.
*
* @link {@link ValidatorFactory}
*/
import { AbstractControl, FormControl, FormGroup, ValidationErrors, ValidatorFn } from '@angular/forms';
import {
ComparisonValidationKeys,
PathProxy,
PathProxyEngine,
Primitives,
Validation,
ValidationKeys,
Validator,
} from '@decaf-ts/decorator-validation';
import { FieldProperties, HTML5InputTypes, parseValueByType, UIKeys } from '@decaf-ts/ui-decorators';
import { patternValidators } from './constants';
import { getLogger } from './helpers';
import { NgxRenderingEngine } from './NgxRenderingEngine';
import { ComparisonValidationKey, KeyValue } from './types';
export class ValidatorFactory {
/**
* @summary Extracts and parses the value from an Angular control.
* @description Retrieves the control's value and converts it to the appropriate type based on field properties.
* Handles undefined values gracefully by returning undefined without parsing.
*
* @param {AbstractControl} control - The Angular form control to extract the value from.
* @param {string} fieldType - The declared type of the field.
* @param {FieldProperties} fieldProps - The field properties containing type conversion metadata.
* @returns {unknown} The parsed value or undefined if the control value is undefined.
*/
static getFieldValue(control: AbstractControl, fieldType: string, fieldProps: FieldProperties): unknown {
return typeof control.value !== 'undefined' ? parseValueByType(fieldType, control.value, fieldProps) : undefined;
}
/**
* @summary Resolves the effective field type from multiple possible type sources.
* @description Determines the field's type by checking customTypes, subType, or type in order of priority.
* For checkbox and array fields with options, converts the type to STRING for proper validation.
*
* @param {string} [type] - The primary field type.
* @param {string | string[]} [customTypes] - Custom type definition with highest priority.
* @param {unknown[]} [options] - Available options for the field (affects checkbox/array handling).
* @param {string} [subType] - Secondary type definition.
* @returns {string} The resolved field type.
*/
static getFieldType(type?: string, customTypes?: string | string[], options?: unknown[], subType?: string): string {
const fieldType = (customTypes || subType || type) as string;
Iif ((fieldType === HTML5InputTypes.CHECKBOX || fieldType === Array.name) && Array.isArray(options)) {
return Primitives.STRING;
}
return fieldType;
}
/**
* @summary Creates a ValidatorFn for a specific validation key.
* @description Generates an Angular ValidatorFn that applies Decaf validation logic to a form control.
* Resolves the appropriate validator based on field type, parses the control value, constructs validation properties,
* and handles comparison validators through proxy access to parent/child form values.
*
* @param {FieldProperties} fieldProps - The field properties containing type and validation metadata.
* @param {string} key - The validation key to create a validator for.
* @returns {ValidatorFn} A validator function that returns ValidationErrors or null.
* @throws {Error} If the validation key is not supported.
*/
static spawn(fieldProps: FieldProperties, key: string): ValidatorFn {
Iif (!Validation.keys().includes(key)) {
throw new Error('Unsupported custom validation');
}
const validatorFn: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
const { type, customTypes, options, subType } = fieldProps || {};
const fieldType = ValidatorFactory.getFieldType(type, customTypes, options, subType);
const customValidator = key === UIKeys.TYPE && subType;
const { validatorKey, props } = this.resolveValidatorKeyProps(
key,
fieldType,
fieldProps,
customValidator ? (subType as string) : undefined
);
const validator = Validation.get(validatorKey) as Validator;
// parseValueByType does not support undefined values
const value = ValidatorFactory.getFieldValue(control, fieldType, fieldProps);
// Create a proxy to enable access to parent and child values
const proxy = ValidatorFactory.getValidatorProxy(control, key as ComparisonValidationKey);
let errs: string | undefined;
try {
const validationMessage = fieldProps?.validationMessage;
Iif (validationMessage && customValidator) {
Object.assign(props, { message: validationMessage });
}
errs = validator.hasErrors(value, props, proxy);
} catch (e: unknown) {
errs = `${key} validator failed to validate: ${e}`;
getLogger(ValidatorFactory).error(errs);
}
return errs ? { [validatorKey]: props?.['message'] ? errs : true } : null;
};
Object.defineProperty(validatorFn, 'name', {
value: `${key}Validator`,
});
return validatorFn;
}
/**
* Retrieves the validator value from field properties, with special handling for checkbox types.
* Returns the field property value, or the type if the field is a checkbox and the types differ.
*
* @param key - The validation key.
* @param type - The field's type.
* @param fieldProps - The field properties object.
* @returns The validator value to use.
*/
static getValidatorValue(key: string, type: string, fieldProps: FieldProperties): unknown {
return key === ValidationKeys.TYPE && HTML5InputTypes.CHECKBOX && fieldProps[key as keyof FieldProperties] !== type
? type
: fieldProps[key as keyof FieldProperties];
}
/**
* Determines whether a validation should be resolved based on the field's type.
* Returns true for TYPE validations with custom types or pattern-based validators.
*
* @param key - The validation key to evaluate.
* @param type - The field's declared type.
* @param customTypes - Optional custom type definition.
* @returns True if validation should use type-based resolution.
*/
static isTypeBasedValidation(key: string, type: string, customTypes: string | undefined): boolean {
return (
key === ValidationKeys.TYPE &&
((typeof customTypes === Primitives.STRING && HTML5InputTypes.TEXT !== customTypes) ||
Object.keys(patternValidators).includes(type))
);
}
/**
* @summary Creates or retrieves a proxy for validator access to parent/child form values.
* @description Returns a PathProxy configured for the given validation key.
* For comparison validators, creates a proxy rooted at the parent FormGroup to enable cross-field validation.
* For other validators, returns an empty proxy for type safety.
*
* @param {AbstractControl | FormGroup} control - The form control to create a proxy for.
* @param {ComparisonValidationKey} key - The validation key determining proxy scope.
* @returns {PathProxy<unknown>} A proxy object for form value access.
*/
static getValidatorProxy(control: AbstractControl | FormGroup, key: ComparisonValidationKey): PathProxy<unknown> {
const proxy = ValidatorFactory.createProxy({} as AbstractControl);
Iif (Object.values(ComparisonValidationKeys).includes(key)) {
return ValidatorFactory.createProxy((control instanceof FormGroup ? control : control.parent) as FormGroup);
}
return proxy;
}
/**
* @summary Creates a proxy wrapper for an Angular AbstractControl to assist with custom validation logic.
* @description Returns a structured proxy object that simulates a hierarchical tree of form values.
* Enables Validators handling method to access parent and child properties using consistent dot-notation in Angular forms.
*
* @param {AbstractControl} control - The control to wrap in a proxy.
* @returns {PathProxy<unknown>} A proxy object exposing form values and enabling recursive parent access.
*/
static createProxy(control: AbstractControl | FormGroup): PathProxy<unknown> {
return PathProxyEngine.create(control, {
getValue(target: AbstractControl, prop: string): unknown {
Iif (target instanceof FormControl) return target.value;
Iif (target instanceof FormGroup) {
const control = target.controls[prop];
return control instanceof FormControl ? control.value : control;
}
// const value = target[prop];
// if (value instanceof FormControl)
// return value.value;
//
// if (value instanceof FormGroup) {
// const control = value.controls[prop];
// return control instanceof FormControl ? control.value : control;
// }
return (target as KeyValue)?.[prop];
},
getParent: function (target: AbstractControl) {
return target?.['_parent'];
},
ignoreUndefined: true,
ignoreNull: true,
});
}
/**
*
* Resolves the correct validator key and its associated properties based on the input key and type.
*
* When the validation key is TYPE, it's necessary to resolve the actual validator based on the
* field's type (e.g., 'password', 'email', 'url') instead of using the generic getValidator("type") logic.
* This allows directly invoking specific validators like getValidator('password'), ensuring the correct
* behavior for type-based validation.
*
* @param key - The validation key (e.g., 'type', 'required', etc.).
* @param value - The value that needs be provided to the validator.
* @param type - The field's declared type.
* @returns An object containing the resolved validator key and its corresponding props.
*/
static resolveValidatorKeyProps = (
key: string,
type: string,
fieldProps: FieldProperties,
customTypes: string | undefined = undefined
): { validatorKey: string; props: KeyValue } => {
const isTypeBased = this.isTypeBasedValidation(key, type, customTypes);
const validatorKey = isTypeBased ? type : key;
const value = this.getValidatorValue(key, type, fieldProps);
const props = this.getValidatorProps(validatorKey, type, isTypeBased, value);
return { validatorKey, props };
};
/**
* Constructs the properties object to be passed to a validator.
* Handles translation of string values and applies pattern validators for type-based validations.
*
* @param validatorKey - The resolved validator key.
* @param type - The field's type.
* @param isTypeBased - Whether this is a type-based validation.
* @param value - The value to validate.
* @returns An object containing validator properties.
*/
static getValidatorProps(validatorKey: string, type: string, isTypeBased: boolean, value: unknown): KeyValue {
return {
[validatorKey]:
!isTypeBased && validatorKey === ValidationKeys.TYPE
? NgxRenderingEngine.get().translate(value as string, false)
: value,
// Email, Password, and URL are validated using the "pattern" key
...(isTypeBased && { [ValidationKeys.PATTERN]: patternValidators[type] }),
};
}
}
|