import type { Constructor } from './mixins' import type { SingleOrArray } from './type' import type { ValidationOptions } from 'class-validator' import { isString, ValidateBy, isInstance, buildMessage } from 'class-validator' import { asArray } from './array' export interface IsInstanceOrArrayOfInstancesValidationOptions extends ValidationOptions { // eslint-disable-next-line @typescript-eslint/no-explicit-any classType: SingleOrArray any> /** * Whether to allow empty arrays to pass validation * @default false */ allowEmptyArray?: boolean } /** * Checks if the value is a string or the specified instance */ export function IsStringOrInstance(targetType: Constructor, validationOptions?: ValidationOptions): PropertyDecorator { return ValidateBy( { name: 'IsStringOrInstance', constraints: [targetType], validator: { validate: (value, args): boolean => isString(value) || isInstance(value, args?.constraints[0]), defaultMessage: buildMessage((eachPrefix, args) => { if (args?.constraints[0]) { return eachPrefix + `$property must be of type string or instance of ${args.constraints[0].name as string}` } else { return eachPrefix + `IsStringOrInstance decorator expects an object as value, but got falsy value.` } }, validationOptions), }, }, validationOptions ) } export function IsInstanceOrArrayOfInstances( validationOptions: IsInstanceOrArrayOfInstancesValidationOptions ): PropertyDecorator { const classTypes = asArray(validationOptions.classType) const allowEmptyArray = validationOptions.allowEmptyArray ?? false return ValidateBy( { name: 'isInstanceOrArrayOfInstances', validator: { validate: (values) => { if (!values) return false if (Array.isArray(values) && values.length === 0) return allowEmptyArray return ( asArray(values) // all values MUST be instance of one of the class types .every((value) => classTypes.some((classType) => isInstance(value, classType))) ) }, defaultMessage: buildMessage( (eachPrefix) => eachPrefix + `$property value must be an instance of, or an array of instances containing ${classTypes .map((c) => c.name) .join(', ')}`, validationOptions ), }, }, validationOptions ) } // eslint-disable-next-line @typescript-eslint/no-explicit-any export function isStringArray(value: any): value is string[] { return Array.isArray(value) && value.every((v) => typeof v === 'string') } export const UriValidator = /\w+:(\/?\/?)[^\s]+/ export function isUri(value: string) { return UriValidator.test(value) } export function IsUri(validationOptions?: ValidationOptions): PropertyDecorator { return ValidateBy( { name: 'isUri', validator: { validate: (value): boolean => isUri(value), defaultMessage: buildMessage( (eachPrefix) => eachPrefix + `$property must be an URI (that matches regex: ${UriValidator.source})`, validationOptions ), }, }, validationOptions ) }