@rc-component/form
Version:
React Form Component
235 lines (220 loc) • 7.79 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.validateRules = validateRules;
var _asyncValidator = _interopRequireDefault(require("@rc-component/async-validator"));
var React = _interopRequireWildcard(require("react"));
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
var _messages = require("./messages");
var _set = require("@rc-component/util/lib/utils/set");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
// Remove incorrect original ts define
const AsyncValidator = _asyncValidator.default;
/**
* Replace with template.
* `I'm ${name}` + { name: 'bamboo' } = I'm bamboo
*/
function replaceMessage(template, kv) {
return template.replace(/\\?\$\{\w+\}/g, str => {
if (str.startsWith('\\')) {
return str.slice(1);
}
const key = str.slice(2, -1);
return kv[key];
});
}
const CODE_LOGIC_ERROR = 'CODE_LOGIC_ERROR';
async function validateRule(name, value, rule, options, messageVariables) {
const cloneRule = {
...rule
};
// Bug of `async-validator`
// https://github.com/react-component/field-form/issues/316
// https://github.com/react-component/field-form/issues/313
delete cloneRule.ruleIndex;
// https://github.com/ant-design/ant-design/issues/40497#issuecomment-1422282378
AsyncValidator.warning = () => void 0;
if (cloneRule.validator) {
const originValidator = cloneRule.validator;
cloneRule.validator = (...args) => {
try {
return originValidator(...args);
} catch (error) {
console.error(error);
return Promise.reject(CODE_LOGIC_ERROR);
}
};
}
// We should special handle array validate
let subRuleField = null;
if (cloneRule && cloneRule.type === 'array' && cloneRule.defaultField) {
subRuleField = cloneRule.defaultField;
delete cloneRule.defaultField;
}
const validator = new AsyncValidator({
[name]: [cloneRule]
});
const messages = (0, _set.merge)(_messages.defaultValidateMessages, options.validateMessages);
validator.messages(messages);
let result = [];
try {
await Promise.resolve(validator.validate({
[name]: value
}, {
...options
}));
} catch (errObj) {
if (errObj.errors) {
result = errObj.errors.map(({
message
}, index) => {
const mergedMessage = message === CODE_LOGIC_ERROR ? messages.default : message;
return /*#__PURE__*/React.isValidElement(mergedMessage) ?
/*#__PURE__*/
// Wrap ReactNode with `key`
React.cloneElement(mergedMessage, {
key: `error_${index}`
}) : mergedMessage;
});
}
}
if (!result.length && subRuleField) {
const subResults = await Promise.all(value.map((subValue, i) => validateRule(`${name}.${i}`, subValue, subRuleField, options, messageVariables)));
return subResults.reduce((prev, errors) => [...prev, ...errors], []);
}
// Replace message with variables
const kv = {
...rule,
name,
enum: (rule.enum || []).join(', '),
...messageVariables
};
const fillVariableResult = result.map(error => {
if (typeof error === 'string') {
return replaceMessage(error, kv);
}
return error;
});
return fillVariableResult;
}
/**
* We use `async-validator` to validate the value.
* But only check one value in a time to avoid namePath validate issue.
*/
function validateRules(namePath, value, rules, options, validateFirst, messageVariables) {
const name = namePath.join('.');
// Fill rule with context
const filledRules = rules.map((currentRule, ruleIndex) => {
const originValidatorFunc = currentRule.validator;
const cloneRule = {
...currentRule,
ruleIndex
};
// Replace validator if needed
if (originValidatorFunc) {
cloneRule.validator = (rule, val, callback) => {
let hasPromise = false;
// Wrap callback only accept when promise not provided
const wrappedCallback = (...args) => {
// Wait a tick to make sure return type is a promise
Promise.resolve().then(() => {
(0, _warning.default)(!hasPromise, 'Your validator function has already return a promise. `callback` will be ignored.');
if (!hasPromise) {
callback(...args);
}
});
};
// Get promise
const promise = originValidatorFunc(rule, val, wrappedCallback);
hasPromise = promise && typeof promise.then === 'function' && typeof promise.catch === 'function';
/**
* 1. Use promise as the first priority.
* 2. If promise not exist, use callback with warning instead
*/
(0, _warning.default)(hasPromise, '`callback` is deprecated. Please return a promise instead.');
if (hasPromise) {
promise.then(() => {
callback();
}).catch(err => {
callback(err || ' ');
});
}
};
}
return cloneRule;
}).sort(({
warningOnly: w1,
ruleIndex: i1
}, {
warningOnly: w2,
ruleIndex: i2
}) => {
if (!!w1 === !!w2) {
// Let keep origin order
return i1 - i2;
}
if (w1) {
return 1;
}
return -1;
});
// Do validate rules
let summaryPromise;
if (validateFirst === true) {
// >>>>> Validate by serialization
summaryPromise = new Promise(async (resolve, reject) => {
/* eslint-disable no-await-in-loop */
for (let i = 0; i < filledRules.length; i += 1) {
const rule = filledRules[i];
const errors = await validateRule(name, value, rule, options, messageVariables);
if (errors.length) {
reject([{
errors,
rule
}]);
return;
}
}
/* eslint-enable */
resolve([]);
});
} else {
// >>>>> Validate by parallel
const rulePromises = filledRules.map(rule => validateRule(name, value, rule, options, messageVariables).then(errors => ({
errors,
rule
})));
summaryPromise = (validateFirst ? finishOnFirstFailed(rulePromises) : finishOnAllFailed(rulePromises)).then(errors => {
// Always change to rejection for Field to catch
return Promise.reject(errors);
});
}
// Internal catch error to avoid console error log.
summaryPromise.catch(e => e);
return summaryPromise;
}
async function finishOnAllFailed(rulePromises) {
return Promise.all(rulePromises).then(errorsList => {
const errors = [].concat(...errorsList);
return errors;
});
}
async function finishOnFirstFailed(rulePromises) {
let count = 0;
return new Promise(resolve => {
rulePromises.forEach(promise => {
promise.then(ruleError => {
if (ruleError.errors.length) {
resolve([ruleError]);
}
count += 1;
if (count === rulePromises.length) {
resolve([]);
}
});
});
});
}