text: "use strict";
  // Copyright IBM Corp. 2018,2019. All Rights Reserved.
  // Node module: @loopback/openapi-v3
  // This file is licensed under the MIT License.
  // License text available at https://opensource.org/licenses/MIT
  Object.defineProperty(exports, "__esModule", { value: true });
  const context_1 = require("@loopback/context");
  const openapi_v3_types_1 = require("@loopback/openapi-v3-types");
  const repository_json_schema_1 = require("@loopback/repository-json-schema");
  const keys_1 = require("./keys");
  const json_to_schema_1 = require("./json-to-schema");
  const _ = require("lodash");
  const generate_schema_1 = require("./generate-schema");
  const debug = require('debug')('loopback:openapi3:metadata:controller-spec');
  exports.TS_TYPE_KEY = 'x-ts-type';
  /**
   * Build the api spec from class and method level decorations
   * @param constructor Controller class
   */
  function resolveControllerSpec(constructor) {
      debug(`Retrieving OpenAPI specification for controller ${constructor.name}`);
      let spec = context_1.MetadataInspector.getClassMetadata(keys_1.OAI3Keys.CLASS_KEY, constructor);
      if (spec) {
          debug('  using class-level spec defined via @api()', spec);
          spec = context_1.DecoratorFactory.cloneDeep(spec);
      }
      else {
          spec = { paths: {} };
      }
      let endpoints = context_1.MetadataInspector.getAllMethodMetadata(keys_1.OAI3Keys.METHODS_KEY, constructor.prototype) || {};
      endpoints = context_1.DecoratorFactory.cloneDeep(endpoints);
      for (const op in endpoints) {
          debug('  processing method %s', op);
          const endpoint = endpoints[op];
          const verb = endpoint.verb;
          const path = endpoint.path;
          let endpointName = '';
          /* istanbul ignore if */
          if (debug.enabled) {
              const className = constructor.name || '<AnonymousClass>';
              const fullMethodName = `${className}.${op}`;
              endpointName = `${fullMethodName} (${verb} ${path})`;
          }
          const defaultResponse = {
              '200': {
                  description: `Return value of ${constructor.name}.${op}`,
              },
          };
          let operationSpec = endpoint.spec;
          if (!operationSpec) {
              // The operation was defined via @operation(verb, path) with no spec
              operationSpec = {
                  responses: defaultResponse,
              };
              endpoint.spec = operationSpec;
          }
          debug('  operation for method %s: %j', op, endpoint);
          debug('  spec responses for method %s: %o', op, operationSpec.responses);
          for (const code in operationSpec.responses) {
              const responseObject = operationSpec.responses[code];
              if (openapi_v3_types_1.isReferenceObject(responseObject))
                  continue;
              const content = responseObject.content || {};
              for (const c in content) {
                  debug('  evaluating response code %s with content: %o', code, c);
                  resolveTSType(spec, content[c].schema);
              }
          }
          debug('  processing parameters for method %s', op);
          let params = context_1.MetadataInspector.getAllParameterMetadata(keys_1.OAI3Keys.PARAMETERS_KEY, constructor.prototype, op);
          debug('  parameters for method %s: %j', op, params);
          if (params != null) {
              params = context_1.DecoratorFactory.cloneDeep(params);
              /**
               * If a controller method uses dependency injection, the parameters
               * might be sparsed. For example,
               * ```ts
               * class MyController {
               *   greet(
               *     @inject('prefix') prefix: string,
               *     @param.query.string('name) name: string) {
               *      return `${prefix}`, ${name}`;
               *   }
               * ```
               */
              operationSpec.parameters = params
                  .filter(p => p != null)
                  .map(p => {
                  // Per OpenAPI spec, `required` must be `true` for path parameters
                  if (p.in === 'path') {
                      p.required = true;
                  }
                  return p;
              });
          }
          debug('  processing requestBody for method %s', op);
          let requestBodies = context_1.MetadataInspector.getAllParameterMetadata(keys_1.OAI3Keys.REQUEST_BODY_KEY, constructor.prototype, op);
          if (requestBodies != null)
              requestBodies = requestBodies.filter(p => p != null);
          let requestBody;
          if (requestBodies) {
              if (requestBodies.length > 1)
                  throw new Error('An operation should only have one parameter decorated by @requestBody');
              requestBody = requestBodies[0];
              debug('  requestBody for method %s: %j', op, requestBody);
              if (requestBody) {
                  operationSpec.requestBody = requestBody;
                  const content = requestBody.content || {};
                  for (const mediaType in content) {
                      resolveTSType(spec, content[mediaType].schema);
                  }
              }
          }
          operationSpec['x-operation-name'] = op;
          operationSpec['x-controller-name'] =
              operationSpec['x-controller-name'] || constructor.name;
          if (operationSpec.operationId == null) {
              // Build the operationId as `<controllerName>.<operationName>`
              // Please note API explorer (https://github.com/swagger-api/swagger-js/)
              // will normalize it as `<controllerName>_<operationName>`
              operationSpec.operationId =
                  operationSpec['x-controller-name'] +
                      '.' +
                      operationSpec['x-operation-name'];
          }
          if (!spec.paths[path]) {
              spec.paths[path] = {};
          }
          if (spec.paths[path][verb]) {
              // Operations from subclasses override those from the base
              debug(`  Overriding ${endpointName} - endpoint was already defined`);
          }
          debug(`  adding ${endpointName}`, operationSpec);
          spec.paths[path][verb] = operationSpec;
          debug(`  inferring schema object for method %s`, op);
          const opMetadata = context_1.MetadataInspector.getDesignTypeForMethod(constructor.prototype, op);
          const paramTypes = opMetadata.parameterTypes;
          const isComplexType = (ctor) => !_.includes([String, Number, Boolean, Array, Object], ctor);
          for (const p of paramTypes) {
              if (isComplexType(p)) {
                  generateOpenAPISchema(spec, p);
              }
          }
      }
      return spec;
  }
  /**
   * Resolve the x-ts-type in the schema object
   * @param spec Controller spec
   * @param schema Schema object
   */
  function resolveTSType(spec, schema) {
      debug('  evaluating schema: %j', schema);
      if (!schema || openapi_v3_types_1.isReferenceObject(schema))
          return;
      const tsType = schema[exports.TS_TYPE_KEY];
      debug('  %s => %o', exports.TS_TYPE_KEY, tsType);
      if (tsType) {
          schema = generate_schema_1.resolveSchema(tsType, schema);
          if (schema.$ref)
              generateOpenAPISchema(spec, tsType);
          // We don't want a Function type in the final spec.
          delete schema[exports.TS_TYPE_KEY];
          return;
      }
      if (schema.type === 'array') {
          resolveTSType(spec, schema.items);
      }
      else if (schema.type === 'object') {
          if (schema.properties) {
              for (const p in schema.properties) {
                  resolveTSType(spec, schema.properties[p]);
              }
          }
      }
  }
  /**
   * Generate json schema for a given x-ts-type
   * @param spec Controller spec
   * @param tsType TS Type
   */
  function generateOpenAPISchema(spec, tsType) {
      if (!spec.components) {
          spec.components = {};
      }
      if (!spec.components.schemas) {
          spec.components.schemas = {};
      }
      if (tsType.name in spec.components.schemas) {
          // Preserve user-provided definitions
          debug('    skipping type %j as already defined', tsType.name || tsType);
          return;
      }
      const jsonSchema = repository_json_schema_1.getJsonSchema(tsType);
      const openapiSchema = json_to_schema_1.jsonToSchemaObject(jsonSchema);
      const outputSchemas = spec.components.schemas;
      if (openapiSchema.definitions) {
          for (const key in openapiSchema.definitions) {
              // Preserve user-provided definitions
              if (key in outputSchemas)
                  continue;
              const relatedSchema = openapiSchema.definitions[key];
              debug('    defining referenced schema for %j: %j', key, relatedSchema);
              outputSchemas[key] = relatedSchema;
          }
          delete openapiSchema.definitions;
      }
      debug('    defining schema for %j: %j', tsType.name, openapiSchema);
      outputSchemas[tsType.name] = openapiSchema;
  }
  /**
   * Get the controller spec for the given class
   * @param constructor Controller class
   */
  function getControllerSpec(constructor) {
      let spec = context_1.MetadataInspector.getClassMetadata(keys_1.OAI3Keys.CONTROLLER_SPEC_KEY, constructor, { ownMetadataOnly: true });
      if (!spec) {
          spec = resolveControllerSpec(constructor);
          context_1.MetadataInspector.defineMetadata(keys_1.OAI3Keys.CONTROLLER_SPEC_KEY.key, spec, constructor);
      }
      return spec;
  }
  exports.getControllerSpec = getControllerSpec;
  //# sourceMappingURL=controller-spec.js.map