From 73d09f043bbcafa8d315796021d110c1aa8a3ab1 Mon Sep 17 00:00:00 2001 From: Francis Pion Date: Sun, 1 Jun 2025 18:59:21 -0400 Subject: [PATCH] Fixed string rules. --- CHANGELOG.md | 4 +++- src/rules/__tests__/allowedCharacters.spec.ts | 6 ++++++ src/rules/__tests__/containsDigits.spec.ts | 8 +++++++- src/rules/__tests__/containsLowercase.spec.ts | 8 +++++++- .../__tests__/containsNonAlphanumeric.spec.ts | 8 +++++++- src/rules/__tests__/containsUppercase.spec.ts | 8 +++++++- src/rules/__tests__/email.spec.ts | 10 ++++++++-- src/rules/__tests__/identifier.spec.ts | 12 ++++++------ src/rules/__tests__/maximumLength.spec.ts | 12 ++++++++++++ src/rules/__tests__/minimumLength.spec.ts | 14 +++++++++++++- src/rules/__tests__/pattern.spec.ts | 8 +++++++- src/rules/__tests__/slug.spec.ts | 6 ++++++ src/rules/__tests__/uniqueCharacters.spec.ts | 8 +++++++- src/rules/__tests__/url.spec.ts | 13 ++++++++++--- src/rules/containsDigits.ts | 14 +++++++------- src/rules/containsLowercase.ts | 14 +++++++------- src/rules/containsNonAlphanumeric.ts | 14 +++++++------- src/rules/containsUppercase.ts | 14 +++++++------- src/rules/identifier.ts | 12 ++++++------ src/rules/minimumLength.ts | 4 ++-- src/rules/pattern.ts | 13 +++++++++---- src/rules/slug.ts | 6 ++++-- src/rules/uniqueCharacters.ts | 14 +++++++------- 23 files changed, 162 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24eb4af..fdd0d85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -Nothing yet. +### Fixed + +- Empty strings are now valid in every rule validating strings, except `required`. This is technically a _breaking change_, but is not _change_ per say because it should have been done since the beginning. ## [1.0.2] - 2025-04-28 diff --git a/src/rules/__tests__/allowedCharacters.spec.ts b/src/rules/__tests__/allowedCharacters.spec.ts index 52f25c4..1e9cacb 100644 --- a/src/rules/__tests__/allowedCharacters.spec.ts +++ b/src/rules/__tests__/allowedCharacters.spec.ts @@ -24,6 +24,12 @@ describe("allowedCharacters", () => { expect(outcome.message).toBe("{{name}} contains the following prohibited characters: !. Only the following characters are allowed: {{allowedCharacters}}"); }); + it.concurrent("should return value when then value is an empty string", () => { + const outcome = rule("", allowedCharacters) as RuleExecutionOutcome; + expect(outcome.severity).toBe("information"); + expect(outcome.message).toBeUndefined(); + }); + it.concurrent("should return valid when the value only contains allowed characters", () => { const outcome = rule("valid", allowedCharacters) as RuleExecutionOutcome; expect(outcome.severity).toBe("information"); diff --git a/src/rules/__tests__/containsDigits.spec.ts b/src/rules/__tests__/containsDigits.spec.ts index c76e036..4d2db26 100644 --- a/src/rules/__tests__/containsDigits.spec.ts +++ b/src/rules/__tests__/containsDigits.spec.ts @@ -5,7 +5,7 @@ import { RuleExecutionOutcome } from "../../types"; describe("containsDigits", () => { test.each([undefined, null, {}, [], true, 0, 0n])("should return invalid when the value is not a string", (value) => { - const outcome = containsDigits(value) as RuleExecutionOutcome; + const outcome = containsDigits(value, 10) as RuleExecutionOutcome; expect(outcome.severity).toBe("error"); expect(outcome.message).toBe("{{name}} must be a string."); }); @@ -36,4 +36,10 @@ describe("containsDigits", () => { expect(outcome.severity).toBe("information"); expect(outcome.message).toBeUndefined(); }); + + it.concurrent("should return valid when the value is an empty string", () => { + const outcome = containsDigits("", 10) as RuleExecutionOutcome; + expect(outcome.severity).toBe("information"); + expect(outcome.message).toBeUndefined(); + }); }); diff --git a/src/rules/__tests__/containsLowercase.spec.ts b/src/rules/__tests__/containsLowercase.spec.ts index b430b67..f080406 100644 --- a/src/rules/__tests__/containsLowercase.spec.ts +++ b/src/rules/__tests__/containsLowercase.spec.ts @@ -5,7 +5,7 @@ import { RuleExecutionOutcome } from "../../types"; describe("containsLowercase", () => { test.each([undefined, null, {}, [], true, 0, 0n])("should return invalid when the value is not a string", (value) => { - const outcome = containsLowercase(value) as RuleExecutionOutcome; + const outcome = containsLowercase(value, 10) as RuleExecutionOutcome; expect(outcome.severity).toBe("error"); expect(outcome.message).toBe("{{name}} must be a string."); }); @@ -36,4 +36,10 @@ describe("containsLowercase", () => { expect(outcome.severity).toBe("information"); expect(outcome.message).toBeUndefined(); }); + + it.concurrent("should return valid when the value is an empty string", () => { + const outcome = containsLowercase("", 10) as RuleExecutionOutcome; + expect(outcome.severity).toBe("information"); + expect(outcome.message).toBeUndefined(); + }); }); diff --git a/src/rules/__tests__/containsNonAlphanumeric.spec.ts b/src/rules/__tests__/containsNonAlphanumeric.spec.ts index 8ee34ef..758b409 100644 --- a/src/rules/__tests__/containsNonAlphanumeric.spec.ts +++ b/src/rules/__tests__/containsNonAlphanumeric.spec.ts @@ -5,7 +5,7 @@ import { RuleExecutionOutcome } from "../../types"; describe("containsNonAlphanumerics", () => { test.each([undefined, null, {}, [], true, 0, 0n])("should return invalid when the value is not a string", (value) => { - const outcome = containsNonAlphanumeric(value) as RuleExecutionOutcome; + const outcome = containsNonAlphanumeric(value, 10) as RuleExecutionOutcome; expect(outcome.severity).toBe("error"); expect(outcome.message).toBe("{{name}} must be a string."); }); @@ -36,4 +36,10 @@ describe("containsNonAlphanumerics", () => { expect(outcome.severity).toBe("information"); expect(outcome.message).toBeUndefined(); }); + + it.concurrent("should return valid when the value is an empty string", () => { + const outcome = containsNonAlphanumeric("", 10) as RuleExecutionOutcome; + expect(outcome.severity).toBe("information"); + expect(outcome.message).toBeUndefined(); + }); }); diff --git a/src/rules/__tests__/containsUppercase.spec.ts b/src/rules/__tests__/containsUppercase.spec.ts index caf1434..9d0221c 100644 --- a/src/rules/__tests__/containsUppercase.spec.ts +++ b/src/rules/__tests__/containsUppercase.spec.ts @@ -5,7 +5,7 @@ import { RuleExecutionOutcome } from "../../types"; describe("containsUppercase", () => { test.each([undefined, null, {}, [], true, 0, 0n])("should return invalid when the value is not a string", (value) => { - const outcome = containsUppercase(value) as RuleExecutionOutcome; + const outcome = containsUppercase(value, 10) as RuleExecutionOutcome; expect(outcome.severity).toBe("error"); expect(outcome.message).toBe("{{name}} must be a string."); }); @@ -36,4 +36,10 @@ describe("containsUppercase", () => { expect(outcome.severity).toBe("information"); expect(outcome.message).toBeUndefined(); }); + + it.concurrent("should return valid when the value is an empty string", () => { + const outcome = containsUppercase("", 10) as RuleExecutionOutcome; + expect(outcome.severity).toBe("information"); + expect(outcome.message).toBeUndefined(); + }); }); diff --git a/src/rules/__tests__/email.spec.ts b/src/rules/__tests__/email.spec.ts index 4a07779..cc81c0d 100644 --- a/src/rules/__tests__/email.spec.ts +++ b/src/rules/__tests__/email.spec.ts @@ -22,8 +22,14 @@ describe("email", () => { expect(outcome.message).toBe("{{name}} must be a valid email address."); }); - test.each(["", "test@example.com"])("should return valid when the value is a valid email address", (value) => { - const outcome = email(value) as RuleExecutionOutcome; + it.concurrent("should return valid when the value is a valid email address", () => { + const outcome = email("test@example.com") as RuleExecutionOutcome; + expect(outcome.severity).toBe("information"); + expect(outcome.message).toBeUndefined(); + }); + + it.concurrent("should return valid when the value is an empty string", () => { + const outcome = email("") as RuleExecutionOutcome; expect(outcome.severity).toBe("information"); expect(outcome.message).toBeUndefined(); }); diff --git a/src/rules/__tests__/identifier.spec.ts b/src/rules/__tests__/identifier.spec.ts index fb6a763..9455f40 100644 --- a/src/rules/__tests__/identifier.spec.ts +++ b/src/rules/__tests__/identifier.spec.ts @@ -10,12 +10,6 @@ describe("identifier", () => { expect(outcome.message).toBe("{{name}} must be a string."); }); - it.concurrent("should return invalid when the value is an empty string", () => { - const outcome = identifier("") as RuleExecutionOutcome; - expect(outcome.severity).toBe("error"); - expect(outcome.message).toBe("{{name}} cannot be an empty string."); - }); - it.concurrent("should return invalid when the value starts with a digit", () => { const outcome = identifier("123") as RuleExecutionOutcome; expect(outcome.severity).toBe("error"); @@ -33,4 +27,10 @@ describe("identifier", () => { expect(outcome.severity).toBe("information"); expect(outcome.message).toBeUndefined(); }); + + it.concurrent("should return valid when the value is an empty string", () => { + const outcome = identifier("") as RuleExecutionOutcome; + expect(outcome.severity).toBe("information"); + expect(outcome.message).toBeUndefined(); + }); }); diff --git a/src/rules/__tests__/maximumLength.spec.ts b/src/rules/__tests__/maximumLength.spec.ts index c4e22bd..8f9c0a0 100644 --- a/src/rules/__tests__/maximumLength.spec.ts +++ b/src/rules/__tests__/maximumLength.spec.ts @@ -39,4 +39,16 @@ describe("maximumLength", () => { expect(outcome.severity).toBe("information"); expect(outcome.message).toBeUndefined(); }); + + it.concurrent("should return valid when the value is an empty array", () => { + const outcome = maximumLength([], 10) as RuleExecutionOutcome; + expect(outcome.severity).toBe("information"); + expect(outcome.message).toBeUndefined(); + }); + + it.concurrent("should return valid when the value is an empty string", () => { + const outcome = maximumLength("", 10) as RuleExecutionOutcome; + expect(outcome.severity).toBe("information"); + expect(outcome.message).toBeUndefined(); + }); }); diff --git a/src/rules/__tests__/minimumLength.spec.ts b/src/rules/__tests__/minimumLength.spec.ts index ad540db..b4ef7de 100644 --- a/src/rules/__tests__/minimumLength.spec.ts +++ b/src/rules/__tests__/minimumLength.spec.ts @@ -11,7 +11,7 @@ describe("minimumLength", () => { }); it.concurrent("should return invalid when the value is a string that is too short", () => { - const outcome = minimumLength("", true) as RuleExecutionOutcome; + const outcome = minimumLength(" ", 10) as RuleExecutionOutcome; expect(outcome.severity).toBe("error"); expect(outcome.message).toBe("{{name}} must be at least {{minimumLength}} character(s) long."); }); @@ -39,4 +39,16 @@ describe("minimumLength", () => { expect(outcome.severity).toBe("information"); expect(outcome.message).toBeUndefined(); }); + + it.concurrent("should return valid when the value is an empty array", () => { + const outcome = minimumLength([], 10) as RuleExecutionOutcome; + expect(outcome.severity).toBe("information"); + expect(outcome.message).toBeUndefined(); + }); + + it.concurrent("should return valid when the value is an empty string", () => { + const outcome = minimumLength("", 10) as RuleExecutionOutcome; + expect(outcome.severity).toBe("information"); + expect(outcome.message).toBeUndefined(); + }); }); diff --git a/src/rules/__tests__/pattern.spec.ts b/src/rules/__tests__/pattern.spec.ts index 50a6b4b..194c893 100644 --- a/src/rules/__tests__/pattern.spec.ts +++ b/src/rules/__tests__/pattern.spec.ts @@ -7,7 +7,7 @@ const pattern = /^[ABCEGHJ-NPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][ -]?\d[ABCEGHJ-NPRSTV-Z describe("pattern", () => { test.each([undefined, null, {}, [], true, 0, 0n])("should return invalid when the value is not a string", (value) => { - const outcome = rule(value) as RuleExecutionOutcome; + const outcome = rule(value, pattern) as RuleExecutionOutcome; expect(outcome.severity).toBe("error"); expect(outcome.message).toBe("{{name}} must be a string."); }); @@ -24,6 +24,12 @@ describe("pattern", () => { expect(outcome.message).toBe("{{name}} must match the pattern {{pattern}}."); }); + it.concurrent("should return valid when the value is an empty string", () => { + const outcome = rule("", pattern) as RuleExecutionOutcome; + expect(outcome.severity).toBe("information"); + expect(outcome.message).toBeUndefined(); + }); + test.each(["H2X3Y2", "H2X 3Y2", "H2X-3Y2"])("should return valid when the value matches the pattern", (value) => { const outcome = rule(value, pattern) as RuleExecutionOutcome; expect(outcome.severity).toBe("information"); diff --git a/src/rules/__tests__/slug.spec.ts b/src/rules/__tests__/slug.spec.ts index 4e31407..c61c9c2 100644 --- a/src/rules/__tests__/slug.spec.ts +++ b/src/rules/__tests__/slug.spec.ts @@ -27,4 +27,10 @@ describe("slug", () => { expect(outcome.severity).toBe("information"); expect(outcome.message).toBeUndefined(); }); + + it.concurrent("should return valid when the value is an empty string", () => { + const outcome = slug("") as RuleExecutionOutcome; + expect(outcome.severity).toBe("information"); + expect(outcome.message).toBeUndefined(); + }); }); diff --git a/src/rules/__tests__/uniqueCharacters.spec.ts b/src/rules/__tests__/uniqueCharacters.spec.ts index 927c2c1..fb9a3e6 100644 --- a/src/rules/__tests__/uniqueCharacters.spec.ts +++ b/src/rules/__tests__/uniqueCharacters.spec.ts @@ -5,7 +5,7 @@ import { RuleExecutionOutcome } from "../../types"; describe("uniqueCharacters", () => { test.each([undefined, null, {}, [], true, 0, 0n])("should return invalid when the value is not a string", (value) => { - const outcome = uniqueCharacters(value) as RuleExecutionOutcome; + const outcome = uniqueCharacters(value, 10) as RuleExecutionOutcome; expect(outcome.severity).toBe("error"); expect(outcome.message).toBe("{{name}} must be a string."); }); @@ -27,4 +27,10 @@ describe("uniqueCharacters", () => { expect(outcome.severity).toBe("information"); expect(outcome.message).toBeUndefined(); }); + + it.concurrent("should return valid when the value is an empty string", () => { + const outcome = uniqueCharacters("", 10) as RuleExecutionOutcome; + expect(outcome.severity).toBe("information"); + expect(outcome.message).toBeUndefined(); + }); }); diff --git a/src/rules/__tests__/url.spec.ts b/src/rules/__tests__/url.spec.ts index f90c3c0..e8fba69 100644 --- a/src/rules/__tests__/url.spec.ts +++ b/src/rules/__tests__/url.spec.ts @@ -31,8 +31,6 @@ describe("url", () => { }); test.each([ - ["", undefined], - ["", false], ["http://example.com", undefined], ["http://example.com", true], ["http://example.com", "http,https"], @@ -45,5 +43,14 @@ describe("url", () => { expect(outcome.message).toBeUndefined(); }); - test.each([])("should return valid when the value is a valid URL with an allowed protocol", (value) => {}); + test.each([])("should return valid when the value is a valid URL with an allowed protocol", () => {}); + + test.each([ + ["", undefined], + ["", false], + ])("should return valid when the value is an empty string", (value, args) => { + const outcome = url(value, args) as RuleExecutionOutcome; + expect(outcome.severity).toBe("information"); + expect(outcome.message).toBeUndefined(); + }); }); diff --git a/src/rules/containsDigits.ts b/src/rules/containsDigits.ts index f9f71c1..e430ce4 100644 --- a/src/rules/containsDigits.ts +++ b/src/rules/containsDigits.ts @@ -11,18 +11,18 @@ const { isDigit } = stringUtils; * @returns The result of the validation rule execution. */ const containsDigits: ValidationRule = (value: unknown, args: unknown): RuleExecutionOutcome => { - if (typeof value !== "string") { - return { severity: "error", message: "{{name}} must be a string." }; - } - const requiredDigits: number = Number(args); if (isNaN(requiredDigits) || requiredDigits <= 0) { return { severity: "warning", message: "The arguments should be a positive number." }; } - const digits: number = [...value].filter(isDigit).length; - if (digits < requiredDigits) { - return { severity: "error", message: "{{name}} must contain at least {{containsDigits}} digit(s)." }; + if (typeof value !== "string") { + return { severity: "error", message: "{{name}} must be a string." }; + } else if (value.length > 0) { + const digits: number = [...value].filter(isDigit).length; + if (digits < requiredDigits) { + return { severity: "error", message: "{{name}} must contain at least {{containsDigits}} digit(s)." }; + } } return { severity: "information" }; diff --git a/src/rules/containsLowercase.ts b/src/rules/containsLowercase.ts index da9d7ef..394d3a1 100644 --- a/src/rules/containsLowercase.ts +++ b/src/rules/containsLowercase.ts @@ -11,18 +11,18 @@ const { isLetter } = stringUtils; * @returns The result of the validation rule execution. */ const containsLowercase: ValidationRule = (value: unknown, args: unknown): RuleExecutionOutcome => { - if (typeof value !== "string") { - return { severity: "error", message: "{{name}} must be a string." }; - } - const requiredLowercase: number = Number(args); if (isNaN(requiredLowercase) || requiredLowercase <= 0) { return { severity: "warning", message: "The arguments should be a positive number." }; } - const lowercase: number = [...value].filter((c) => isLetter(c) && c.toLowerCase() === c).length; - if (lowercase < requiredLowercase) { - return { severity: "error", message: "{{name}} must contain at least {{containsLowercase}} lowercase letter(s)." }; + if (typeof value !== "string") { + return { severity: "error", message: "{{name}} must be a string." }; + } else if (value.length > 0) { + const lowercase: number = [...value].filter((c) => isLetter(c) && c.toLowerCase() === c).length; + if (lowercase < requiredLowercase) { + return { severity: "error", message: "{{name}} must contain at least {{containsLowercase}} lowercase letter(s)." }; + } } return { severity: "information" }; diff --git a/src/rules/containsNonAlphanumeric.ts b/src/rules/containsNonAlphanumeric.ts index 2e720e4..1a92912 100644 --- a/src/rules/containsNonAlphanumeric.ts +++ b/src/rules/containsNonAlphanumeric.ts @@ -11,18 +11,18 @@ const { isLetterOrDigit } = stringUtils; * @returns The result of the validation rule execution. */ const containsNonAlphanumeric: ValidationRule = (value: unknown, args: unknown): RuleExecutionOutcome => { - if (typeof value !== "string") { - return { severity: "error", message: "{{name}} must be a string." }; - } - const requiredNonAlphanumeric: number = Number(args); if (isNaN(requiredNonAlphanumeric) || requiredNonAlphanumeric <= 0) { return { severity: "warning", message: "The arguments should be a positive number." }; } - const nonAlphanumeric: number = [...value].filter((c) => !isLetterOrDigit(c)).length; - if (nonAlphanumeric < requiredNonAlphanumeric) { - return { severity: "error", message: "{{name}} must contain at least {{containsNonAlphanumeric}} non-alphanumeric character(s)." }; + if (typeof value !== "string") { + return { severity: "error", message: "{{name}} must be a string." }; + } else if (value.length > 0) { + const nonAlphanumeric: number = [...value].filter((c) => !isLetterOrDigit(c)).length; + if (nonAlphanumeric < requiredNonAlphanumeric) { + return { severity: "error", message: "{{name}} must contain at least {{containsNonAlphanumeric}} non-alphanumeric character(s)." }; + } } return { severity: "information" }; diff --git a/src/rules/containsUppercase.ts b/src/rules/containsUppercase.ts index 6a8a89f..926d53a 100644 --- a/src/rules/containsUppercase.ts +++ b/src/rules/containsUppercase.ts @@ -11,18 +11,18 @@ const { isLetter } = stringUtils; * @returns The result of the validation rule execution. */ const containsUppercase: ValidationRule = (value: unknown, args: unknown): RuleExecutionOutcome => { - if (typeof value !== "string") { - return { severity: "error", message: "{{name}} must be a string." }; - } - const requiredUppercase: number = Number(args); if (isNaN(requiredUppercase) || requiredUppercase <= 0) { return { severity: "warning", message: "The arguments should be a positive number." }; } - const uppercase: number = [...value].filter((c) => isLetter(c) && c.toUpperCase() === c).length; - if (uppercase < requiredUppercase) { - return { severity: "error", message: "{{name}} must contain at least {{containsUppercase}} uppercase letter(s)." }; + if (typeof value !== "string") { + return { severity: "error", message: "{{name}} must be a string." }; + } else if (value.length > 0) { + const uppercase: number = [...value].filter((c) => isLetter(c) && c.toUpperCase() === c).length; + if (uppercase < requiredUppercase) { + return { severity: "error", message: "{{name}} must contain at least {{containsUppercase}} uppercase letter(s)." }; + } } return { severity: "information" }; diff --git a/src/rules/identifier.ts b/src/rules/identifier.ts index cd7bec8..dcccd20 100644 --- a/src/rules/identifier.ts +++ b/src/rules/identifier.ts @@ -12,12 +12,12 @@ const { isDigit, isLetterOrDigit, isNullOrEmpty } = stringUtils; const identifier: ValidationRule = (value: unknown): RuleExecutionOutcome => { if (typeof value !== "string") { return { severity: "error", message: "{{name}} must be a string." }; - } else if (isNullOrEmpty(value)) { - return { severity: "error", message: "{{name}} cannot be an empty string." }; - } else if (isDigit(value[0])) { - return { severity: "error", message: "{{name}} cannot start with a digit." }; - } else if ([...value].some((c) => !isLetterOrDigit(c) && c !== "_")) { - return { severity: "error", message: "{{name}} may only contain letters, digits and underscores (_)." }; + } else if (value.length > 0) { + if (isDigit(value[0])) { + return { severity: "error", message: "{{name}} cannot start with a digit." }; + } else if ([...value].some((c) => !isLetterOrDigit(c) && c !== "_")) { + return { severity: "error", message: "{{name}} may only contain letters, digits and underscores (_)." }; + } } return { severity: "information" }; }; diff --git a/src/rules/minimumLength.ts b/src/rules/minimumLength.ts index a356b88..a451d32 100644 --- a/src/rules/minimumLength.ts +++ b/src/rules/minimumLength.ts @@ -13,11 +13,11 @@ const minimumLength: ValidationRule = (value: unknown, args: unknown): RuleExecu } if (typeof value === "string") { - if (value.length < minimumLength) { + if (value.length > 0 && value.length < minimumLength) { return { severity: "error", message: "{{name}} must be at least {{minimumLength}} character(s) long." }; } } else if (Array.isArray(value)) { - if (value.length < minimumLength) { + if (value.length > 0 && value.length < minimumLength) { return { severity: "error", message: "{{name}} must contain at least {{minimumLength}} element(s)." }; } } else { diff --git a/src/rules/pattern.ts b/src/rules/pattern.ts index 6390b5f..f18d04d 100644 --- a/src/rules/pattern.ts +++ b/src/rules/pattern.ts @@ -7,13 +7,18 @@ import type { RuleExecutionOutcome, ValidationRule } from "../types"; * @returns The result of the validation rule execution. */ const pattern: ValidationRule = (value: unknown, args: unknown): RuleExecutionOutcome => { + if (typeof args !== "string" && !(args instanceof RegExp)) { + return { severity: "warning", message: "The arguments should be a regular expression." }; + } + if (typeof value !== "string") { return { severity: "error", message: "{{name}} must be a string." }; - } else if (typeof args !== "string" && !(args instanceof RegExp)) { - return { severity: "warning", message: "The arguments should be a regular expression." }; - } else if (!new RegExp(args).test(value)) { - return { severity: "error", message: "{{name}} must match the pattern {{pattern}}." }; + } else if (value.length > 0) { + if (!new RegExp(args).test(value)) { + return { severity: "error", message: "{{name}} must match the pattern {{pattern}}." }; + } } + return { severity: "information" }; }; diff --git a/src/rules/slug.ts b/src/rules/slug.ts index 83c5ae5..0d6bdd5 100644 --- a/src/rules/slug.ts +++ b/src/rules/slug.ts @@ -12,8 +12,10 @@ const { isLetterOrDigit, isNullOrEmpty } = stringUtils; const slug: ValidationRule = (value: unknown): RuleExecutionOutcome => { if (typeof value !== "string") { return { severity: "error", message: "{{name}} must be a string." }; - } else if (value.split("-").some((word) => isNullOrEmpty(word) || [...word].some((c) => !isLetterOrDigit(c)))) { - return { severity: "error", message: "{{name}} must be composed of non-empty alphanumeric words separated by hyphens (-)." }; + } else if (value.length > 0) { + if (value.split("-").some((word) => isNullOrEmpty(word) || [...word].some((c) => !isLetterOrDigit(c)))) { + return { severity: "error", message: "{{name}} must be composed of non-empty alphanumeric words separated by hyphens (-)." }; + } } return { severity: "information" }; }; diff --git a/src/rules/uniqueCharacters.ts b/src/rules/uniqueCharacters.ts index bc1cff9..ed5e388 100644 --- a/src/rules/uniqueCharacters.ts +++ b/src/rules/uniqueCharacters.ts @@ -7,18 +7,18 @@ import type { RuleExecutionOutcome, ValidationRule } from "../types"; * @returns The result of the validation rule execution. */ const uniqueCharacters: ValidationRule = (value: unknown, args: unknown): RuleExecutionOutcome => { - if (typeof value !== "string") { - return { severity: "error", message: "{{name}} must be a string." }; - } - const uniqueCharacters: number = Number(args); if (isNaN(uniqueCharacters) || uniqueCharacters <= 0) { return { severity: "warning", message: "The arguments should be a positive number." }; } - const count: number = [...new Set(value)].length; - if (count < uniqueCharacters) { - return { severity: "error", message: "{{name}} must contain at least {{uniqueCharacters}} unique character(s)." }; + if (typeof value !== "string") { + return { severity: "error", message: "{{name}} must be a string." }; + } else if (value.length > 0) { + const count: number = [...new Set(value)].length; + if (count < uniqueCharacters) { + return { severity: "error", message: "{{name}} must contain at least {{uniqueCharacters}} unique character(s)." }; + } } return { severity: "information" };