Source

validation/decorators.ts

import {
  ComparisonValidatorOptions,
  DateValidatorOptions,
  DiffValidatorOptions,
  EnumValidatorOptions,
  EqualsValidatorOptions,
  GreaterThanOrEqualValidatorOptions,
  GreaterThanValidatorOptions,
  LessThanOrEqualValidatorOptions,
  LessThanValidatorOptions,
  ListValidatorOptions,
  MaxLengthValidatorOptions,
  MaxValidatorOptions,
  MinLengthValidatorOptions,
  MinValidatorOptions,
  PatternValidatorOptions,
  StepValidatorOptions,
  ValidationMetadata,
  ValidatorOptions,
} from "./types";
import {
  DEFAULT_ERROR_MESSAGES,
  DEFAULT_PATTERNS,
  ValidationKeys,
} from "./Validators/constants";
import { sf } from "../utils/strings";
import { ModelConstructor } from "../model/types";
import { parseDate } from "../utils/dates";
import { Validation } from "./Validation";
import {
  Constructor,
  Decoration,
  apply,
  propMetadata,
} from "@decaf-ts/decoration";
import { ASYNC_META_KEY } from "../constants";

/**
 * @description Combined property decorator factory for metadata and attribute marking
 * @summary Creates a decorator that both marks a property as a model attribute and assigns metadata to it
 *
 * @template V
 * @param {PropertyDecorator} decorator - The metadata key
 * @param {string} key - The metadata key
 * @param {V} value - The metadata value to associate with the property
 * @return {Function} - Combined decorator function
 * @function validationMetadata
 * @category Property Decorators
 */
export function validationMetadata<V>(decorator: any, key: string, value: V) {
  return apply(propMetadata(key, value));
}

export function innerValidationDecorator(dec: any, key: string, meta: any) {
  Validation.registerDecorator(key, dec);
  return function innerValidationDecorator(obj: any, prop: any) {
    return validationMetadata(
      dec,
      `${ValidationKeys.REFLECT}.${prop}.${key}`,
      meta
    )(obj, prop);
  };
}

export function async() {
  return (model: object): void => {
    if (!Object.prototype.hasOwnProperty.call(model, ASYNC_META_KEY))
      (model as any)[ASYNC_META_KEY] = true;
  };
}

/**
 * @description Property decorator that marks a field as required
 * @summary Marks the property as required, causing validation to fail if the property is undefined, null, or empty.
 * Validators to validate a decorated property must use key {@link ValidationKeys#REQUIRED}.
 * This decorator is commonly used as the first validation step for important fields.
 *
 * @param {string} [message] - The error message to display when validation fails. Defaults to {@link DEFAULT_ERROR_MESSAGES#REQUIRED}
 * @return {PropertyDecorator} A decorator function that can be applied to class properties
 *
 * @function required
 * @category Property Decorators
 *
 * @example
 * ```typescript
 * class User {
 *   @required()
 *   username: string;
 *
 *   @required("Email address is mandatory")
 *   email: string;
 * }
 * ```
 */
export function required(message: string = DEFAULT_ERROR_MESSAGES.REQUIRED) {
  const key = ValidationKeys.REQUIRED;
  const meta: ValidatorOptions = {
    message: message,
    description: `defines the attribute as required`,
    async: false,
  };
  return Decoration.for(key)
    .define({
      decorator: innerValidationDecorator,
      args: [required, key, meta],
    })
    .apply();
}

/**
 * @description Property decorator that enforces a minimum value constraint
 * @summary Defines a minimum value for the property, causing validation to fail if the property value is less than the specified minimum.
 * Validators to validate a decorated property must use key {@link ValidationKeys#MIN}.
 * This decorator works with numeric values and dates.
 *
 * @param {number | Date | string} value - The minimum value allowed. For dates, can be a Date object or a string that can be converted to a date
 * @param {string} [message] - The error message to display when validation fails. Defaults to {@link DEFAULT_ERROR_MESSAGES#MIN}
 * @return {PropertyDecorator} A decorator function that can be applied to class properties
 *
 * @function min
 * @category Property Decorators
 *
 * @example
 * ```typescript
 * class Product {
 *   @min(0)
 *   price: number;
 *
 *   @min(new Date(2023, 0, 1), "Date must be after January 1, 2023")
 *   releaseDate: Date;
 * }
 * ```
 */
