Typescript migration:
* utils/resources.js * validation/constraints.js (only server side)
This commit is contained in:
parent
d97635d32a
commit
b1755047af
7 changed files with 459 additions and 234 deletions
|
@ -15,7 +15,7 @@
|
|||
}
|
||||
|
||||
.task-state-running {
|
||||
color: darkblue;
|
||||
color: #204d74;
|
||||
}
|
||||
|
||||
.task-state-failed {
|
||||
|
|
|
@ -1,229 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
const Constraints = require('../validation/constraints')
|
||||
const ErrorTypes = require('../utils/errorTypes')
|
||||
const Logger = require('../logger')
|
||||
const Validator = require('../validation/validator')
|
||||
|
||||
function respond(res, httpCode, data, type) {
|
||||
switch (type) {
|
||||
case 'html':
|
||||
res.writeHead(httpCode, {'Content-Type': 'text/html'});
|
||||
res.end(data);
|
||||
break;
|
||||
|
||||
default:
|
||||
res.writeHead(httpCode, {'Content-Type': 'application/json'});
|
||||
res.end(JSON.stringify(data));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function orderByClause(restParams, defaultSortField, allowedSortFields) {
|
||||
let sortField = _.includes(allowedSortFields, restParams._sortField) ? restParams._sortField : undefined;
|
||||
if (!sortField) {
|
||||
sortField = defaultSortField;
|
||||
}
|
||||
|
||||
return {
|
||||
query: 'ORDER BY ' + sortField + ' ' + (restParams._sortDir === 'ASC' ? 'ASC' : 'DESC'),
|
||||
params: []
|
||||
};
|
||||
}
|
||||
|
||||
function limitOffsetClause(restParams) {
|
||||
const page = restParams._page;
|
||||
const perPage = restParams._perPage;
|
||||
|
||||
return {
|
||||
query: 'LIMIT ? OFFSET ?',
|
||||
params: [perPage, ((page - 1) * perPage)]
|
||||
};
|
||||
}
|
||||
|
||||
function escapeForLikePattern(str) {
|
||||
return str
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/%/g, '\\%')
|
||||
.replace(/_/g, '\\_');
|
||||
}
|
||||
|
||||
function filterCondition(restParams, filterFields) {
|
||||
if (_.isEmpty(filterFields)) {
|
||||
return {
|
||||
query: '1 = 1',
|
||||
params: []
|
||||
};
|
||||
}
|
||||
|
||||
let query = _.join(
|
||||
_.map(filterFields, function (field) {
|
||||
return 'LOWER(' + field + ') LIKE ?';
|
||||
}),
|
||||
' OR '
|
||||
);
|
||||
|
||||
query += ' ESCAPE \'\\\'';
|
||||
|
||||
const search = '%' + (_.isString(restParams.q) ? escapeForLikePattern(_.toLower(restParams.q.trim())) : '') + '%';
|
||||
const params = _.times(filterFields.length, _.constant(search));
|
||||
|
||||
return {
|
||||
query: query,
|
||||
params: params
|
||||
};
|
||||
}
|
||||
|
||||
function getConstrainedValues(data, constraints) {
|
||||
const values = {};
|
||||
_.each(_.keys(constraints), function (key) {
|
||||
const value = data[key];
|
||||
values[key] =
|
||||
_.isUndefined(value) && !_.isUndefined(constraints[key].default) ? constraints[key].default : value;
|
||||
});
|
||||
return values;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getData (req) {
|
||||
return _.extend({}, req.body, req.params, req.query);
|
||||
},
|
||||
|
||||
getValidRestParams(type, subtype, req, callback) {
|
||||
const constraints = Constraints.rest[type];
|
||||
if (!_.isPlainObject(constraints)) {
|
||||
Logger.tag('validation', 'rest').error('Unknown REST resource type: {}', type);
|
||||
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
|
||||
}
|
||||
|
||||
let filterConstraints = {};
|
||||
if (subtype) {
|
||||
filterConstraints = Constraints[subtype + 'Filters'];
|
||||
if (!_.isPlainObject(filterConstraints)) {
|
||||
Logger.tag('validation', 'rest').error('Unknown REST resource subtype: {}', subtype);
|
||||
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
|
||||
}
|
||||
}
|
||||
|
||||
const data = this.getData(req);
|
||||
|
||||
const restParams = getConstrainedValues(data, constraints);
|
||||
const filterParams = getConstrainedValues(data, filterConstraints);
|
||||
|
||||
const areValidParams = Validator.forConstraints(constraints);
|
||||
const areValidFilters = Validator.forConstraints(filterConstraints);
|
||||
if (!areValidParams(restParams) || !areValidFilters(filterParams)) {
|
||||
return callback({data: 'Invalid REST parameters.', type: ErrorTypes.badRequest});
|
||||
}
|
||||
|
||||
restParams.filters = filterParams;
|
||||
|
||||
callback(null, restParams);
|
||||
},
|
||||
|
||||
filter (entities, allowedFilterFields, restParams) {
|
||||
let query = restParams.q;
|
||||
if (query) {
|
||||
query = _.toLower(query.trim());
|
||||
}
|
||||
|
||||
function queryMatches(entity) {
|
||||
if (!query) {
|
||||
return true;
|
||||
}
|
||||
return _.some(allowedFilterFields, function (field) {
|
||||
let value = entity[field];
|
||||
if (_.isNumber(value)) {
|
||||
value = value.toString();
|
||||
}
|
||||
|
||||
if (!_.isString(value) || _.isEmpty(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value = _.toLower(value);
|
||||
if (field === 'mac') {
|
||||
return _.includes(value.replace(/:/g, ''), query.replace(/:/g, ''));
|
||||
}
|
||||
|
||||
return _.includes(value, query);
|
||||
});
|
||||
}
|
||||
|
||||
const filters = restParams.filters;
|
||||
|
||||
function filtersMatch(entity) {
|
||||
if (_.isEmpty(filters)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return _.every(filters, function (value, key) {
|
||||
if (_.isUndefined(value)) {
|
||||
return true;
|
||||
}
|
||||
if (_.startsWith(key, 'has')) {
|
||||
const entityKey = key.substr(3, 1).toLowerCase() + key.substr(4);
|
||||
return _.isEmpty(entity[entityKey]).toString() !== value;
|
||||
}
|
||||
return entity[key] === value;
|
||||
});
|
||||
}
|
||||
|
||||
return _.filter(entities, function (entity) {
|
||||
return queryMatches(entity) && filtersMatch(entity);
|
||||
});
|
||||
},
|
||||
|
||||
sort (entities, allowedSortFields, restParams) {
|
||||
const sortField = _.includes(allowedSortFields, restParams._sortField) ? restParams._sortField : undefined;
|
||||
if (!sortField) {
|
||||
return entities;
|
||||
}
|
||||
|
||||
const sorted = _.sortBy(entities, [sortField]);
|
||||
|
||||
return restParams._sortDir === 'ASC' ? sorted : _.reverse(sorted);
|
||||
},
|
||||
|
||||
getPageEntities (entities, restParams) {
|
||||
const page = restParams._page;
|
||||
const perPage = restParams._perPage;
|
||||
|
||||
return entities.slice((page - 1) * perPage, page * perPage);
|
||||
},
|
||||
|
||||
whereCondition: filterCondition,
|
||||
|
||||
filterClause (restParams, defaultSortField, allowedSortFields, filterFields) {
|
||||
const orderBy = orderByClause(
|
||||
restParams,
|
||||
defaultSortField,
|
||||
allowedSortFields
|
||||
);
|
||||
const limitOffset = limitOffsetClause(restParams);
|
||||
|
||||
const filter = filterCondition(
|
||||
restParams,
|
||||
filterFields
|
||||
);
|
||||
|
||||
return {
|
||||
query: filter.query + ' ' + orderBy.query + ' ' + limitOffset.query,
|
||||
params: _.concat(filter.params, orderBy.params, limitOffset.params)
|
||||
};
|
||||
},
|
||||
|
||||
success (res, data) {
|
||||
respond(res, 200, data, 'json');
|
||||
},
|
||||
|
||||
successHtml (res, html) {
|
||||
respond(res, 200, html, 'html');
|
||||
},
|
||||
|
||||
error (res, err) {
|
||||
respond(res, err.type.code, err.data, 'json');
|
||||
}
|
||||
}
|
271
server/utils/resources.ts
Normal file
271
server/utils/resources.ts
Normal file
|
@ -0,0 +1,271 @@
|
|||
import _ from "lodash";
|
||||
|
||||
import CONSTRAINTS from "../validation/constraints";
|
||||
import ErrorTypes from "../utils/errorTypes";
|
||||
import Logger from "../logger";
|
||||
import {Constraints, forConstraints, isConstraints} from "../validation/validator";
|
||||
import {Request, Response} from "express";
|
||||
|
||||
export type Entity = {[key: string]: any};
|
||||
|
||||
export type RestParams = {
|
||||
q?: string;
|
||||
|
||||
_sortField?: string;
|
||||
_sortDir?: string;
|
||||
|
||||
_page: number;
|
||||
_perPage: number;
|
||||
|
||||
filters?: FilterClause;
|
||||
};
|
||||
|
||||
export type OrderByClause = {query: string, params: any[]};
|
||||
export type LimitOffsetClause = {query: string, params: any[]};
|
||||
export type FilterClause = {query: string, params: any[]};
|
||||
|
||||
function respond(res: Response, httpCode: number, data: any, type: string): void {
|
||||
switch (type) {
|
||||
case 'html':
|
||||
res.writeHead(httpCode, {'Content-Type': 'text/html'});
|
||||
res.end(data);
|
||||
break;
|
||||
|
||||
default:
|
||||
res.writeHead(httpCode, {'Content-Type': 'application/json'});
|
||||
res.end(JSON.stringify(data));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function orderByClause(
|
||||
restParams: RestParams,
|
||||
defaultSortField: string,
|
||||
allowedSortFields: string[]
|
||||
): OrderByClause {
|
||||
let sortField = _.includes(allowedSortFields, restParams._sortField) ? restParams._sortField : undefined;
|
||||
if (!sortField) {
|
||||
sortField = defaultSortField;
|
||||
}
|
||||
|
||||
return {
|
||||
query: 'ORDER BY ' + sortField + ' ' + (restParams._sortDir === 'ASC' ? 'ASC' : 'DESC'),
|
||||
params: []
|
||||
};
|
||||
}
|
||||
|
||||
function limitOffsetClause(restParams: RestParams): LimitOffsetClause {
|
||||
const page = restParams._page;
|
||||
const perPage = restParams._perPage;
|
||||
|
||||
return {
|
||||
query: 'LIMIT ? OFFSET ?',
|
||||
params: [perPage, ((page - 1) * perPage)]
|
||||
};
|
||||
}
|
||||
|
||||
function escapeForLikePattern(str: string): string {
|
||||
return str
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/%/g, '\\%')
|
||||
.replace(/_/g, '\\_');
|
||||
}
|
||||
|
||||
function filterCondition(restParams: RestParams, filterFields: string[]): FilterClause {
|
||||
if (_.isEmpty(filterFields)) {
|
||||
return {
|
||||
query: '1 = 1',
|
||||
params: []
|
||||
};
|
||||
}
|
||||
|
||||
let query = _.join(
|
||||
_.map(filterFields, function (field) {
|
||||
return 'LOWER(' + field + ') LIKE ?';
|
||||
}),
|
||||
' OR '
|
||||
);
|
||||
|
||||
query += ' ESCAPE \'\\\'';
|
||||
|
||||
const search = '%' + (_.isString(restParams.q) ? escapeForLikePattern(_.toLower(restParams.q.trim())) : '') + '%';
|
||||
const params = _.times(filterFields.length, _.constant(search));
|
||||
|
||||
return {
|
||||
query: query,
|
||||
params: params
|
||||
};
|
||||
}
|
||||
|
||||
function getConstrainedValues(data: {[key: string]: any}, constraints: Constraints): {[key: string]: any} {
|
||||
const values: {[key: string]: any} = {};
|
||||
_.each(_.keys(constraints), (key: string): void => {
|
||||
const value = data[key];
|
||||
values[key] =
|
||||
_.isUndefined(value) && key in constraints && !_.isUndefined(constraints[key].default)
|
||||
? constraints[key].default
|
||||
: value;
|
||||
});
|
||||
return values;
|
||||
}
|
||||
|
||||
export function getData (req: Request): any {
|
||||
return _.extend({}, req.body, req.params, req.query);
|
||||
}
|
||||
|
||||
// TODO: Promisify.
|
||||
export function getValidRestParams(
|
||||
type: string,
|
||||
subtype: string,
|
||||
req: Request,
|
||||
callback: (err: {data: any, type: {code: number}} | null, restParams?: RestParams) => void
|
||||
) {
|
||||
const restConstraints = CONSTRAINTS.rest as {[key: string]: any};
|
||||
let constraints: Constraints;
|
||||
if (!(type in restConstraints) || !isConstraints(restConstraints[type])) {
|
||||
Logger.tag('validation', 'rest').error('Unknown REST resource type: {}', type);
|
||||
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
|
||||
}
|
||||
constraints = restConstraints[type];
|
||||
|
||||
let filterConstraints: Constraints = {};
|
||||
if (subtype) {
|
||||
const subtypeFilters = subtype + 'Filters';
|
||||
const constraintsObj = CONSTRAINTS as {[key: string]: any};
|
||||
if (!(subtypeFilters in constraintsObj) || !isConstraints(constraintsObj[subtypeFilters])) {
|
||||
Logger.tag('validation', 'rest').error('Unknown REST resource subtype: {}', subtype);
|
||||
return callback({data: 'Internal error.', type: ErrorTypes.internalError});
|
||||
}
|
||||
filterConstraints = constraintsObj[subtypeFilters];
|
||||
}
|
||||
|
||||
const data = getData(req);
|
||||
|
||||
const restParams = getConstrainedValues(data, constraints);
|
||||
const filterParams = getConstrainedValues(data, filterConstraints);
|
||||
|
||||
const areValidParams = forConstraints(constraints, false);
|
||||
const areValidFilters = forConstraints(filterConstraints, false);
|
||||
if (!areValidParams(restParams) || !areValidFilters(filterParams)) {
|
||||
return callback({data: 'Invalid REST parameters.', type: ErrorTypes.badRequest});
|
||||
}
|
||||
|
||||
restParams.filters = filterParams;
|
||||
|
||||
callback(null, restParams as RestParams);
|
||||
}
|
||||
|
||||
export function filter (entities: {[key: string]: Entity}, allowedFilterFields: string[], restParams: RestParams) {
|
||||
let query = restParams.q;
|
||||
if (query) {
|
||||
query = _.toLower(query.trim());
|
||||
}
|
||||
|
||||
function queryMatches(entity: Entity): boolean {
|
||||
if (!query) {
|
||||
return true;
|
||||
}
|
||||
return _.some(allowedFilterFields, (field: string): boolean => {
|
||||
if (!query) {
|
||||
return true;
|
||||
}
|
||||
let value = entity[field];
|
||||
if (_.isNumber(value)) {
|
||||
value = value.toString();
|
||||
}
|
||||
|
||||
if (!_.isString(value) || _.isEmpty(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value = _.toLower(value);
|
||||
if (field === 'mac') {
|
||||
return _.includes(value.replace(/:/g, ''), query.replace(/:/g, ''));
|
||||
}
|
||||
|
||||
return _.includes(value, query);
|
||||
});
|
||||
}
|
||||
|
||||
const filters = restParams.filters;
|
||||
|
||||
function filtersMatch(entity: Entity): boolean {
|
||||
if (_.isEmpty(filters)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return _.every(filters, (value: any, key: string): boolean => {
|
||||
if (_.isUndefined(value)) {
|
||||
return true;
|
||||
}
|
||||
if (_.startsWith(key, 'has')) {
|
||||
const entityKey = key.substr(3, 1).toLowerCase() + key.substr(4);
|
||||
return _.isEmpty(entity[entityKey]).toString() !== value;
|
||||
}
|
||||
return entity[key] === value;
|
||||
});
|
||||
}
|
||||
|
||||
return _.filter(entities, function (entity) {
|
||||
return queryMatches(entity) && filtersMatch(entity);
|
||||
});
|
||||
}
|
||||
|
||||
export function sort<T>(entities: ArrayLike<T>, allowedSortFields: string[], restParams: RestParams): ArrayLike<T> {
|
||||
const sortField = _.includes(allowedSortFields, restParams._sortField) ? restParams._sortField : undefined;
|
||||
if (!sortField) {
|
||||
return entities;
|
||||
}
|
||||
|
||||
const sorted: T[] = _.sortBy(entities, [sortField]);
|
||||
if (restParams._sortDir === 'ASC') {
|
||||
return sorted;
|
||||
} else {
|
||||
return _.reverse(sorted);
|
||||
}
|
||||
}
|
||||
|
||||
export function getPageEntities (entities: Entity[], restParams: RestParams) {
|
||||
const page = restParams._page;
|
||||
const perPage = restParams._perPage;
|
||||
|
||||
return entities.slice((page - 1) * perPage, page * perPage);
|
||||
}
|
||||
|
||||
export {filterCondition as whereCondition};
|
||||
|
||||
export function filterClause (
|
||||
restParams: RestParams,
|
||||
defaultSortField: string,
|
||||
allowedSortFields: string[],
|
||||
filterFields: string[],
|
||||
): FilterClause {
|
||||
const orderBy = orderByClause(
|
||||
restParams,
|
||||
defaultSortField,
|
||||
allowedSortFields
|
||||
);
|
||||
const limitOffset = limitOffsetClause(restParams);
|
||||
|
||||
const filter = filterCondition(
|
||||
restParams,
|
||||
filterFields
|
||||
);
|
||||
|
||||
return {
|
||||
query: filter.query + ' ' + orderBy.query + ' ' + limitOffset.query,
|
||||
params: _.concat(filter.params, orderBy.params, limitOffset.params)
|
||||
};
|
||||
}
|
||||
|
||||
export function success (res: Response, data: any) {
|
||||
respond(res, 200, data, 'json');
|
||||
}
|
||||
|
||||
export function successHtml (res: Response, html: string) {
|
||||
respond(res, 200, html, 'html');
|
||||
}
|
||||
|
||||
export function error (res: Response, err: {data: any, type: {code: number}}) {
|
||||
respond(res, err.type.code, err.data, 'json');
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
../../shared/validation/constraints.js
|
119
server/validation/constraints.ts
Normal file
119
server/validation/constraints.ts
Normal file
|
@ -0,0 +1,119 @@
|
|||
// ATTENTION: Those constraints are no longer the same file as for the client / admin interface.
|
||||
// Make sure changes are also reflected in /shared/validation/constraints.js.
|
||||
|
||||
const CONSTRAINTS = {
|
||||
id:{
|
||||
type: 'string',
|
||||
regex: /^[1-9][0-9]*$/,
|
||||
optional: false
|
||||
},
|
||||
token:{
|
||||
type: 'string',
|
||||
regex: /^[0-9a-f]{16}$/i,
|
||||
optional: false
|
||||
},
|
||||
node: {
|
||||
hostname: {
|
||||
type: 'string',
|
||||
regex: /^[-a-z0-9_]{1,32}$/i,
|
||||
optional: false
|
||||
},
|
||||
key: {
|
||||
type: 'string',
|
||||
regex: /^([a-f0-9]{64})$/i,
|
||||
optional: true
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
regex: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i,
|
||||
optional: false
|
||||
},
|
||||
nickname: {
|
||||
type: 'string',
|
||||
regex: /^[-a-z0-9_ äöüß]{1,64}$/i,
|
||||
optional: false
|
||||
},
|
||||
mac: {
|
||||
type: 'string',
|
||||
regex: /^([a-f0-9]{12}|([a-f0-9]{2}:){5}[a-f0-9]{2})$/i,
|
||||
optional: false
|
||||
},
|
||||
coords: {
|
||||
type: 'string',
|
||||
regex: /^(-?[0-9]{1,3}(\.[0-9]{1,15})? -?[0-9]{1,3}(\.[0-9]{1,15})?)$/,
|
||||
optional: true
|
||||
},
|
||||
monitoring: {
|
||||
type: 'boolean',
|
||||
optional: false
|
||||
}
|
||||
},
|
||||
nodeFilters: {
|
||||
hasKey: {
|
||||
type: 'boolean',
|
||||
optional: true
|
||||
},
|
||||
hasCoords: {
|
||||
type: 'boolean',
|
||||
optional: true
|
||||
},
|
||||
onlineState: {
|
||||
type: 'string',
|
||||
regex: /^(ONLINE|OFFLINE)$/,
|
||||
optional: true
|
||||
},
|
||||
monitoringState: {
|
||||
type: 'string',
|
||||
regex: /^(disabled|active|pending)$/,
|
||||
optional: true
|
||||
},
|
||||
site: {
|
||||
type: 'string',
|
||||
regex: /^[a-z0-9_-]{1,32}$/,
|
||||
optional: true
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
regex: /^[a-z0-9_-]{1,32}$/,
|
||||
optional: true
|
||||
}
|
||||
},
|
||||
rest: {
|
||||
list: {
|
||||
_page: {
|
||||
type: 'number',
|
||||
min: 1,
|
||||
optional: true,
|
||||
default: 1
|
||||
},
|
||||
_perPage: {
|
||||
type: 'number',
|
||||
min: 1,
|
||||
max: 50,
|
||||
optional: true,
|
||||
default: 20
|
||||
},
|
||||
_sortDir: {
|
||||
type: 'enum',
|
||||
allowed: ['ASC', 'DESC'],
|
||||
optional: true,
|
||||
default: 'ASC'
|
||||
},
|
||||
_sortField: {
|
||||
type: 'string',
|
||||
regex: /^[a-zA-Z0-9_]{1,32}$/,
|
||||
optional: true
|
||||
},
|
||||
q: {
|
||||
type: 'string',
|
||||
regex: /^[äöüß a-z0-9!#$%&@:.'*+/=?^_`{|}~-]{1,64}$/i,
|
||||
optional: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default CONSTRAINTS;
|
||||
|
||||
// TODO: Remove after refactoring.
|
||||
module.exports = CONSTRAINTS;
|
|
@ -3,9 +3,11 @@ import _ from "lodash";
|
|||
import {parseInteger} from "../utils/strings";
|
||||
import Logger from "../logger";
|
||||
|
||||
interface Constraint {
|
||||
export interface Constraint {
|
||||
type: string,
|
||||
|
||||
default?: any,
|
||||
|
||||
optional?: boolean,
|
||||
|
||||
allowed?: string[],
|
||||
|
@ -16,8 +18,68 @@ interface Constraint {
|
|||
regex?: RegExp,
|
||||
}
|
||||
|
||||
type Constraints = {[key: string]: Constraint};
|
||||
type Values = {[key: string]: any};
|
||||
export type Constraints = {[key: string]: Constraint};
|
||||
export type Values = {[key: string]: any};
|
||||
|
||||
function isStringArray(arr: any): arr is string[] {
|
||||
return _.isArray(arr) && _.every(arr, (val: any) => _.isString(val));
|
||||
}
|
||||
|
||||
export function isConstraint(val: any): val is Constraint {
|
||||
if (!_.isObject(val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const constraint = val as {[key: string]: any};
|
||||
|
||||
if (!("type" in constraint) || !_.isString(constraint.type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("optional" in constraint
|
||||
&& !_.isUndefined(constraint.optional)
|
||||
&& !_.isBoolean(constraint.optional)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("allowed" in constraint
|
||||
&& !_.isUndefined(constraint.allowed)
|
||||
&& !isStringArray(constraint.allowed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("min" in constraint
|
||||
&& !_.isUndefined(constraint.min)
|
||||
&& !_.isNumber(constraint.min)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("max" in constraint
|
||||
&& !_.isUndefined(constraint.max)
|
||||
&& !_.isNumber(constraint.max)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// noinspection RedundantIfStatementJS
|
||||
if ("regex" in constraint
|
||||
&& !_.isUndefined(constraint.regex)
|
||||
&& !_.isRegExp(constraint.regex)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function isConstraints(constraints: any): constraints is Constraints {
|
||||
if (!_.isObject(constraints)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _.every(
|
||||
constraints,
|
||||
(constraint: any, key: any) => _.isString(key) && isConstraint(constraint)
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: sanitize input for further processing as specified by constraints (correct types, trimming, etc.)
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
// ATTENTION: Those constraints are no longer the same file as for the server.
|
||||
// Make sure changes are also reflected in /server/validation/constraints.ts.
|
||||
|
||||
(function () {
|
||||
var constraints = {
|
||||
id:{
|
||||
|
|
Loading…
Reference in a new issue