From 836b1449bf6fbbc1f40a3888fdaeb01e76c57bae Mon Sep 17 00:00:00 2001 From: Sebastian Seedorf Date: Tue, 26 May 2020 19:40:41 +0200 Subject: [PATCH] Changed to constructor functions --- Validator.test.ts | 85 ++++++++++++++++++++------ Validator.ts | 8 +++ validators/array.test.ts | 4 +- validators/array.ts | 26 ++++---- validators/boolean.test.ts | 4 +- validators/boolean.ts | 26 ++++---- validators/empty.test.ts | 12 ++-- validators/empty.ts | 118 ++++++++++++++++++++----------------- validators/logic.test.ts | 10 ++-- validators/number.test.ts | 8 +-- validators/number.ts | 52 ++++++++-------- validators/string.test.ts | 24 +++++++- validators/string.ts | 42 +++++++++---- validators/symbol.test.ts | 4 +- validators/symbol.ts | 26 ++++---- 15 files changed, 281 insertions(+), 168 deletions(-) diff --git a/Validator.test.ts b/Validator.test.ts index 3cad67c..0098b4f 100644 --- a/Validator.test.ts +++ b/Validator.test.ts @@ -14,15 +14,42 @@ import { Deno.test("validate schema (match)", async () => { const values: [any, Validatable][] = [ - ["string", isString], - ["string", [isString]], - [["arr", "ay"], { [ArraySymbol]: isString }], - [{ foo: 3.1415, lorem: "ipsum" }, { foo: isNumber, lorem: [isString] }], - [{}, { optional: [isString] }], - [{ foo: { bar: "" } }, { foo: { bar: [isRequired, isString] } }], - [{ foo: { bar: "" } }, { foo: { bar: [isString] } }], - [{ foo: {} }, { foo: { bar: [isString] } }], - [{}, { foo: { bar: [isString] } }], + [ + "string", + isString(), + ], + [ + "string", + [isString()], + ], + [ + ["arr", "ay"], + { [ArraySymbol]: isString() }, + ], + [ + { foo: 3.1415, lorem: "ipsum" }, + { foo: isNumber(), lorem: [isString()] }, + ], + [ + {}, + { optional: [isString()] }, + ], + [ + { foo: { bar: "" } }, + { foo: { bar: [isRequired(), isString()] } }, + ], + [ + { foo: { bar: "" } }, + { foo: { bar: [isString()] } }, + ], + [ + { foo: {} }, + { foo: { bar: [isString()] } }, + ], + [ + {}, + { foo: { bar: [isString()] } }, + ], ]; for (const [value, constraints] of values) { assertEquals([], await validate(value, constraints)); @@ -31,14 +58,38 @@ Deno.test("validate schema (match)", async () => { Deno.test("validate schema (no match)", async () => { const values: [any, Validatable][] = [ - [6, isString], - [false, [isString]], - [["arr", ["ay"]], { [ArraySymbol]: isString }], - [{ foo: 3.1415, lorem: "ipsum" }, { foo: isInteger, lorem: [isString] }], - [{}, { required: [isRequired, isString] }], - [{ foo: { bar: 1 } }, { foo: { bar: [isRequired, isString] } }], - [{ foo: {} }, { foo: { bar: [isRequired, isString] } }], - [{}, { foo: { bar: [isRequired, isString] } }], + [ + 6, + isString(), + ], + [ + false, + [isString()], + ], + [ + ["arr", ["ay"]], + { [ArraySymbol]: isString() }, + ], + [ + { foo: 3.1415, lorem: "ipsum" }, + { foo: isInteger(), lorem: [isString()] }, + ], + [ + {}, + { required: [isRequired(), isString()] }, + ], + [ + { foo: { bar: 1 } }, + { foo: { bar: [isRequired(), isString()] } }, + ], + [ + { foo: {} }, + { foo: { bar: [isRequired(), isString()] } }, + ], + [ + {}, + { foo: { bar: [isRequired(), isString()] } }, + ], ]; for (const [value, constraints] of values) { assertNotEquals([], await validate(value, constraints)); diff --git a/Validator.ts b/Validator.ts index 9a65feb..dbe0100 100644 --- a/Validator.ts +++ b/Validator.ts @@ -2,6 +2,7 @@ export type Args = { [_: string]: any }; export interface Validator { type: string; + extends?: Validator[]; check: (value: any) => Promise | Args | undefined; message: ( value: any, @@ -54,6 +55,13 @@ async function validateValue( } const result: ValidationError[] = []; for (const validator of validators) { + if (validator.extends) { + const prerequisites = await validate(value, validator.extends); + if (prerequisites.length > 0) { + result.push(...prerequisites); + continue; + } + } const args = await validator.check(value); if (args !== undefined) { const message = await validator.message(value, args); diff --git a/validators/array.test.ts b/validators/array.test.ts index e634f0b..3681d0c 100644 --- a/validators/array.test.ts +++ b/validators/array.test.ts @@ -13,7 +13,7 @@ Deno.test("isArray (match)", async () => { ["foo"], ]; for (const value of values) { - assertEquals(await validate(value, isArray), []); + assertEquals(await validate(value, isArray()), []); } }); @@ -33,6 +33,6 @@ Deno.test("isArray (no match)", async () => { Symbol(), ]; for (const value of values) { - assertNotEquals(await validate(value, isArray), []); + assertNotEquals(await validate(value, isArray()), []); } }); diff --git a/validators/array.ts b/validators/array.ts index e3db194..d5fd666 100644 --- a/validators/array.ts +++ b/validators/array.ts @@ -1,14 +1,16 @@ import { Validator, Args } from "../mod.ts"; -export const isArray: Validator = { - type: "isArray", - check: (value: any) => { - if (value === null || value === undefined) return; - if (!Array.isArray(value)) { - return {}; - } - }, - message: (value: any, args?: Args) => { - return `This value has to be an array.`; - }, -}; +export function isArray(): Validator { + return { + type: "isArray", + check: (value: any) => { + if (value === null || value === undefined) return; + if (!Array.isArray(value)) { + return {}; + } + }, + message: (value: any, args?: Args) => { + return `This value has to be an array.`; + }, + }; +} diff --git a/validators/boolean.test.ts b/validators/boolean.test.ts index 48725d7..15aaaaa 100644 --- a/validators/boolean.test.ts +++ b/validators/boolean.test.ts @@ -13,7 +13,7 @@ Deno.test("isBoolean (match)", async () => { false, ]; for (const value of values) { - assertEquals(await validate(value, isBoolean), []); + assertEquals(await validate(value, isBoolean()), []); } }); @@ -31,6 +31,6 @@ Deno.test("isBoolean (no match)", async () => { Symbol(), ]; for (const value of values) { - assertNotEquals(await validate(value, isBoolean), []); + assertNotEquals(await validate(value, isBoolean()), []); } }); diff --git a/validators/boolean.ts b/validators/boolean.ts index d9c787a..61d690b 100644 --- a/validators/boolean.ts +++ b/validators/boolean.ts @@ -1,14 +1,16 @@ import { Validator, Args } from "../mod.ts"; -export const isBoolean: Validator = { - type: "isBoolean", - check: (value: any) => { - if (value === null || value === undefined) return; - if (value !== true && value !== false) { - return {}; - } - }, - message: (value: any, args?: Args) => { - return `This value has to be a boolean.`; - }, -}; +export function isBoolean(): Validator { + return { + type: "isBoolean", + check: (value: any) => { + if (value === null || value === undefined) return; + if (value !== true && value !== false) { + return {}; + } + }, + message: (value: any, args?: Args) => { + return `This value has to be a boolean.`; + }, + }; +} diff --git a/validators/empty.test.ts b/validators/empty.test.ts index 9d39139..7469bb2 100644 --- a/validators/empty.test.ts +++ b/validators/empty.test.ts @@ -22,7 +22,7 @@ Deno.test("isRequired (match)", async () => { Symbol("foo"), ]; for (const value of values) { - assertEquals(await validate(value, isRequired), []); + assertEquals(await validate(value, isRequired()), []); } }); @@ -32,7 +32,7 @@ Deno.test("isRequired (no match)", async () => { null, ]; for (const value of values) { - assertNotEquals(await validate(value, isRequired), []); + assertNotEquals(await validate(value, isRequired()), []); } }); @@ -54,7 +54,7 @@ Deno.test("isDefined (match)", async () => { Symbol("foo"), ]; for (const value of values) { - assertEquals(await validate(value, isDefined), []); + assertEquals(await validate(value, isDefined()), []); } }); @@ -63,7 +63,7 @@ Deno.test("isDefined (no match)", async () => { undefined, ]; for (const value of values) { - assertNotEquals(await validate(value, isDefined), []); + assertNotEquals(await validate(value, isDefined()), []); } }); @@ -80,7 +80,7 @@ Deno.test("isNotEmpty (match)", async () => { Symbol("foo"), ]; for (const value of values) { - assertEquals(await validate(value, isNotEmpty), []); + assertEquals(await validate(value, isNotEmpty()), []); } }); @@ -94,6 +94,6 @@ Deno.test("isNotEmpty (no match)", async () => { [], ]; for (const value of values) { - assertNotEquals(await validate(value, isNotEmpty), []); + assertNotEquals(await validate(value, isNotEmpty()), []); } }); diff --git a/validators/empty.ts b/validators/empty.ts index b477974..86da8fa 100644 --- a/validators/empty.ts +++ b/validators/empty.ts @@ -8,63 +8,71 @@ import { isBoolean, } from "../mod.ts"; -export const isRequired: Validator = { - type: "isRequired", - check: (value: any) => { - if (value === null || value === undefined) { - return {}; - } - }, - message: (value: any, args?: Args) => { - return `This value is required.`; - }, -}; - -export const isDefined: Validator = { - type: "isDefined", - check: (value: any) => { - if (value === undefined) { - return {}; - } - }, - message: (value: any, args?: Args) => { - return `This value has to be defined.`; - }, -}; - -export const isNotEmpty: Validator = { - type: "isNotEmpty", - check: async (value: any) => { - if (value === null || value === undefined) { - return {}; - } - if ((await isString.check(value)) === undefined) { - if (/^\s*$/.test(value)) { +export function isRequired(): Validator { + return { + type: "isRequired", + check: (value: any) => { + if (value === null || value === undefined) { return {}; } - return; - } - if ((await isNumber.check(value)) === undefined || Number.isNaN(value)) { - return; - } - if (await isSymbol.check(value) === undefined) { - return; - } - if (await isBoolean.check(value) === undefined) { - return; - } - if ((await isArray.check(value)) === undefined) { - if (value.length > 0) { + }, + message: (value: any, args?: Args) => { + return `This value is required.`; + }, + }; +} + +export function isDefined(): Validator { + return { + type: "isDefined", + check: (value: any) => { + if (value === undefined) { + return {}; + } + }, + message: (value: any, args?: Args) => { + return `This value has to be defined.`; + }, + }; +} + +export function isNotEmpty(): Validator { + return { + type: "isNotEmpty", + check: async (value: any) => { + if (value === null || value === undefined) { + return {}; + } + if ((await isString().check(value)) === undefined) { + if (/^\s*$/.test(value)) { + return {}; + } + return; + } + if ( + (await isNumber().check(value)) === undefined || Number.isNaN(value) + ) { + return; + } + if (await isSymbol().check(value) === undefined) { + return; + } + if (await isBoolean().check(value) === undefined) { + return; + } + if ((await isArray().check(value)) === undefined) { + if (value.length > 0) { + return; + } + return {}; + } + for (const key in value) { return; } return {}; - } - for (const key in value) { - return; - } - return {}; - }, - message: (value: any, args?: Args) => { - return `This value has to be non-empty.`; - }, -}; + }, + message: (value: any, args?: Args) => { + return `This value has to be non-empty.`; + }, + }; +} diff --git a/validators/logic.test.ts b/validators/logic.test.ts index cf80128..3849290 100644 --- a/validators/logic.test.ts +++ b/validators/logic.test.ts @@ -7,8 +7,8 @@ import { Deno.test("or (match)", async () => { const values: [any, Validator[]][] = [ - ["", [isString, isInteger]], - [1, [isString, isInteger]], + ["", [isString(), isInteger()]], + [1, [isString(), isInteger()]], ]; for (const [value, validators] of values) { assertEquals(await validate(value, or(...validators)), []); @@ -17,9 +17,9 @@ Deno.test("or (match)", async () => { Deno.test("or (no match)", async () => { const values: [any, Validator[]][] = [ - [true, [isString, isInteger]], - [3.1415, [isString, isInteger]], - [1, [isString]], + [true, [isString(), isInteger()]], + [3.1415, [isString(), isInteger()]], + [1, [isString()]], ]; for (const [value, validators] of values) { assertNotEquals(await validate(value, or(...validators)), []); diff --git a/validators/number.test.ts b/validators/number.test.ts index 2ed6de1..d198946 100644 --- a/validators/number.test.ts +++ b/validators/number.test.ts @@ -17,7 +17,7 @@ Deno.test("isNumber (match)", async () => { Math.PI, ]; for (const value of values) { - assertEquals(await validate(value, isNumber), []); + assertEquals(await validate(value, isNumber()), []); } }); @@ -33,7 +33,7 @@ Deno.test("isNumber (no match)", async () => { Symbol(), ]; for (const value of values) { - assertNotEquals(await validate(value, isNumber), []); + assertNotEquals(await validate(value, isNumber()), []); } }); @@ -47,7 +47,7 @@ Deno.test("isInteger (match)", async () => { -1, ]; for (const value of values) { - assertEquals(await validate(value, isInteger), []); + assertEquals(await validate(value, isInteger()), []); } }); @@ -64,6 +64,6 @@ Deno.test("isInteger (no match)", async () => { 0.1, ]; for (const value of values) { - assertNotEquals(await validate(value, isInteger), []); + assertNotEquals(await validate(value, isInteger()), []); } }); diff --git a/validators/number.ts b/validators/number.ts index 6fb7c0b..cc192d0 100644 --- a/validators/number.ts +++ b/validators/number.ts @@ -1,27 +1,31 @@ import { Validator, Args } from "../mod.ts"; -export const isNumber: Validator = { - type: "isNumber", - check: (value: any) => { - if (value === null || value === undefined) return; - if (!Number.isFinite(value)) { - return {}; - } - }, - message: (value: any, args?: Args) => { - return `This value has to be a number.`; - }, -}; +export function isNumber(): Validator { + return { + type: "isNumber", + check: (value: any) => { + if (value === null || value === undefined) return; + if (!Number.isFinite(value)) { + return {}; + } + }, + message: (value: any, args?: Args) => { + return `This value has to be a number.`; + }, + }; +} -export const isInteger: Validator = { - type: "isInteger", - check: (value: any) => { - if (value === null || value === undefined) return; - if (!Number.isInteger(value)) { - return {}; - } - }, - message: (value: any, args?: Args) => { - return `This value has to be an integer.`; - }, -}; +export function isInteger(): Validator { + return { + type: "isInteger", + check: (value: any) => { + if (value === null || value === undefined) return; + if (!Number.isInteger(value)) { + return {}; + } + }, + message: (value: any, args?: Args) => { + return `This value has to be an integer.`; + }, + }; +} diff --git a/validators/string.test.ts b/validators/string.test.ts index 23ba2a5..180c70d 100644 --- a/validators/string.test.ts +++ b/validators/string.test.ts @@ -1,4 +1,4 @@ -import { isString } from "./string.ts"; +import { isString, isURL } from "./string.ts"; import { validate } from "../mod.ts"; import { assertEquals, @@ -15,7 +15,7 @@ Deno.test("isString (match)", async () => { new String("bar"), ]; for (const value of values) { - assertEquals(await validate(value, isString), []); + assertEquals(await validate(value, isString()), []); } }); @@ -31,6 +31,24 @@ Deno.test("isString (no match)", async () => { Symbol(), ]; for (const value of values) { - assertNotEquals(await validate(value, isString), []); + assertNotEquals(await validate(value, isString()), []); + } +}); + +Deno.test("isURL (match)", async () => { + const values = [ + "http://google.com", + ]; + for (const value of values) { + assertEquals(await validate(value, isURL()), []); + } +}); + +Deno.test("isURL (no match)", async () => { + const values = [ + "invalid", + ]; + for (const value of values) { + assertNotEquals(await validate(value, isURL()), []); } }); diff --git a/validators/string.ts b/validators/string.ts index f26a85e..39a010f 100644 --- a/validators/string.ts +++ b/validators/string.ts @@ -1,14 +1,32 @@ import { Validator, Args } from "../mod.ts"; -export const isString: Validator = { - type: "isString", - check: (value: any) => { - if (value === null || value === undefined) return; - if (typeof value !== "string" && !(value instanceof String)) { - return {}; - } - }, - message: (value: any, args?: Args) => { - return `This value has to be a string.`; - }, -}; +export function isString(): Validator { + return { + type: "isString", + check: (value: any) => { + if (value === null || value === undefined) return; + if (typeof value !== "string" && !(value instanceof String)) { + return {}; + } + }, + message: (value: any, args?: Args) => { + return `This value has to be a string.`; + }, + }; +} + +export function isURL(): Validator { + return { + type: "isURL", + extends: [isString()], + check: (value: any) => { + if (value === null || value === undefined) return; + if (value !== "http://google.com") { + return {}; + } + }, + message: (value: any, args?: Args) => { + return `This value is not a valid URL.`; + }, + }; +} diff --git a/validators/symbol.test.ts b/validators/symbol.test.ts index bfd309e..baa2069 100644 --- a/validators/symbol.test.ts +++ b/validators/symbol.test.ts @@ -12,7 +12,7 @@ Deno.test("isSymbol (match)", async () => { Symbol(), ]; for (const value of values) { - assertEquals(await validate(value, isSymbol), []); + assertEquals(await validate(value, isSymbol()), []); } }); @@ -31,6 +31,6 @@ Deno.test("isSymbol (no match)", async () => { new Object(), ]; for (const value of values) { - assertNotEquals(await validate(value, isSymbol), []); + assertNotEquals(await validate(value, isSymbol()), []); } }); diff --git a/validators/symbol.ts b/validators/symbol.ts index 35b80db..13a8031 100644 --- a/validators/symbol.ts +++ b/validators/symbol.ts @@ -1,14 +1,16 @@ import { Validator, Args } from "../mod.ts"; -export const isSymbol: Validator = { - type: "isSymbol", - check: (value: any) => { - if (value === null || value === undefined) return; - if (typeof value !== "symbol") { - return {}; - } - }, - message: (value: any, args?: Args) => { - return `This value has to be a symbol.`; - }, -}; +export function isSymbol(): Validator { + return { + type: "isSymbol", + check: (value: any) => { + if (value === null || value === undefined) return; + if (typeof value !== "symbol") { + return {}; + } + }, + message: (value: any, args?: Args) => { + return `This value has to be a symbol.`; + }, + }; +}