/**
* Validators for Mongoose.js utilising validator.js
* @module mongoose-validator
* @author Lee Powell lee@leepowell.co.uk
* @copyright MIT
*/
const validatorjs = require('validator')
const is = require('is')
const defaultErrorMessages = require('./default-error-messages')
const customValidators = {}
const customErrorMessages = {}
const omit = function omit (obj, ...keys) {
return Object.keys(obj)
.filter(key => !keys.includes(key))
.reduce((acc, val) => {
acc[val] = obj[val]
return acc
}, {})
}
const getValidatorFn = function getValidatorFn (validator) {
// Validator has been passed as a function so just return it
if (is.function(validator)) {
return validator
}
// Validator has been passed as a string (i.e. 'isLength'), try to find the validator is validator.js or custom validators
if (is.string(validator) && !is.empty(validator)) {
return validatorjs[validator] || customValidators[validator] || undefined
}
}
const toArray = (val = []) => (is.array(val) ? val : Array.of(val))
const findFirstString = (...vals) => vals.filter(is.string).shift()
const interpolateMessageWithArgs = (message = '', args = []) =>
message.replace(/{ARGS\[(\d+)\]}/g, (match, submatch) => args[submatch] || '')
const createValidator = function createValidator (fn, args, passIfEmpty) {
return function validator (val) {
const validatorArgs = [val].concat(args)
if ((passIfEmpty && (is.empty(val) || is.nil(val))) || is.undef(val)) {
return true
}
return fn.apply(this, validatorArgs)
}
}
/**
* Create a validator object
*
* @alias module:mongoose-validator
*
* @param {object} options Options object
* @param {string} options.validator Validator name to use
* @param {*} [options.arguments=[]] Arguments to pass to validator. If more than one argument is required an array must be used. Single arguments will internally be coerced into an array
* @param {boolean} [options.passIfEmpty=false] Weather the validator should pass if the value being validated is empty
* @param {string} [options.message=Error] Validator error message
*
* @return {object} Returns validator compatible with mongoosejs
*
* @throws If validator option property is not defined
* @throws If validator option is not a function or string
* @throws If validator option is a validator method (string) and method does not exist in validate.js or as a custom validator
*
* @example
* require('mongoose-validator').validate({ validator: 'isLength', arguments: [4, 40], passIfEmpty: true, message: 'Value should be between 4 and 40 characters' )
*/
const validate = function validate (options) {
if (is.undef(options.validator)) {
throw new Error('validator option undefined')
}
if (!is.function(options.validator) && !is.string(options.validator)) {
throw new Error(
`validator must be of type function or string, received ${typeof options.validator}`
)
}
const validatorName = is.string(options.validator) ? options.validator : ''
const validatorFn = getValidatorFn(options.validator)
if (is.undef(validatorFn)) {
throw new Error(
`validator \`${validatorName}\` does not exist in validator.js or as a custom validator`
)
}
const passIfEmpty = !!options.passIfEmpty
const mongooseOpts = omit(
options,
'passIfEmpty',
'message',
'validator',
'arguments'
)
const args = toArray(options.arguments)
const messageStr = findFirstString(
options.message,
customErrorMessages[validatorName],
defaultErrorMessages[validatorName],
'Error'
)
const message = interpolateMessageWithArgs(messageStr, args)
const validator = createValidator(validatorFn, args, passIfEmpty)
return Object.assign(
{
validator,
message
},
mongooseOpts
)
}
/**
* Extend the mongoose-validator with a custom validator
*
* @param {string} name Validator method name
* @param {function} fn Validator method function
* @param {string} [msg=Error] Validator error message
*
* @return {undefined}
*
* @throws If name is not a string
* @throws If validator is not a function
* @throws If message is not a string
* @throws If name is empty i.e ''
* @throws If a validator of the same method name already exists
*
* @example
* require('mongoose-validator').extend('isString', function (str) { return typeof str === 'string' }, 'Not a string')
*/
validate.extend = function extend (name, fn, msg = 'Error') {
if (typeof name !== 'string') {
throw new Error(`name must be a string, received ${typeof name}`)
}
if (typeof fn !== 'function') {
throw new Error(`validator must be a function, received ${typeof fn}`)
}
if (typeof msg !== 'string') {
throw new Error(`message must be a string, received ${typeof msg}`)
}
if (name === '') {
throw new Error('name is required')
}
if (customValidators[name]) {
throw new Error(`validator \`${name}\` already exists`)
}
customValidators[name] = function (...args) {
return fn.apply(this, args)
}
customErrorMessages[name] = msg
}
/**
* Default error messages
*/
validate.defaultErrorMessages = defaultErrorMessages
module.exports = validate