export function min(
  value: number | Date | string,
  message: string = DEFAULT_ERROR_MESSAGES.MIN
) {
  const key = ValidationKeys.MIN;
  const meta: MinValidatorOptions = {
    [ValidationKeys.MIN]: value,
    message: message,
    description: `defines the max value of the attribute as ${value} (applies to numbers or Dates)`,
    async: false,
  };
  return Decoration.for(key)
    .define({
      decorator: innerValidationDecorator,
      args: [min, key, meta],
    })
    .apply();
}

/**
 * @summary Defines a maximum value for the property
 * @description Validators to validate a decorated property must use key {@link ValidationKeys#MAX}
 *
 * @param {number | Date} value
 * @param {string} [message] the error message. Defaults to {@link DEFAULT_ERROR_MESSAGES#MAX}
 *
 * @function max
 * @category Property Decorators
 */
export function max(
  value: number | Date | string,
  message: string = DEFAULT_ERROR_MESSAGES.MAX
) {
  const key = ValidationKeys.MAX;
  const meta: MaxValidatorOptions = {
    [ValidationKeys.MAX]: value,
    message: message,
    description: `defines the max value of the attribute as ${value} (applies to numbers or Dates)`,
    async: false,
  };
  return Decoration.for(key)
    .define({
      decorator: innerValidationDecorator,
      args: [max, key, meta],
    })
    .apply();
}

/**
 * @summary Defines a step value for the property
 * @description Validators to validate a decorated property must use key {@link ValidationKeys#STEP}
 *
 * @param {number} value
 * @param {string} [message] the error message. Defaults to {@link DEFAULT_ERROR_MESSAGES#STEP}
 *
 * @function step
 * @category Property Decorators
 */
export function step(
  value: number,
  message: string = DEFAULT_ERROR_MESSAGES.STEP
) {
  const key = ValidationKeys.STEP;
  const meta: StepValidatorOptions = {
    [ValidationKeys.STEP]: value,
    message: message,
    description: `defines the step of the attribute as ${value}`,
    async: false,
  };
  return Decoration.for(key)
    .define({
      decorator: innerValidationDecorator,
      args: [step, key, meta],
    })
    .apply();
}

/**
 * @summary Defines a minimum length for the property
 * @description Validators to validate a decorated property must use key {@link ValidationKeys#MIN_LENGTH}
 *
 * @param {string} value
 * @param {string} [message] the error message. Defaults to {@link DEFAULT_ERROR_MESSAGES#MIN_LENGTH}
 *
 * @function minlength
 * @category Property Decorators
 */
export function minlength(
  value: number,
  message: string = DEFAULT_ERROR_MESSAGES.MIN_LENGTH
) {
  const key = ValidationKeys.MIN_LENGTH;
  const meta: MinLengthValidatorOptions = {
    [ValidationKeys.MIN_LENGTH]: value,
    message: message,
    description: `defines the min length of the attribute as ${value} (applies to strings or lists)`,
    async: false,
  };
  return Decoration.for(key)
    .define({
      decorator: innerValidationDecorator,
      args: [minlength, key, meta],
    })
    .apply();
}

/**
 * @summary Defines a maximum length for the property
 * @description Validators to validate a decorated property must use key {@link ValidationKeys#MAX_LENGTH}
 *
 * @param {string} value
 * @param {string} [message] the error message. Defaults to {@link DEFAULT_ERROR_MESSAGES#MAX_LENGTH}
 *
 * @function maxlength
 * @category Property Decorators
 */
