vtils
Version:
一个面向业务的 JavaScript/TypeScript 实用程序库。
358 lines (354 loc) • 12.3 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
exports.__esModule = true;
exports.default = ObjectSchema;
var _createForOfIteratorHelperLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/createForOfIteratorHelperLoose"));
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _utils = require("../../utils");
var _propertyExpr = require("property-expr");
var _locale = require("./locale.js");
var _mixed = _interopRequireDefault(require("./mixed"));
var _inherits = _interopRequireDefault(require("./util/inherits"));
var _reach = _interopRequireDefault(require("./util/reach"));
var _runTests = _interopRequireDefault(require("./util/runTests"));
var _sortByKeyOrder = _interopRequireDefault(require("./util/sortByKeyOrder"));
var _sortFields = _interopRequireDefault(require("./util/sortFields"));
var isObject = function isObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]';
};
function unknown(ctx, value) {
var known = Object.keys(ctx.fields);
return Object.keys(value).filter(function (key) {
return known.indexOf(key) === -1;
});
}
function ObjectSchema(spec) {
var _this2 = this;
if (!(this instanceof ObjectSchema)) return typeof spec === 'function' ? spec(new ObjectSchema()) : new ObjectSchema(spec);
_mixed.default.call(this, {
type: 'object',
default: function _default() {
var _this = this;
if (!this._nodes.length) return undefined;
var dft = {};
this._nodes.forEach(function (key) {
dft[key] = _this.fields[key].default ? _this.fields[key].getDefault() : undefined;
});
return dft;
}
});
this.fields = Object.create(null);
this._sortErrors = (0, _sortByKeyOrder.default)([]);
this._nodes = [];
this._excludedEdges = [];
this.withMutation(function () {
_this2.transform(function coerce(value) {
if (typeof value === 'string') {
try {
value = JSON.parse(value);
} catch (err) {
value = null;
}
}
if (this.isType(value)) return value;
return null;
});
if (spec) {
_this2.shape(spec);
}
});
}
(0, _inherits.default)(ObjectSchema, _mixed.default, {
_typeCheck: function _typeCheck(value) {
return isObject(value) || typeof value === 'function';
},
_cast: function _cast(_value, options) {
var _this3 = this;
if (options === void 0) {
options = {};
}
var value = _mixed.default.prototype._cast.call(this, _value);
//should ignore nulls here
if (value === undefined) return this.getDefault();
if (!this._typeCheck(value)) return value;
var fields = this.fields;
var strip = this._option('stripUnknown', options) === true;
var props = this._nodes.concat(Object.keys(value).filter(function (v) {
return _this3._nodes.indexOf(v) === -1;
}));
var intermediateValue = {}; // is filled during the transform below
var innerOptions = (0, _extends2.default)({}, options, {
parent: intermediateValue,
__validating: options.__validating || false
});
var isChanged = false;
for (var _iterator = (0, _createForOfIteratorHelperLoose2.default)(props), _step; !(_step = _iterator()).done;) {
var prop = _step.value;
var field = fields[prop];
var exists = (0, _utils.has)(value, prop);
if (field) {
var fieldValue = void 0;
var strict = field._options && field._options.strict;
// safe to mutate since this is fired in sequence
innerOptions.path = (options.path ? options.path + "." : '') + prop;
innerOptions.value = value[prop];
field = field.resolve(innerOptions);
if (field._strip === true) {
isChanged = isChanged || prop in value;
continue;
}
fieldValue = !options.__validating || !strict ? field.cast(value[prop], innerOptions) : value[prop];
if (fieldValue !== undefined) {
intermediateValue[prop] = fieldValue;
}
} else if (exists && !strip) {
intermediateValue[prop] = value[prop];
}
if (intermediateValue[prop] !== value[prop]) {
isChanged = true;
}
}
return isChanged ? intermediateValue : value;
},
/**
* @typedef {Object} Ancestor
* @property {Object} schema - a string property of SpecialType
* @property {*} value - a number property of SpecialType
*/
/**
*
* @param {*} _value
* @param {Object} opts
* @param {string=} opts.path
* @param {*=} opts.parent
* @param {Object=} opts.context
* @param {boolean=} opts.sync
* @param {boolean=} opts.stripUnknown
* @param {boolean=} opts.strict
* @param {boolean=} opts.recursive
* @param {boolean=} opts.abortEarly
* @param {boolean=} opts.__validating
* @param {Object=} opts.originalValue
* @param {Ancestor[]=} opts.from
* @param {Object} [opts.from]
* @param {Function} callback
*/
_validate: function _validate(_value, opts, callback) {
var _this4 = this;
if (opts === void 0) {
opts = {};
}
var errors = [];
var _opts = opts,
sync = _opts.sync,
_opts$from = _opts.from,
from = _opts$from === void 0 ? [] : _opts$from,
_opts$originalValue = _opts.originalValue,
originalValue = _opts$originalValue === void 0 ? _value : _opts$originalValue,
_opts$abortEarly = _opts.abortEarly,
abortEarly = _opts$abortEarly === void 0 ? this._options.abortEarly : _opts$abortEarly,
_opts$recursive = _opts.recursive,
recursive = _opts$recursive === void 0 ? this._options.recursive : _opts$recursive;
from = [{
schema: this,
value: originalValue
}].concat(from);
// this flag is needed for handling `strict` correctly in the context of
// validation vs just casting. e.g strict() on a field is only used when validating
opts.__validating = true;
opts.originalValue = originalValue;
opts.from = from;
_mixed.default.prototype._validate.call(this, _value, opts, function (err, value) {
if (err) {
if (abortEarly) return void callback(err);
errors.push(err);
value = err.value;
}
if (!recursive || !isObject(value)) {
callback(errors[0] || null, value);
return;
}
originalValue = originalValue || value;
var tests = _this4._nodes.map(function (key) {
return function (_, cb) {
var path = key.indexOf('.') === -1 ? (opts.path ? opts.path + "." : '') + key : (opts.path || '') + "[\"" + key + "\"]";
var field = _this4.fields[key];
if (field && field.validate) {
field.validate(value[key], (0, _extends2.default)({}, opts, {
path: path,
from: from,
// inner fields are always strict:
// 1. this isn't strict so the casting will also have cast inner values
// 2. this is strict in which case the nested values weren't cast either
strict: true,
parent: value,
originalValue: originalValue[key]
}), cb);
return;
}
cb(null);
};
});
(0, _runTests.default)({
sync: sync,
tests: tests,
value: value,
errors: errors,
endEarly: abortEarly,
sort: _this4._sortErrors,
path: opts.path
}, callback);
});
},
concat: function concat(schema) {
var next = _mixed.default.prototype.concat.call(this, schema);
next._nodes = (0, _sortFields.default)(next.fields, next._excludedEdges);
return next;
},
shape: function shape(schema, excludes) {
if (excludes === void 0) {
excludes = [];
}
var next = this.clone();
var fields = Object.assign(next.fields, schema);
next.fields = fields;
next._sortErrors = (0, _sortByKeyOrder.default)(Object.keys(fields));
if (excludes.length) {
if (!Array.isArray(excludes[0])) excludes = [excludes];
var keys = excludes.map(function (_ref) {
var first = _ref[0],
second = _ref[1];
return first + "-" + second;
});
next._excludedEdges = next._excludedEdges.concat(keys);
}
next._nodes = (0, _sortFields.default)(fields, next._excludedEdges);
return next;
},
pick: function pick(keys) {
var picked = {};
for (var _iterator2 = (0, _createForOfIteratorHelperLoose2.default)(keys), _step2; !(_step2 = _iterator2()).done;) {
var key = _step2.value;
if (this.fields[key]) picked[key] = this.fields[key];
}
return this.clone().withMutation(function (next) {
next.fields = {};
return next.shape(picked);
});
},
omit: function omit(keys) {
var next = this.clone();
var fields = next.fields;
next.fields = {};
for (var _iterator3 = (0, _createForOfIteratorHelperLoose2.default)(keys), _step3; !(_step3 = _iterator3()).done;) {
var key = _step3.value;
delete fields[key];
}
return next.withMutation(function (next) {
return next.shape(fields);
});
},
from: function from(_from, to, alias) {
var fromGetter = (0, _propertyExpr.getter)(_from, true);
return this.transform(function (obj) {
if (obj == null) return obj;
var newObj = obj;
if ((0, _utils.has)(obj, _from)) {
newObj = (0, _extends2.default)({}, obj);
if (!alias) delete newObj[_from];
newObj[to] = fromGetter(obj);
}
return newObj;
});
},
noUnknown: function noUnknown(noAllow, message) {
if (noAllow === void 0) {
noAllow = true;
}
if (message === void 0) {
message = _locale.object.noUnknown;
}
if (typeof noAllow === 'string') {
message = noAllow;
noAllow = true;
}
var next = this.test({
name: 'noUnknown',
exclusive: true,
message: message,
test: function test(value) {
if (value == null) return true;
var unknownKeys = unknown(this.schema, value);
return !noAllow || unknownKeys.length === 0 || this.createError({
params: {
unknown: unknownKeys.join(', ')
}
});
}
});
next._options.stripUnknown = noAllow;
return next;
},
unknown: function unknown(allow, message) {
if (allow === void 0) {
allow = true;
}
if (message === void 0) {
message = _locale.object.noUnknown;
}
return this.noUnknown(!allow, message);
},
transformKeys: function transformKeys(fn) {
return this.transform(function (obj) {
return obj && (0, _utils.mapKeys)(obj, function (_, key) {
return fn(key);
});
});
},
camelCase: function camelCase() {
return this.transformKeys(_utils.camelCase);
},
snakeCase: function snakeCase() {
return this.transformKeys(_utils.snakeCase);
},
constantCase: function constantCase() {
return this.transformKeys(function (key) {
return (0, _utils.snakeCase)(key).toUpperCase();
});
},
describe: function describe() {
var base = _mixed.default.prototype.describe.call(this);
base.fields = (0, _utils.mapValues)(this.fields, function (value) {
return value.describe();
});
return base;
},
// 新增
validateInOrder: function validateInOrder(data, options) {
var _this5 = this;
return Object.keys(data).reduce(function (prev, key) {
return prev.then(function () {
var schema;
try {
schema = (0, _reach.default)(_this5, key);
} catch (e) {}
return schema ? _this5.validateAt(key, data, options) : undefined;
});
}, Promise.resolve()).then(function () {
return _this5.cast(data);
});
},
validateInOrderSync: function validateInOrderSync(data, options) {
for (var _i = 0, _Object$keys = Object.keys(data); _i < _Object$keys.length; _i++) {
var key = _Object$keys[_i];
var schema = void 0;
try {
schema = (0, _reach.default)(this, key);
} catch (e) {}
if (schema) {
this.validateSyncAt(key, data, options);
}
}
return this.cast(data);
}
});