Skip to content

Commit

Permalink
feat(validation): implement Contains decorator and validator
Browse files Browse the repository at this point in the history
- Added Contains decorator and validateContains function.
- Supported options: seed, ignoreCase, minOccurrences, message.
- Included examples and tests for the decorator and validator.

Provides dynamic substring checks for string properties.
Enhances validation flexibility with case insensitivity and custom error messages.

feat(validation): implement IsAlpha decorator and validator

- Added IsAlpha decorator and validateIsAlpha function.
- Supported options: locale, ignore, message.
- Included examples and tests for the decorator and validator.

Ensures string properties contain only alphabetic characters, supporting multiple locales.
Provides flexibility by allowing specific characters to be ignored during validation.
Enhances user feedback with custom error messages.
  • Loading branch information
SeanLuis committed Jun 30, 2024
1 parent 2ff01b0 commit 1779c45
Show file tree
Hide file tree
Showing 17 changed files with 740 additions and 0 deletions.
16 changes: 16 additions & 0 deletions src/decorators/AlphaValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import "reflect-metadata";
import { IAlphaValidationOptions } from '../interfaces';
import { addValidationMetadata } from "../metadata/AddValidationMetadata";
import { IValidationGroupOptions } from "../interfaces/IValidationGroupOptions";

/**
* Decorator function for validating if a string contains only letters.
* @param options - The options for alpha validation.
* @param groups - The groups options.
* @returns A decorator function that can be used to apply alpha validation to a property.
*/
export function Alpha(options: IAlphaValidationOptions, groups: IValidationGroupOptions = {}) {
return function(target: any, propertyName: string | symbol) {
addValidationMetadata(target, propertyName, { type: 'alpha', options, groups });
};
}
16 changes: 16 additions & 0 deletions src/decorators/ContainsValidator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import "reflect-metadata";
import { IContainsValidationOptions } from '../interfaces';
import { addValidationMetadata } from "../metadata/AddValidationMetadata";
import { IValidationGroupOptions } from "../interfaces";

/**
* Decorator function for validating if a string contains a specific seed.
* @param options - The options for contains validation.
* @param groups - The groups options.
* @returns A decorator function that can be used to apply contains validation to a property.
*/
export function Contains(options: IContainsValidationOptions, groups: IValidationGroupOptions = {}) {
return function(target: any, propertyName: string | symbol) {
addValidationMetadata(target, propertyName, { type: 'contains', options, groups });
};
}
2 changes: 2 additions & 0 deletions src/decorators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ export { Dependency } from "./DependencyValidator";
export { Accessors, Getter, Setter } from "./accessors";
export { Password } from "./PasswordValidator";
export { Security } from "./SecurityValidator";
export { Contains } from "./ContainsValidator";
export { Alpha } from "./AlphaValidator"
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
* @module Interfaces
*/
export {
IAlphaValidationOptions,
IArrayValidationOptions,
IChainValidationOptions,
IContainsValidationOptions,
IContextualValidationOptions,
ICustomValidationOptions,
IDateValidationOptions,
Expand Down Expand Up @@ -51,6 +53,8 @@ export {
validatePassword,
validateDependency,
validateSecurity,
validateContains,
validateAlpha
} from "./validators";

/**
Expand Down Expand Up @@ -79,6 +83,8 @@ export {
Setter,
Dependency,
Security,
Contains,
Alpha
} from "./decorators";

/**
Expand Down
13 changes: 13 additions & 0 deletions src/interfaces/IAlphaValidationOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { IValidationOptionsBase } from "./IValidationOptionsBase";

/**
* The IAlphaValidationOptions interface represents the options for validating if a string contains only letters.
*
* @interface
* @property {string} [locale='en-US'] - Optional: The locale to use for validation. Defaults to 'en-US'.
* @property {string | RegExp} [ignore] - Optional: Characters to ignore during validation.
*/
export interface IAlphaValidationOptions extends IValidationOptionsBase {
locale?: string;
ignore?: string | RegExp;
}
15 changes: 15 additions & 0 deletions src/interfaces/IContainsValidationOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IValidationOptionsBase } from "./IValidationOptionsBase";