export function maxlength(
  value: number,
  message: string = DEFAULT_ERROR_MESSAGES.MAX_LENGTH
) {
  const key = ValidationKeys.MAX_LENGTH;
  const meta: MaxLengthValidatorOptions = {
    [ValidationKeys.MAX_LENGTH]: value,
    message: message,
    description: `defines the max length of the attribute as ${value} (applies to strings or lists)`,
    async: false,
  };
  return Decoration.for(key)
    .define({
      decorator: innerValidationDecorator,
      args: [maxlength, key, meta],
    })
    .apply();
}

/**
 * @summary Defines a RegExp pattern the property must respect
 * @description Validators to validate a decorated property must use key {@link ValidationKeys#PATTERN}
 *
 * @param {string} value
 * @param {string} [message] the error message. Defaults to {@link DEFAULT_ERROR_MESSAGES#PATTERN}
 *
 * @function pattern
 * @category Property Decorators
 */
export function pattern(
  value: RegExp | string,
  message: string = DEFAULT_ERROR_MESSAGES.PATTERN
) {
  const key = ValidationKeys.PATTERN;
  const meta: PatternValidatorOptions = {
    [ValidationKeys.PATTERN]:
      typeof value === "string" ? value : value.toString(),
    message: message,
    description: `assigns the ${value === "string" ? value : value.toString()} pattern to the attribute`,
    async: false,
  };
  return Decoration.for(key)
    .define({
      decorator: innerValidationDecorator,
      args: [pattern, key, meta],
    })
    .apply();
}

/**
 * @summary Defines the property as an email
 * @description Validators to validate a decorated property must use key {@link ValidationKeys#EMAIL}
 *
 * @param {string} [message] the error message. Defaults to {@link DEFAULT_ERROR_MESSAGES#EMAIL}
 *
 * @function email
 * @category Property Decorators
 */
export function email(message: string = DEFAULT_ERROR_MESSAGES.EMAIL) {
  const key = ValidationKeys.EMAIL;
  const meta: PatternValidatorOptions = {
    [ValidationKeys.PATTERN]: DEFAULT_PATTERNS.EMAIL.toString(),
    message: message,
    description: "marks the attribute as an email",
    async: false,
  };
  return Decoration.for(key)
    .define({
      decorator: innerValidationDecorator,
      args: [email, key, meta],
    })
    .apply();
}

/**
 * @summary Defines the property as an URL
 * @description Validators to validate a decorated property must use key {@link ValidationKeys#URL}
 *
 * @param {string} [message] the error message. Defaults to {@link DEFAULT_ERROR_MESSAGES#URL}
 *
 * @function url
 * @category Property Decorators
 */
export function url(message: string = DEFAULT_ERROR_MESSAGES.URL) {
  const key = ValidationKeys.URL;
  const meta: PatternValidatorOptions = {
    [ValidationKeys.PATTERN]: DEFAULT_PATTERNS.URL.toString(),
    message: message,
    description: "marks the attribute as an url",
    async: false,
  };
  return Decoration.for(key)
    .define({
      decorator: innerValidationDecorator,
      args: [url, key, meta],
    })
    .apply();
}

export type TypeConstructor = Constructor | typeof BigInt;

export interface TypeMetadata extends ValidatorOptions {
  customTypes:
    | (TypeConstructor | (() => TypeConstructor))[]
    | TypeConstructor
    | (() => TypeConstructor);
}

/**
 * @summary Enforces type verification
 * @description Validators to validate a decorated property must use key {@link ValidationKeys#TYPE}
 *
 * @param {Constructor[] | Constructor} types accepted types
 * @param {Constructor} [message] the error message. Defaults to {@link DEFAULT_ERROR_MESSAGES#TYPE}
 *
 * @function type
 * @category Property Decorators
 */
//TODO
export function type(
  types:
    | (TypeConstructor | (() => TypeConstructor))[]
    | TypeConstructor
    | (() => TypeConstructor),
  message: string = DEFAULT_ERROR_MESSAGES.TYPE
) {
  const key = ValidationKeys.TYPE;
  const meta: TypeMetadata = {
    customTypes: types,
    message: message,
    description: "defines the accepted types for the attribute",
    async: false,
  };
  return Decoration.for(key)
    .define({
      decorator: innerValidationDecorator,
      args: [type, key, meta],
    })
    .apply();
}

