This commit is contained in:
Sebastian Seedorf
2020-05-26 00:20:44 +02:00
parent cd6ed23d47
commit 218e596ede
5 changed files with 72 additions and 39 deletions

View File

@@ -1,15 +1,20 @@
import { assertEquals, assertNotEquals } from "https://deno.land/std@0.53.0/testing/asserts.ts";
import {
validate, Validatable, ArraySymbol,
isString
assertEquals,
assertNotEquals,
} from "https://deno.land/std@0.53.0/testing/asserts.ts";
import {
validate,
Validatable,
ArraySymbol,
isString,
} from "./mod.ts";
Deno.test("validate schema (match)", async () => {
const values: [any, Validatable][] = [
["string", isString],
["string", [isString]],
[["arr", "ay"], {[ArraySymbol]: isString}],
[{foo: "bar", lorem: "ipsum"}, {foo: isString, lorem: [isString]}],
[["arr", "ay"], { [ArraySymbol]: isString }],
[{ foo: "bar", lorem: "ipsum" }, { foo: isString, lorem: [isString] }],
];
for (const [value, constraints] of values) {
assertEquals([], await validate(value, constraints));
@@ -20,10 +25,10 @@ Deno.test("validate schema (no match)", async () => {
const values: [any, Validatable][] = [
[6, isString],
[false, [isString]],
[["arr", ["ay"]], {[ArraySymbol]: isString}],
[{foo: {}, lorem: "ipsum"}, {foo: isString, lorem: [isString]}],
[["arr", ["ay"]], { [ArraySymbol]: isString }],
[{ foo: {}, lorem: "ipsum" }, { foo: isString, lorem: [isString] }],
];
for (const [value, constraints] of values) {
assertNotEquals([], await validate(value, constraints));
}
});
});

View File

@@ -1,24 +1,32 @@
export type Args = {[_: string]: any};
export type Args = { [_: string]: any };
export interface Validator {
type: string;
check: (value: any) => Promise<Args|undefined>|Args|undefined;
message: (value: any, args?: Args) => Promise<string|undefined>|string|undefined;
check: (value: any) => Promise<Args | undefined> | Args | undefined;
message: (
value: any,
args?: Args,
) => Promise<string | undefined> | string | undefined;
}
export interface ValidationError {
type: string;
param?: string[];
message?: string|null;
message?: string | null;
args?: Args;
}
export type Validatable = Schema|Validator|Validator[];
export type Validatable = Schema | Validator | Validator[];
export const ArraySymbol: unique symbol = Symbol("ArraySymbol");
export type Schema = {[key: string]: Validatable}|{[ArraySymbol]: Validatable};
export type Schema = { [key: string]: Validatable } | {
[ArraySymbol]: Validatable;
};
export async function validate(value: any, validators: Validatable): Promise<ValidationError[]|undefined> {
export async function validate(
value: any,
validators: Validatable,
): Promise<ValidationError[] | undefined> {
if (instanceofValidatorArray(validators) || instanceofValidator(validators)) {
return validateValue(value, validators);
} else {
@@ -34,10 +42,13 @@ function instanceofValidator(value: any): value is Validator {
}
function instanceofValidatorArray(value: any): value is Validator[] {
return Array.isArray(value) && value.every(x => instanceofValidator(x));
return Array.isArray(value) && value.every((x) => instanceofValidator(x));
}
async function validateValue(value: any, validators: Validator|Validator[]): Promise<ValidationError[]> {
async function validateValue(
value: any,
validators: Validator | Validator[],
): Promise<ValidationError[]> {
if (!Array.isArray(validators)) {
validators = [validators];
}
@@ -49,33 +60,40 @@ async function validateValue(value: any, validators: Validator|Validator[]): Pro
result.push({
type: validator.type,
args,
message
message,
});
}
}
return result;
}
async function validateSchema(value: any, validators: Schema): Promise<ValidationError[]> {
async function validateSchema(
value: any,
validators: Schema,
): Promise<ValidationError[]> {
if (validators.hasOwnProperty(ArraySymbol)) {
const v = validators as {[ArraySymbol]: Validatable};
const v = validators as { [ArraySymbol]: Validatable };
if (Array.isArray(value)) {
const arr = await Promise.all(value.map(val => validate(val, v[ArraySymbol])));
const errors = arr.flatMap((val, idx) => (val ?? []).map(error => ({
...error,
param: [`[${idx}]`, ...(error.param || [])]
})));
const arr = await Promise.all(
value.map((val) => validate(val, v[ArraySymbol])),
);
const errors = arr.flatMap((val, idx) =>
(val ?? []).map((error) => ({
...error,
param: [`[${idx}]`, ...(error.param || [])],
}))
);
return errors;
} else {
return [{
type: "array",
param: ["[]"],
message: "Array expected!",
args: {}
args: {},
}];
}
}
const v = validators as {[key: string]: Validatable};
const v = validators as { [key: string]: Validatable };
const valErrors: ValidationError[] = [];
for (const prop in validators) {
if (!validators.hasOwnProperty(prop)) {
@@ -89,9 +107,9 @@ async function validateSchema(value: any, validators: Schema): Promise<Validatio
type: "property",
param: [prop],
message: `Property '${prop}' expected but not found!`,
args: {property: prop}
args: { property: prop },
});
}
}
return valErrors;
}
}

11
mod.ts
View File

@@ -1,2 +1,9 @@
export { Args, Validator, ValidationError, validate, Validatable, ArraySymbol } from "./Validator.ts";
export * from "./validators/string.ts";
export {
Args,
Validator,
ValidationError,
validate,
Validatable,
ArraySymbol,
} from "./Validator.ts";
export * from "./validators/string.ts";

View File

@@ -1,13 +1,16 @@
import { isString } from "./string.ts";
import { validate } from "../mod.ts";
import { assertEquals, assertNotEquals } from "https://deno.land/std@0.53.0/testing/asserts.ts";
import {
assertEquals,
assertNotEquals,
} from "https://deno.land/std@0.53.0/testing/asserts.ts";
Deno.test("isString (match)", async () => {
const values = [
"",
"foo",
new String(),
new String("bar")
new String("bar"),
];
for (const value of values) {
assertEquals([], await validate(value, isString));
@@ -25,9 +28,9 @@ Deno.test("isString (no match)", async () => {
() => {},
function named() {},
new Object(),
Symbol()
Symbol(),
];
for (const value of values) {
assertNotEquals([], await validate(value, isString));
}
});
});

View File

@@ -3,11 +3,11 @@ import { Validator, Args } from "../mod.ts";
export const isString: Validator = {
type: "isString",
check: (value: any) => {
if (typeof value !== 'string' && !(value instanceof String)) {
if (typeof value !== "string" && !(value instanceof String)) {
return {};
}
},
message: (value: any, args?: Args) => {
return `The value '${value && value.toString()}' has to be a string.`
}
}
return `The value '${value && value.toString()}' has to be a string.`;
},
};