node-validator
Version:
A simple, extensible object property validator for node.js. Supports express.js.
529 lines (464 loc) • 13.3 kB
JavaScript
var util = require('util');
var moment = require('moment');
var ValidationError = require('./ValidationError');
exports.run = run;
exports.runYieldable = runYieldable;
exports.express = expressMiddleWare;
exports.bodyValidator = bodyValidator;
exports.expressParams = expressParamsMiddleWare;
exports.paramsValidator = paramsValidator;
exports.expressQuery = expressQueryMiddleWare;
exports.queryValidator = queryValidator;
exports.koaBody = checkKoaBody;
exports.koaQuery = checkKoaQuery;
exports.koaParams = checkKoaParams;
exports.isObject = isObject;
exports.isString = isString;
exports.isStringOrNull = isStringOrNull;
exports.isBoolean = isBoolean;
exports.isNumber = isNumber;
exports.isInteger = isInteger;
exports.isDate = isDate;
exports.isIsoDate = isIsoDate;
exports.isIsoDateTime = isIsoDateTime;
exports.isAnyObject = isAnyObject;
exports.isArray = isArray;
function run(validator, value, callback) {
var errors = [];
function onError(message, childName, childValue) {
errors.push({
parameter: childName,
value: childValue,
message: message
});
}
validator.validate(value, onError);
callback(errors.length, errors);
}
function expressMiddleWare(check) {
return function checkBody(req, res, next) {
run(check, req.body, function(errors, details) {
if (errors > 0) {
res.status(400).send({errors: details});
return;
}
next();
});
}
}
function bodyValidator(check) {
return function checkBody(req, res, next) {
run(check, req.body, function(errors, details) {
if (errors > 0) {
return next(new ValidationError(details));
}
next();
});
}
}
function expressParamsMiddleWare(check) {
return function checkParams(req, res, next) {
run(check, req.params, function(errors, details) {
if (errors > 0) {
res.status(400).send({errors: details});
return;
}
next();
});
}
}
function paramsValidator(check) {
return function checkParams(req, res, next) {
run(check, req.params, function(errors, details) {
if (errors > 0) {
return next(new ValidationError(details));
}
next();
});
}
}
function expressQueryMiddleWare(check) {
return function checkQuery(req, res, next) {
run(check, req.query, function(errors, details) {
if (errors > 0) {
res.status(400).send({errors: details});
return;
}
next();
});
}
}
function queryValidator(check) {
return function checkQuery(req, res, next) {
run(check, req.query, function(errors, details) {
if (errors > 0) {
return next(new ValidationError(details));
}
next();
});
}
}
function runYieldable(validator, value) {
return function(callback) {
run(validator, value, function(errors, details) {
if (errors > 0) {
callback(new ValidationError(details));
}
callback();
});
};
}
function checkKoaBody(validator) {
return function *(next) {
try {
yield runYieldable(validator, this.request.body);
yield next;
} catch (err) {
if (!(err instanceof ValidationError)) {
throw err;
}
this.status = 400;
this.body = {
errors: err.details
};
}
};
}
function checkKoaQuery(validator) {
return function *(next) {
try {
yield runYieldable(validator, this.query);
yield next;
} catch (err) {
if (!(err instanceof ValidationError)) {
throw err;
}
this.status = 400;
this.body = {
errors: err.details
};
}
};
}
function checkKoaParams(validator) {
return function *(next) {
try {
yield runYieldable(validator, this.params);
yield next;
} catch (err) {
if (!(err instanceof ValidationError)) {
throw err;
}
this.status = 400;
this.body = {
errors: err.details
};
}
};
}
function isObject() {
var v = {
withRequired: required,
withOptional: optional,
withCustom: custom,
validate: validate
};
var rules = {};
var globalRules = [];
return v;
function required(name, validator) {
rules[name] = {required: true, validator: validator};
return v;
}
function optional(name, validator) {
rules[name] = {validator: validator};
return v;
}
function custom(customValidator) {
globalRules.push({validator: {validate: customValidator}});
return v;
}
function validate(value, onError) {
if (value === null || value === undefined) {
return onError('Required value.');
}
if (typeof value !== 'object') {
return onError('Incorrect type. Expected object.');
}
var result = null;
// check for unexpected values
for (var parameter in value) {
if (rules[parameter]) {
continue;
}
result |= onError('Unexpected value.', parameter, value[parameter]);
}
// check rules
for (var parameterName in rules) {
var parameterValue = value[parameterName];
var rule = rules[parameterName];
if ((parameterValue === undefined || parameterValue === null) && rule.required) {
result |= onError('Required value.', parameterName, parameterValue);
continue;
}
if (parameterValue === undefined || parameterValue === null || !rule.validator) {
continue;
}
if (rule.validator) {
rule.validator.validate(parameterValue, function(message, childName, childValue) {
var name;
if (childName) {
if (childName[0] === '[') {
name = parameterName + childName;
} else {
name = parameterName + '.' + childName;
}
} else {
name = parameterName;
}
result |= onError(message, name, childValue !== undefined ? childValue : parameterValue)
});
}
}
// global rules
for (var i in globalRules) {
var globalRule = globalRules[i];
if (globalRule.validator) {
result |= globalRule.validator.validate(value, onError);
}
}
return result;
}
}
function isString(options) {
return {
validate: validate
};
function validate(value, onError) {
if (value === null || value === undefined) {
return onError('Required value.');
}
if (!util.isString(value)) {
return onError('Incorrect type. Expected string.');
}
if (options) {
if (options.regex && !options.regex.test(value)) {
return onError(options.message || 'Invalid value. Value must match required pattern.');
}
}
return null;
}
}
function isStringOrNull(options) {
return {
validate: validate
};
function validate(value, onError) {
if (value !== null && !util.isString(value)) {
return onError('Incorrect type. Expected string.');
}
if (options) {
if (options.regex && !options.regex.test(value)) {
return onError(options.message || 'Invalid value. Value must match required pattern.');
}
}
return null;
}
}
function isBoolean() {
return {
validate: validate
};
function validate(value, onError) {
if (value === null || value === undefined) {
return onError('Required value.');
}
if (!util.isBoolean(value)) {
return onError('Incorrect type. Expected boolean.');
}
return null;
}
}
function isNumber(options) {
options = options || {};
return {
validate: validate
};
function validate(value, onError) {
if (value === null || value === undefined) {
return onError('Required value.');
}
var acceptType = util.isNumber(value) || (options['allowString'] && util.isString(value) && !isNaN(value));
if (!acceptType || isNaN(value)) {
return onError('Incorrect type. Expected number.');
}
if (options) {
if ((options.min || options.min === 0) && value < options.min) {
return onError('Value must be greater than or equal to ' + options.min + '.');
}
if ((options.max || options.max === 0) && value > options.max) {
return onError('Value must be less than or equal to ' + options.max + '.');
}
}
return null;
}
}
function isInteger(options) {
options = options || {};
return {
validate: validate
};
function validate(value, onError) {
if (value === null || value === undefined) {
return onError('Required value.');
}
var acceptType = util.isNumber(value) || (options['allowString'] && util.isString(value) && !isNaN(value));
if (!acceptType || isNaN(value)) {
return onError('Incorrect type. Expected number.');
}
if (!/^-?\d+$/.test(value)) {
return onError('Value must be integer (whole number).');
}
if (options) {
if ((options.min || options.min === 0) && value < options.min) {
return onError('Value must be greater than or equal to ' + options.min + '.');
}
if ((options.max || options.max === 0) && value > options.max) {
return onError('Value must be less than or equal to ' + options.max + '.');
}
}
return null;
}
}
function isDate(options) {
options = options || {};
return {
validate: validate
};
function validate(value, onError) {
if (value === null || value === undefined) {
return onError('Required value.');
}
if (value instanceof Date) {
return null;
}
if (options.format) {
if (!moment(value, options.format, true).isValid()) {
return onError(options.message || ('Invalid value. Format is not valid. Expected ' + options.format));
}
} else {
if (!moment(new Date(value)).isValid()) {
return onError(options.message || 'Invalid value. Format is not valid.');
}
}
return null;
}
}
function isIsoDate(options) {
options = options || {};
options.format = 'YYYY-MM-DD';
return isDate(options);
}
function isIsoDateTime(options) {
options = options || {};
options.format = ['YYYY-MM-DDTHH:mm:ssZZ', 'YYYY-MM-DDTHH:mm:ss.SSSZZ', 'YYYY-MM-DD HH:mm:ssZZ', 'YYYY-MM-DD HH:mm:ss.SSSZZ'];
return isDate(options);
}
function isAnyObject() {
var v = {
withRequired: required,
withOptional: optional,
withCustom: custom,
validate: validate
};
var rules = {};
var globalRules = [];
return v;
function required(name, validator) {
rules[name] = {required: true, validator: validator};
return v;
}
function optional(name, validator) {
rules[name] = {validator: validator};
return v;
}
function custom(customValidator) {
globalRules.push({validator: {validate: customValidator}});
return v;
}
function validate(value, onError) {
if (value === null || value === undefined) {
return onError('Required value.');
}
if (typeof value !== 'object') {
return onError('Incorrect type. Expected object.');
}
// check rules
var result = null;
for (var parameterName in rules) {
var parameterValue = value[parameterName];
var rule = rules[parameterName];
if ((parameterValue === undefined || parameterValue === null) && rule.required) {
result |= onError('Required value.', parameterName, parameterValue);
continue;
}
if (parameterValue === undefined || parameterValue === null || !rule.validator) {
continue;
}
function bubbleError(message, childName, childValue) {
var name;
if (childName) {
if (childName[0] === '[') {
name = parameterName + childName;
} else {
name = parameterName + '.' + childName;
}
} else {
name = parameterName;
}
result |= onError(message, name, childValue !== undefined ? childValue : parameterValue)
}
if (rule.validator) {
rule.validator.validate(parameterValue, bubbleError);
}
}
// global rules
for (var i in globalRules) {
var globalRule = globalRules[i];
if (globalRule.validator) {
result |= globalRule.validator.validate(value, onError);
}
}
return result;
}
}
function isArray(validator, options) {
return {
validate: validate
};
function validate(value, onError) {
if (value === null || value === undefined) {
return onError('Required value.');
}
if (!(value instanceof Array)) {
return onError('Incorrect type. Expected array.');
}
if (options) {
if (options.min && value.length < options.min) {
return onError('Count must be greater than or equal to ' + options.min + '.', 'length', value.length);
}
if (options.max && value.length > options.max) {
return onError('Count must be less than or equal to ' + options.max + '.', 'length', value.length);
}
}
var result = null;
if (validator) {
for (var j in value) {
validator.validate(value[j], function(message, childName, childValue) {
result |= onError(message, childName ? '[' + j + '].' + childName : '[' + j + ']', childValue !== undefined ? childValue : value[j])
});
}
}
return result;
}
}