verificator
Version:
Client and server-side validation JavaScript library
226 lines (225 loc) • 7.93 kB
JavaScript
import { setData, setRules, startValidate, stopValidate } from './actions';
import { DEPENDENT_RULES, DEFAULT_LOCALE } from './constants';
import DEFAULT_RULES from './rules';
import deepmerge from 'deepmerge';
import reducer from './reducer';
import { createStore, applyMiddleware } from 'redux';
import ErrorBag from './ErrorBag';
import Translator from './Translator';
import * as utils from './utils';
let RULES = DEFAULT_RULES;
let LOCALE = DEFAULT_LOCALE;
export default class Validator {
constructor(data, rules, customLocale = {}) {
this._listeners = [];
this._rules = {};
this._store = createStore(reducer, applyMiddleware(store => next => action => {
const result = next(action);
this._listeners.forEach(listener => listener(action.type));
return result;
}));
this._store.dispatch(setData(data || {}));
this.setRules(rules);
this._errors = new ErrorBag(this._store);
this._translator = new Translator(Object.assign({}, LOCALE, { customMessages: customLocale.messages || {}, customAttributes: customLocale.attributes || {} }), this);
}
static useLocale(locale) {
LOCALE = locale || DEFAULT_LOCALE;
}
static extend(name, validate) {
if (typeof validate !== 'function') {
throw new TypeError(`The rule [${name}] must be a function`);
}
RULES[name] = validate;
}
get errors() {
return this._errors;
}
isValidating(attribute) {
const { validating } = this._store.getState();
if (attribute) {
return Boolean(validating[attribute]);
}
return Object.keys(validating).some(key => validating[key]);
}
validateAll() {
const { rules } = this._store.getState();
const attributes = Object.keys(rules);
this.errors.clear();
const promises = [];
for (let attribute of attributes) {
promises.push(this.validate(attribute));
}
return Promise.all(promises).then(results => results.every(result => result));
}
validate(attribute) {
if (this.isValidating(attribute) || !this._passesOptionalCheck(attribute)) {
return Promise.resolve(true);
}
this._store.dispatch(startValidate(attribute));
if (this._errors.has(attribute)) {
this._errors.remove(attribute);
}
const { rules } = this._store.getState();
let queue = [];
for (let rule of rules[attribute]) {
queue.push(((a, r) => {
return (result) => {
if (result === true) {
return this._validateRule(a, r);
}
return result;
};
})(attribute, rule));
}
return queue.reduce((promise, queueFn) => promise.then(queueFn), Promise.resolve(true)).then(result => {
this._store.dispatch(stopValidate(attribute));
return result;
}).catch(e => {
this._store.dispatch(stopValidate(attribute));
return e;
});
}
setData(data) {
const { initialRules } = this._store.getState();
this._store.dispatch(setData(data || {}));
this.setRules(initialRules);
return this;
}
getData() {
const { data } = this._store.getState();
return data;
}
getValue(attribute, defaultValue) {
const { data } = this._store.getState();
return utils.get(data, attribute, defaultValue);
}
setRules(rules) {
const { data } = this._store.getState();
this._store.dispatch(setRules(data, rules || {}));
return this;
}
addRules(rules) {
const { initialRules } = this._store.getState();
this.setRules(deepmerge(initialRules, rules));
return this;
}
getRules() {
const { rules } = this._store.getState();
return rules;
}
getPrimaryAttribute(attribute) {
const { implicitAttributes } = this._store.getState();
for (let unparsed of Object.keys(implicitAttributes)) {
if (implicitAttributes[unparsed].indexOf(attribute) > -1) {
return unparsed;
}
}
return attribute;
}
subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.');
}
let isListener = true;
this._listeners.push(listener);
return () => {
if (isListener === false) {
return;
}
isListener = false;
const index = this._listeners.indexOf(listener);
this._listeners.splice(index, 1);
};
}
getRule(attribute, target) {
const { rules } = this._store.getState();
if (!(attribute in rules)) {
return null;
}
if (typeof target === 'string') {
target = [target];
}
for (let rule of rules[attribute]) {
const { name } = rule;
if (target.indexOf(name) > -1) {
return rule;
}
}
return null;
}
hasRule(attribute, target) {
return this.getRule(attribute, target) !== null;
}
extend(name, validate) {
if (typeof validate !== 'function') {
throw new TypeError(`The rule [${name}] must be a function`);
}
this._rules[name] = validate;
return this;
}
getImplicitAttributes() {
const { implicitAttributes } = this._store.getState();
return implicitAttributes;
}
setCustomMessages(messages) {
this._translator.setCustomMessages(messages);
return this;
}
addCustomMessages(messages) {
this._translator.addCustomMessages(messages);
return this;
}
setAttributeNames(attributes) {
this._translator.setCustomAttributes(attributes);
return this;
}
addAttributeNames(attributes) {
this._translator.addCustomAttributes(attributes);
return this;
}
_validateRule(attribute, rule) {
const value = this.getValue(attribute);
const { name } = rule;
const parameters = this._replaceAsterisksInParameters(rule, attribute);
const validate = this._rules[name] || RULES[name];
if (validate) {
return Promise.resolve(validate(attribute, value, parameters, this)).then(result => {
if (result === false) {
const message = this._translator.getMessage(name, attribute, value, parameters);
this._errors.add(attribute, message);
}
return result;
});
}
return Promise.reject(new Error(`The rule [${name}] does not exist`));
}
_passesOptionalCheck(attribute) {
if (!this.hasRule(attribute, 'sometimes')) {
return true;
}
return this.getValue(attribute, '__MISSING__') !== '__MISSING__';
}
_replaceAsterisksInParameters(rule, attribute) {
const { name, parameters } = rule;
if (DEPENDENT_RULES.indexOf(name) === -1) {
return parameters;
}
const pattern = utils.escape(this.getPrimaryAttribute(attribute)).replace(/\\\*/g, '([^\.]+)');
const regex = new RegExp(`^${pattern}`);
const match = regex.exec(attribute);
if (match) {
match.shift();
const keys = match.slice(0);
return parameters.map(field => {
if (typeof field === 'string') {
keys.forEach(key => {
field = field.replace('*', key);
});
}
return field;
});
}
return parameters;
}
}