Skip to content

Instantly share code, notes, and snippets.

@mholtzhausen
Created July 28, 2020 12:11
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 mholtzhausen/2d59f689606a951b93836548ed805490 to your computer and use it in GitHub Desktop.
Save mholtzhausen/2d59f689606a951b93836548ed805490 to your computer and use it in GitHub Desktop.
Simple Js validation - schemas for objects - extensible

Simple Js validation - schemas for objects - extensible

validation types work like this: [+]typename[(arg,[arg,...])]

The + in the beginning makes it required any arguments applied will be passed to that type's validation function

const Validity = require('./Validity')
const valid=new Validity()
require('./validityTypes')(valid)
const UserSchema = {
firstname: '+string(2,150)',
lastname: '+string(,150)',
sa_id: '+sa_id',
email: '+email(150)',
cellphone: '+sa_cellphone(true,false)',
meet_lattitude: 'gis_latt',
meet_longitude: 'gis_long',
meet_address: 'string(,250)',
username: '+string(3,50)',
password: '+password(5,50,true,true,true)',
}
valid.registerSchema('User', UserSchema)
const errors = valid.validateData('User',{
firstname: 'Peter',
lastname: 'Pan',
sa_id: '7504223210087',
email: 'example@gmail.com',
cellphone: '0823546721',
meet_lattitude: '',
meet_longitude: '',
meet_address: '',
username: 'someUser',
password: 'weakP@ss',
})
console.log('testing parseType')
Object.values(UserSchema).forEach(str => console.log(str, valid.parseType(str)))
console.log('testing validateValue')
const value = '7504223210087'
const type = 'sa_id'
console.log(`validating '${value}' against '${type}'`, valid.validateValue(value, type))
console.log(errors)
/**
* Validity
*
* Self-contained regex-based schema driven validation suite
*
*/
class Validity{
constructor(){
this.typeRegistry={}
this.schemaRegistry={}
}
static regexMatch (regex, str) {
let m;
let g = []
while ((m = regex.exec(str)) !== null) {
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
g.push({ ...m.groups })
}
return g
}
validateData (schemaName, data) {
if(!(schemaName in this.schemaRegistry)) throw new Error(`Validity.validateData: '${schemaName}' is not a known schema`)
let schema = this.schemaRegistry[schemaName]
let errors = []
for(let field in schema){
let value=undefined
let type = schema[field]
if(field in data) value=data[field]
if(!this.validateValue(value,type)){
errors.push(field)
}
}
return errors.length?errors:true
}
parseType (type) {
let regex = /(?<required>\+)?(?<type>[^(]+)(\((?<args>.*?)\))?\s?/gm
let typeArray = Validity.regexMatch(regex, type)
if (typeArray.length === 0) return false
let [typeDef] = typeArray
return {
validationType: typeDef.type,
args: typeDef.args === undefined ? '' : typeDef.args.split(','),
required: !!typeDef.required
}
}
validateValue (value, type) {
let typeDef = this.parseType(type)
if (!typeDef) return false
if (!(typeDef.validationType in this.typeRegistry))
throw new Error(`Validity.validateValue: ${typeDef.validationType} is not a known validator`)
let notDefined=(value === undefined || value === '')
if (typeDef.required && notDefined) return false
if (notDefined) return true
return !!this.typeRegistry[typeDef.validationType].call({}, value, ...typeDef.args)
}
registerType (type, fn, force = false) {
if (type in this.typeRegistry && !force) return false
if (typeof fn !== 'function') return false
this.typeRegistry[type] = fn
}
registerSchema (schemaName, schema = [], force = false) {
if (schemaName in this.schemaRegistry && !force) return false
this.schemaRegistry[schemaName] = schema
}
}
module.exports = Validity
module.exports = validityInstance => {
validityInstance.registerType('string', (value, minLen, maxLen) => {
value = `${value}`
if (minLen !== null && value.length < minLen) return false
if (maxLen !== null && value.length > maxLen) return false
return true
})
validityInstance.registerType('password', (value, minLen, maxLen, requireNumber, requireCapital, requireSpecial) => {
requireNumber = `${requireNumber}`.toLowerCase() === 'true'
requireCapital = `${requireCapital}`.toLowerCase() === 'true'
requireSpecial = `${requireSpecial}`.toLowerCase() === 'true'
value = `${value}`
let re
if (minLen !== null && value.length < minLen) return false
if (maxLen !== null && value.length > maxLen) return false
re = /\d/g
if (requireNumber && !re.test(value)) return false
re = /[A-Z]/g
if (requireCapital && !re.test(value)) return false
re = /[\@\#\!\$\%\^\&\*\-\=\+\[\]\{\}\|\\\/\`\;\:\'\"\±\§]/g
if (requireSpecial && !re.test(value)) return false
return true
})
validityInstance.registerType('sa_id', (value) => {
if (`${value}`.length !== 13) return false
let arr = (value + '')
.split('')
.reverse()
.map(x => parseInt(x))
let lastDigit = arr.splice(0, 1)[0]
let sum = arr.reduce((acc, val, i) => (i % 2 !== 0 ? acc + val : acc + ((val * 2) % 9) || 9), 0)
sum += lastDigit
return sum % 10 === 0
})
validityInstance.registerType('email', (value, maxLen) => {
if (maxLen !== null && value.length > maxLen) return false
let re = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/i
return re.test(value)
})
validityInstance.registerType('sa_cellphone', (value, accept_international, force_international) => {
accept_international = `${accept_international}`.toLowerCase() === 'true'
force_international = `${force_international}`.toLowerCase() === 'true'
//test regular
let re = /^0\d{9,}$/i
let regular_number = re.test(value)
//test international
re = /^\+27[1-9]\d{8,}$/i
let int_number = re.test(value)
if (force_international) return int_number
if (accept_international) return int_number || regular_number
return regular_number
})
validityInstance.registerType('gis_latt', (value) => {
let re = /^\-?\d{1,2}\.\d{6,}$/g
return re.test(`${value}`)
})
validityInstance.registerType('gis_long', (value) => {
let re = /^\-?\d{1,2}\.\d{6,}$/g
return re.test(`${value}`)
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment