express-validator
Advanced tools
Comparing version 3.2.1 to 4.0.0
394
index.d.ts
@@ -7,4 +7,11 @@ // Type definitions for express-validator 3.0.0 | ||
///<reference types="express"/> | ||
///<reference types="bluebird"/> | ||
import * as express from 'express'; | ||
import { | ||
Dictionary, | ||
Result, | ||
MappedError, | ||
Options, | ||
Validator as BaseValidator | ||
} from './shared-typings'; | ||
// Add RequestValidation Interface on to Express's Request Interface. | ||
@@ -21,3 +28,3 @@ declare global { | ||
*/ | ||
declare function ExpressValidator(options?: ExpressValidator.Options.ExpressValidatorOptions): express.RequestHandler; | ||
declare function ExpressValidator(options?: Options.ExpressValidatorOptions): express.RequestHandler; | ||
export = ExpressValidator; | ||
@@ -37,5 +44,5 @@ // Internal Module. | ||
[param: string]: | ||
ExpressValidator.Options.ValidationSchemaParamOptions // standard validators | ||
Options.ValidationSchemaParamOptions // standard validators | ||
| // or | ||
{ [customValidator: string]: ExpressValidator.Options.ValidatorSchemaOptions } // custom ones | ||
{ [customValidator: string]: Options.ValidatorSchemaOptions } // custom ones | ||
} | ||
@@ -53,33 +60,2 @@ | ||
interface SanitizerFunction { (item: string): Sanitizer; } | ||
interface Dictionary<T> { [key: string]: T; } | ||
interface Result { | ||
/** | ||
* @return A boolean determining whether there were errors or not. | ||
*/ | ||
isEmpty(): boolean | ||
/** | ||
* @return All errors for all validated parameters will be included, unless you specify that you want only the first | ||
* error of each param by invoking `result.useFirstErrorOnly()`. | ||
*/ | ||
array(): MappedError[] | ||
/** | ||
* @return An object of errors, where the key is the parameter name, and the value is an error object as returned by | ||
* the error formatter. | ||
* Because of historical reasons, by default this method will return the last error of each parameter. | ||
* You can change this behavior by invoking result.useFirstErrorOnly(), so the first error is returned instead. | ||
*/ | ||
mapped(): Dictionary<MappedError> | ||
/** | ||
* Sets the `firstErrorOnly` flag of this result object, which modifies the way other methods like `result.array()` | ||
* and `result.mapped()` work. | ||
*/ | ||
useFirstErrorOnly(): Result | ||
/** | ||
* Useful for dealing with the validation errors in the catch block of a try..catch or promise. | ||
* | ||
* @throws If there are errors, throws an Error object which is decorated with the same API as the validation | ||
* result object. | ||
*/ | ||
throw(): Result | ||
} | ||
@@ -132,109 +108,7 @@ export interface RequestValidation { | ||
export interface Validator { | ||
export interface Validator extends BaseValidator { | ||
// Additional legacy validators | ||
/* | ||
* Hi fellow contributor, | ||
* TODO if you add a validator here, please add it also to ValidationSchemaParamOptions | ||
* preferably in the same order/position, just to make it easier for comparision. | ||
*/ | ||
isEmail(options?: ExpressValidator.Options.IsEmailOptions): Validator; | ||
isURL(options?: ExpressValidator.Options.IsURLOptions): Validator; | ||
isMACAddress(): Validator; | ||
/** | ||
* | ||
* @param version IP version number 4 or 6 | ||
*/ | ||
isIP(version?: IPVersion): Validator; | ||
isFQDN(options?: ExpressValidator.Options.IsFQDNOptions): Validator; | ||
isBoolean(): Validator; | ||
/** | ||
* @param locale Optional. Defaults to en-US | ||
*/ | ||
isAlpha(locale?: AlphaLocale): Validator; | ||
/** | ||
* @param locale Optional. Defaults to en-US | ||
*/ | ||
isAlphanumeric(locale?: AlphanumericLocale): Validator; | ||
isNumeric(): Validator; | ||
isLowercase(): Validator; | ||
isUppercase(): Validator; | ||
isAscii(): Validator; | ||
isFullWidth(): Validator; | ||
isHalfWidth(): Validator; | ||
isVariableWidth(): Validator; | ||
isMultibyte(): Validator; | ||
isSurrogatePair(): Validator; | ||
isInt(options?: ExpressValidator.Options.IsIntOptions): Validator; | ||
isFloat(options?: ExpressValidator.Options.MinMaxExtendedOptions): Validator; | ||
isDecimal(): Validator; | ||
isHexadecimal(): Validator; | ||
isDivisibleBy(num: number): Validator; | ||
isHexColor(): Validator; | ||
isMD5(): Validator; | ||
isJSON(): Validator; | ||
isEmpty(): Validator; | ||
isLength(options: ExpressValidator.Options.MinMaxOptions): Validator; | ||
isByteLength(options: ExpressValidator.Options.MinMaxOptions): Validator; | ||
/** | ||
* @param version 3, 4, 5 or 'all'. Default is 'all'. | ||
* @see http://en.wikipedia.org/wiki/Universally_unique_identifier | ||
*/ | ||
isUUID(version?: UUIDVersion): Validator; | ||
/** | ||
* @see https://docs.mongodb.com/manual/reference/bson-types/#objectid | ||
*/ | ||
isMongoId(): Validator; | ||
isDate(): Validator; | ||
/** | ||
* @param date Optional. Default to now. | ||
*/ | ||
isAfter(date?: Date): Validator; | ||
/** | ||
* @param date Optional. Default to now. | ||
*/ | ||
isBefore(date?: Date): Validator; | ||
isIn(options: string | string[]): Validator; | ||
isCreditCard(): Validator; | ||
isISIN(): Validator; | ||
/** | ||
* @param version | ||
* @see https://en.wikipedia.org/wiki/International_Standard_Book_Number | ||
*/ | ||
isISBN(version?: number): Validator; | ||
/** | ||
* @param options | ||
* @see https://en.wikipedia.org/wiki/International_Standard_Serial_Number | ||
*/ | ||
isISSN(options?: ExpressValidator.Options.IsISSNOptions): Validator | ||
isMobilePhone(locale: MobilePhoneLocal): Validator; | ||
isCurrency(options: ExpressValidator.Options.IsCurrencyOptions): Validator; | ||
/** | ||
* @see https://en.wikipedia.org/wiki/ISO_8601 | ||
*/ | ||
isISO8601(): Validator; | ||
/** | ||
* @see https://en.wikipedia.org/wiki/Base64 | ||
*/ | ||
isBase64(): Validator; | ||
/** | ||
* @see https://en.wikipedia.org/wiki/Data_URI_scheme | ||
*/ | ||
isDataURI(): Validator; | ||
isWhitelisted(chars: string | string[]): Validator; | ||
// Additional Validators provided by validator.js | ||
equals(equals: any): Validator; | ||
contains(str: string): Validator; | ||
matches(pattern: RegExp | string, modifiers?: string): Validator; | ||
// Additional ValidatorChain.prototype.* validators | ||
notEmpty(): Validator; | ||
len(options: ExpressValidator.Options.MinMaxOptions): Validator; | ||
optional(options?: ExpressValidator.Options.OptionalOptions): Validator; | ||
withMessage(message: string): Validator; | ||
notEmpty(): this; | ||
len(options: Options.MinMaxOptions): this; | ||
} | ||
@@ -285,241 +159,5 @@ | ||
normalizeEmail(options?: ExpressValidator.Options.NormalizeEmailOptions): Sanitizer; | ||
normalizeEmail(options?: Options.NormalizeEmailOptions): Sanitizer; | ||
} | ||
interface MappedError { | ||
param: string; | ||
msg: string; | ||
value: string; | ||
} | ||
} | ||
declare namespace ExpressValidator.Options { | ||
export interface ExpressValidatorOptions { | ||
customValidators?: { [validatorName: string]: (...value: any[]) => boolean | Promise<any> } | ||
customSanitizers?: { [sanitizername: string]: (value: any) => any } | ||
errorFormatter?: (param?: string, msg?: string, value?: any) => any | ||
} | ||
interface ValidatorSchemaOptions { | ||
options?: any[] | ||
errorMessage?: string | ||
} | ||
interface ValidationSchemaParamOptions { | ||
in?: Location | ||
errorMessage?: string | ||
// Additional ValidatorChain.prototype.* validators | ||
optional?: boolean | { checkFalsy: boolean } | ||
notEmpty?: boolean | { errorMessage: string } | ||
len?: ValidatorSchemaOptions | ||
// exported from validator.js | ||
isEmail?: ValidatorSchemaOptions | ||
isURL?: ValidatorSchemaOptions | ||
isMACAddress?: ValidatorSchemaOptions | ||
isIP?: ValidatorSchemaOptions | ||
isFQDN?: ValidatorSchemaOptions | ||
isBoolean?: ValidatorSchemaOptions | ||
isAlpha?: ValidatorSchemaOptions | ||
isAlphanumeric?: ValidatorSchemaOptions | ||
isNumeric?: ValidatorSchemaOptions | ||
isLowercase?: ValidatorSchemaOptions | ||
isUppercase?: ValidatorSchemaOptions | ||
isAscii?: ValidatorSchemaOptions | ||
isFullWidth?: ValidatorSchemaOptions | ||
isHalfWidth?: ValidatorSchemaOptions | ||
isVariableWidth?: ValidatorSchemaOptions | ||
isMultibyte?: ValidatorSchemaOptions | ||
isSurrogatePair?: ValidatorSchemaOptions | ||
isInt?: ValidatorSchemaOptions | ||
isFloat?: ValidatorSchemaOptions | ||
isDecimal?: ValidatorSchemaOptions | ||
isHexadecimal?: ValidatorSchemaOptions | ||
isDivisibleBy?: ValidatorSchemaOptions | ||
isHexColor?: ValidatorSchemaOptions | ||
isMD5?: ValidatorSchemaOptions | ||
isJSON?: ValidatorSchemaOptions | ||
isEmpty?: ValidatorSchemaOptions | ||
isLength?: ValidatorSchemaOptions | ||
isByteLength?: ValidatorSchemaOptions | ||
isUUID?: ValidatorSchemaOptions | ||
isMongoId?: ValidatorSchemaOptions | ||
isDate?: ValidatorSchemaOptions | ||
isAfter?: ValidatorSchemaOptions | ||
isBefore?: ValidatorSchemaOptions | ||
isIn?: ValidatorSchemaOptions | ||
isCreditCard?: ValidatorSchemaOptions | ||
isISIN?: ValidatorSchemaOptions | ||
isISBN?: ValidatorSchemaOptions | ||
isISSN?: ValidatorSchemaOptions | ||
isMobilePhone?: ValidatorSchemaOptions | ||
isCurrency?: ValidatorSchemaOptions | ||
isISO8601?: ValidatorSchemaOptions | ||
isBase64?: ValidatorSchemaOptions | ||
isDataURI?: ValidatorSchemaOptions | ||
isWhitelisted?: ValidatorSchemaOptions | ||
// Additional Validators provided by validator.js | ||
equals?: ValidatorSchemaOptions | ||
contains?: ValidatorSchemaOptions | ||
matches?: ValidatorSchemaOptions | ||
} | ||
// VALIDATORS | ||
interface MinMaxOptions { | ||
min?: number; | ||
max?: number; | ||
} | ||
interface MinMaxExtendedOptions extends MinMaxOptions { | ||
lt?: number; | ||
gt?: number; | ||
} | ||
interface IsIntOptions extends MinMaxExtendedOptions { | ||
allow_leading_zeroes?: boolean; | ||
} | ||
/** | ||
* defaults to | ||
* { | ||
* allow_display_name: false, | ||
* require_display_name: false, | ||
* allow_utf8_local_part: true, | ||
* require_tld: true | ||
* } | ||
*/ | ||
interface IsEmailOptions { | ||
allow_display_name?: boolean; | ||
allow_utf8_local_part?: boolean; | ||
require_tld?: boolean; | ||
} | ||
/** | ||
* defaults to | ||
* { | ||
* protocols: ['http','https','ftp'], | ||
* require_tld: true, | ||
* require_protocol: false, | ||
* require_host: true, | ||
* require_valid_protocol: true, | ||
* allow_underscores: false, | ||
* host_whitelist: false, | ||
* host_blacklist: false, | ||
* allow_trailing_dot: false, | ||
* allow_protocol_relative_urls: false | ||
* } | ||
*/ | ||
interface IsURLOptions { | ||
protocols?: URLProtocol[]; | ||
require_tld?: boolean; | ||
require_protocol?: boolean; | ||
require_host?: boolean; | ||
require_valid_protocol?: boolean; | ||
allow_underscores?: boolean; | ||
host_whitelist?: (string | RegExp)[]; | ||
host_blacklist?: (string | RegExp)[]; | ||
allow_trailing_dot?: boolean; | ||
allow_protocol_relative_urls?: boolean; | ||
} | ||
/** | ||
* defaults to | ||
* { | ||
* require_tld: true, | ||
* allow_underscores: false, | ||
* allow_trailing_dot: false | ||
* } | ||
*/ | ||
interface IsFQDNOptions { | ||
require_tld?: boolean; | ||
allow_underscores?: boolean; | ||
allow_trailing_dot?: boolean; | ||
} | ||
/** | ||
* defaults to | ||
* { | ||
* case_sensitive: false, | ||
* require_hyphen: false | ||
* } | ||
*/ | ||
interface IsISSNOptions { | ||
case_sensitive?: boolean | ||
require_hyphen?: boolean | ||
} | ||
/** | ||
* defaults to | ||
* { | ||
* symbol: '$', | ||
* require_symbol: false, | ||
* allow_space_after_symbol: false, | ||
* symbol_after_digits: false, | ||
* allow_negatives: true, | ||
* parens_for_negatives: false, | ||
* negative_sign_before_digits: false, | ||
* negative_sign_after_digits: false, | ||
* allow_negative_sign_placeholder: false, | ||
* thousands_separator: ',', | ||
* decimal_separator: '.', | ||
* allow_space_after_digits: false | ||
* } | ||
*/ | ||
interface IsCurrencyOptions { | ||
symbol?: string; | ||
require_symbol?: boolean; | ||
allow_space_after_symbol?: boolean; | ||
symbol_after_digits?: boolean; | ||
allow_negatives?: boolean; | ||
parens_for_negatives?: boolean; | ||
negative_sign_before_digits?: boolean; | ||
negative_sign_after_digits?: boolean; | ||
allow_negative_sign_placeholder?: boolean; | ||
thousands_separator?: string; | ||
decimal_separator?: string; | ||
allow_space_after_digits?: boolean; | ||
} | ||
interface OptionalOptions { | ||
checkFalsy?: boolean; | ||
} | ||
// SANITIZERS | ||
/** | ||
* Defaults to | ||
* { | ||
* all_lowercase: true | ||
* gmail_lowercase: true | ||
* gmail_remove_dots: true | ||
* gmail_remove_subaddress: true | ||
* gmail_convert_googlemaildotcom: true | ||
* outlookdotcom_lowercase: true | ||
* outlookdotcom_remove_subaddress: true | ||
* yahoo_lowercase: true | ||
* yahoo_remove_subaddress: true | ||
* icloud_lowercase: true | ||
* icloud_remove_subaddress: true | ||
* } | ||
*/ | ||
interface NormalizeEmailOptions { | ||
all_lowercase?: boolean | ||
gmail_lowercase?: boolean | ||
gmail_remove_dots?: boolean | ||
gmail_remove_subaddress?: boolean | ||
gmail_convert_googlemaildotcom?: boolean | ||
outlookdotcom_lowercase?: boolean | ||
outlookdotcom_remove_subaddress?: boolean | ||
yahoo_lowercase?: boolean | ||
yahoo_remove_subaddress?: boolean | ||
icloud_lowercase?: boolean | ||
icloud_remove_subaddress?: boolean | ||
} | ||
} |
var deprecate = require('util').deprecate; | ||
var validator = require('validator'); | ||
var _ = require('lodash'); | ||
var Promise = require('bluebird'); | ||
var utils = require('./utils'); | ||
var check = require('../check/check'); | ||
var selectFields = require('../check/select-fields'); | ||
var validationResult = require('../check/validation-result'); | ||
var validatorChainSymbol = Symbol('express-validator.validatorChain'); | ||
var sanitizerSymbol = Symbol('express-validator.sanitizer'); | ||
// Because req.validationErrors and req.asyncValidationErrors are build dynamically, | ||
@@ -21,19 +26,2 @@ // these warnings would appear everytime a new request comes in. | ||
/** | ||
* display warnings once per each validator | ||
* which returns null or undefined as a validation | ||
* result | ||
*/ | ||
var warnValidatorNilReturn = (function() { | ||
var warned = {}; | ||
return function(methodName, returnedValue) { | ||
if (warned[methodName]) { | ||
return; | ||
} | ||
warned[methodName] = true; | ||
console.warn('WARNING: unexpected return value: `' + returnedValue + '` returned by `' + methodName + '` validator'); | ||
} | ||
}()); | ||
// When validator upgraded to v5, they removed automatic string coercion | ||
@@ -81,5 +69,6 @@ // The next few methods (up to validator.init()) restores that functionality | ||
// validators and sanitizers not prefixed with is/to | ||
var additionalValidators = ['contains', 'equals', 'matches']; | ||
var additionalSanitizers = ['trim', 'ltrim', 'rtrim', 'escape', 'unescape', 'stripLow', 'whitelist', 'blacklist', 'normalizeEmail']; | ||
var allLocations = ['params', 'query', 'body', 'headers', 'cookies']; | ||
/** | ||
@@ -110,24 +99,2 @@ * Adds validation methods to request object via express middleware | ||
/** | ||
* Initializes a chain of validators | ||
* | ||
* @class | ||
* @param {(string|string[])} param path to property to validate | ||
* @param {string} failMsg validation failure message | ||
* @param {Request} req request to attach validation errors | ||
* @param {string} location request property to find value (body, params, query, etc.) | ||
* @param {object} options options containing error formatter | ||
*/ | ||
function ValidatorChain(param, failMsg, req, location, options) { | ||
this.errorFormatter = options.errorFormatter; | ||
this.param = param; | ||
this.value = location ? _.get(req[location], param) : undefined; | ||
this.validationErrors = []; | ||
this.failMsg = failMsg; | ||
this.req = req; | ||
this.lastError = null; // used by withMessage to get the values of the last error | ||
return this; | ||
} | ||
/** | ||
* Initializes a sanitizer | ||
@@ -152,2 +119,26 @@ * | ||
function createValidationChain(field, location, message, req, contexts) { | ||
const chain = check([field], Array.isArray(location) ? location : [location], message); | ||
contexts.push(chain._context); | ||
chain.notEmpty = () => chain.isLength({ min: 1 }); | ||
chain.len = chain.isLength; | ||
req[validatorChainSymbol].forEach(customValidators => { | ||
Object.keys(customValidators).forEach(name => { | ||
chain[name] = (...options) => { | ||
chain._context.validators.push({ | ||
options, | ||
negated: chain._context.negateNext, | ||
validator: customValidators[name] | ||
}); | ||
chain._context.negateNext = false; | ||
return chain; | ||
}; | ||
}); | ||
}); | ||
return chain; | ||
} | ||
/** | ||
@@ -178,9 +169,7 @@ * validate an object using a schema, using following format: | ||
* @param {string} loc request property to find value (body, params, query, etc.) | ||
* @param {Object} options options containing custom validators & errorFormatter | ||
* @return {object[]} array of errors | ||
*/ | ||
function validateSchema(schema, req, loc, options) { | ||
var locations = ['body', 'params', 'query', 'headers'], | ||
currentLoc = loc; | ||
function validateSchema(schema, req, loc, contexts) { | ||
var currentLoc = loc; | ||
@@ -191,3 +180,3 @@ for (var param in schema) { | ||
if (schema[param].hasOwnProperty('in')) { | ||
if (locations.indexOf(schema[param].in) !== -1) { | ||
if (allLocations.indexOf(schema[param].in) !== -1) { | ||
currentLoc = schema[param].in; | ||
@@ -199,7 +188,7 @@ } else { | ||
} else { | ||
currentLoc = loc === 'any' ? locate(req, param) : currentLoc; | ||
currentLoc = loc === 'any' ? allLocations : currentLoc; | ||
} | ||
var validator = new ValidatorChain(param, null, req, currentLoc, options); | ||
var paramErrorMessage = schema[param].errorMessage; | ||
const paramErrorMessage = schema[param].errorMessage; | ||
var validator = createValidationChain(param, currentLoc, paramErrorMessage, req, contexts); | ||
@@ -210,7 +199,2 @@ var opts; | ||
validator.optional.apply(validator, schema[param].optional.options); | ||
if (validator.skipValidating) { | ||
validator.failMsg = schema[param].optional.errorMessage || paramErrorMessage || 'Invalid param'; | ||
continue; // continue with the next param in schema | ||
} | ||
} | ||
@@ -228,13 +212,10 @@ | ||
if (methodName === 'errorMessage') { | ||
/* Also do not validate if methodName | ||
* represent parameter error message | ||
*/ | ||
if (methodName === 'errorMessage' || !schema[param][methodName]) { | ||
// Also do not validate if methodName represent parameter error message | ||
// or if the value is falsy | ||
continue; | ||
} | ||
validator.failMsg = schema[param][methodName].errorMessage || paramErrorMessage || 'Invalid param'; | ||
opts = schema[param][methodName].options || []; | ||
opts = schema[param][methodName].options; | ||
if (opts != null && !Array.isArray(opts)) { | ||
@@ -244,3 +225,5 @@ opts = [opts]; | ||
validator[methodName].apply(validator, opts); | ||
validator | ||
[methodName](...opts) | ||
.withMessage(schema[param][methodName].errorMessage); | ||
} | ||
@@ -250,99 +233,72 @@ } | ||
// _.set validators and sanitizers as prototype methods on corresponding chains | ||
// _.set sanitizers as prototype methods on corresponding chains | ||
_.forEach(validator, function(method, methodName) { | ||
if (methodName.match(/^is/) || _.includes(additionalValidators, methodName)) { | ||
ValidatorChain.prototype[methodName] = makeValidator(methodName, validator); | ||
} | ||
if (methodName.match(/^to/) || _.includes(additionalSanitizers, methodName)) { | ||
Sanitizer.prototype[methodName] = makeSanitizer(methodName, validator); | ||
Sanitizer.prototype[methodName] = utils.makeSanitizer(methodName, validator); | ||
} | ||
}); | ||
ValidatorChain.prototype.notEmpty = function() { | ||
return this.isLength({ | ||
min: 1 | ||
}); | ||
}; | ||
utils.mapAndExtend(options.customSanitizers, Sanitizer.prototype, utils.makeSanitizer); | ||
ValidatorChain.prototype.len = function() { | ||
return this.isLength.apply(this, arguments); | ||
}; | ||
return function(req, res, next) { | ||
const contexts = []; | ||
function runContexts() { | ||
contexts.filter(context => !context.promise).forEach(context => { | ||
const field = selectFields(req, context)[0]; | ||
if (!field) { | ||
context.promise = Promise.resolve(); | ||
return; | ||
} | ||
ValidatorChain.prototype.optional = function(opts) { | ||
opts = opts || {}; | ||
// By default, optional checks if the key exists, but the user can pass in | ||
// checkFalsy: true to skip validation if the property is falsy | ||
var defaults = { | ||
checkFalsy: false | ||
}; | ||
const promises = context.validators.map(validatorCfg => { | ||
const result = validatorCfg.validator(field.value, ...validatorCfg.options); | ||
const errorObj = options.errorFormatter( | ||
utils.formatParamOutput(field.path), | ||
utils.replaceArgs( | ||
validatorCfg.message || context.message || 'Invalid value', | ||
[field.value, ...validatorCfg.options] | ||
), | ||
field.value | ||
); | ||
var options = _.assign(defaults, opts); | ||
if (result && result.then) { | ||
req._asyncValidationErrors.push(result.then(() => { | ||
validatorCfg.negated && req._validationErrors.push(errorObj); | ||
}, () => { | ||
!validatorCfg.negated && req._validationErrors.push(errorObj); | ||
})); | ||
} else if ((!validatorCfg.negated && !result) || (validatorCfg.negated && result)) { | ||
req._validationErrors.push(errorObj); | ||
} | ||
}); | ||
if (options.checkFalsy) { | ||
if (!this.value) { | ||
this.skipValidating = true; | ||
} | ||
} else { | ||
if (this.value === undefined) { | ||
this.skipValidating = true; | ||
} | ||
context.promise = Promise.all(promises); | ||
}); | ||
} | ||
return this; | ||
}; | ||
var locations = ['body', 'params', 'query', 'cookies']; | ||
ValidatorChain.prototype.withMessage = function(message) { | ||
if (this.lastError) { | ||
if (this.lastError.isAsync) { | ||
this.req._asyncValidationErrors.pop().catch(function() { | ||
// Suppress errors from original promise - they should go to the new one. | ||
// Otherwise bluebird throws an 'unhandled rejection' error | ||
}); | ||
var error = formatErrors.call(this.lastError.context, this.lastError.param, message, this.lastError.value); | ||
var promise = this.lastError.promise.catch(function() { | ||
return Promise.reject(error); | ||
}); | ||
this.req._asyncValidationErrors.push(promise); | ||
} else { | ||
this.validationErrors.pop(); | ||
this.req._validationErrors.pop(); | ||
var errorMessage = formatErrors.call(this, this.lastError.param, message, this.lastError.value); | ||
this.validationErrors.push(errorMessage); | ||
this.req._validationErrors.push(errorMessage); | ||
this.lastError = null; | ||
} | ||
// Extend existing validators. Fixes bug #341 | ||
req[validatorChainSymbol] = req[validatorChainSymbol] || []; | ||
req[validatorChainSymbol].push(options.customValidators); | ||
// Extend existing sanitizer. Fixes bug #341 | ||
if (req[sanitizerSymbol] && req[sanitizerSymbol] !== Sanitizer) { | ||
Sanitizer = req[sanitizerSymbol]; | ||
utils.mapAndExtend(options.customSanitizers, Sanitizer.prototype, utils.makeSanitizer); | ||
} | ||
return this; | ||
}; | ||
req[sanitizerSymbol] = Sanitizer; | ||
_.forEach(options.customValidators, function(method, customValidatorName) { | ||
ValidatorChain.prototype[customValidatorName] = makeValidator(customValidatorName, options.customValidators); | ||
}); | ||
_.forEach(options.customSanitizers, function(method, customSanitizerName) { | ||
Sanitizer.prototype[customSanitizerName] = makeSanitizer(customSanitizerName, options.customSanitizers); | ||
}); | ||
return function(req, res, next) { | ||
var locations = ['body', 'params', 'query']; | ||
req._validationErrors = []; | ||
req._asyncValidationErrors = []; | ||
req.validationErrors = function(mapped, promisesResolved) { | ||
req.validationErrors = function(mapped) { | ||
warnValidationErrors(); | ||
if (!promisesResolved && req._asyncValidationErrors.length > 0) { | ||
console.warn('WARNING: You have asynchronous validators but you have not used asyncValidateErrors to check for errors.'); | ||
} | ||
runContexts(); | ||
if (mapped && req._validationErrors.length > 0) { | ||
var errors = {}; | ||
req._validationErrors.forEach(function(err) { | ||
errors[err.param] = err; | ||
}); | ||
return errors; | ||
var result = validationResult(req); | ||
if (result.isEmpty()) { | ||
return false; | ||
} | ||
return req._validationErrors.length > 0 ? req._validationErrors : false; | ||
return mapped ? result.mapped() : result.array(); | ||
}; | ||
@@ -352,24 +308,9 @@ | ||
warnAsyncValidationErrors(); | ||
return new Promise(function(resolve, reject) { | ||
var promises = req._asyncValidationErrors; | ||
// Migrated using the recommended fix from | ||
// http://bluebirdjs.com/docs/api/reflect.html | ||
Promise.all(promises.map(function(promise) { | ||
// Must convert to Bluebird promise in case they are using native | ||
// Node promises since reflect() is not a native promise method | ||
// http://bluebirdjs.com/docs/api/reflect.html#comment-2369616577 | ||
return Promise.resolve(promise).reflect(); | ||
})).then(function(results) { | ||
runContexts(); | ||
return Promise.all(req._asyncValidationErrors).then(() => { | ||
if (req._validationErrors.length > 0) { | ||
return Promise.reject(req.validationErrors(mapped, true)); | ||
} | ||
results.forEach(function(result) { | ||
if (result.isRejected()) { | ||
req._validationErrors.push(result.reason()); | ||
} | ||
}); | ||
if (req._validationErrors.length > 0) { | ||
return reject(req.validationErrors(mapped, true)); | ||
} | ||
resolve(); | ||
}); | ||
return Promise.resolve(); | ||
}); | ||
@@ -379,20 +320,5 @@ }; | ||
req.getValidationResult = function() { | ||
return new Promise(function(resolve) { | ||
var promises = req._asyncValidationErrors; | ||
// Migrated using the recommended fix from | ||
// http://bluebirdjs.com/docs/api/reflect.html | ||
Promise.all(promises.map(function(promise) { | ||
// Must convert to Bluebird promise in case they are using native | ||
// Node promises since reflect() is not a native promise method | ||
// http://bluebirdjs.com/docs/api/reflect.html#comment-2369616577 | ||
return Promise.resolve(promise).reflect(); | ||
})).then(function(results) { | ||
results.forEach(function(result) { | ||
if (result.isRejected()) { | ||
req._validationErrors.push(result.reason()); | ||
} | ||
}); | ||
return resolve(utils.decorateAsValidationResult({}, req._validationErrors)); | ||
}); | ||
runContexts(); | ||
return Promise.all(req._asyncValidationErrors).then(() => { | ||
return validationResult(req); | ||
}); | ||
@@ -402,17 +328,17 @@ }; | ||
locations.forEach(function(location) { | ||
/** | ||
* @name req.sanitizeQuery | ||
* @see sanitize | ||
* @param param | ||
*/ | ||
/** | ||
* @name req.sanitizeParams | ||
* @see sanitize | ||
* @param param | ||
*/ | ||
/** | ||
* @name req.sanitizeBody | ||
* @see sanitize | ||
* @param param | ||
*/ | ||
/** | ||
* @name req.sanitizeQuery | ||
* @see sanitize | ||
* @param param | ||
*/ | ||
/** | ||
* @name req.sanitizeParams | ||
* @see sanitize | ||
* @param param | ||
*/ | ||
/** | ||
* @name req.sanitizeBody | ||
* @see sanitize | ||
* @param param | ||
*/ | ||
req['sanitize' + _.capitalize(location)] = function(param) { | ||
@@ -431,6 +357,2 @@ return new Sanitizer(param, req, [location]); | ||
req.sanitizeCookies = function(param) { | ||
return new Sanitizer(param, req, ['cookies']); | ||
}; | ||
req.sanitize = function(param) { | ||
@@ -459,7 +381,13 @@ return new Sanitizer(param, req, locations); | ||
*/ | ||
/** | ||
* @name req.checkCookies | ||
* @see check | ||
* @param param | ||
* @param [failMsg] | ||
*/ | ||
req['check' + _.capitalize(location)] = function(param, failMsg) { | ||
if (_.isPlainObject(param)) { | ||
return validateSchema(param, req, location, options); | ||
return validateSchema(param, req, location, contexts); | ||
} | ||
return new ValidatorChain(param, failMsg, req, location, options); | ||
return createValidationChain(param, location, failMsg, req, contexts); | ||
}; | ||
@@ -470,3 +398,3 @@ }); | ||
if (_.isPlainObject(param)) { | ||
return validateSchema(param, req, 'headers', options); | ||
return validateSchema(param, req, 'headers', contexts); | ||
} | ||
@@ -478,14 +406,10 @@ | ||
return new ValidatorChain(param.toLowerCase(), failMsg, req, 'headers', options); | ||
return createValidationChain(param.toLowerCase(), 'headers', failMsg, req, contexts); | ||
}; | ||
req.checkCookies = function(param, failMsg) { | ||
return new ValidatorChain(param, failMsg, req, 'cookies', options); | ||
}; | ||
req.check = function(param, failMsg) { | ||
if (_.isPlainObject(param)) { | ||
return validateSchema(param, req, 'any', options); | ||
return validateSchema(param, req, 'any', contexts); | ||
} | ||
return new ValidatorChain(param, failMsg, req, locate(req, param), options); | ||
return createValidationChain(param, allLocations, failMsg, req, contexts); | ||
}; | ||
@@ -501,128 +425,4 @@ | ||
/** | ||
* Validates and handles errors, return instance of itself to allow for chaining | ||
* | ||
* @method makeValidator | ||
* @param {string} methodName | ||
* @param {object} container | ||
* @return {function} | ||
*/ | ||
function makeValidator(methodName, container) { | ||
return function() { | ||
if (this.skipValidating) { | ||
return this; | ||
} | ||
var args = []; | ||
args.push(this.value); | ||
args = args.concat(Array.prototype.slice.call(arguments)); | ||
var isValid = container[methodName].apply(container, args); | ||
// Perform string replacement in the error message | ||
var msg = this.failMsg; | ||
if (typeof msg === 'string') { | ||
args.forEach(function(arg, i) { msg = msg.replace('%' + i, arg); }); | ||
} | ||
var error = formatErrors.call(this, this.param, msg || 'Invalid value', this.value); | ||
var isNilValue = isValid === undefined || isValid === null | ||
if (isNilValue) { | ||
warnValidatorNilReturn(methodName, isValid); | ||
} | ||
if (!isNilValue && isValid.then) { | ||
var promise = isValid.catch(function() { | ||
return Promise.reject(error); | ||
}); | ||
this.lastError = { | ||
promise: isValid, | ||
param: this.param, | ||
value: this.value, | ||
context: this, | ||
isAsync: true | ||
}; | ||
this.req._asyncValidationErrors.push(promise); | ||
} else if (!isValid) { | ||
this.validationErrors.push(error); | ||
this.req._validationErrors.push(error); | ||
this.lastError = { param: this.param, value: this.value, isAsync: false }; | ||
} else { | ||
this.lastError = null; | ||
} | ||
return this; | ||
}; | ||
} | ||
/** | ||
* Sanitizes and sets sanitized value on the request, then return instance of itself to allow for chaining | ||
* | ||
* @method makeSanitizer | ||
* @param {string} methodName | ||
* @param {object} container | ||
* @return {function} | ||
*/ | ||
function makeSanitizer(methodName, container) { | ||
return function() { | ||
var _arguments = arguments; | ||
var result; | ||
this.values.forEach(function(value, i) { | ||
if (value != null) { | ||
var args = [value]; | ||
args = args.concat(Array.prototype.slice.call(_arguments)); | ||
result = container[methodName].apply(container, args); | ||
_.set(this.req[this.locations[i]], this.param, result); | ||
this.values[i] = result; | ||
} | ||
}.bind(this)); | ||
return result; | ||
}; | ||
} | ||
/** | ||
* find location of param | ||
* | ||
* @method param | ||
* @param {Request} req express request object | ||
* @param {(string|string[])} name [description] | ||
* @return {string} | ||
*/ | ||
function locate(req, name) { | ||
if (_.get(req.params, name)) { | ||
return 'params'; | ||
} else if (_.has(req.query, name)) { | ||
return 'query'; | ||
} else if (_.has(req.body, name)) { | ||
return 'body'; | ||
} | ||
return undefined; | ||
} | ||
/** | ||
* format param output if passed in as array (for nested) | ||
* before calling errorFormatter | ||
* | ||
* @method param | ||
* @param {(string|string[])} param parameter as a string or array | ||
* @param {string} msg | ||
* @param {string} value | ||
* @return {function} | ||
*/ | ||
function formatErrors(param, msg, value) { | ||
var formattedParam = utils.formatParamOutput(param); | ||
return this.errorFormatter(formattedParam, msg, value); | ||
} | ||
module.exports = expressValidator; | ||
module.exports.validator = validator; | ||
module.exports.utils = utils; |
@@ -0,1 +1,2 @@ | ||
var _ = require('lodash'); | ||
var validator = require('validator'); | ||
@@ -28,43 +29,37 @@ | ||
exports.decorateAsValidationResult = function decorateAsValidationResult(obj, errors) { | ||
var onlyFirstError = false; | ||
exports.mapAndExtend = function createCustomValidators(src, dest, mapFunction) { | ||
Object.keys(src).forEach(function (name) { | ||
dest[name] = mapFunction(name, src); | ||
}); | ||
}; | ||
obj.isEmpty = function isEmpty() { | ||
return !errors.length; | ||
}; | ||
exports.replaceArgs = function replaceArgs(msg, args) { | ||
return args.reduce((msg, arg, index) => msg.replace('%' + index, arg), msg); | ||
}; | ||
obj.array = function allErrors() { | ||
var used = {}; | ||
return !onlyFirstError ? errors : errors.filter(function(error) { | ||
if (used[error.param]) { | ||
return false; | ||
} | ||
/** | ||
* Sanitizes and sets sanitized value on the request, then return instance of itself to allow for chaining | ||
* | ||
* @method makeSanitizer | ||
* @param {string} methodName | ||
* @param {object} container | ||
* @return {function} | ||
*/ | ||
exports.makeSanitizer = function makeSanitizer(methodName, container) { | ||
return function() { | ||
var _arguments = arguments; | ||
var result; | ||
this.values.forEach(function(value, i) { | ||
if (value != null) { | ||
var args = [value]; | ||
args = args.concat(Array.prototype.slice.call(_arguments)); | ||
result = container[methodName].apply(container, args); | ||
used[error.param] = true; | ||
return true; | ||
}); | ||
}; | ||
obj.mapped = function mappedErrors() { | ||
return errors.reduce(function(mapping, error) { | ||
if (!onlyFirstError || !mapping[error.param]) { | ||
mapping[error.param] = error; | ||
_.set(this.req[this.locations[i]], this.param, result); | ||
this.values[i] = result; | ||
} | ||
}.bind(this)); | ||
return mapping; | ||
}, {}); | ||
return result; | ||
}; | ||
obj.useFirstErrorOnly = function useFirstErrorOnly(flag) { | ||
onlyFirstError = flag === undefined || flag; | ||
return obj; | ||
}; | ||
obj.throw = function throwError() { | ||
if (errors.length) { | ||
throw decorateAsValidationResult(new Error('Validation failed'), errors); | ||
} | ||
}; | ||
return obj; | ||
}; | ||
} |
@@ -14,3 +14,3 @@ { | ||
], | ||
"version": "3.2.1", | ||
"version": "4.0.0", | ||
"homepage": "https://github.com/ctavan/express-validator", | ||
@@ -31,21 +31,16 @@ "license": "MIT", | ||
"engines": { | ||
"node": ">= 0.10" | ||
"node": ">= 6.0.0" | ||
}, | ||
"dependencies": { | ||
"@types/bluebird": "^3.4.0", | ||
"@types/express": "~4.0.34", | ||
"bluebird": "^3.4.0", | ||
"lodash": "^4.16.0", | ||
"validator": "~6.2.0" | ||
"validator": "~8.1.0" | ||
}, | ||
"devDependencies": { | ||
"body-parser": "1.12.3", | ||
"chai": "2.3.0", | ||
"cookie-parser": "1.4.1", | ||
"coveralls": "2.11.14", | ||
"eslint": "^3.13.1", | ||
"eslint": "^4.5.0", | ||
"express": "4.12.3", | ||
"mocha": "2.2.4", | ||
"nyc": "8.4.0", | ||
"supertest": "0.15.0", | ||
"mocha": "^3.5.0", | ||
"nyc": "^11.1.0", | ||
"typescript": "^2.3.4" | ||
@@ -52,0 +47,0 @@ }, |
677
README.md
@@ -9,21 +9,18 @@ # express-validator | ||
An [express.js]( https://github.com/visionmedia/express ) middleware for | ||
[node-validator]( https://github.com/chriso/validator.js ). | ||
[validator]( https://github.com/chriso/validator.js ). | ||
- [Upgrade notice](#upgrade-notice) | ||
- [Installation](#installation) | ||
- [Usage](#usage) | ||
- [Middleware options](#middleware-options) | ||
- [Validation](#validation) | ||
- [Validation by schema](#validation-by-schema) | ||
- [Validation result](#validation-result) | ||
+ [Result API](#result-api) | ||
+ [Deprecated API](#deprecated-api) | ||
+ [String formatting for error messages](#string-formatting-for-error-messages) | ||
+ [Per-validation messages](#per-validation-messages) | ||
- [Optional input](#optional-input) | ||
- [Sanitizer](#sanitizer) | ||
- [Regex routes](#regex-routes) | ||
- [TypeScript](#typescript) | ||
- [`check` API](#check-api) | ||
- [`filter` API](#filter-api) | ||
- [Validation Chain API](#validation-chain-api) | ||
- [Validation Result API](#validation-result-api) | ||
- [Legacy API](#legacy-api) | ||
- [Changelog](#changelog) | ||
- [License](#license) | ||
## Upgrade notice | ||
If you're arriving here as a express-validator v3 user after upgrading to v4, please check the [upgrade guide](UPGRADE_GUIDE.md) in order to find out what's different! | ||
## Installation | ||
@@ -36,493 +33,389 @@ | ||
## Usage | ||
> The version 3 style of doing validations is still available. | ||
> Please check the [legacy API](#legacy-api) for the docs. | ||
```javascript | ||
var util = require('util'), | ||
bodyParser = require('body-parser'), | ||
express = require('express'), | ||
expressValidator = require('express-validator'), | ||
app = express(); | ||
const { check, validationResult } = require('express-validator/check'); | ||
const { matchedData } = require('express-validator/filter'); | ||
app.use(bodyParser.bodyParser({ extended: true })); | ||
app.use(expressValidator([options])); // this line must be immediately after any of the bodyParser middlewares! | ||
app.post('/user', [ | ||
check('username') | ||
// Every validator method in the validator lib is available as a | ||
// method in the check() APIs. | ||
// You can customize per validator messages with .withMessage() | ||
.isEmail().withMessage('must be an email') | ||
app.post('/:urlparam', function(req, res) { | ||
// ...or throw your own errors using validators created with .custom() | ||
.custom(value => { | ||
return findUserByEmail(value).then(user => { | ||
throw new Error('this email is already in use'); | ||
}) | ||
}), | ||
// VALIDATION | ||
// checkBody only checks req.body; none of the other req parameters | ||
// Similarly checkParams only checks in req.params (URL params) and | ||
// checkQuery only checks req.query (GET params). | ||
req.checkBody('postparam', 'Invalid postparam').notEmpty().isInt(); | ||
req.checkParams('urlparam', 'Invalid urlparam').isAlpha(); | ||
req.checkQuery('getparam', 'Invalid getparam').isInt(); | ||
// General error messages can be given as a 2nd argument in the check APIs | ||
check('password', 'passwords must be at least 5 chars long and contain one number') | ||
.isLength({ min: 5 }) | ||
.matches(/\d/), | ||
// OR assert can be used to check on all 3 types of params. | ||
// req.assert('postparam', 'Invalid postparam').notEmpty().isInt(); | ||
// req.assert('urlparam', 'Invalid urlparam').isAlpha(); | ||
// req.assert('getparam', 'Invalid getparam').isInt(); | ||
// No special validation required? Just check if data exists: | ||
check('addresses.*.street').exists(), | ||
// SANITIZATION | ||
// as with validation these will only validate the corresponding | ||
// request object | ||
req.sanitizeBody('postparam').toBoolean(); | ||
req.sanitizeParams('urlparam').toBoolean(); | ||
req.sanitizeQuery('getparam').toBoolean(); | ||
// Wildcards * are accepted! | ||
check('addresses.*.postalCode').isPostalCode(), | ||
], (req, res, next) => { | ||
// Get the validation result whenever you want | ||
const errors = validationResult(req).throw(); | ||
if (!errors.isEmpty()) { | ||
return res.status(422).json({ errors: err.mapped() }); | ||
} | ||
// OR find the relevent param in all areas | ||
req.sanitize('postparam').toBoolean(); | ||
// Alternatively use `var result = yield req.getValidationResult();` | ||
// when using generators e.g. with co-express | ||
req.getValidationResult().then(function(result) { | ||
if (!result.isEmpty()) { | ||
res.status(400).send('There have been validation errors: ' + util.inspect(result.array())); | ||
return; | ||
} | ||
res.json({ | ||
urlparam: req.params.urlparam, | ||
getparam: req.query.getparam, | ||
postparam: req.body.postparam | ||
}); | ||
}); | ||
// matchedData returns only the subset of data validated by the middleware | ||
const user = matchedData(req); | ||
createUser(user).then(user => res.json(user)); | ||
}); | ||
app.listen(8888); | ||
``` | ||
Which will result in: | ||
--- | ||
``` | ||
$ curl -d 'postparam=1' http://localhost:8888/test?getparam=1 | ||
{"urlparam":"test","getparam":"1","postparam":true} | ||
## `check` API | ||
These methods are all available via `require('express-validator/check')`. | ||
$ curl -d 'postparam=1' http://localhost:8888/t1est?getparam=1 | ||
There have been validation errors: [ | ||
{ param: 'urlparam', msg: 'Invalid urlparam', value: 't1est' } ] | ||
### `check(field[, message])` | ||
- `field`: a string or an array of strings of field names to validate against. | ||
- `message` *(optional)*: an error message to use when failed validators don't specify a message. Defaults to `Invalid value`. | ||
> *Returns:* a [Validation Chain](#validation-chain-api) | ||
$ curl -d 'postparam=1' http://localhost:8888/t1est?getparam=1ab | ||
There have been validation errors: [ | ||
{ param: 'getparam', msg: 'Invalid getparam', value: '1ab' }, | ||
{ param: 'urlparam', msg: 'Invalid urlparam', value: 't1est' } ] | ||
Creates a validation chain for one or more fields. They may be located in any of the following request objects: | ||
- `req.body` | ||
- `req.cookies` | ||
- `req.headers` | ||
- `req.params` | ||
- `req.query` | ||
$ curl http://localhost:8888/test?getparam=1&postparam=1 | ||
There have been validation errors: [ | ||
{ param: 'postparam', msg: 'Invalid postparam', value: undefined} ] | ||
``` | ||
If any of the fields are present in more than one location, then all instances of that field value must pass the validation. | ||
## Middleware Options | ||
#### `errorFormatter` | ||
_function(param,msg,value)_ | ||
The validators will always be executed serially for the same field. | ||
This means that if the chain targets more than one field, those will run in parallel, but each of their validators are serial. | ||
The `errorFormatter` option can be used to specify a function that must build the error objects used in the validation result returned by `req.getValidationResult()`.<br> | ||
It should return an `Object` that has `param`, `msg`, and `value` keys defined. | ||
### `body(fields[, message])` | ||
Same as `check(fields[, message])`, but only checking `req.body`. | ||
```javascript | ||
// In this example, the formParam value is going to get morphed into form body format useful for printing. | ||
app.use(expressValidator({ | ||
errorFormatter: function(param, msg, value) { | ||
var namespace = param.split('.') | ||
, root = namespace.shift() | ||
, formParam = root; | ||
### `cookie(fields[, message])` | ||
Same as `check(fields[, message])`, but only checking `req.cookies`. | ||
while(namespace.length) { | ||
formParam += '[' + namespace.shift() + ']'; | ||
} | ||
return { | ||
param : formParam, | ||
msg : msg, | ||
value : value | ||
}; | ||
} | ||
})); | ||
``` | ||
### `header(fields[, message])` | ||
Same as `check(fields[, message])`, but only checking `req.headers`. | ||
#### `customValidators` | ||
_{ "validatorName": function(value, [additional arguments]), ... }_ | ||
### `param(fields[, message])` | ||
Same as `check(fields[, message])`, but only checking `req.params`. | ||
### `query(fields[, message])` | ||
Same as `check(fields[, message])`, but only checking `req.query`. | ||
The `customValidators` option can be used to add additional validation methods as needed. This option should be an `Object` defining the validator names and associated validation functions. | ||
### `oneOf(validationChains[, message])` | ||
- `validationChains`: an array of [validation chains](#validation-chain-api) created with `check()` or any of its variations. | ||
- `message` *(optional)*: an error message to use when all chains failed. Defaults to `Invalid value(s)`. | ||
> *Returns:* a middleware instance | ||
Define your custom validators: | ||
Creates a middleware instance that will ensure at least one of the given chains passes the validation. | ||
If none of the given chains passes, an error will be pushed to the `_error` pseudo-field, | ||
using the given `message`, and the errors of each chain will be made available under a key `nestedErrors`. | ||
```javascript | ||
app.use(expressValidator({ | ||
customValidators: { | ||
isArray: function(value) { | ||
return Array.isArray(value); | ||
}, | ||
gte: function(param, num) { | ||
return param >= num; | ||
} | ||
} | ||
})); | ||
``` | ||
Use them with their validator name: | ||
```javascript | ||
req.checkBody('users', 'Users must be an array').isArray(); | ||
req.checkQuery('time', 'Time must be an integer great than or equal to 5').isInt().gte(5) | ||
``` | ||
#### `customSanitizers` | ||
_{ "sanitizerName": function(value, [additional arguments]), ... }_ | ||
Example: | ||
The `customSanitizers` option can be used to add additional sanitizers methods as needed. This option should be an `Object` defining the sanitizer names and associated functions. | ||
```js | ||
const { check, oneOf, validationResult } = require('express-validator/check'); | ||
app.post('/start-freelancing', oneOf([ | ||
check('programming_language').isIn(['javascript', 'java', 'php']), | ||
check('design_tools').isIn(['photoshop', 'gimp']) | ||
]), (req, res, next) => { | ||
try { | ||
validationResult(req).throw(); | ||
Define your custom sanitizers: | ||
```javascript | ||
app.use(expressValidator({ | ||
customSanitizers: { | ||
toSanitizeSomehow: function(value) { | ||
var newValue = value;//some operations | ||
return newValue; | ||
}, | ||
} | ||
})); | ||
// yay! we're good to start selling our skilled services :))) | ||
res.json(...); | ||
} catch (err) { | ||
// Oh noes. This user doesn't have enough skills for this... | ||
res.status(422).json(...); | ||
} | ||
}); | ||
``` | ||
Use them with their sanitizer name: | ||
```javascript | ||
req.sanitize('address').toSanitizeSomehow(); | ||
``` | ||
## Validation | ||
The execution of those validation chains are made in parallel, | ||
while the execution within a chain still respects the rule defined in the [`check()` function](#checkfield-message). | ||
#### req.check(); | ||
```javascript | ||
req.check('testparam', 'Error Message').notEmpty().isInt(); | ||
req.check('testparam.child', 'Error Message').isInt(); // find nested params | ||
req.check(['testparam', 'child'], 'Error Message').isInt(); // find nested params | ||
``` | ||
### `validationResult(req)` | ||
- `req`: the express request object. | ||
> *Returns:* a [validation result](#validation-result-api) object | ||
Starts the validation of the specifed parameter, will look for the parameter in `req` in the order `params`, `query`, `body`, then validate, you can use 'dot-notation' or an array to access nested values. | ||
Extracts the validation errors from a request and makes it available in the form of a validation result object. | ||
If a validator takes in params, you would call it like `req.assert('reqParam').contains('thisString');`. | ||
--- | ||
Validators are appended and can be chained. See [chriso/validator.js](https://github.com/chriso/validator.js) for available validators, or [add your own](#customvalidators). | ||
## `filter` API | ||
These methods are all available via `require('express-validator/filter')`. | ||
#### req.assert(); | ||
Alias for [req.check()](#reqcheck). | ||
### `matchedData(req[, options])` | ||
- `req`: the express request object. | ||
- `options` *(optional)*: an object of options. Defaults to `{ onlyValidData: true }` | ||
> *Returns:* an object of data validated by the `check` APIs. | ||
#### req.validate(); | ||
Alias for [req.check()](#reqcheck). | ||
Extracts data validated by the `check` APIs from the request and builds | ||
an object with them. Nested paths and wildcards are properly handled as well. | ||
#### req.checkBody(); | ||
Same as [req.check()](#reqcheck), but only looks in `req.body`. | ||
By default, only valid data is included; this means if a field didn't pass | ||
its validation, it won't be included in the returned object. | ||
You can include invalid data by passing the option `onlyValidData` as `false`. | ||
#### req.checkQuery(); | ||
Same as [req.check()](#reqcheck), but only looks in `req.query`. | ||
--- | ||
#### req.checkParams(); | ||
Same as [req.check()](#reqcheck), but only looks in `req.params`. | ||
## Validation Chain API | ||
Any of the validation methods listed by [validator.js](https://github.com/chriso/validator.js) are made available in all validation chains created by express-validator, as long as we're supporting the most up-to-date validator version. | ||
#### req.checkHeaders(); | ||
Only checks `req.headers`. This method is not covered by the general `req.check()`. | ||
Additionally, the following methods are also available: | ||
#### req.checkCookies(); | ||
Only checks `req.cookies`. This method is not covered by the general `req.check()`. | ||
### `.custom(validator)` | ||
- `validator(value, { req, location, path })`: the custom validator function. | ||
Receives the value of the field being validated, as well as the express request, the location and the field path. | ||
> *Returns:* the current validation chain instance | ||
## Validation by Schema | ||
Adds a custom validator to the current validation chain. | ||
The custom validator may return a promise to indicate an async validation task. In case it's rejected, the field is considered invalid. | ||
Alternatively you can define all your validations at once using a simple schema. | ||
Schema validation will be used if you pass an object to any of the validator methods. | ||
The custom validator may also throw JavaScript exceptions (eg `throw new Error()`) and return falsy values to indicate the field is invalid. | ||
You may pass per-validator error messages with the `errorMessage` key. | ||
Validator options may be passed via `options` key as an array when various values are needed, | ||
or as a single non-null value otherwise. | ||
### `.exists()` | ||
> *Returns:* the current validation chain instance | ||
```javascript | ||
req.checkBody({ | ||
'email': { | ||
optional: { | ||
options: { checkFalsy: true } // or: [{ checkFalsy: true }] | ||
}, | ||
isEmail: { | ||
errorMessage: 'Invalid Email' | ||
} | ||
}, | ||
'password': { | ||
notEmpty: true, | ||
matches: { | ||
options: ['example', 'i'] // pass options to the validator with the options property as an array | ||
// options: [/example/i] // matches also accepts the full expression in the first parameter | ||
}, | ||
errorMessage: 'Invalid Password' // Error message for the parameter | ||
}, | ||
'name.first': { // | ||
optional: true, // won't validate if field is empty | ||
isLength: { | ||
options: [{ min: 2, max: 10 }], | ||
errorMessage: 'Must be between 2 and 10 chars long' // Error message for the validator, takes precedent over parameter message | ||
}, | ||
errorMessage: 'Invalid First Name' | ||
} | ||
}); | ||
``` | ||
Adds a validator to check for the existence of the current fields in the request. | ||
This means the value of the fields may not be `undefined`; any other values are acceptable. | ||
You can also define a specific location to validate against in the schema by adding `in` parameter as shown below: | ||
### `.not()` | ||
> *Returns:* the current validation chain instance | ||
```javascript | ||
req.check({ | ||
'email': { | ||
in: 'query', | ||
notEmpty: true, | ||
isEmail: { | ||
errorMessage: 'Invalid Email' | ||
} | ||
} | ||
}); | ||
``` | ||
Negates the result of the next validator. | ||
Please remember that the `in` attribute will have always highest priority. This mean if you use `in: 'query'` then checkQuery() will be called inside even if you do `checkParams()` or `checkBody()`. For example, all of these calls will check query params for email param: | ||
```javascript | ||
var schema = { | ||
'email': { | ||
in: 'query', | ||
notEmpty: true, | ||
isEmail: { | ||
errorMessage: 'Invalid Email' | ||
} | ||
}, | ||
'password': { | ||
notEmpty: true, | ||
matches: { | ||
options: ['example', 'i'] // pass options to the validator with the options property as an array | ||
// options: [/example/i] // matches also accepts the full expression in the first parameter | ||
}, | ||
errorMessage: 'Invalid Password' // Error message for the parameter | ||
} | ||
}; | ||
req.check(schema); // will check 'password' no matter where it is but 'email' in query params | ||
req.checkQuery(schema); // will check 'password' and 'email' in query params | ||
req.checkBody(schema); // will check 'password' in body but 'email' in query params | ||
req.checkParams(schema); // will check 'password' in path params but 'email' in query params | ||
req.checkHeaders(schema); // will check 'password' in headers but 'email' in query params | ||
```js | ||
check('weekday').not().isIn(['sunday', 'saturday']) | ||
``` | ||
Currently supported location are `'body', 'params', 'query', 'headers'`. If you provide a location parameter that is not supported, the validation process for current parameter will be skipped. | ||
### `.optional(options)` | ||
- `options` *(optional)*: an object of options to customize the optionality behaviour. Defaults to `{ checkFalsy: false }`. | ||
> *Returns:* the current validation chain instance | ||
## Validation result | ||
Marks the current validation chain as optional. | ||
This is useful to remove values that are not essential to your busines and that would cause validation failures in case they were not provided in the request. | ||
### Result API | ||
The method `req.getValidationResult()` returns a Promise which resolves to a result object. | ||
By default, this means fields with `undefined` values will be completely ignored. | ||
However, if you specify the option `{ checkFalsy: true }`, then falsy values (eg `""`, `0`, `false`, `null`) will also be ignored. | ||
```js | ||
req.assert('email', 'required').notEmpty(); | ||
req.assert('email', 'valid email required').isEmail(); | ||
req.assert('password', '6 to 20 characters required').len(6, 20); | ||
### `.withMessage(message)` | ||
- `message`: the error message to use for the previous validator | ||
> *Returns:* the current validation chain instance | ||
req.getValidationResult().then(function(result) { | ||
// do something with the validation result | ||
}); | ||
``` | ||
Sets the error message for the previous validator. | ||
This will have precedence over errors thrown by a custom validator. | ||
The API for the result object is the following: | ||
--- | ||
#### `result.isEmpty()` | ||
Returns a boolean determining whether there were errors or not. | ||
## Validation Result API | ||
This is an unified API for dealing with errors, both in legacy and check APIs. | ||
#### `result.useFirstErrorOnly()` | ||
Sets the `firstErrorOnly` flag of this result object, which modifies the way | ||
other methods like `result.array()` and `result.mapped()` work.<br> | ||
Each error returned by `.array()` and `.mapped()` methods have the following format: | ||
This method is chainable, so the following is OK: | ||
```js | ||
{ | ||
"msg": "The error message", | ||
"param": "param.name.with.index[0]", | ||
"value": "param value", | ||
// Location of the param that generated this error. | ||
// It's either body, query, params, cookies or headers. | ||
"location": "body", | ||
```js | ||
result.useFirstErrorOnly().array(); | ||
// nestedErrors only exist when using the oneOf function | ||
"nestedErrors": [{ ... }] | ||
} | ||
``` | ||
#### `result.array()` | ||
Returns an array of errors.<br> | ||
All errors for all validated parameters will be included, unless you specify that you want only the first error of each param by invoking `result.useFirstErrorOnly()`. | ||
### `.isEmpty()` | ||
> *Returns:* a boolean indicating whether this result object contains no errors at all. | ||
```javascript | ||
var errors = result.array(); | ||
### `.array([options])` | ||
- `options` *(optional)*: an object of options. Defaults to `{ onlyFirstError: false }` | ||
> *Returns:* an array of validation errors. | ||
// errors will now contain something like this: | ||
[ | ||
{param: "email", msg: "required", value: "<received input>"}, | ||
{param: "email", msg: "valid email required", value: "<received input>"}, | ||
{param: "password", msg: "6 to 20 characters required", value: "<received input>"} | ||
] | ||
``` | ||
Gets all validation errors contained in this result object. | ||
#### `result.mapped()` | ||
Returns an object of errors, where the key is the parameter name, and the value is an error object as returned by the error formatter. | ||
If the option `onlyFirstError` is set to `true`, then only the first | ||
error for each field will be included. | ||
Because of historical reasons, by default this method will return the last error of each parameter.<br> | ||
You can change this behavior by invoking `result.useFirstErrorOnly()`, so the first error is returned instead. | ||
### `.mapped()` | ||
> *Returns:* an object where the keys are the field names, and the values are the validation errors | ||
```javascript | ||
var errors = result.mapped(); | ||
Gets the first validation error of each failed field in the form of an object. | ||
// errors will now be similar to this: | ||
{ | ||
email: { | ||
param: "email", | ||
msg: "valid email required", | ||
value: "<received input>" | ||
}, | ||
password: { | ||
param: "password", | ||
msg: "6 to 20 characters required", | ||
value: "<received input>" | ||
} | ||
} | ||
``` | ||
### `.throw()` | ||
If this result object has errors, then this method will throw an exception | ||
decorated with the same validation result API. | ||
#### `result.throw()` | ||
If there are errors, throws an `Error` object which is decorated with the same API as the validation result object.<br> | ||
Useful for dealing with the validation errors in the `catch` block of a `try..catch` or promise. | ||
```js | ||
try { | ||
result.throw(); | ||
res.send('success!'); | ||
} catch (e) { | ||
console.log(e.array()); | ||
res.send('oops, validation failed!'); | ||
validationResult(req).throw(); | ||
// Oh look at ma' success! All validations passed! | ||
} catch (err) { | ||
console.log(err.mapped()); // Oh noes! | ||
} | ||
``` | ||
### Deprecated API | ||
The following methods are deprecated.<br> | ||
While they work, their API is unflexible and sometimes return weird results if compared to the bleeding edge `req.getValidationResult()`. | ||
--- | ||
Additionally, these methods may be removed in a future version. | ||
## Legacy API | ||
The "legacy API" is the same API used by version 3 and older releases of express-validator. | ||
#### `req.validationErrors([mapped])` | ||
Returns synchronous errors in the form of an array, or an object that maps parameter to error in case `mapped` is passed as `true`.<br> | ||
If there are no errors, the returned value is `false`. | ||
It's based around setting a global middleware in your express app and decorating the request object with new methods. | ||
```js | ||
var errors = req.validationErrors(); | ||
if (errors) { | ||
// do something with the errors | ||
} | ||
``` | ||
> This API **MUST NOT** be used by new apps, since it may not receive new updates and can even be removed in a future major version. | ||
#### `req.asyncValidationErrors([mapped])` | ||
Returns a promise that will either resolve if no validation errors happened, or reject with an errors array/mapping object. For reference on this, see `req.validationErrors()`. | ||
### Setup | ||
You must mount the middleware in your app before you get access to the validation/sanitization methods: | ||
```js | ||
req.asyncValidationErrors().then(function() { | ||
// all good here | ||
}, function(errors) { | ||
// damn, validation errors! | ||
}); | ||
const expressValidator = require('express-validator'); | ||
app.use(expressValidator(middlewareOptions)); | ||
``` | ||
### String formatting for error messages | ||
### Middleware options | ||
- `errorFormatter (param, msg, value)`: a function that formats the error objects before returning them to your route handlers. | ||
- `customValidators`: an object where you can specify custom validators. | ||
The key will be the name of the validator, while the value is the validation function, receiving the value and any option. | ||
- `customSanitizers`: an object where you can specify custom sanitizers. | ||
The key will be the name of the sanitizer, while the value is the sanitization function, receiving the value and any option. | ||
Error messages can be customized to include both the value provided by the user, as well as the value of any parameters passed to the validation function, using a standard string replacement format: | ||
### Legacy Validation Chain | ||
The Legacy Validation Chain instances provides further functionality than the one provided by the base [Validation Chain](#validation-chain-api) objects. | ||
It also differs in that the legacy one is not a middleware *per se*. | ||
`%0` is replaced with user input | ||
`%1` is replaced with the first parameter to the validator | ||
`%2` is replaced with the second parameter to the validator | ||
etc... | ||
Any custom validator specified in the middleware will be made available | ||
in instances of this validation chain. | ||
Example: | ||
```javascript | ||
req.assert('number', '%0 is not an integer').isInt(); | ||
req.assert('number', '%0 is not divisible by %1').isDivisibleBy(5); | ||
``` | ||
Additionally, the following validators are also available: | ||
*Note:* string replacement does **not** work with the `.withMessage()` syntax. If you'd like to have per-validator error messages with string formatting, please use the [Validation by Schema](#validation-by-schema) method instead. | ||
- `.notEmpty()`: alias of `.isLength({ min: 1 })` | ||
- `.len()`: alias of `.isLength()` | ||
### Per-validation messages | ||
### `req.check(field[, message])` | ||
- `field`: the name of a single field to validate against. | ||
- `message` *(optional)*: an error message to use when failed validators don't specify a message. Defaults to `Invalid value`. | ||
> *Returns:* a [legacy validation chain](#legacy-validation-chain) | ||
You can provide an error message for a single validation with `.withMessage()`. This can be chained with the rest of your validation, and if you don't use it for one of the validations then it will fall back to the default. | ||
Creates a validation chain for one field. It may be located in any of the following request objects: | ||
- `req.params` | ||
- `req.query` | ||
- `req.body` | ||
- `req.headers` | ||
- `req.cookies` | ||
```javascript | ||
req.assert('email', 'Invalid email') | ||
.notEmpty().withMessage('Email is required') | ||
.isEmail(); | ||
If it's present in more than one location, then only the first one (following the above order) will be validated against. | ||
req.getValidationResult() | ||
.then(function(result){ | ||
console.log(result.array()); | ||
}); | ||
> This function is also aliased as `req.assert()` and `req.validate()`. | ||
``` | ||
### `req.checkBody(field[, message])` | ||
Same as `req.check(field[, message])`, but only checking `req.body`. | ||
prints: | ||
### `req.checkCookies(field[, message])` | ||
Same as `req.check(field[, message])`, but only checking `req.cookies`. | ||
```javascript | ||
[ | ||
{param: 'email', msg: 'Email is required', value: '<received input>'} | ||
{param: 'email', msg: 'Invalid Email', value: '<received input>'} | ||
] | ||
``` | ||
### `req.checkHeaders(field[, message])` | ||
Same as `req.check(field[, message])`, but only checking `req.headers`. | ||
## Optional input | ||
### `req.checkParams(field[, message])` | ||
Same as `req.check(field[, message])`, but only checking `req.params`. | ||
You can use the `optional()` method to skip validation. By default, it only skips validation if the key does not exist on the request object. If you want to skip validation based on the property being falsy (null, undefined, etc), you can pass in `{ checkFalsy: true }`. | ||
### `req.checkQuery(field[, message])` | ||
Same as `req.check(field[, message])`, but only checking `req.query`. | ||
```javascript | ||
req.checkBody('email').optional().isEmail(); | ||
//if there is no error, req.body.email is either undefined or a valid mail. | ||
``` | ||
### `req.sanitize(field)` | ||
> *Returns:* a sanitizer chain | ||
## Sanitizer | ||
Creates a sanitizer chain that, when any of the sanitization methods is used, the return value is the sanitized value. | ||
Also, the parameter is sanitized in-place; that is, in the below example, | ||
`req.body.comment` will be updated to the sanitized value. | ||
#### req.sanitize(); | ||
```javascript | ||
```js | ||
const comment = req.sanitize('comment').trim(); | ||
console.log(comment === req.body.comment); | ||
``` | ||
req.body.comment = 'a <span>comment</span>'; | ||
req.body.username = ' a user '; | ||
If the sanitized parameter is present in more than one location (eg `req.query.comment` and `req.body.comment`), the will all be sanitized. | ||
req.sanitize('comment').escape(); // returns 'a <span>comment</span>' | ||
req.sanitize('username').trim(); // returns 'a user' | ||
> This function is also aliased as `req.filter()`. | ||
console.log(req.body.comment); // 'a <span>comment</span>' | ||
console.log(req.body.username); // 'a user' | ||
### `req.sanitizeBody(field[, message])` | ||
Same as `req.sanitize(field[, message])`, but only sanitizing `req.body`. | ||
``` | ||
### `req.sanitizeCookies(field[, message])` | ||
Same as `req.sanitize(field[, message])`, but only sanitizing `req.cookies`. | ||
Sanitizes the specified parameter (using 'dot-notation' or array), the parameter will be updated to the sanitized result. Cannot be chained, and will return the result. See [chriso/validator.js](https://github.com/chriso/validator.js) for available sanitizers, or [add your own](#customsanitizers). | ||
### `req.sanitizeHeaders(field[, message])` | ||
Same as `req.sanitize(field[, message])`, but only sanitizing `req.headers`. | ||
If a sanitizer takes in params, you would call it like `req.sanitize('reqParam').whitelist(['a', 'b', 'c']);`. | ||
### `req.sanitizeParams(field[, message])` | ||
Same as `req.sanitize(field[, message])`, but only sanitizing `req.params`. | ||
If the parameter is present in multiple places with the same name e.g. `req.params.comment` & `req.query.comment`, they will all be sanitized. | ||
### `req.sanitizeQuery(field[, message])` | ||
Same as `req.sanitize(field[, message])`, but only sanitizing `req.query`. | ||
#### req.filter(); | ||
Alias for [req.sanitize()](#reqsanitize). | ||
### `req.getValidationResult()` | ||
> *Returns:* a promise for a [Validation Result](#validation-result-api) object | ||
#### req.sanitizeBody(); | ||
Same as [req.sanitize()](#reqsanitize), but only looks in `req.body`. | ||
Runs all validations and returns a validation result object for the errors gathered, for both sync and async validators. | ||
#### req.sanitizeQuery(); | ||
Same as [req.sanitize()](#reqsanitize), but only looks in `req.query`. | ||
### `req.asyncValidationErrors([mapped])` | ||
- `mapped` *(optional)*: whether the result must be an object instead of an array. Defaults to `false`. | ||
> *Returns:* `false` if no errors happened, an array of errors or an object of errors (in case `mapped` argument is `true`). | ||
#### req.sanitizeParams(); | ||
Same as [req.sanitize()](#reqsanitize), but only looks in `req.params`. | ||
Runs all validations and returns the errors gathered for all of them. | ||
#### req.sanitizeHeaders(); | ||
Only sanitizes `req.headers`. This method is not covered by the general `req.sanitize()`. | ||
### `req.validationErrors([mapped])` | ||
- `mapped` *(optional)*: whether the result must be an object instead of an array. Defaults to `false`. | ||
> *Returns:* `false` if no errors happened, an array of errors or an object of errors (in case `mapped` argument is `true`). | ||
#### req.sanitizeCookies(); | ||
Only sanitizes `req.cookies`. This method is not covered by the general `req.sanitize()`. | ||
Runs all validations and returns the errors gathered *only* for the completed validators. | ||
This probably means any async validator will not be completed by the time this method responds. | ||
## Regex routes | ||
### Schema validation | ||
All `req.check` methods can do schema validation. This is a special way of validating data were you pass an object of your expected schema, and all the validations you want: | ||
Express allows you to define regex routes like: | ||
```javascript | ||
app.get(/\/test(\d+)/, function() {}); | ||
```js | ||
req.checkBody({ | ||
email: { | ||
notEmpty: true, | ||
isEmail: true | ||
}, | ||
password: { | ||
notEmpty: true, | ||
matches: { | ||
// more than one options must be passed as arrays | ||
options: ['someregex', 'i'], | ||
// single options may be passed directly | ||
// options: /someregex/i | ||
}, | ||
errorMessage: 'Invalid password' | ||
}, | ||
// Wildcards and nested paths are supported as well | ||
'name.first': { | ||
optional: { | ||
options: { checkFalsy: true } | ||
} | ||
}, | ||
termsAndConditionsAgreement: { | ||
isBoolean: { | ||
errorMessage: 'should be a boolean' | ||
} | ||
} | ||
}); | ||
``` | ||
You can validate the extracted matches like this: | ||
--- | ||
```javascript | ||
req.assert(0, 'Not a three-digit integer.').len(3, 3).isInt(); | ||
``` | ||
## TypeScript | ||
If you have been using this library with [TypeScript](http://www.typescriptlang.org/), | ||
you must have been using the type definitions from [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/e2af6d0/express-validator/express-validator.d.ts). | ||
However, as of v3.1.0, the type definitions are shipped with the library. | ||
So please uninstall the typings from DT. Otherwise they may cause conflicts | ||
## Changelog | ||
@@ -534,2 +427,2 @@ | ||
Copyright (c) 2010 Chris O'Hara <cohara87@gmail.com>, MIT License | ||
MIT License |
@@ -17,4 +17,7 @@ { | ||
}, | ||
"files": [ "index.d.ts", "./test/type-definition.spec.ts" ], | ||
"include": [ | ||
"**/*.d.ts", | ||
"**/*.spec.ts" | ||
], | ||
"exclude": [ "node_modules", "coverage" ] | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
3
7
30
66442
1184
426
1
+ Addedvalidator@8.1.0(transitive)
- Removed@types/bluebird@^3.4.0
- Removedbluebird@^3.4.0
- Removed@types/bluebird@3.5.42(transitive)
- Removedbluebird@3.7.2(transitive)
- Removedvalidator@6.2.1(transitive)
Updatedvalidator@~8.1.0