Skip to content

Instantly share code, notes, and snippets.

@schleumer
Last active September 1, 2015 23:05
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 schleumer/e6426a3112788e052658 to your computer and use it in GitHub Desktop.
Save schleumer/e6426a3112788e052658 to your computer and use it in GitHub Desktop.
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;
};
@chrisfls
Copy link

chrisfls commented Sep 1, 2015

Querendo entender o que se passa e a fazer essas tretas com Ramda

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment