Changed to constructor functions

This commit is contained in:
Sebastian Seedorf
2020-05-26 19:40:41 +02:00
parent 9fa06685ba
commit 836b1449bf
15 changed files with 281 additions and 168 deletions

View File

@@ -14,15 +14,42 @@ import {
Deno.test("validate schema (match)", async () => { Deno.test("validate schema (match)", async () => {
const values: [any, Validatable][] = [ const values: [any, Validatable][] = [
["string", isString], [
["string", [isString]], "string",
[["arr", "ay"], { [ArraySymbol]: isString }], isString(),
[{ foo: 3.1415, lorem: "ipsum" }, { foo: isNumber, lorem: [isString] }], ],
[{}, { optional: [isString] }], [
[{ foo: { bar: "" } }, { foo: { bar: [isRequired, isString] } }], "string",
[{ foo: { bar: "" } }, { foo: { bar: [isString] } }], [isString()],
[{ foo: {} }, { foo: { bar: [isString] } }], ],
[{}, { foo: { bar: [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) { for (const [value, constraints] of values) {
assertEquals([], await validate(value, constraints)); assertEquals([], await validate(value, constraints));
@@ -31,14 +58,38 @@ Deno.test("validate schema (match)", async () => {
Deno.test("validate schema (no match)", async () => { Deno.test("validate schema (no match)", async () => {
const values: [any, Validatable][] = [ const values: [any, Validatable][] = [
[6, isString], [
[false, [isString]], 6,
[["arr", ["ay"]], { [ArraySymbol]: isString }], isString(),
[{ foo: 3.1415, lorem: "ipsum" }, { foo: isInteger, lorem: [isString] }], ],
[{}, { required: [isRequired, isString] }], [
[{ foo: { bar: 1 } }, { foo: { bar: [isRequired, isString] } }], false,
[{ foo: {} }, { foo: { bar: [isRequired, isString] } }], [isString()],
[{}, { foo: { bar: [isRequired, 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) { for (const [value, constraints] of values) {
assertNotEquals([], await validate(value, constraints)); assertNotEquals([], await validate(value, constraints));

View File

@@ -2,6 +2,7 @@ export type Args = { [_: string]: any };
export interface Validator { export interface Validator {
type: string; type: string;
extends?: Validator[];
check: (value: any) => Promise<Args | undefined> | Args | undefined; check: (value: any) => Promise<Args | undefined> | Args | undefined;
message: ( message: (
value: any, value: any,
@@ -54,6 +55,13 @@ async function validateValue(
} }
const result: ValidationError[] = []; const result: ValidationError[] = [];
for (const validator of validators) { 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); const args = await validator.check(value);
if (args !== undefined) { if (args !== undefined) {
const message = await validator.message(value, args); const message = await validator.message(value, args);

View File

@@ -13,7 +13,7 @@ Deno.test("isArray (match)", async () => {
["foo"], ["foo"],
]; ];
for (const value of values) { 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(), Symbol(),
]; ];
for (const value of values) { for (const value of values) {
assertNotEquals(await validate(value, isArray), []); assertNotEquals(await validate(value, isArray()), []);
} }
}); });

View File

@@ -1,14 +1,16 @@
import { Validator, Args } from "../mod.ts"; import { Validator, Args } from "../mod.ts";
export const isArray: Validator = { export function isArray(): Validator {
type: "isArray", return {
check: (value: any) => { type: "isArray",
if (value === null || value === undefined) return; check: (value: any) => {
if (!Array.isArray(value)) { if (value === null || value === undefined) return;
return {}; if (!Array.isArray(value)) {
} return {};
}, }
message: (value: any, args?: Args) => { },
return `This value has to be an array.`; message: (value: any, args?: Args) => {
}, return `This value has to be an array.`;
}; },
};
}

View File

@@ -13,7 +13,7 @@ Deno.test("isBoolean (match)", async () => {
false, false,
]; ];
for (const value of values) { 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(), Symbol(),
]; ];
for (const value of values) { for (const value of values) {
assertNotEquals(await validate(value, isBoolean), []); assertNotEquals(await validate(value, isBoolean()), []);
} }
}); });

View File

@@ -1,14 +1,16 @@
import { Validator, Args } from "../mod.ts"; import { Validator, Args } from "../mod.ts";
export const isBoolean: Validator = { export function isBoolean(): Validator {
type: "isBoolean", return {
check: (value: any) => { type: "isBoolean",
if (value === null || value === undefined) return; check: (value: any) => {
if (value !== true && value !== false) { if (value === null || value === undefined) return;
return {}; if (value !== true && value !== false) {
} return {};
}, }
message: (value: any, args?: Args) => { },
return `This value has to be a boolean.`; message: (value: any, args?: Args) => {
}, return `This value has to be a boolean.`;
}; },
};
}

View File

@@ -22,7 +22,7 @@ Deno.test("isRequired (match)", async () => {
Symbol("foo"), Symbol("foo"),
]; ];
for (const value of values) { 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, null,
]; ];
for (const value of values) { 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"), Symbol("foo"),
]; ];
for (const value of values) { 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, undefined,
]; ];
for (const value of values) { 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"), Symbol("foo"),
]; ];
for (const value of values) { 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) { for (const value of values) {
assertNotEquals(await validate(value, isNotEmpty), []); assertNotEquals(await validate(value, isNotEmpty()), []);
} }
}); });

View File

@@ -8,63 +8,71 @@ import {
isBoolean, isBoolean,
} from "../mod.ts"; } from "../mod.ts";
export const isRequired: Validator = { export function isRequired(): Validator {
type: "isRequired", return {
check: (value: any) => { type: "isRequired",
if (value === null || value === undefined) { check: (value: any) => {
return {}; if (value === null || value === undefined) {
}
},
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)) {
return {}; return {};
} }
return; },
} message: (value: any, args?: Args) => {
if ((await isNumber.check(value)) === undefined || Number.isNaN(value)) { return `This value is required.`;
return; },
} };
if (await isSymbol.check(value) === undefined) { }
return;
} export function isDefined(): Validator {
if (await isBoolean.check(value) === undefined) { return {
return; type: "isDefined",
} check: (value: any) => {
if ((await isArray.check(value)) === undefined) { if (value === undefined) {
if (value.length > 0) { 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;
} }
return {}; return {};
} },
for (const key in value) { message: (value: any, args?: Args) => {
return; return `This value has to be non-empty.`;
} },
return {}; };
}, }
message: (value: any, args?: Args) => {
return `This value has to be non-empty.`;
},
};

View File

@@ -7,8 +7,8 @@ import {
Deno.test("or (match)", async () => { Deno.test("or (match)", async () => {
const values: [any, Validator[]][] = [ const values: [any, Validator[]][] = [
["", [isString, isInteger]], ["", [isString(), isInteger()]],
[1, [isString, isInteger]], [1, [isString(), isInteger()]],
]; ];
for (const [value, validators] of values) { for (const [value, validators] of values) {
assertEquals(await validate(value, or(...validators)), []); assertEquals(await validate(value, or(...validators)), []);
@@ -17,9 +17,9 @@ Deno.test("or (match)", async () => {
Deno.test("or (no match)", async () => { Deno.test("or (no match)", async () => {
const values: [any, Validator[]][] = [ const values: [any, Validator[]][] = [
[true, [isString, isInteger]], [true, [isString(), isInteger()]],
[3.1415, [isString, isInteger]], [3.1415, [isString(), isInteger()]],
[1, [isString]], [1, [isString()]],
]; ];
for (const [value, validators] of values) { for (const [value, validators] of values) {
assertNotEquals(await validate(value, or(...validators)), []); assertNotEquals(await validate(value, or(...validators)), []);

View File

@@ -17,7 +17,7 @@ Deno.test("isNumber (match)", async () => {
Math.PI, Math.PI,
]; ];
for (const value of values) { 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(), Symbol(),
]; ];
for (const value of values) { 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, -1,
]; ];
for (const value of values) { 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, 0.1,
]; ];
for (const value of values) { for (const value of values) {
assertNotEquals(await validate(value, isInteger), []); assertNotEquals(await validate(value, isInteger()), []);
} }
}); });

View File

@@ -1,27 +1,31 @@
import { Validator, Args } from "../mod.ts"; import { Validator, Args } from "../mod.ts";
export const isNumber: Validator = { export function isNumber(): Validator {
type: "isNumber", return {
check: (value: any) => { type: "isNumber",
if (value === null || value === undefined) return; check: (value: any) => {
if (!Number.isFinite(value)) { if (value === null || value === undefined) return;
return {}; if (!Number.isFinite(value)) {
} return {};
}, }
message: (value: any, args?: Args) => { },
return `This value has to be a number.`; message: (value: any, args?: Args) => {
}, return `This value has to be a number.`;
}; },
};
}
export const isInteger: Validator = { export function isInteger(): Validator {
type: "isInteger", return {
check: (value: any) => { type: "isInteger",
if (value === null || value === undefined) return; check: (value: any) => {
if (!Number.isInteger(value)) { if (value === null || value === undefined) return;
return {}; if (!Number.isInteger(value)) {
} return {};
}, }
message: (value: any, args?: Args) => { },
return `This value has to be an integer.`; message: (value: any, args?: Args) => {
}, return `This value has to be an integer.`;
}; },
};
}

View File

@@ -1,4 +1,4 @@
import { isString } from "./string.ts"; import { isString, isURL } from "./string.ts";
import { validate } from "../mod.ts"; import { validate } from "../mod.ts";
import { import {
assertEquals, assertEquals,
@@ -15,7 +15,7 @@ Deno.test("isString (match)", async () => {
new String("bar"), new String("bar"),
]; ];
for (const value of values) { 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(), Symbol(),
]; ];
for (const value of values) { 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()), []);
} }
}); });

View File

@@ -1,14 +1,32 @@
import { Validator, Args } from "../mod.ts"; import { Validator, Args } from "../mod.ts";
export const isString: Validator = { export function isString(): Validator {
type: "isString", return {
check: (value: any) => { type: "isString",
if (value === null || value === undefined) return; check: (value: any) => {
if (typeof value !== "string" && !(value instanceof String)) { if (value === null || value === undefined) return;
return {}; if (typeof value !== "string" && !(value instanceof String)) {
} return {};
}, }
message: (value: any, args?: Args) => { },
return `This value has to be a string.`; 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.`;
},
};
}

View File

@@ -12,7 +12,7 @@ Deno.test("isSymbol (match)", async () => {
Symbol(), Symbol(),
]; ];
for (const value of values) { 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(), new Object(),
]; ];
for (const value of values) { for (const value of values) {
assertNotEquals(await validate(value, isSymbol), []); assertNotEquals(await validate(value, isSymbol()), []);
} }
}); });

View File

@@ -1,14 +1,16 @@
import { Validator, Args } from "../mod.ts"; import { Validator, Args } from "../mod.ts";
export const isSymbol: Validator = { export function isSymbol(): Validator {
type: "isSymbol", return {
check: (value: any) => { type: "isSymbol",
if (value === null || value === undefined) return; check: (value: any) => {
if (typeof value !== "symbol") { if (value === null || value === undefined) return;
return {}; if (typeof value !== "symbol") {
} return {};
}, }
message: (value: any, args?: Args) => { },
return `This value has to be a symbol.`; message: (value: any, args?: Args) => {
}, return `This value has to be a symbol.`;
}; },
};
}