From 5c0b5abf730e36cf0c1a7307c12f7ccd88b0240d Mon Sep 17 00:00:00 2001 From: baldo Date: Wed, 8 Apr 2020 22:45:21 +0200 Subject: [PATCH] Typescript migration: * validation/validator.js --- server/utils/strings.ts | 2 +- server/validation/validator.js | 96 ------------------------ server/validation/validator.ts | 121 +++++++++++++++++++++++++++++++ shared/validation/constraints.js | 2 +- 4 files changed, 123 insertions(+), 98 deletions(-) delete mode 100644 server/validation/validator.js create mode 100644 server/validation/validator.ts diff --git a/server/utils/strings.ts b/server/utils/strings.ts index 5f9c345..0c9c218 100644 --- a/server/utils/strings.ts +++ b/server/utils/strings.ts @@ -17,7 +17,7 @@ export function normalizeMac (mac: string): string { return macParts.join(':'); } -export function parseInt (str: string): number | undefined { +export function parseInteger (str: string): number | undefined { const parsed = _.parseInt(str, 10); return parsed.toString() === str ? parsed : undefined; } diff --git a/server/validation/validator.js b/server/validation/validator.js deleted file mode 100644 index 92b19cd..0000000 --- a/server/validation/validator.js +++ /dev/null @@ -1,96 +0,0 @@ -'use strict'; - -const _ = require('lodash') - -const Strings = require('../utils/strings') -const Logger = require('../logger') - -// TODO: sanitize input for further processing as specified by constraints (correct types, trimming, etc.) - -function isValidBoolean(value) { - return _.isBoolean(value) || value === 'true' || value === 'false'; -} - -function isValidNumber(constraint, value) { - if (_.isString(value)) { - value = Strings.parseInt(value); - } - - if (!_.isNumber(value)) { - return false; - } - - if (_.isNaN(value) || !_.isFinite(value)) { - return false; - } - - if (_.isNumber(constraint.min) && value < constraint.min) { - return false; - } - - if (_.isNumber(constraint.max) && value > constraint.max) { - return false; - } - - return true; -} - -function isValidEnum(constraint, value) { - if (!_.isString(value)) { - return false; - } - - return _.indexOf(constraint.allowed, value) >= 0; -} - -function isValidString(constraint, value) { - if (!_.isString(value)) { - return false; - } - - const trimmed = value.trim(); - return (trimmed === '' && constraint.optional) || trimmed.match(constraint.regex); -} - -function isValid(constraint, acceptUndefined, value) { - if (value === undefined) { - return acceptUndefined || constraint.optional; - } - - 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, acceptUndefined, values) { - const fields = Object.keys(constraints); - for (let i = 0; i < fields.length; i ++) { - const field = fields[i]; - if (!isValid(constraints[field], acceptUndefined, values[field])) { - return false; - } - } - return true; -} - -module.exports = { - forConstraint (constraint, acceptUndefined) { - return _.partial(isValid, constraint, acceptUndefined); - }, - forConstraints (constraints, acceptUndefined) { - return _.partial(areValid, constraints, acceptUndefined); - } -} diff --git a/server/validation/validator.ts b/server/validation/validator.ts new file mode 100644 index 0000000..fe6278f --- /dev/null +++ b/server/validation/validator.ts @@ -0,0 +1,121 @@ +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)); +} diff --git a/shared/validation/constraints.js b/shared/validation/constraints.js index 5eab05a..c9ca4a8 100644 --- a/shared/validation/constraints.js +++ b/shared/validation/constraints.js @@ -4,7 +4,7 @@ var constraints = { id:{ type: 'string', - regex: /^[1-9][0-9]*/, + regex: /^[1-9][0-9]*$/, optional: false }, token:{