text: 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); }
  
  import has from 'lodash/has';
  import snakeCase from 'lodash/snakeCase';
  import camelCase from 'lodash/camelCase';
  import mapKeys from 'lodash/mapKeys';
  import mapValues from 'lodash/mapValues';
  import { getter } from 'property-expr';
  import { object as locale } from './locale';
  import sortFields from './util/sortFields';
  import sortByKeyOrder from './util/sortByKeyOrder';
  import runTests from './util/runTests';
  import ValidationError from './ValidationError';
  import BaseSchema from './schema';
  
  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 = sortByKeyOrder([]);
  export default class ObjectSchema extends BaseSchema {
    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 = has(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.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);
        });
  
        runTests({
          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 BaseSchema && schemaOrRef instanceof BaseSchema) {
          nextFields[field] = schemaOrRef.concat(target);
        }
      }
  
      return next.withMutation(() => next.shape(nextFields, this._excludedEdges));
    }
  
    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 = sortByKeyOrder(Object.keys(fields));
  
      if (excludes.length) {
        // this is a convenience for when users only supply a single pair
        if (!Array.isArray(excludes[0])) excludes = [excludes];
        next._excludedEdges = [...next._excludedEdges, ...excludes];
      }
  
      next._nodes = sortFields(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 = getter(from, true);
      return this.transform(obj => {
        if (obj == null) return obj;
        let newObj = obj;
  
        if (has(obj, from)) {
          newObj = _extends({}, obj);
          if (!alias) delete newObj[from];
          newObj[to] = fromGetter(obj);
        }
  
        return newObj;
      });
    }
  
    noUnknown(noAllow = true, message = locale.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.noUnknown) {
      return this.noUnknown(!allow, message);
    }
  
    transformKeys(fn) {
      return this.transform(obj => obj && mapKeys(obj, (_, key) => fn(key)));
    }
  
    camelCase() {
      return this.transformKeys(camelCase);
    }
  
    snakeCase() {
      return this.transformKeys(snakeCase);
    }
  
    constantCase() {
      return this.transformKeys(key => snakeCase(key).toUpperCase());
    }
  
    describe() {
      let base = super.describe();
      base.fields = mapValues(this.fields, value => value.describe());
      return base;
    }
  
  }
  export function create(spec) {
    return new ObjectSchema(spec);
  }
  create.prototype = ObjectSchema.prototype;