/**
* The IContainsValidationOptions interface represents the options for validating if a string contains a specific seed.
*
* @interface
* @property {string} seed - The seed string to look for within the main string. This is a required property.
* @property {boolean} [ignoreCase=false] - Optional: Whether to ignore case when comparing. Defaults to false.
* @property {number} [minOccurrences=1] - Optional: The minimum number of occurrences of the seed in the string. Defaults to 1.
*/
export interface IContainsValidationOptions extends IValidationOptionsBase {
seed: string;
ignoreCase?: boolean;
minOccurrences?: number;
}
2 changes: 2 additions & 0 deletions src/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export { IAlphaValidationOptions } from './IAlphaValidationOptions';
export { IArrayValidationOptions } from './IArrayValidationOptions';
export { IValidationGroupOptions } from './IValidationGroupOptions';
export { IChainValidationOptions } from './IChainValidationOptions';
export { IContainsValidationOptions } from './IContainsValidationOptions';
export { IContextualValidationOptions } from './IContextualValidationOptions';
export { ICustomValidationOptions } from './ICustomValidationOptions';
export { IDateValidationOptions } from './IDateValidationOptions';
Expand Down
218 changes: 218 additions & 0 deletions src/utils/common/Alpha.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/**
* Regular expressions for alpha validation by locale.
* Taken from: https://github.com/validatorjs/validator.js/blob/master/src/lib/alpha.js
*/
export const alpha: { [key: string]: RegExp } = {
"en-US": /^[A-Z]+$/i,
"az-AZ": /^[A-VXYZÇƏĞİıÖŞÜ]+$/i,
"bg-BG": /^[А-Я]+$/i,
"cs-CZ": /^[A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]+$/i,
"da-DK": /^[A-ZÆØÅ]+$/i,
"de-DE": /^[A-ZÄÖÜß]+$/i,
"el-GR": /^[Α-ώ]+$/i,
"es-ES": /^[A-ZÁÉÍÑÓÚÜ]+$/i,
"fa-IR": /^[ابپتثجچحخدذرزژسشصضطظعغفقکگلمنوهی]+$/i,
"fi-FI": /^[A-ZÅÄÖ]+$/i,
"fr-FR": /^[A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]+$/i,
"it-IT": /^[A-ZÀÉÈÌÎÓÒÙ]+$/i,
"ja-JP": /^[ぁ-んァ-ヶヲ-゚一-龠ー・。、]+$/i,
"nb-NO": /^[A-ZÆØÅ]+$/i,
"nl-NL": /^[A-ZÁÉËÏÓÖÜÚ]+$/i,
"nn-NO": /^[A-ZÆØÅ]+$/i,
"hu-HU": /^[A-ZÁÉÍÓÖŐÚÜŰ]+$/i,
"pl-PL": /^[A-ZĄĆĘŚŁŃÓŻŹ]+$/i,
"pt-PT": /^[A-ZÃÁÀÂÄÇÉÊËÍÏÕÓÔÖÚÜ]+$/i,
"ru-RU": /^[А-ЯЁ]+$/i,
"kk-KZ": /^[А-ЯЁ\u04D8\u04B0\u0406\u04A2\u0492\u04AE\u049A\u04E8\u04BA]+$/i,
"sl-SI": /^[A-ZČĆĐŠŽ]+$/i,
"sk-SK": /^[A-ZÁČĎÉÍŇÓŠŤÚÝŽĹŔĽÄÔ]+$/i,
"sr-RS@latin": /^[A-ZČĆŽŠĐ]+$/i,
"sr-RS": /^[А-ЯЂЈЉЊЋЏ]+$/i,
"sv-SE": /^[A-ZÅÄÖ]+$/i,
"th-TH": /^[ก-๙\s]+$/i,
"tr-TR": /^[A-ZÇĞİıÖŞÜ]+$/i,
"uk-UA": /^[А-ЩЬЮЯЄIЇҐі]+$/i,
"ko-KR": /^[ㄱ-ㅎㅏ-ㅣ가-힣]*$/,
"ku-IQ": /^[ئابپتجچحخدرڕزژسشعغفڤقکگلڵمنوۆھەیێيطؤثآإأكضصةظذ]+$/i,
"vi-VN":
/^[A-ZÀÁẠẢÃÂẦẤẬẨẪĂẰẮẶẲẴĐÈÉẸẺẼÊỀẾỆỂỄÌÍỊỈĨÒÓỌỎÕÔỒỐỘỔỖƠỜỚỢỞỠÙÚỤỦŨƯỪỨỰỬỮỲÝỴỶỸ]+$/i,
ar: /^[ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/,
he: /^[א-ת]+$/,
fa: /^['آاءأؤئبپتثجچحخدذرزژسشصضطظعغفقکگلمنوهةی']+$/i,
bn: /^['ঀঁংঃঅআইঈউঊঋঌএঐওঔকখগঘঙচছজঝঞটঠডঢণতথদধনপফবভমযরলশষসহ়ঽািীুূৃৄেৈোৌ্ৎৗড়ঢ়য়ৠৡৢৣ০১২৩৪৫৬৭৮৯ৰৱ৲৳৴৵৶৷৸৹৺৻']+$/,
eo: /^[ABCĈD-GĜHĤIJĴK-PRSŜTUŬVZ]+$/i,
"hi-IN": /^[\u0900-\u0961]+[\u0972-\u097F]*$/i,
"si-LK": /^[\u0D80-\u0DFF]+$/,
};

/**
* Regular expressions for alphanumeric validation by locale.
* Taken from: https://github.com/validatorjs/validator.js/blob/master/src/lib/alpha.js
*/
export const alphanumeric: { [key: string]: RegExp } = {
"en-US": /^[0-9A-Z]+$/i,
"az-AZ": /^[0-9A-VXYZÇƏĞİıÖŞÜ]+$/i,
"bg-BG": /^[0-9А-Я]+$/i,
"cs-CZ": /^[0-9A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]+$/i,
"da-DK": /^[0-9A-ZÆØÅ]+$/i,
"de-DE": /^[0-9A-ZÄÖÜß]+$/i,
"el-GR": /^[0-9Α-ω]+$/i,
"es-ES": /^[0-9A-ZÁÉÍÑÓÚÜ]+$/i,
"fi-FI": /^[0-9A-ZÅÄÖ]+$/i,
"fr-FR": /^[0-9A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]+$/i,
"it-IT": /^[0-9A-ZÀÉÈÌÎÓÒÙ]+$/i,
"ja-JP": /^[0-90-9ぁ-んァ-ヶヲ-゚一-龠ー・。、]+$/i,
"hu-HU": /^[0-9A-ZÁÉÍÓÖŐÚÜŰ]+$/i,
"nb-NO": /^[0-9A-ZÆØÅ]+$/i,
"nl-NL": /^[0-9A-ZÁÉËÏÓÖÜÚ]+$/i,
"nn-NO": /^[0-9A-ZÆØÅ]+$/i,
"pl-PL": /^[0-9A-ZĄĆĘŚŁŃÓŻŹ]+$/i,
"pt-PT": /^[0-9A-ZÃÁÀÂÄÇÉÊËÍÏÕÓÔÖÚÜ]+$/i,
"ru-RU": /^[0-9А-ЯЁ]+$/i,
"kk-KZ":
/^[0-9А-ЯЁ\u04D8\u04B0\u0406\u04A2\u0492\u04AE\u049A\u04E8\u04BA]+$/i,
"sl-SI": /^[0-9A-ZČĆĐŠŽ]+$/i,
"sk-SK": /^[0-9A-ZÁČĎÉÍŇÓŠŤÚÝŽĹŔĽÄÔ]+$/i,
"sr-RS@latin": /^[0-9A-ZČĆŽŠĐ]+$/i,
"sr-RS": /^[0-9А-ЯЂЈЉЊЋЏ]+$/i,
"sv-SE": /^[0-9A-ZÅÄÖ]+$/i,
"th-TH": /^[ก-๙\s]+$/i,
"tr-TR": /^[0-9A-ZÇĞİıÖŞÜ]+$/i,
"uk-UA": /^[0-9А-ЩЬЮЯЄIЇҐі]+$/i,
"ko-KR": /^[0-9ㄱ-ㅎㅏ-ㅣ가-힣]*$/,
"ku-IQ": /^[٠١٢٣٤٥٦٧٨٩0-9ئابپتجچحخدرڕزژسشعغفڤقکگلڵمنوۆھەیێيطؤثآإأكضصةظذ]+$/i,
"vi-VN":
/^[0-9A-ZÀÁẠẢÃÂẦẤẬẨẪĂẰẮẶẲẴĐÈÉẸẺẼÊỀẾỆỂỄÌÍỊỈĨÒÓỌỎÕÔỒỐỘỔỖƠỜỚỢỞỠÙÚỤỦŨƯỪỨỰỬỮỲÝỴỶỸ]+$/i,
ar: /^[٠١٢٣٤٥٦٧٨٩0-9ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/,
he: /^[0-9א-ת]+$/,
fa: /^['0-9آاءأؤئبپتثجچحخدذرزژسشصضطظعغفقکگلمنوهةی۱۲۳۴۵۶۷۸۹۰']+$/i,
bn: /^['ঀঁংঃঅআইঈউঊঋঌএঐওঔকখগঘঙচছজঝঞটঠডঢণতথদধনপফবভমযরলশষসহ়ঽািীুূৃৄেৈোৌ্ৎৗড়ঢ়য়ৠৡৢৣ০১২৩৪৫৬৭৮৯ৰৱ৲৳৴৵৶৷৸৹৺৻']+$/,
eo: /^[0-9ABCĈD-GĜHĤIJĴK-PRSŜTUŬVZ]+$/i,
"hi-IN": /^[\u0900-\u0963]+[\u0966-\u097F]*$/i,
"si-LK": /^[0-9\u0D80-\u0DFF]+$/,
};

/**
* Decimal marks for different locales.
* Taken from: https://github.com/validatorjs/validator.js/blob/master/src/lib/alpha.js
*/
export const decimal: { [key: string]: string } = {
"en-US": ".",
ar: "٫",
};

/**
* Assigning English locales to alpha, alphanumeric, and decimal.
* Taken from: https://github.com/validatorjs/validator.js/blob/master/src/lib/alpha.js
*/
export const englishLocales = ["AU", "GB", "HK", "IN", "NZ", "ZA", "ZM"];

for (let i = 0; i < englishLocales.length; i++) {
const locale = `en-${englishLocales[i]}`;
alpha[locale] = alpha["en-US"];
alphanumeric[locale] = alphanumeric["en-US"];
decimal[locale] = decimal["en-US"];
}

// Source: http://www.localeplanet.com/java/
export const arabicLocales = [
"AE",
"BH",
"DZ",
"EG",
"IQ",
"JO",
"KW",
"LB",
"LY",
"MA",
"QM",
"QA",
"SA",
"SD",
"SY",
"TN",
"YE",
];

for (let i = 0; i < arabicLocales.length; i++) {
const locale = `ar-${arabicLocales[i]}`;
alpha[locale] = alpha.ar;
alphanumeric[locale] = alphanumeric.ar;
decimal[locale] = decimal.ar;
}

export const farsiLocales = ["IR", "AF"];

for (let i = 0; i < farsiLocales.length; i++) {
const locale = `fa-${farsiLocales[i]}`;
alphanumeric[locale] = alphanumeric.fa;
decimal[locale] = decimal.ar;
}

export const bengaliLocales = ["BD", "IN"];

for (let i = 0; i < bengaliLocales.length; i++) {
const locale = `bn-${bengaliLocales[i]}`;
alpha[locale] = alpha.bn;
alphanumeric[locale] = alphanumeric.bn;
decimal[locale] = decimal["en-US"];
}

// Source: https://en.wikipedia.org/wiki/Decimal_mark
export const dotDecimal = ["ar-EG", "ar-LB", "ar-LY"];
export const commaDecimal = [
"bg-BG",
"cs-CZ",
"da-DK",
"de-DE",
"el-GR",
"en-ZM",
"eo",
"es-ES",
"fr-CA",
"fr-FR",
"id-ID",
"it-IT",
"ku-IQ",
"hi-IN",
"hu-HU",
"nb-NO",
"nn-NO",
"nl-NL",
"pl-PL",
"pt-PT",
"ru-RU",
"kk-KZ",
"si-LK",
"sl-SI",
"sr-RS@latin",
"sr-RS",
"sv-SE",
"tr-TR",
"uk-UA",
"vi-VN",
];

for (let i = 0; i < dotDecimal.length; i++) {
decimal[dotDecimal[i]] = decimal["en-US"];
}

for (let i = 0; i < commaDecimal.length; i++) {
decimal[commaDecimal[i]] = ",";
}

alpha["fr-CA"] = alpha["fr-FR"];
alphanumeric["fr-CA"] = alphanumeric["fr-FR"];

alpha["pt-BR"] = alpha["pt-PT"];
alphanumeric["pt-BR"] = alphanumeric["pt-PT"];
decimal["pt-BR"] = decimal["pt-PT"];

// see #862
alpha["pl-Pl"] = alpha["pl-PL"];
alphanumeric["pl-Pl"] = alphanumeric["pl-PL"];
decimal["pl-Pl"] = decimal["pl-PL"];

// see #1455
alpha["fa-AF"] = alpha.fa;
21 changes: 21 additions & 0 deletions src/utils/common/AssertString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Asserts that the input is a string.
* Taken from: https://github.com/validatorjs/validator.js/blob/master/src/lib/util/assertString.js
*/
export const assertString = (input: any): void => {
type InvalidType = "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | "null";
let invalidType: InvalidType = typeof input;

if (input === null) {
invalidType = 'null';
} else if (invalidType === 'object') {
invalidType = input.constructor.name as InvalidType;
}

const isString = typeof input === 'string' || input instanceof String;

if (!isString) {
throw new TypeError(`Expected a string but received a ${invalidType}`);
}
};

8 changes: 8 additions & 0 deletions src/utils/validations/ValidationUtils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {IValidationGroupOptions, IValidationResult} from "../../interfaces";

import {
validateAlpha,
validateArray,
validateContains,
validateContextual,
validateCustom,
validateDate,
Expand Down Expand Up @@ -90,6 +92,12 @@ export class ValidationUtils {
case 'security':
result = validateSecurity(obj[propertyName], validation.options, validation.groups);
break;
case 'contains':
result = validateContains(obj[propertyName], validation.options, validation.groups);
break;
case 'alpha':
result = validateAlpha(obj[propertyName], validation.options, validation.groups);
break;
default:
result = {isValid: false, errors: [`Validation type '${validation.type}' is not supported.`]};
break;
Expand Down
Loading

0 comments on commit 1779c45

Please sign in to comment.