export type DateMetadata = DateValidatorOptions;

/**
 * @summary Date Handler Decorator
 * @description Validators to validate a decorated property must use key {@link ValidationKeys#DATE}
 *
 * Will enforce serialization according to the selected format
 *
 * @param {string} format accepted format according to {@link formatDate}
 * @param {string} [message] the error message. Defaults to {@link DEFAULT_ERROR_MESSAGES#DATE}
 *
 * @function date
 *
 * @category Property Decorators
 */
export function date(
  format: string = "dd/MM/yyyy",
  message: string = DEFAULT_ERROR_MESSAGES.DATE
) {
  const key = ValidationKeys.DATE;

  function innerDateDec(format: string, message: string) {
    const meta: DateMetadata = {
      [ValidationKeys.FORMAT]: format,
      message: message,
      description: `defines the attribute as a date with the format ${format}`,
      async: false,
    };

    // return function dateDec(
    //   target: Record<string, any>,
    //   propertyKey?: any
    // ): any {
    //   const definitionTarget =
    //     typeof target === "function" ? target.prototype : target;
    //   const values = new WeakMap<any, Date | undefined>();
    //
    //   const ensureInstanceDescriptor = (instance: any) => {
    //     if (
    //       Object.prototype.hasOwnProperty.call(instance, propertyKey) &&
    //       !Object.getOwnPropertyDescriptor(instance, propertyKey)?.configurable
    //     )
    //       return;
    //
    //     Object.defineProperty(instance, propertyKey, {
    //       enumerable: true,
    //       configurable: false,
    //       get(this: any) {
    //         return values.get(this);
    //       },
    //       set(this: any, newValue: string | Date | number | undefined | null) {
    //         if (typeof newValue === "undefined" || newValue === null) {
    //           values.delete(this);
    //           return;
    //         }
    //
    //         try {
    //           const val = parseDate(format, newValue);
    //           values.set(this, val);
    //         } catch (e: any) {
    //           console.error(sf("Failed to parse date: {0}", e.message || e));
    //         }
    //       },
    //     });
    //   };
    //
    //   Object.defineProperty(definitionTarget, propertyKey, {
    //     configurable: true,
    //     enumerable: true,
    //     get(this: any) {
    //       ensureInstanceDescriptor(this);
    //       return (this as any)[propertyKey];
    //     },
    //     set(this: any, newValue: string | Date | number | undefined | null) {
    //       ensureInstanceDescriptor(this);
    //       (this as any)[propertyKey] = newValue;
    //     },
    //   });
    //
    //   return innerValidationDecorator(date, key, meta)(target, propertyKey);
    // };
    return function dateDec(
      target: Record<string, any>,
      propertyKey?: any
    ): any {
      const values = new WeakMap();
      Object.defineProperty(target, propertyKey, {
        configurable: true,
        set(this: any, newValue: string | Date) {
          const descriptor = Object.getOwnPropertyDescriptor(this, propertyKey);
          if (!descriptor || descriptor.configurable)
            Object.defineProperty(this, propertyKey, {
              enumerable: true,
              configurable: false,
              get: () => values.get(this),
              set: (newValue: string | Date | number) => {
                let val: Date | undefined;
                try {
                  val = parseDate(format, newValue);
                  values.set(this, val);
                } catch (e: any) {
                  console.error(
                    sf("Failed to parse date: {0}", e.message || e)
                  );
                }
              },
            });
          this[propertyKey] = newValue;
        },
        get() {
          return values.get(this);
        },
      });

      return innerValidationDecorator(date, key, meta)(target, propertyKey);
    };
  }

  return Decoration.for(key)
    .define({
      decorator: innerDateDec,
      args: [format, message],
    })
    .apply();
}

