ffffng/server/validation/validator.ts
baldo 5c0b5abf73 Typescript migration:
* validation/validator.js
2020-04-08 22:45:21 +02:00

122 lines
3.2 KiB
TypeScript

import _ from "lodash";
import {parseInteger} from "../utils/strings";
import Logger from "../logger";
interface Constraint {
type: string,
optional?: boolean,
allowed?: string[],
min?: number,
max?: number,
regex?: RegExp,
}
type Constraints = {[key: string]: Constraint};
type Values = {[key: string]: any};
// TODO: sanitize input for further processing as specified by constraints (correct types, trimming, etc.)
function isValidBoolean(value: any): boolean {
return _.isBoolean(value) || value === 'true' || value === 'false';
}
function isValidNumber(constraint: Constraint, value: any): boolean {
if (_.isString(value)) {
value = parseInteger(value);
}
if (!_.isNumber(value)) {
return false;
}
if (_.isNaN(value) || !_.isFinite(value)) {
return false;
}
if (_.isNumber(constraint.min) && value < constraint.min) {
return false;
}
// noinspection RedundantIfStatementJS
if (_.isNumber(constraint.max) && value > constraint.max) {
return false;
}
return true;
}
function isValidEnum(constraint: Constraint, value: any): boolean {
if (!_.isString(value)) {
return false;
}
return _.indexOf(constraint.allowed, value) >= 0;
}
function isValidString(constraint: Constraint, value: any): boolean {
if (!constraint.regex) {
throw new Error("String constraints must have regex set: " + constraint);
}
if (!_.isString(value)) {
return false;
}
const trimmed = value.trim();
return (trimmed === '' && constraint.optional) || constraint.regex.test(trimmed);
}
function isValid(constraint: Constraint, acceptUndefined: boolean, value: any): boolean {
if (value === undefined) {
return acceptUndefined || constraint.optional === true;
}
switch (constraint.type) {
case 'boolean':
return isValidBoolean(value);
case 'number':
return isValidNumber(constraint, value);
case 'enum':
return isValidEnum(constraint, value);
case 'string':
return isValidString(constraint, value);
}
Logger.tag('validation').error('No validation method for constraint type: {}', constraint.type);
return false;
}
function areValid(constraints: Constraints, acceptUndefined: boolean, values: Values): boolean {
const fields = new Set(Object.keys(constraints));
for (const field of fields) {
if (!isValid(constraints[field], acceptUndefined, values[field])) {
return false;
}
}
for (const field of Object.keys(values)) {
if (!fields.has(field)) {
Logger.tag('validation').error('Validation failed: No constraint for field: {}', field);
return false;
}
}
return true;
}
export function forConstraint (constraint: Constraint, acceptUndefined: boolean): (value: any) => boolean {
return ((value: any): boolean => isValid(constraint, acceptUndefined, value));
}
export function forConstraints (constraints: Constraints, acceptUndefined: boolean): (values: Values) => boolean {
return ((values: Values): boolean => areValid(constraints, acceptUndefined, values));
}