Last active
September 1, 2015 23:05
-
-
Save schleumer/e6426a3112788e052658 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var validator = require('validator') | |
, R = require("ramda") | |
, Promise = require("bluebird"); | |
var skip = R.defaultTo(null); | |
var pairObjectify = R.pipe( | |
R.splitEvery(2), | |
R.map(x => ({key: R.head(x), value: R.last(x)})) | |
); | |
var hasFailure = R.pipe( | |
R.values, | |
R.map(R.values), | |
R.flatten, | |
R.reduce(R.and, true), | |
R.not | |
); | |
// O-O-O-O-O-O-O-O-O-O-O-O-O-OVERKILL | |
var evalRules = R.pipe( | |
R.ifElse( | |
R.is(String), | |
R.split(/\|/), | |
skip | |
), | |
R.ifElse( | |
R.is(Array), | |
R.pipe( | |
R.map( | |
R.ifElse( | |
R.is(String), | |
R.split(/:/), | |
skip | |
) | |
), | |
R.fromPairs | |
), | |
skip | |
), | |
R.mapObj( | |
R.ifElse( | |
R.isNil, | |
R.defaultTo(null), | |
R.ifElse( | |
R.is(String), | |
R.split(','), | |
skip | |
) | |
) | |
) | |
); | |
/** | |
* Cuida da validação de dados, | |
* a ideia é que a implementação seja identica ao do Laravel para | |
* não haver redundancia de implementações | |
* | |
* FAQ: | |
* Q: Por que usar $q e bluebird ao mesmo tempo? | |
* R: O $q implica que cada promise resolvida irá rodar um digest no $rootScope o que irá atualizar a interface, | |
* mas aqui a gente só quer fazer a validação de boas, e quando a validação estiver pronta a gente resolve com o $q | |
* para rolar o digest. | |
* | |
* Q: Por que não reject ao invés de resolve(boolean) ou boolean? | |
* R: ¯\_(ツ)_/¯ | |
* | |
* NOTE: a performance é questionável | |
*/ | |
module.exports = /* @ngInject */ function ValidatorFactory($q) { | |
/** | |
* não utilizar a sintaxe: | |
* key() {} | |
* utilizar: | |
* key: () => {} | |
*/ | |
var validators = { | |
/** | |
* The field under validation must be yes, on, or 1. | |
* This is useful for validating "Terms of Service" acceptance. | |
*/ | |
"accepted": value => false, | |
/** | |
* The field under validation must be a valid URL according to the checkdnsrr PHP function. | |
*/ | |
// "active_url": value => false, | |
/** | |
* The field under validation must be a value after a given date. | |
* The dates will be passed into the PHP strtotime function. | |
*/ | |
"after": (value, data, ref) => false, | |
/** | |
* The field under validation must be entirely alphabetic characters. | |
*/ | |
"alpha": (value, data) => false, | |
/** | |
* The field under validation may have alpha-numeric characters, as well as dashes and underscores. | |
*/ | |
"alpha_dash": (value, data) => false, | |
/** | |
* The field under validation must be entirely alpha-numeric characters. | |
*/ | |
"alpha_num": (value, data) => false, | |
/** | |
* he field under validation must be of type array. | |
*/ | |
"array": (value, data) => false, | |
/** | |
* The field under validation must be a value preceding the given date. | |
* The dates will be passed into the PHP strtotime function. | |
*/ | |
"before": (value, data, ref) => false, | |
/** | |
* The field under validation must have a size between the given min and max. | |
* Strings, numerics, and files are evaluated in the same fashion as the size rule. | |
*/ | |
"between": (value, data, min, max) => false, | |
/** | |
* The field under validation must be able to be cast as a boolean. | |
* Accepted input are true, false, 1, 0, "1" and "0". | |
*/ | |
"boolean": (value, params, data) => false, | |
/** | |
* The field under validation must have a matching field of foo_confirmation. | |
* For example, if the field under validation is password, | |
* a matching password_confirmation field must be present in the input. | |
*/ | |
"confirmed": (value, params, data) => false, | |
/** | |
* The field under validation must be a valid date according to the strtotime PHP function. | |
*/ | |
"date": (value, params, data) => false, | |
/** | |
* The field under validation must match the format defined according to the date_parse_from_format PHP function. | |
*/ | |
"date_format": (value, data, format) => false, | |
/** | |
* The given field must be different than the field under validation. | |
*/ | |
"different": (value, data, ref) => false, | |
/** | |
* The field under validation must be numeric and must have an exact length of value. | |
*/ | |
"digits": (value, data, len) => false, | |
/** | |
* The field under validation must have a length between the given min and max. | |
*/ | |
"digits_between": (value, data, min, max) => false, | |
/** | |
* The field under validation must be formatted as an e-mail address. | |
*/ | |
"email": (value, data) => false, | |
/** | |
* The field under validation must exist on a given database table. | |
*/ | |
//"exists": (value, data) => false, | |
/** | |
* The file under validation must be an image (jpeg, png, bmp, or gif) | |
*/ | |
//"image": (value, data) => false, | |
/** | |
* The field under validation must be included in the given list of values. | |
*/ | |
"in": (value, data, ...values) => false, | |
/** | |
* The field under validation must have an integer value. | |
*/ | |
"integer": (value, data) => false, | |
/** | |
* The field under validation must be formatted as an IP address. | |
*/ | |
"ip": (value, data) => false, | |
/** | |
* The field under validation must be less than or equal to a maximum value. | |
* Strings, numerics, and files are evaluated in the same fashion as the size rule. | |
*/ | |
"max": (value, data, ref) => false, | |
/** | |
* The file under validation must have a MIME type corresponding to one of the listed extensions. | |
*/ | |
// "mimes": (value, data) => false, | |
/** | |
* The field under validation must have a minimum value. | |
* Strings, numerics, and files are evaluated in the same fashion as the size rule. | |
*/ | |
"min": (value, data, ref) => false, | |
/** | |
* The field under validation must not be included in the given list of values. | |
*/ | |
"not_in": (value, data, values) => false, | |
/** | |
* The field under validation must have a numeric value. | |
*/ | |
"numeric": (value, data) => validator.isFloat(value), | |
/** | |
* The field under validation must match the given regular expression. | |
*/ | |
"regex": (value, data, pattern) => !!value.match(pattern), | |
/** | |
* The field under validation must be present in the input data. | |
*/ | |
"required": value => !validator.isNull(value), | |
/** | |
* The field under validation must be present if the field field is equal to any value. | |
* @todo zip resolves? | |
*/ | |
"required_if": (value, data, ...params) => R.pipe( | |
pairObjectify, | |
R.map((item) => (data[item.key] !== null && data[item.key] == item.value) ? !validator.isNull(value) : true), | |
R.values, | |
R.reduce(R.and, true) | |
)(params), | |
/** | |
* The field under validation must be present only if any of the other specified fields are present. | |
*/ | |
"required_with": (value, data, ...fields) => false, | |
/** | |
* The field under validation must be present only if all of the other specified fields are present. | |
*/ | |
"required_with_all": (value, data, ...fields) => false, | |
/** | |
* The field under validation must be present only when any of the other specified fields are not present. | |
*/ | |
"required_without": (value, data, ...fields) => false, | |
/** | |
* The field under validation must be present only when the all of the other specified fields are not present. | |
*/ | |
"required_without_all": (value, data, ...fields) => false, | |
/** | |
* The given field must match the field under validation. | |
*/ | |
"same": (value, data, field) => false, | |
/** | |
* The field under validation must have a size matching the given value. | |
* For string data, value corresponds to the number of characters. | |
* For numeric data, value corresponds to a given integer value. | |
* For files, size corresponds to the file size in kilobytes. | |
*/ | |
"size": (value, data, ref) => false, | |
/** | |
* The field under validation must be a string type. | |
*/ | |
"string": (value, data) => false, | |
/** | |
* The field under validation must be a valid timezone identifier according to the timezone_identifiers_list PHP function. | |
*/ | |
"timezone": (value, data) => false, | |
/** | |
* The field under validation must be unique on a given database table. If the column option is not specified, the field name will be used. | |
*/ | |
//"unique": (value, data) => false | |
/** | |
* The field under validation must be formatted as an URL. | |
*/ | |
"url": (value, data) => false, | |
"test-promise": (value, data) => new Promise((resolve, reject) => { | |
setTimeout(_=> resolve(true), 1000); | |
}) | |
}; | |
var messages = { | |
"accepted": "", | |
"after": "", | |
"alpha": "", | |
"alpha_dash": "", | |
"alpha_num": "", | |
"array": "", | |
"before": "", | |
"between": "", | |
"boolean": "", | |
"confirmed": "", | |
"date": "", | |
"date_format": "", | |
"different": "", | |
"digits": "", | |
"digits_between": "", | |
"email": "", | |
"in": "", | |
"integer": "", | |
"ip": "", | |
"max": "", | |
"min": "", | |
"not_in": "", | |
"numeric": "O campo <b>$field</b> deve ser numérico", | |
"regex": "", | |
"required": "O campo <b>$field</b> é necessário", | |
"required_if": "O campo <b>$field</b> é necessário", | |
"required_with": "", | |
"required_with_all": "", | |
"required_without": "", | |
"required_without_all": "", | |
"same": "", | |
"size": "", | |
"string": "", | |
"timezone": "", | |
"url": "" | |
}; | |
/** | |
* @constructor | |
*/ | |
var Validator = function (context, rules, fieldsNames = {}) { | |
var parsedRules = R.mapObj(value => evalRules(value), rules); | |
this.translateField = field => fieldsNames[field] || field; | |
this.validateRule = R.curryN(2, (rule, value, data = null, ...params) => { | |
if (validators.hasOwnProperty(rule)) { | |
return validators[rule].apply(validators, [value, data].concat(params)); | |
} | |
return false; | |
}); | |
this.getMessage = (rule, field, params) => { | |
var message = null; | |
if (messages.hasOwnProperty(`${field}.${rule}`)) { | |
message = messages[`${field}.${rule}`]; | |
} else if (messages.hasOwnProperty(field)) { | |
message = messages[field]; | |
} else if (messages.hasOwnProperty(rule)) { | |
message = messages[rule]; | |
} else { | |
return `O campo ${field} não obedeceu a regra ${rule}`; | |
} | |
if (typeof message == "function") { | |
return message(field, rule, params); | |
} | |
return message | |
.replace('$field', this.translateField(field)) | |
}; | |
this.validate = data => { | |
var deferred = $q.defer(); | |
Promise.props(R.mapObjIndexed((rules, field) => { | |
return Promise.props(R.mapObjIndexed((params, rule) => { | |
return this.validateRule.apply(this, [rule, data[field], data].concat(params)); | |
}, rules)) | |
}, parsedRules)).then(result => { | |
var messages = R.pipe( | |
R.mapObjIndexed((rules, field) => { | |
return R.pipe( | |
R.pickBy(R.not), | |
R.mapObjIndexed((result, rule) => { | |
return this.getMessage(rule, field, /* bem errado */parsedRules[field][rule]); | |
}), | |
R.values | |
)(rules); | |
}), | |
R.pickBy(x => x.length > 0) | |
)(result); | |
return { | |
result, | |
messages: R.zipObj(R.map(x => `${context}.${x}`, R.keys(messages)), R.values(messages)) | |
}; | |
}).then(f => { | |
setTimeout(_ => { | |
if (hasFailure(f.result)) { | |
deferred.reject(f.messages); | |
} else { | |
deferred.resolve(f.result); | |
} | |
}, 500); | |
}).catch(e => { | |
/// ??????? | |
throw e; | |
}); | |
return deferred.promise; | |
}; | |
}; | |
return Validator; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Querendo entender o que se passa e a fazer essas tretas com Ramda