/**
 * @summary Password Handler Decorator
 * @description Validators to validate a decorated property must use key {@link ValidationKeys#PASSWORD}
 *
 * @param {RegExp} [pattern] defaults to {@link DEFAULT_PATTERNS#CHAR8_ONE_OF_EACH}
 * @param {string} [message] the error message. Defaults to {@link DEFAULT_ERROR_MESSAGES#PASSWORD}
 *
 * @function password
 *
 * @category Property Decorators
 */
export function password(
  pattern: RegExp = DEFAULT_PATTERNS.PASSWORD.CHAR8_ONE_OF_EACH,
  message: string = DEFAULT_ERROR_MESSAGES.PASSWORD
) {
  const key = ValidationKeys.PASSWORD;
  const meta: PatternValidatorOptions = {
    [ValidationKeys.PATTERN]: pattern.toString(),
    message: message,
    description: `attribute as a password`,
    async: false,
  };
  return Decoration.for(key)
    .define({
      decorator: innerValidationDecorator,
      args: [password, key, meta],
    })
    .apply();
}

export interface ListMetadata extends ListValidatorOptions {
  type: "Array" | "Set";
}

/**
 * @summary List Decorator
 * @description Also sets the {@link type} to the provided collection
 *
 * @param {ModelConstructor} clazz
 * @param {string} [collection] The collection being used. defaults to Array
 * @param {string} [message] defaults to {@link DEFAULT_ERROR_MESSAGES#LIST}
 *
 * @function list
 *
 * @category Property Decorators
 */
export function list(
  clazz:
    | Constructor
    | (() => Constructor)
    | (Constructor | (() => Constructor))[],
  collection: "Array" | "Set" = "Array",
  message: string = DEFAULT_ERROR_MESSAGES.LIST
) {
  const key = ValidationKeys.LIST;
  const meta: ListMetadata = {
    clazz: (Array.isArray(clazz) ? clazz : [clazz]) as (
      | Constructor
      | (() => Constructor)
    )[],
    type: collection,
    message: message,
    async: false,
    description: `defines the attribute as a ${collection} of ${(clazz as ModelConstructor<any>).name}`,
  };
  return Decoration.for(key)
    .define({
      decorator: innerValidationDecorator,
      args: [list, key, meta],
    })
    .apply();
}

/**
 * @summary Set Decorator
 * @description Wrapper for {@link list} with the 'Set' Collection
 *
 * @param {ModelConstructor} clazz
 * @param {string} [message] defaults to {@link DEFAULT_ERROR_MESSAGES#LIST}
 *
 * @function set
 *
 * @category Property Decorators
 */
export function set(
  clazz: ModelConstructor<any>,
  message: string = DEFAULT_ERROR_MESSAGES.LIST
) {
  return list(clazz, "Set", message);
}

/**
 * @summary Declares that the decorated property must be equal to another specified property.
 * @description Applies the {@link ValidationKeys.EQUALS} validator to ensure the decorated value matches the value of the given property.
 *
 * @param {string} propertyToCompare - The name of the property to compare equality against.
 * @param {ComparisonValidatorOptions} options - Options for the validator.
 * @param {string} [options.label] - The label text displayed in the error message.
 * @param {string} [options.message=DEFAULT_ERROR_MESSAGES.EQUALS] - Custom error message to be returned if validation fails.
 *
 * @returns {PropertyDecorator} A property decorator used to register the equality validation metadata.
 *
 * @function eq
 * @category Property Decorators
 */
export function eq(
  propertyToCompare: string,
  options?: Omit<ComparisonValidatorOptions, "async" | "description">
  // message: string = DEFAULT_ERROR_MESSAGES.EQUALS
) {
  const equalsOptions: EqualsValidatorOptions = {
    label: options?.label || propertyToCompare,
    message: options?.message || DEFAULT_ERROR_MESSAGES.EQUALS,
    [ValidationKeys.EQUALS]: propertyToCompare,
    description: `defines attribute as equal to ${propertyToCompare}`,
  };

  return innerValidationDecorator(eq, ValidationKeys.EQUALS, {
    ...equalsOptions,
    async: false,
  } as ValidationMetadata);
}

