import { Validator } from "./Validator";
import { DEFAULT_ERROR_MESSAGES, ValidationKeys } from "./constants";
import { validator } from "./decorators";
import { ListValidatorOptions } from "../types";
/**
* @description Validator for checking if elements in a list or set match expected types
* @summary The ListValidator validates that all elements in an array or Set match the expected types.
* It checks each element against a list of allowed class types and ensures type consistency.
* This validator is typically used with the @list decorator.
*
* @param {string} [message] - Custom error message to display when validation fails, defaults to {@link DEFAULT_ERROR_MESSAGES#LIST}
*
* @class ListValidator
* @extends Validator
*
* @example
* ```typescript
* // Create a list validator with default error message
* const listValidator = new ListValidator();
*
* // Create a list validator with custom error message
* const customListValidator = new ListValidator("All items must be of the specified type");
*
* // Validate a list
* const options = { clazz: ["String", "Number"] };
* const result = listValidator.hasErrors(["test", 123], options); // undefined (valid)
* const invalidResult = listValidator.hasErrors([new Date()], options); // Returns error message (invalid)
* ```
*
* @mermaid
* sequenceDiagram
* participant C as Client
* participant V as ListValidator
*
* C->>V: new ListValidator(message)
* C->>V: hasErrors(value, options)
* alt value is empty
* V-->>C: undefined (valid)
* else value has elements
* V->>V: Check each element's type
* alt All elements match allowed types
* V-->>C: undefined (valid)
* else Some elements don't match
* V-->>C: Error message
* end
* end
*
* @category Validators
*/
@validator(ValidationKeys.LIST)
export class ListValidator extends Validator<ListValidatorOptions> {
constructor(message: string = DEFAULT_ERROR_MESSAGES.LIST) {
super(message, Array.name, Set.name);
}
/**
* @description Checks if all elements in a list or set match the expected types
* @summary Validates that each element in the provided array or Set matches one of the
* class types specified in the options. For object types, it checks the constructor name,
* and for primitive types, it compares against the lowercase type name.
*
* @param {any[] | Set<any>} value - The array or Set to validate
* @param {ListValidatorOptions} options - Configuration options containing the allowed class types
*
* @return {string | undefined} Error message if validation fails, undefined if validation passes
*
* @override
*
* @see Validator#hasErrors
*/
hasErrors(
value: any[] | Set<any>,
options: ListValidatorOptions
): string | undefined {
if (!value || (Array.isArray(value) ? !value.length : !value.size)) return;
const clazz = Array.isArray(options.clazz)
? options.clazz
: [options.clazz];
let val: any,
isValid = true;
for (
let i = 0;
i < (Array.isArray(value) ? value.length : value.size);
i++
) {
val = (value as any)[i];
switch (typeof val) {
case "object":
case "function":
isValid = clazz.includes(((val ?? {}) as object).constructor?.name); // null is an object
break;
default:
isValid = clazz.some((c: string) => typeof val === c.toLowerCase());
break;
}
}
return isValid
? undefined
: this.getMessage(options.message || this.message, clazz);
}
}
Source