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}`) | |
}) | |
} |