"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.ObjectType = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _typeDetect = _interopRequireDefault(require("type-detect"));
var _internals = require("../internals");
var _type = require("./type");
var _errors = require("../errors");
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */

// Because of https://github.com/Microsoft/TypeScript/issues/14041
// this might not have perfect _rendering_ output, but it will be typed.

class ObjectType extends _type.Type {
  constructor(props, options = {}) {
    const schemaKeys = {};
    const {
      unknowns = 'forbid',
      ...typeOptions
    } = options;
    for (const [key, value] of Object.entries(props)) {
      schemaKeys[key] = value.getSchema();
    }
    const schema = _internals.internals.object().keys(schemaKeys).default().optional().unknown(unknowns === 'allow').options({
      stripUnknown: {
        objects: unknowns === 'ignore'
      }
    });
    super(schema, typeOptions);
    (0, _defineProperty2.default)(this, "props", void 0);
    (0, _defineProperty2.default)(this, "options", void 0);
    (0, _defineProperty2.default)(this, "propSchemas", void 0);
    this.props = props;
    this.propSchemas = schemaKeys;
    this.options = options;
  }

  /**
   * Return a new `ObjectType` instance extended with given `newProps` properties.
   * Original properties can be deleted from the copy by passing a `null` or `undefined` value for the key.
   *
   * @example
   * How to add a new key to an object schema
   * ```ts
   * const origin = schema.object({
   *   initial: schema.string(),
   * });
   *
   * const extended = origin.extends({
   *   added: schema.number(),
   * });
   * ```
   *
   * How to remove an existing key from an object schema
   * ```ts
   * const origin = schema.object({
   *   initial: schema.string(),
   *   toRemove: schema.number(),
   * });
   *
   * const extended = origin.extends({
   *   toRemove: undefined,
   * });
   * ```
   *
   * How to override the schema's options
   * ```ts
   * const origin = schema.object({
   *   initial: schema.string(),
   * }, { defaultValue: { initial: 'foo' }});
   *
   * const extended = origin.extends({
   *   added: schema.number(),
   * }, { defaultValue: { initial: 'foo', added: 'bar' }});
   *
   * @remarks
   * `extends` only support extending first-level properties. It's currently not possible to perform deep/nested extensions.
   *
   * ```ts
   * const origin = schema.object({
   *   foo: schema.string(),
   *   nested: schema.object({
   *     a: schema.string(),
   *     b: schema.string(),
   *   }),
   * });
   *
   * const extended = origin.extends({
   *   nested: schema.object({
   *     c: schema.string(),
   *   }),
   * });
   *
   * // TypeOf<typeof extended> is `{ foo: string; nested: { c: string } }`
   * ```
   */
  extends(newProps, newOptions) {
    const extendedProps = Object.entries({
      ...this.props,
      ...newProps
    }).reduce((memo, [key, value]) => {
      if (value !== null && value !== undefined) {
        return {
          ...memo,
          [key]: value
        };
      }
      return memo;
    }, {});
    const extendedOptions = {
      ...this.options,
      ...newOptions
    };
    return new ObjectType(extendedProps, extendedOptions);
  }
  extendsDeep(options) {
    const extendedProps = Object.entries(this.props).reduce((memo, [key, value]) => {
      if (value !== null && value !== undefined) {
        return {
          ...memo,
          [key]: value.extendsDeep(options)
        };
      }
      return memo;
    }, {});
    const extendedOptions = {
      ...this.options,
      ...(options.unknowns ? {
        unknowns: options.unknowns
      } : {})
    };
    return new ObjectType(extendedProps, extendedOptions);
  }
  handleError(type, {
    reason,
    value
  }) {
    switch (type) {
      case 'any.required':
      case 'object.base':
        return `expected a plain object value, but found [${(0, _typeDetect.default)(value)}] instead.`;
      case 'object.parse':
        return `could not parse object value from json input`;
      case 'object.unknown':
        return `definition for this key is missing`;
      case 'object.child':
        return reason[0];
    }
  }
  validateKey(key, value) {
    if (!this.propSchemas[key]) {
      throw new Error(`${key} is not a valid part of this schema`);
    }
    const {
      value: validatedValue,
      error
    } = this.propSchemas[key].validate(value);
    if (error) {
      throw new _errors.ValidationError(error, key);
    }
    return validatedValue;
  }
}
exports.ObjectType = ObjectType;