Skip to content

Instantly share code, notes, and snippets.

@aarongeorge
Last active July 1, 2020 00:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aarongeorge/bbdd7ea97841817f90fb797d613c8285 to your computer and use it in GitHub Desktop.
Save aarongeorge/bbdd7ea97841817f90fb797d613c8285 to your computer and use it in GitHub Desktop.
A runtime property validator for JS objects
/**
* Property Validator
*
* @desc A runtime property validator for JS objects
* @usage:
* const model = new PropertyValidator({
* 'propName': {
* 'type': 'any'|'array'|'boolean'|'custom'|'date'|'element'|'function'|'number'|'object'|'regExp'|'string'
* 'required': true|false,
* 'validateFn': a function that takes one param and returns true or false. Only works if `type` is `custom`
* },
* ...
* })
* model({
* 'propName': propertyValue,
* ...
* })
* @notes:
* - Any property passed to an instance of PropertyValidator that isn't defined in `model` will be removed from output
* - `validateFn` must only be passed if `type` is `custom`
*/
const toTitleCase = str => str.charAt(0).toUpperCase() + str.slice(1)
const isArray = arr => Object.prototype.toString.call(arr) === '[object Array]'
const isBoolean = bool => typeof bool === 'boolean'
const isDate = date => Object.prototype.toString.call(date) === '[object Date]'
const isElement = el => el instanceof HTMLElement
const isFunction = fn => typeof fn === 'function'
const isNumber = num => typeof num === 'number'
const isObject = obj => Object.prototype.toString.call(obj) === '[object Object]'
const isRegExp = regexp => Object.prototype.toString.call(regexp) === '[object RegExp]'
const isString = str => typeof str === 'string'
const isUndefined = prop => typeof prop === 'undefined'
const isWindow = obj => obj === window
const typeValues = ['any', 'array', 'boolean', 'custom', 'date', 'element', 'function', 'number', 'object', 'regExp', 'string']
const requiredValues = [!!0, !!1]
class PropertyValidator {
constructor (model) {
if (isUndefined(model)) throw new Error('`model` was not passed')
if (!isObject(model)) throw new Error('`model` was not an object')
Object.entries(model).forEach(([propName, propOpts]) => {
if (isUndefined(propOpts.type)) throw new Error(`\`type\` must be passed for property \`${propName}\`.`)
else if (!typeValues.includes(propOpts.type)) throw new Error(`\`type\` of \`${propOpts.type}\` is not valid. Please use one of the following: ${typeValues.join(', ')}.`)
if (isUndefined(propOpts.required)) throw new Error(`\`required\` must be passed for property \`${propName}\`.`)
else if (!requiredValues.includes(propOpts.required)) throw new Error(`\`required\` of \`${propOpts.required}\` is not valid. Please use one of the following: ${requiredValues.join(', ')}.`)
if (propOpts.type === 'custom') {
if (isUndefined(propOpts.validateFn)) throw new Error(`\`type\` of \`${propOpts.type}\` requires that \`validateFn\` is passed`)
if (!isFunction(propOpts.validateFn)) throw new Error(`\`validateFn\` of type \`${typeof propOpts.validateFn}\` is not a function.`)
}
else if (!isUndefined(propOpts.validateFn)) throw new Error(`\`validateFn\` should only be passed if \`type\` is \`custom\`. \`type\` is currently ${propOpts.type}.`)
})
return props => this.validateProperties(props, model)
}
validateProperties (props, model) {
if (!isObject(props)) throw new Error('`props` was not an object')
const validatedProperties = {}
Object.entries(model).forEach(([propName, propOpts]) => {
if (propOpts.required && isUndefined(props[propName])) throw new Error(`\`${propName}\` is required.`)
if (!propOpts.required && isUndefined(props[propName])) return
switch (propOpts.type) {
case 'custom': {
const err = propOpts.validateFn(props[propName], props)
if (err) throw new Error(err)
else validatedProperties[propName] = props[propName]
break
}
case 'any': {
if (!isUndefined(props[propName])) validatedProperties[propName] = props[propName]
break
}
default: {
if (!isFunction(eval(`is${toTitleCase(propOpts.type)}`))) throw new Error(`Validation for \`is${toTitleCase(propertyRulesObject.type)}\` does not exist natively. Please check your spelling or create a custom validation function for this property.`)
else {
if (!eval(`is${toTitleCase(propOpts.type)}`)(props[propName])) throw new Error(`Property \`${propName}\` of value \`${props[propName]}\` is not of type \`${propOpts.type}\`.`)
validatedProperties[propName] = props[propName]
}
}
}
})
return validatedProperties
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment