diff --git a/packages/fabric/core/utils/ensure-value.ts b/packages/fabric/core/utils/ensure.ts similarity index 76% rename from packages/fabric/core/utils/ensure-value.ts rename to packages/fabric/core/utils/ensure.ts index 9ad9f6b..a902e68 100644 --- a/packages/fabric/core/utils/ensure-value.ts +++ b/packages/fabric/core/utils/ensure.ts @@ -1,6 +1,6 @@ import { UnexpectedError } from "../error/unexpected-error.ts"; -export function ensureValue(value?: T): T { +export function ensure(value?: T): T { if (!value) { throw new UnexpectedError("Value is nullish."); } diff --git a/packages/fabric/core/utils/index.ts b/packages/fabric/core/utils/index.ts index edf865a..32f4fea 100644 --- a/packages/fabric/core/utils/index.ts +++ b/packages/fabric/core/utils/index.ts @@ -1,4 +1 @@ -export * from "./ensure-value.ts"; -export * from "./is-not-a-number.ts"; -export * from "./is-nullish.ts"; -export * from "./sanitize-string.ts"; +export * from "./ensure.ts"; diff --git a/packages/fabric/core/utils/is-not-a-number.test.ts b/packages/fabric/core/utils/is-not-a-number.test.ts deleted file mode 100644 index 6f7f7d9..0000000 --- a/packages/fabric/core/utils/is-not-a-number.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { describe, expect, test } from "@fabric/testing"; -import { isNotANumber } from "./is-not-a-number.ts"; - -describe("Is not a number", () => { - test("Given a number it should return false", () => { - expect(isNotANumber(1)).toBe(false); - }); - - test("Given a string it should return true", () => { - expect(isNotANumber("a")).toBe(true); - }); - - test("Given a string number it should return false", () => { - expect(isNotANumber("5")).toBe(false); - }); - - test("Given an empty string it should return true", () => { - expect(isNotANumber("")).toBe(true); - }); - - test("Given a boolean it should return true", () => { - expect(isNotANumber(true)).toBe(true); - }); - - test("Given an object it should return true", () => { - expect(isNotANumber({})).toBe(true); - }); - - test("Given an array it should return true", () => { - expect(isNotANumber([])).toBe(true); - }); - - test("Given a null it should return true", () => { - expect(isNotANumber(null)).toBe(true); - }); - - test("Given an undefined it should return true", () => { - expect(isNotANumber(undefined)).toBe(true); - }); -}); diff --git a/packages/fabric/core/utils/is-not-a-number.ts b/packages/fabric/core/utils/is-not-a-number.ts deleted file mode 100644 index 486368f..0000000 --- a/packages/fabric/core/utils/is-not-a-number.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { isNullish } from "./is-nullish.ts"; -import { parseAndSanitizeString } from "./sanitize-string.ts"; - -export function isNotANumber(value: unknown): boolean { - if (isNullish(value)) { - return true; - } - - if (typeof value === "string") { - const sanitized = parseAndSanitizeString(value); - if (sanitized === "") { - return true; - } - } - - if ( - typeof value === "boolean" || - typeof value === "object" || - Array.isArray(value) - ) { - return true; - } - - return isNaN(Number(value)); -} diff --git a/packages/fabric/core/utils/is-nullish.ts b/packages/fabric/core/utils/is-nullish.ts deleted file mode 100644 index dcdfc62..0000000 --- a/packages/fabric/core/utils/is-nullish.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function isNullish(value: unknown): value is null | undefined { - return isNull(value) || isUndefined(value); -} - -export function isUndefined(value: unknown): value is undefined { - return value === undefined; -} - -export function isNull(value: unknown): value is null { - return value === null; -} diff --git a/packages/fabric/core/utils/sanitize-string.ts b/packages/fabric/core/utils/sanitize-string.ts deleted file mode 100644 index 9fbc445..0000000 --- a/packages/fabric/core/utils/sanitize-string.ts +++ /dev/null @@ -1,15 +0,0 @@ -import validator from "validator"; -import { isUndefined } from "./is-nullish.ts"; - -const { stripLow, trim } = validator; - -/** - * Parses and sanitizes an unknown value into a string - * The string is trimmed and all low characters are removed - */ -export function parseAndSanitizeString(value: unknown): string | undefined { - if (isUndefined(value)) { - return value; - } - return stripLow(trim(String(value))); -} diff --git a/packages/fabric/validations/deno.json b/packages/fabric/validations/deno.json new file mode 100644 index 0000000..e504a18 --- /dev/null +++ b/packages/fabric/validations/deno.json @@ -0,0 +1,9 @@ +{ + "name": "@fabric/validations", + "exports": { + ".": "./index.ts" + }, + "imports": { + "@fabric/core": "jsr:@fabric/core" + } +} diff --git a/packages/fabric/validations/index.ts b/packages/fabric/validations/index.ts new file mode 100644 index 0000000..2dc7041 --- /dev/null +++ b/packages/fabric/validations/index.ts @@ -0,0 +1,3 @@ +export * from "./nullish/index.ts"; +export * from "./number/index.ts"; +export * from "./string/index.ts"; diff --git a/packages/fabric/validations/nullish/index.ts b/packages/fabric/validations/nullish/index.ts new file mode 100644 index 0000000..5259e80 --- /dev/null +++ b/packages/fabric/validations/nullish/index.ts @@ -0,0 +1,3 @@ +export * from "./is-null.ts"; +export * from "./is-nullish.ts"; +export * from "./is-undefined.ts"; diff --git a/packages/fabric/validations/nullish/is-null.ts b/packages/fabric/validations/nullish/is-null.ts new file mode 100644 index 0000000..181dc7a --- /dev/null +++ b/packages/fabric/validations/nullish/is-null.ts @@ -0,0 +1,3 @@ +export function isNull(value: unknown): value is null { + return value === null; +} diff --git a/packages/fabric/validations/nullish/is-nullish.ts b/packages/fabric/validations/nullish/is-nullish.ts new file mode 100644 index 0000000..3e3bf6d --- /dev/null +++ b/packages/fabric/validations/nullish/is-nullish.ts @@ -0,0 +1,6 @@ +import { isNull } from "./is-null.ts"; +import { isUndefined } from "./is-undefined.ts"; + +export function isNullish(value: unknown): value is null | undefined { + return isNull(value) || isUndefined(value); +} diff --git a/packages/fabric/validations/nullish/is-undefined.ts b/packages/fabric/validations/nullish/is-undefined.ts new file mode 100644 index 0000000..46723c0 --- /dev/null +++ b/packages/fabric/validations/nullish/is-undefined.ts @@ -0,0 +1,3 @@ +export function isUndefined(value: unknown): value is undefined { + return value === undefined; +} diff --git a/packages/fabric/validations/number/index.ts b/packages/fabric/validations/number/index.ts new file mode 100644 index 0000000..2ac43f4 --- /dev/null +++ b/packages/fabric/validations/number/index.ts @@ -0,0 +1 @@ +export * from "./is-number.ts"; diff --git a/packages/fabric/validations/number/is-number.test.ts b/packages/fabric/validations/number/is-number.test.ts new file mode 100644 index 0000000..84b9145 --- /dev/null +++ b/packages/fabric/validations/number/is-number.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, test } from "@fabric/testing"; +import { isNumber } from "./is-number.ts"; + +describe("Is a number", () => { + test("Given a number it should return true", () => { + expect(isNumber(1)).toBe(true); + }); + + test("Given a string it should return false", () => { + expect(isNumber("a")).toBe(false); + }); + + test("Given an empty string it should return false", () => { + expect(isNumber("")).toBe(false); + }); + + test("Given a boolean it should return false", () => { + expect(isNumber(false)).toBe(false); + }); + + test("Given an object it should return false", () => { + expect(isNumber({})).toBe(false); + }); + + test("Given an array it should return false", () => { + expect(isNumber([])).toBe(false); + }); + + test("Given a null it should return false", () => { + expect(isNumber(null)).toBe(false); + }); + + test("Given an undefined it should return false", () => { + expect(isNumber(undefined)).toBe(false); + }); +}); diff --git a/packages/fabric/validations/number/is-number.ts b/packages/fabric/validations/number/is-number.ts new file mode 100644 index 0000000..2ce82b4 --- /dev/null +++ b/packages/fabric/validations/number/is-number.ts @@ -0,0 +1,9 @@ +/** + * Checks if a value is a number even if it is a string that can be converted to a number + */ +export function isNumber(value: unknown): value is number { + if (typeof value === "number") { + return !isNaN(value); + } + return false; +} diff --git a/packages/fabric/validations/string/index.ts b/packages/fabric/validations/string/index.ts new file mode 100644 index 0000000..72c8a75 --- /dev/null +++ b/packages/fabric/validations/string/index.ts @@ -0,0 +1,3 @@ +export * from "./is-string.ts"; +export * from "./is-uuid.ts"; +export * from "./sanitize-string.ts"; diff --git a/packages/fabric/validations/string/is-string.ts b/packages/fabric/validations/string/is-string.ts new file mode 100644 index 0000000..a1ad274 --- /dev/null +++ b/packages/fabric/validations/string/is-string.ts @@ -0,0 +1,3 @@ +export function isString(value: unknown): value is string { + return typeof value === "string"; +} diff --git a/packages/fabric/validations/string/is-uuid.test.ts b/packages/fabric/validations/string/is-uuid.test.ts new file mode 100644 index 0000000..c1fe677 --- /dev/null +++ b/packages/fabric/validations/string/is-uuid.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, test } from "@fabric/testing"; +import { isUUID } from "./is-uuid.ts"; + +describe("isUUID", () => { + test("should return true for a valid UUID", () => { + const validUUID = "123e4567-e89b-12d3-a456-426614174000"; + expect(isUUID(validUUID)).toBe(true); + }); + + test("should return true for a valid UUID with uppercase letters", () => { + const validUUID = "123E4567-E89B-12D3-A456-426614174000"; + expect(isUUID(validUUID)).toBe(true); + }); + + test("should return true for a nil UUID", () => { + const nilUUID = "00000000-0000-0000-0000-000000000000"; + expect(isUUID(nilUUID)).toBe(true); + }); + + test("should return true for a max UUID", () => { + const maxUUID = "ffffffff-ffff-ffff-ffff-ffffffffffff"; + expect(isUUID(maxUUID)).toBe(true); + }); + + test("should return false for an invalid UUID", () => { + const invalidUUID = "123e4567-e89b-12d3-a456-42661417400"; + expect(isUUID(invalidUUID)).toBe(false); + }); + + test("should return false for a string that is not a UUID", () => { + const notUUID = "not-a-uuid"; + expect(isUUID(notUUID)).toBe(false); + }); + + test("should return false for a number", () => { + const number = 1234567890; + expect(isUUID(number)).toBe(false); + }); + + test("should return false for a boolean", () => { + const boolean = true; + expect(isUUID(boolean)).toBe(false); + }); + + test("should return false for null", () => { + const nullValue = null; + expect(isUUID(nullValue)).toBe(false); + }); + + test("should return false for undefined", () => { + const undefinedValue = undefined; + expect(isUUID(undefinedValue)).toBe(false); + }); +}); diff --git a/packages/fabric/validations/string/is-uuid.ts b/packages/fabric/validations/string/is-uuid.ts new file mode 100644 index 0000000..206f7bf --- /dev/null +++ b/packages/fabric/validations/string/is-uuid.ts @@ -0,0 +1,10 @@ +import type { UUID } from "@fabric/core"; +import { isString } from "./is-string.ts"; + +// From https://github.com/uuidjs/uuid/blob/main/src/regex.ts +const uuidRegex = + /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i; + +export function isUUID(value: unknown): value is UUID { + return isString(value) && uuidRegex.test(value); +} diff --git a/packages/fabric/core/utils/sanitize-string.test.ts b/packages/fabric/validations/string/sanitize-string.test.ts similarity index 72% rename from packages/fabric/core/utils/sanitize-string.test.ts rename to packages/fabric/validations/string/sanitize-string.test.ts index 1945871..a023af4 100644 --- a/packages/fabric/core/utils/sanitize-string.test.ts +++ b/packages/fabric/validations/string/sanitize-string.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from "@fabric/testing"; -import { parseAndSanitizeString } from "./sanitize-string.ts"; +import { parseAndSanitizeString } from "../string/sanitize-string.ts"; describe("Sanitize String", () => { - test("Given a string with low characters it should sanitize it", () => { + test("Given a string with low (control) characters it should sanitize it", () => { const sanitized = parseAndSanitizeString("John\x00"); expect(sanitized).toBe("John"); @@ -26,13 +26,13 @@ describe("Sanitize String", () => { expect(sanitized).toBe("true"); }); - test("Given a null value it should convert it to an empty string", () => { + test("Given a null value it should return null", () => { const sanitized = parseAndSanitizeString(null); - expect(sanitized).toBe("null"); + expect(sanitized).toBe(undefined); }); - test("Given an undefined value it should convert it to an empty string", () => { + test("Given an undefined value it should return undefined", () => { const sanitized = parseAndSanitizeString(undefined); expect(sanitized).toBe(undefined); diff --git a/packages/fabric/validations/string/sanitize-string.ts b/packages/fabric/validations/string/sanitize-string.ts new file mode 100644 index 0000000..f3ec9f6 --- /dev/null +++ b/packages/fabric/validations/string/sanitize-string.ts @@ -0,0 +1,17 @@ +import { isNullish } from "../nullish/is-nullish.ts"; + +/** + * Parses and sanitizes an unknown value into a string + * The string is trimmed and all low characters are removed + */ +export function parseAndSanitizeString( + value: unknown, +): string | undefined { + if (isNullish(value)) return undefined; + return stripLow((String(value)).trim()); +} + +// deno-lint-ignore no-control-regex +const lowCharsRegex = /[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/g; + +const stripLow = (str: string) => str.replace(lowCharsRegex, "");