/**
 * @summary Declares that the decorated property must be different from another specified property.
 * @description Applies the {@link ValidationKeys.DIFF} validator to ensure the decorated value is different from the value of the given property.
 *
 * @param {string} propertyToCompare - The name of the property to compare difference against.
 * @param {ComparisonValidatorOptions} options - Options for the validator.
 * @param {string} [options.label] - The label text displayed in the error message.
 * @param {string} [options.message=DEFAULT_ERROR_MESSAGES.DIFF] - Custom error message to be returned if validation fails.
 *
 * @returns {PropertyDecorator} A property decorator used to register the difference validation metadata.
 *
 * @function diff
 * @category Property Decorators
 */
export function diff(
  propertyToCompare: string,
  options?: Omit<ComparisonValidatorOptions, "async" | "description">
) {
  const diffOptions: DiffValidatorOptions = {
    label: options?.label || propertyToCompare,
    message: options?.message || DEFAULT_ERROR_MESSAGES.DIFF,
    [ValidationKeys.DIFF]: propertyToCompare,
    description: `defines attribute as different to ${propertyToCompare}`,
  };

  return innerValidationDecorator(diff, ValidationKeys.DIFF, {
    ...diffOptions,
    async: false,
  } as ValidationMetadata);
}

/**
 * @summary Declares that the decorated property must be less than another specified property.
 * @description Applies the {@link ValidationKeys.LESS_THAN} validator to ensure the decorated value is less than the value of the given property.
 *
 * @param {string} propertyToCompare - The name of the property to compare against.
 * @param {ComparisonValidatorOptions} options - Options for the validator.
 * @param {string} [options.label] - The label text displayed in the error message.
 * @param {string} [options.message=DEFAULT_ERROR_MESSAGES.LESS_THAN] - Custom error message to be returned if validation fails.
 *
 * @returns {PropertyDecorator} A property decorator used to register the less than validation metadata.
 *
 * @function lt
 * @category Property Decorators
 */
export function lt(
  propertyToCompare: string,
  options?: Omit<ComparisonValidatorOptions, "async" | "description">
) {
  const ltOptions: LessThanValidatorOptions = {
    label: options?.label || propertyToCompare,
    message: options?.message || DEFAULT_ERROR_MESSAGES.LESS_THAN,
    [ValidationKeys.LESS_THAN]: propertyToCompare,
    description: `defines attribute as less than to ${propertyToCompare}`,
  };

  return innerValidationDecorator(lt, ValidationKeys.LESS_THAN, {
    ...ltOptions,
    async: false,
  } as ValidationMetadata);
}

/**
 * @summary Declares that the decorated property must be equal or less than another specified property.
 * @description Applies the {@link ValidationKeys.LESS_THAN_OR_EQUAL} validator to ensure the decorated value is equal or less than the value of the given property.
 *
 * @param {string} propertyToCompare - The name of the property to compare against.
 * @param {ComparisonValidatorOptions} options - Options for the validator.
 * @param {string} [options.label] - The label text displayed in the error message.
 * @param {string} [options.message=DEFAULT_ERROR_MESSAGES.LESS_THAN_OR_EQUAL] - Custom error message to be returned if validation fails.
 *
 * @returns {PropertyDecorator} A property decorator used to register the less than or equal validation metadata.
 *
 * @function lte
 * @category Property Decorators
 */
export function lte(
  propertyToCompare: string,
  options?: Omit<ComparisonValidatorOptions, "async" | "description">
) {
  const lteOptions: LessThanOrEqualValidatorOptions = {
    label: options?.label || propertyToCompare,
    message: options?.message || DEFAULT_ERROR_MESSAGES.LESS_THAN_OR_EQUAL,
    [ValidationKeys.LESS_THAN_OR_EQUAL]: propertyToCompare,
    description: `defines attribute as less or equal to ${propertyToCompare}`,
  };

  return innerValidationDecorator(lte, ValidationKeys.LESS_THAN_OR_EQUAL, {
    ...lteOptions,
    async: false,
  } as ValidationMetadata);
}

