UNPKG

verificator

Version:

Client and server-side validation JavaScript library

226 lines (225 loc) 7.93 kB
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; } }