Skip to content

Commit

Permalink
feat: add support for promise
Browse files Browse the repository at this point in the history
- maintain backwards compatible for callbacks
- new method `validatePromise`
  • Loading branch information
jdkahn committed Jul 25, 2019
1 parent f77446b commit 55842dd
Show file tree
Hide file tree
Showing 5 changed files with 561 additions and 27 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"build": "ice-scripts build",
"prepublishOnly": "npm run build",
"test": "ice-scripts test",
"test-watch": "ice-scripts test --jest-watchAll",
"precommit": "lint-staged",
"eslint": "eslint '@(src|test)/**/*.@(js|jsx)'"
},
Expand Down Expand Up @@ -55,7 +56,8 @@
"prettier": "^1.18.2",
"react": "^16.3.0",
"react-dom": "^16.3.0",
"semantic-release": "^15.13.18"
"semantic-release": "^15.13.18",
"sinon": "^7.3.2"
},
"componentConfig": {
"name": "validate",
Expand Down
108 changes: 83 additions & 25 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
/* eslint-disable callback-return */
import { complementError, asyncMap } from './util';
import { getValidationMethod } from './validator';
import {
complementError,
asyncMap,
asyncMapPromise,
serializeRules,
processErrorResults,
} from './util';
import defaultMessages from './messages';

function noop() {}
Expand Down Expand Up @@ -32,37 +37,29 @@ class Schema {
);
}

/**
*
* @param {Object} source - map of field names and values to use in validation
* @param {Function} callback - OPTIONAL - callback to run after all
* @returns {null | Promise}
* - { null } - if using callbacks
* - { Promise }
* - { null } - if no rules or no errors
* - { errors: Array, fields: Object } - errors from validation and fields that have errors
*/
validate(source, callback) {
if (!callback) {
return this.validatePromise(source);
}

if (!this._rules || Object.keys(this._rules).length === 0) {
if (callback) {
callback(null);
}
return;
}

// serialize rules
let arr;
let value;
const series = {};
const names = Object.keys(this._rules);
names.forEach(name => {
arr = this._rules[name];
value = source[name];

if (!Array.isArray(arr)) {
arr = [arr];
}

arr.forEach(rule => {
rule.validator = getValidationMethod(rule);
rule.field = name;
if (!rule.validator) {
return;
}
series[name] = series[name] || [];
series[name].push({ rule, value, source, field: name });
});
});
const series = serializeRules(source, this._rules);

if (Object.keys(series).length === 0) {
callback(null);
Expand Down Expand Up @@ -136,6 +133,67 @@ class Schema {
}
);
}

/**
*
* @param {Object} source - map of field names and values to use in validation
* @returns {Promise}
* - {null} if no rules or no errors
* - { errors: Array, fields: Object } - errors from validation and fields that have errors
*/
async validatePromise(source) {
if (!this._rules || Object.keys(this._rules).length === 0) {
return Promise.resolve(null);
}

const series = serializeRules(source, this._rules);

if (Object.keys(series).length === 0) {
return Promise.resolve(null);
}

const results = await asyncMapPromise(
series,
this._options,
async data => {
const rule = data.rule;
rule.field = data.field;

let errors;

try {
errors = await rule.validator(
rule,
data.value,
this._options
);
} catch (error) {
errors = error;
}

if (errors) {
if (!Array.isArray(errors)) {
errors = [errors];
}

// 自定义错误
if (errors.length && rule.message) {
errors = [].concat(rule.message);
}

return errors.map(complementError(rule));
} else {
return [];
}
}
);

if (!results) {
return { errors: results };
}

return processErrorResults(results);
}
}

export default Schema;
101 changes: 101 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { getValidationMethod } from './validator';

const formatRegExp = /%[sdj%]/g;

export function format(...args) {
Expand Down Expand Up @@ -103,6 +105,37 @@ export function asyncMap(objArr, option, func, callback) {
});
}

async function resolveErrorPromiseInSeries(arr, func) {
return arr.reduce(async (prevPromise, next) => {
let errors;
try {
errors = await prevPromise;
} catch (e) {
errors = e;
}

if (errors && errors.length) {
return errors;
}

return func(next);
}, Promise.resolve());
}

export async function asyncMapPromise(objArr, option, func) {
if (option.first) {
const flatObjArr = flattenObjArr(objArr);

return resolveErrorPromiseInSeries(flatObjArr, func);
}

const objArrValues = Object.values(objArr);

return Promise.all(
objArrValues.map(val => resolveErrorPromiseInSeries(val, func))
);
}

export function complementError(rule) {
return oe => {
if (oe && oe.message) {
Expand All @@ -115,3 +148,71 @@ export function complementError(rule) {
};
};
}

export function serializeRules(source, rules) {
// serialize rules
let arr;
let value;
const series = {};
const names = Object.keys(rules);
names.forEach(name => {
arr = rules[name];
value = source[name];

if (!Array.isArray(arr)) {
arr = [arr];
}

arr.forEach(rule => {
rule.validator = getValidationMethod(rule);
rule.field = name;
if (!rule.validator) {
return;
}
series[name] = series[name] || [];
series[name].push({ rule, value, source, field: name });
});
});

return series;
}

/**
*
* @param {Array} results errors from running validation
* @returns {Object} { errors: Array, fields: Object }
*/
export function processErrorResults(results) {
let errors = [];
let fields = {};

function add(e) {
if (Array.isArray(e)) {
errors = errors.concat(e);
} else {
errors.push(e);
}
}

for (let i = 0; i < results.length; i++) {
add(results[i]);
}

if (!errors.length) {
errors = null;
fields = null;
} else {
for (let i = 0; i < errors.length; i++) {
const field = errors[i].field;
if (field) {
fields[field] = fields[field] || [];
fields[field].push(errors[i]);
}
}
}

return {
errors,
fields,
};
}
Loading

0 comments on commit 55842dd

Please sign in to comment.