/**
 * @summary Declares that the decorated property must be greater than another specified property.
 * @description Applies the {@link ValidationKeys.GREATER_THAN} validator to ensure the decorated value is greater than the value of the given property.
 *
 * @param {string} propertyToCompare - The name of the property to compare against.
 * @param {ComparisonValidatorOptions} options - Options for the validator.
 * @param {string} [options.label] - The label text displayed in the error message.
 * @param {string} [options.message=DEFAULT_ERROR_MESSAGES.GREATER_THAN] - Custom error message to be returned if validation fails.
 *
 * @returns {PropertyDecorator} A property decorator used to register the greater than validation metadata.
 *
 * @function gt
 * @category Property Decorators
 */
export function gt(
  propertyToCompare: string,
  options?: Omit<ComparisonValidatorOptions, "async" | "description">
) {
  const gtOptions: GreaterThanValidatorOptions = {
    label: options?.label || propertyToCompare,
    message: options?.message || DEFAULT_ERROR_MESSAGES.GREATER_THAN,
    [ValidationKeys.GREATER_THAN]: propertyToCompare,
    description: `defines attribute as greater than ${propertyToCompare}`,
  };

  return innerValidationDecorator(gt, ValidationKeys.GREATER_THAN, {
    ...gtOptions,
    async: false,
  } as ValidationMetadata);
}

/**
 * @summary Declares that the decorated property must be equal or greater than another specified property.
 * @description Applies the {@link ValidationKeys.GREATER_THAN_OR_EQUAL} validator to ensure the decorated value is equal or greater than the value of the given property.
 *
 * @param {string} propertyToCompare - The name of the property to compare against.
 * @param {ComparisonValidatorOptions} options - Options for the validator.
 * @param {string} [options.label] - The label text displayed in the error message.
 * @param {string} [options.message=DEFAULT_ERROR_MESSAGES.GREATER_THAN_OR_EQUAL] - Custom error message to be returned if validation fails.
 *
 * @returns {PropertyDecorator} A property decorator used to register the greater than or equal validation metadata.
 *
 * @function gte
 * @category Property Decorators
 */
export function gte(
  propertyToCompare: string,
  options?: Omit<ComparisonValidatorOptions, "async" | "description">
) {
  const gteOptions: GreaterThanOrEqualValidatorOptions = {
    label: options?.label || propertyToCompare,
    message: options?.message || DEFAULT_ERROR_MESSAGES.GREATER_THAN_OR_EQUAL,
    [ValidationKeys.GREATER_THAN_OR_EQUAL]: propertyToCompare,
    description: `defines attribute as greater or equal to ${propertyToCompare}`,
  };

  return innerValidationDecorator(gte, ValidationKeys.GREATER_THAN_OR_EQUAL, {
    ...gteOptions,
    async: false,
  } as ValidationMetadata);
}

/**
 * @summary Defines a list or an object of accepted values for the property
 * @description Validators to validate a decorated property must use key {@link ValidationKeys#ENUM}
 *
 * @param {any[] | Record<any, any>} value
 * @param {string} [message] the error message. Defaults to {@link DEFAULT_ERROR_MESSAGES#ENUM}
 *
 * @function option
 * @category Property Decorators
 */
export function option(
  value: any[] | Record<any, any>,
  message: string = DEFAULT_ERROR_MESSAGES.ENUM
) {
  const key = ValidationKeys.ENUM;
  const meta: EnumValidatorOptions = {
    [ValidationKeys.ENUM]: value,
    message: message,
    description: `defines a list or an object of accepted values for the attribute`,
    async: false,
  };
  return Decoration.for(key)
    .define({
      decorator: innerValidationDecorator,
      args: [option, key, meta],
    })
    .apply();
}