"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.create = create; exports.default = void 0; var _has = _interopRequireDefault(require("lodash/has")); var _snakeCase = _interopRequireDefault(require("lodash/snakeCase")); var _camelCase = _interopRequireDefault(require("lodash/camelCase")); var _mapKeys = _interopRequireDefault(require("lodash/mapKeys")); var _mapValues = _interopRequireDefault(require("lodash/mapValues")); var _propertyExpr = require("property-expr"); var _locale = require("./locale"); var _sortFields = _interopRequireDefault(require("./util/sortFields")); var _sortByKeyOrder = _interopRequireDefault(require("./util/sortByKeyOrder")); var _runTests = _interopRequireDefault(require("./util/runTests")); var _ValidationError = _interopRequireDefault(require("./ValidationError")); var _schema = _interopRequireDefault(require("./schema")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } let isObject = obj => Object.prototype.toString.call(obj) === '[object Object]'; function unknown(ctx, value) { let known = Object.keys(ctx.fields); return Object.keys(value).filter(key => known.indexOf(key) === -1); } const defaultSort = (0, _sortByKeyOrder.default)([]); class ObjectSchema extends _schema.default { constructor(spec) { super({ type: 'object' }); this.fields = Object.create(null); this._sortErrors = defaultSort; this._nodes = []; this._excludedEdges = []; this.withMutation(() => { this.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) { this.shape(spec); } }); } _typeCheck(value) { return isObject(value) || typeof value === 'function'; } _cast(_value, options = {}) { var _options$stripUnknown; let value = super._cast(_value, options); //should ignore nulls here if (value === undefined) return this.getDefault(); if (!this._typeCheck(value)) return value; let fields = this.fields; let strip = (_options$stripUnknown = options.stripUnknown) != null ? _options$stripUnknown : this.spec.noUnknown; let props = this._nodes.concat(Object.keys(value).filter(v => this._nodes.indexOf(v) === -1)); let intermediateValue = {}; // is filled during the transform below let innerOptions = _extends({}, options, { parent: intermediateValue, __validating: options.__validating || false }); let isChanged = false; for (const prop of props) { let field = fields[prop]; let exists = (0, _has.default)(value, prop); if (field) { let fieldValue; let inputValue = value[prop]; // safe to mutate since this is fired in sequence innerOptions.path = (options.path ? `${options.path}.` : '') + prop; // innerOptions.value = value[prop]; field = field.resolve({ value: inputValue, context: options.context, parent: intermediateValue }); let fieldSpec = 'spec' in field ? field.spec : undefined; let strict = fieldSpec == null ? void 0 : fieldSpec.strict; if (fieldSpec == null ? void 0 : fieldSpec.strip) { isChanged = isChanged || prop in value; continue; } fieldValue = !options.__validating || !strict ? // TODO: use _cast, this is double resolving 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; } _validate(_value, opts = {}, callback) { let errors = []; let { sync, from = [], originalValue = _value, abortEarly = this.spec.abortEarly, recursive = this.spec.recursive } = opts; from = [{ schema: this, value: originalValue }, ...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; super._validate(_value, opts, (err, value) => { if (err) { if (!_ValidationError.default.isError(err) || abortEarly) { return void callback(err, value); } errors.push(err); } if (!recursive || !isObject(value)) { callback(errors[0] || null, value); return; } originalValue = originalValue || value; let tests = this._nodes.map(key => (_, cb) => { let path = key.indexOf('.') === -1 ? (opts.path ? `${opts.path}.` : '') + key : `${opts.path || ''}["${key}"]`; let field = this.fields[key]; if (field && 'validate' in field) { field.validate(value[key], _extends({}, opts, { // @ts-ignore path, 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, tests, value, errors, endEarly: abortEarly, sort: this._sortErrors, path: opts.path }, callback); }); } clone(spec) { const next = super.clone(spec); next.fields = _extends({}, this.fields); next._nodes = this._nodes; next._excludedEdges = this._excludedEdges; next._sortErrors = this._sortErrors; return next; } concat(schema) { let next = super.concat(schema); let nextFields = next.fields; for (let [field, schemaOrRef] of Object.entries(this.fields)) { const target = nextFields[field]; if (target === undefined) { nextFields[field] = schemaOrRef; } else if (target instanceof _schema.default && schemaOrRef instanceof _schema.default) { nextFields[field] = schemaOrRef.concat(target); } } return next.withMutation(() => next.shape(nextFields)); } getDefaultFromShape() { let dft = {}; this._nodes.forEach(key => { const field = this.fields[key]; dft[key] = 'default' in field ? field.getDefault() : undefined; }); return dft; } _getDefault() { if ('default' in this.spec) { return super._getDefault(); } // if there is no default set invent one if (!this._nodes.length) { return undefined; } return this.getDefaultFromShape(); } shape(additions, excludes = []) { let next = this.clone(); let fields = Object.assign(next.fields, additions); next.fields = fields; next._sortErrors = (0, _sortByKeyOrder.default)(Object.keys(fields)); if (excludes.length) { if (!Array.isArray(excludes[0])) excludes = [excludes]; let keys = excludes.map(([first, second]) => `${first}-${second}`); next._excludedEdges = next._excludedEdges.concat(keys); } next._nodes = (0, _sortFields.default)(fields, next._excludedEdges); return next; } pick(keys) { const picked = {}; for (const key of keys) { if (this.fields[key]) picked[key] = this.fields[key]; } return this.clone().withMutation(next => { next.fields = {}; return next.shape(picked); }); } omit(keys) { const next = this.clone(); const fields = next.fields; next.fields = {}; for (const key of keys) { delete fields[key]; } return next.withMutation(() => next.shape(fields)); } from(from, to, alias) { let fromGetter = (0, _propertyExpr.getter)(from, true); return this.transform(obj => { if (obj == null) return obj; let newObj = obj; if ((0, _has.default)(obj, from)) { newObj = _extends({}, obj); if (!alias) delete newObj[from]; newObj[to] = fromGetter(obj); } return newObj; }); } noUnknown(noAllow = true, message = _locale.object.noUnknown) { if (typeof noAllow === 'string') { message = noAllow; noAllow = true; } let next = this.test({ name: 'noUnknown', exclusive: true, message: message, test(value) { if (value == null) return true; const unknownKeys = unknown(this.schema, value); return !noAllow || unknownKeys.length === 0 || this.createError({ params: { unknown: unknownKeys.join(', ') } }); } }); next.spec.noUnknown = noAllow; return next; } unknown(allow = true, message = _locale.object.noUnknown) { return this.noUnknown(!allow, message); } transformKeys(fn) { return this.transform(obj => obj && (0, _mapKeys.default)(obj, (_, key) => fn(key))); } camelCase() { return this.transformKeys(_camelCase.default); } snakeCase() { return this.transformKeys(_snakeCase.default); } constantCase() { return this.transformKeys(key => (0, _snakeCase.default)(key).toUpperCase()); } describe() { let base = super.describe(); base.fields = (0, _mapValues.default)(this.fields, value => value.describe()); return base; } } exports.default = ObjectSchema; function create(spec) { return new ObjectSchema(spec); } create.prototype = ObjectSchema.prototype;