@decaf-ts/decorator-validation
Version:
simple decorator based validation engine
613 lines • 25 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.validationMetadata = validationMetadata;
exports.async = async;
exports.required = required;
exports.min = min;
exports.max = max;
exports.step = step;
exports.minlength = minlength;
exports.maxlength = maxlength;
exports.pattern = pattern;
exports.email = email;
exports.url = url;
exports.type = type;
exports.date = date;
exports.password = password;
exports.list = list;
exports.set = set;
exports.eq = eq;
exports.diff = diff;
exports.lt = lt;
exports.lte = lte;
exports.gt = gt;
exports.gte = gte;
require("reflect-metadata");
const constants_1 = require("./Validators/constants.cjs");
const strings_1 = require("./../utils/strings.cjs");
const dates_1 = require("./../utils/dates.cjs");
const decorators_1 = require("./../utils/decorators.cjs");
const Validation_1 = require("./Validation.cjs");
const Decoration_1 = require("./../utils/Decoration.cjs");
const reflection_1 = require("@decaf-ts/reflection");
const constants_2 = require("./../constants/index.cjs");
/**
* @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
*/
function validationMetadata(decorator, key, value) {
Validation_1.Validation.registerDecorator(key, decorator);
return (0, reflection_1.apply)((0, decorators_1.propMetadata)(key, value));
}
function async() {
return (model) => {
if (!Object.prototype.hasOwnProperty.call(model, constants_2.ASYNC_META_KEY))
model[constants_2.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;
* }
* ```
*/
function required(message = constants_1.DEFAULT_ERROR_MESSAGES.REQUIRED) {
const key = Validation_1.Validation.key(constants_1.ValidationKeys.REQUIRED);
const meta = {
message: message,
description: `defines the attribute as required`,
async: false,
};
return Decoration_1.Decoration.for(key)
.define({
decorator: (validationMetadata),
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;
* }
* ```
*/
function min(value, message = constants_1.DEFAULT_ERROR_MESSAGES.MIN) {
const key = Validation_1.Validation.key(constants_1.ValidationKeys.MIN);
const meta = {
[constants_1.ValidationKeys.MIN]: value,
message: message,
types: [Number.name, Date.name],
description: `defines the max value of the attribute as ${value} (applies to numbers or Dates)`,
async: false,
};
return Decoration_1.Decoration.for(key)
.define({
decorator: (validationMetadata),
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
*/
function max(value, message = constants_1.DEFAULT_ERROR_MESSAGES.MAX) {
const key = Validation_1.Validation.key(constants_1.ValidationKeys.MAX);
const meta = {
[constants_1.ValidationKeys.MAX]: value,
message: message,
types: [Number.name, Date.name],
description: `defines the max value of the attribute as ${value} (applies to numbers or Dates)`,
async: false,
};
return Decoration_1.Decoration.for(key)
.define({
decorator: (validationMetadata),
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
*/
function step(value, message = constants_1.DEFAULT_ERROR_MESSAGES.STEP) {
const key = Validation_1.Validation.key(constants_1.ValidationKeys.STEP);
const meta = {
[constants_1.ValidationKeys.STEP]: value,
message: message,
types: [Number.name],
description: `defines the step of the attribute as ${value}`,
async: false,
};
return Decoration_1.Decoration.for(key)
.define({
decorator: (validationMetadata),
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
*/
function minlength(value, message = constants_1.DEFAULT_ERROR_MESSAGES.MIN_LENGTH) {
const key = Validation_1.Validation.key(constants_1.ValidationKeys.MIN_LENGTH);
const meta = {
[constants_1.ValidationKeys.MIN_LENGTH]: value,
message: message,
types: [String.name, Array.name, Set.name],
description: `defines the min length of the attribute as ${value} (applies to strings or lists)`,
async: false,
};
return Decoration_1.Decoration.for(key)
.define({
decorator: (validationMetadata),
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
*/
function maxlength(value, message = constants_1.DEFAULT_ERROR_MESSAGES.MAX_LENGTH) {
const key = Validation_1.Validation.key(constants_1.ValidationKeys.MAX_LENGTH);
const meta = {
[constants_1.ValidationKeys.MAX_LENGTH]: value,
message: message,
types: [String.name, Array.name, Set.name],
description: `defines the max length of the attribute as ${value} (applies to strings or lists)`,
async: false,
};
return Decoration_1.Decoration.for(key)
.define({
decorator: (validationMetadata),
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
*/
function pattern(value, message = constants_1.DEFAULT_ERROR_MESSAGES.PATTERN) {
const key = Validation_1.Validation.key(constants_1.ValidationKeys.PATTERN);
const meta = {
[constants_1.ValidationKeys.PATTERN]: typeof value === "string" ? value : value.toString(),
message: message,
types: [String.name],
description: `assigns the ${value === "string" ? value : value.toString()} pattern to the attribute`,
async: false,
};
return Decoration_1.Decoration.for(key)
.define({
decorator: (validationMetadata),
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
*/
function email(message = constants_1.DEFAULT_ERROR_MESSAGES.EMAIL) {
const key = Validation_1.Validation.key(constants_1.ValidationKeys.EMAIL);
const meta = {
[constants_1.ValidationKeys.PATTERN]: constants_1.DEFAULT_PATTERNS.EMAIL.toString(),
message: message,
types: [String.name],
description: "marks the attribute as an email",
async: false,
};
return Decoration_1.Decoration.for(key)
.define({
decorator: (validationMetadata),
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
*/
function url(message = constants_1.DEFAULT_ERROR_MESSAGES.URL) {
const key = Validation_1.Validation.key(constants_1.ValidationKeys.URL);
const meta = {
[constants_1.ValidationKeys.PATTERN]: constants_1.DEFAULT_PATTERNS.URL.toString(),
message: message,
types: [String.name],
description: "marks the attribute as an url",
async: false,
};
return Decoration_1.Decoration.for(key)
.define({
decorator: (validationMetadata),
args: [url, key, meta],
})
.apply();
}
/**
* @summary Enforces type verification
* @description Validators to validate a decorated property must use key {@link ValidationKeys#TYPE}
*
* @param {string[] | string} types accepted types
* @param {string} [message] the error message. Defaults to {@link DEFAULT_ERROR_MESSAGES#TYPE}
*
* @function type
* @category Property Decorators
*/
function type(types, message = constants_1.DEFAULT_ERROR_MESSAGES.TYPE) {
const key = Validation_1.Validation.key(constants_1.ValidationKeys.TYPE);
const meta = {
customTypes: types,
message: message,
description: "defines the accepted types for the attribute",
async: false,
};
return Decoration_1.Decoration.for(key)
.define({
decorator: (validationMetadata),
args: [type, key, meta],
})
.apply();
}
/**
* @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
*/
function date(format = "dd/MM/yyyy", message = constants_1.DEFAULT_ERROR_MESSAGES.DATE) {
const key = Validation_1.Validation.key(constants_1.ValidationKeys.DATE);
function dateDec(format, message) {
const meta = {
[constants_1.ValidationKeys.FORMAT]: format,
message: message,
types: [Date.name],
description: `defines the attribute as a date with the format ${format}`,
async: false,
};
return function dateDec(target, propertyKey) {
const values = new WeakMap();
Object.defineProperty(target, propertyKey, {
configurable: false,
set(newValue) {
const descriptor = Object.getOwnPropertyDescriptor(this, propertyKey);
if (!descriptor || descriptor.configurable)
Object.defineProperty(this, propertyKey, {
enumerable: true,
configurable: false,
get: () => values.get(this),
set: (newValue) => {
let val;
try {
val = (0, dates_1.parseDate)(format, newValue);
values.set(this, val);
}
catch (e) {
console.error((0, strings_1.sf)("Failed to parse date: {0}", e.message || e));
}
},
});
this[propertyKey] = newValue;
},
get() {
return values.get(this);
},
});
return validationMetadata(date, key, meta)(target, propertyKey);
};
}
return Decoration_1.Decoration.for(key)
.define({
decorator: dateDec,
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
*/
function password(pattern = constants_1.DEFAULT_PATTERNS.PASSWORD.CHAR8_ONE_OF_EACH, message = constants_1.DEFAULT_ERROR_MESSAGES.PASSWORD) {
const key = Validation_1.Validation.key(constants_1.ValidationKeys.PASSWORD);
const meta = {
[constants_1.ValidationKeys.PATTERN]: pattern.toString(),
message: message,
types: [String.name],
description: `attribute as a password`,
async: false,
};
return Decoration_1.Decoration.for(key)
.define({
decorator: validationMetadata,
args: [password, key, meta],
})
.apply();
}
/**
* @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
*/
function list(clazz, collection = "Array", message = constants_1.DEFAULT_ERROR_MESSAGES.LIST) {
const key = Validation_1.Validation.key(constants_1.ValidationKeys.LIST);
const meta = {
clazz: (Array.isArray(clazz)
? clazz.map((c) => (c.name ? c.name : c))
: [clazz.name ? clazz.name : clazz]),
type: collection,
message: message,
async: false,
description: `defines the attribute as a ${collection} of ${clazz.name}`,
};
return Decoration_1.Decoration.for(key)
.define({
decorator: validationMetadata,
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
*/
function set(clazz, message = constants_1.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
*/
function eq(propertyToCompare, options
// message: string = DEFAULT_ERROR_MESSAGES.EQUALS
) {
const equalsOptions = {
label: options?.label || propertyToCompare,
message: options?.message || constants_1.DEFAULT_ERROR_MESSAGES.EQUALS,
[constants_1.ValidationKeys.EQUALS]: propertyToCompare,
description: `defines attribute as equal to ${propertyToCompare}`,
};
return validationMetadata(eq, Validation_1.Validation.key(constants_1.ValidationKeys.EQUALS), { ...equalsOptions, async: false });
}
/**
* @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
*/
function diff(propertyToCompare, options) {
const diffOptions = {
label: options?.label || propertyToCompare,
message: options?.message || constants_1.DEFAULT_ERROR_MESSAGES.DIFF,
[constants_1.ValidationKeys.DIFF]: propertyToCompare,
description: `defines attribute as different to ${propertyToCompare}`,
};
return validationMetadata(diff, Validation_1.Validation.key(constants_1.ValidationKeys.DIFF), {
...diffOptions,
async: false,
});
}
/**
* @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
*/
function lt(propertyToCompare, options) {
const ltOptions = {
label: options?.label || propertyToCompare,
message: options?.message || constants_1.DEFAULT_ERROR_MESSAGES.LESS_THAN,
[constants_1.ValidationKeys.LESS_THAN]: propertyToCompare,
description: `defines attribute as less than to ${propertyToCompare}`,
};
return validationMetadata(lt, Validation_1.Validation.key(constants_1.ValidationKeys.LESS_THAN), { ...ltOptions, async: false });
}
/**
* @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
*/
function lte(propertyToCompare, options) {
const lteOptions = {
label: options?.label || propertyToCompare,
message: options?.message || constants_1.DEFAULT_ERROR_MESSAGES.LESS_THAN_OR_EQUAL,
[constants_1.ValidationKeys.LESS_THAN_OR_EQUAL]: propertyToCompare,
description: `defines attribute as less or equal to ${propertyToCompare}`,
};
return validationMetadata(lte, Validation_1.Validation.key(constants_1.ValidationKeys.LESS_THAN_OR_EQUAL), { ...lteOptions, async: false });
}
/**
* @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
*/
function gt(propertyToCompare, options) {
const gtOptions = {
label: options?.label || propertyToCompare,
message: options?.message || constants_1.DEFAULT_ERROR_MESSAGES.GREATER_THAN,
[constants_1.ValidationKeys.GREATER_THAN]: propertyToCompare,
description: `defines attribute as greater than ${propertyToCompare}`,
};
return validationMetadata(gt, Validation_1.Validation.key(constants_1.ValidationKeys.GREATER_THAN), { ...gtOptions, async: false });
}
/**
* @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
*/
function gte(propertyToCompare, options) {
const gteOptions = {
label: options?.label || propertyToCompare,
message: options?.message || constants_1.DEFAULT_ERROR_MESSAGES.GREATER_THAN_OR_EQUAL,
[constants_1.ValidationKeys.GREATER_THAN_OR_EQUAL]: propertyToCompare,
description: `defines attribute as greater or equal to ${propertyToCompare}`,
};
return validationMetadata(gte, Validation_1.Validation.key(constants_1.ValidationKeys.GREATER_THAN_OR_EQUAL), { ...gteOptions, async: false });
}
//# sourceMappingURL=decorators.js.map