BigstickCarpet/swagger-express-middleware

View on GitHub
lib/middleware.js

Summary

Maintainability
B
5 hrs
Test Coverage
"use strict";

module.exports = Middleware;

const _ = require("lodash");
const SwaggerParser = require("@apidevtools/swagger-parser");
const util = require("./helpers/util");
const MiddlewareContext = require("./context");
const DataStore = require("./data-store");
const requestMetadata = require("./request-metadata");
const fileServer = require("./file-server");
const CORS = require("./cors");
const requestParser = require("./request-parser");
const paramParser = require("./param-parser");
const pathParser = require("./path-parser");
const requestValidator = require("./request-validator");
const mock = require("./mock");

/**
 * Express middleware for the given Swagger API.
 *
 * @param   {express#Router}    [sharedRouter]
 * - An Express Application or Router. If provided, this will be used to determine routing settings
 * (case sensitivity, strictness), and to register path-param middleware via {@link Router#param}
 * (see http://expressjs.com/4x/api.html#router.param).
 *
 * @constructor
 */
function Middleware (sharedRouter) {
  sharedRouter = util.isExpressRouter(sharedRouter) ? sharedRouter : undefined;

  let self = this;
  let context = new MiddlewareContext(sharedRouter);

  /**
   * Initializes the middleware with the given Swagger API.
   * This method can be called again to re-initialize with a new or modified API.
   *
   * @param   {string|object}     [swagger]
   * - The file path or URL of a Swagger 2.0 API spec, in YAML or JSON format.
   * Or a valid Swagger API object (see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#swagger-object).
   *
   * @param   {function}          [callback]
   * - It will be called when the API has been parsed, validated, and dereferenced, or when an error occurs.
   */
  this.init = function (swagger, callback) {
    // the swagger variable should only ever be a string or a populated object.
    let invalidSwagger = _.isFunction(swagger) || _.isDate(swagger) || _.isEmpty(swagger);

    if (invalidSwagger) {
      throw new Error("Expected a Swagger file or object");
    }

    // Need to retrieve the Swagger API and metadata from the Swagger .yaml or .json file.
    let parser = new SwaggerParser();
    parser.dereference(swagger, (err, api) => {
      if (err) {
        util.warn(err);
      }

      context.error = err;
      context.api = api;
      context.pathRegexCache = {};
      context.parser = parser;
      context.emit("change");

      if (_.isFunction(callback)) {
        callback(err, self, context.api, context.parser);
      }
    });
  };

  /**
   * Serves the Swagger API file(s) in JSON and YAML formats,
   * so they can be used with third-party front-end tools like Swagger UI and Swagger Editor.
   *
   * @param   {express#Router}    [router]
   * - Express routing options (e.g. `caseSensitive`, `strict`).
   * If an Express Application or Router is passed, then its routing settings will be used.
   *
   * @param   {fileServer.defaultOptions}  [options]
   * - Options for how the files are served (see {@link fileServer.defaultOptions})
   *
   * @returns {function[]}
   */
  this.files = function (router, options) {
    if (arguments.length === 1 && !util.isExpressRouter(router) && !util.isExpressRoutingOptions(router)) {
      // Shift arguments
      options = router;
      router = sharedRouter;
    }

    return fileServer(context, router, options);
  };

  /**
   * Annotates the HTTP request (the `req` object) with Swagger metadata.
   * This middleware populates {@link Request#swagger}.
   *
   * @param   {express#Router}    [router]
   * - Express routing options (e.g. `caseSensitive`, `strict`).
   * If an Express Application or Router is passed, then its routing settings will be used.
   *
   * @returns {function[]}
   */
  this.metadata = function (router) {
    return requestMetadata(context, router);
  };

  /**
   * Handles CORS preflight requests and sets CORS headers for all requests
   * according the Swagger API definition.
   *
   * @returns {function[]}
   */
  this.CORS = function () {
    return CORS();
  };

  /**
   * Parses the HTTP request into typed values.
   * This middleware populates {@link Request#params}, {@link Request#headers}, {@link Request#cookies},
   * {@link Request#signedCookies}, {@link Request#query}, {@link Request#body}, and {@link Request#files}.
   *
   * @param   {express#Router}    [router]
   * - An Express Application or Router. If provided, this will be used to register path-param middleware
   * via {@link Router#param} (see http://expressjs.com/4x/api.html#router.param).
   * If not provided, then path parameters will always be parsed as strings.
   *
   * @param   {requestParser.defaultOptions}  [options]
   * - Options for each of the request-parsing middleware (see {@link requestParser.defaultOptions})
   *
   * @returns {function[]}
   */
  this.parseRequest = function (router, options) {
    if (arguments.length === 1 && !util.isExpressRouter(router) && !util.isExpressRoutingOptions(router)) {
      // Shift arguments
      options = router;
      router = sharedRouter;
    }

    return requestParser(options)
      .concat(paramParser())
      .concat(pathParser(context, router));
  };

  /**
   * Validates the HTTP request against the Swagger API.
   * An error is sent downstream if the request is invalid for any reason.
   *
   * @returns {function[]}
   */
  this.validateRequest = function () {
    return requestValidator(context);
  };

  /**
   * Implements mock behavior for HTTP requests, based on the Swagger API.
   *
   * @param   {express#Router}    [router]
   * - Express routing options (e.g. `caseSensitive`, `strict`).
   * If an Express Application or Router is passed, then its routing settings will be used.
   *
   * @param   {DataStore}         [dataStore]
   * - The data store that will be used to persist REST resources.
   * If `router` is an Express Application, then you can set/get the data store
   * using `router.get("mock data store")`.
   *
   * @returns {function[]}
   */
  this.mock = function (router, dataStore) {
    if (arguments.length === 1 &&
      router instanceof DataStore) {
      // Shift arguments
      dataStore = router;
      router = undefined;
    }

    return mock(context, router, dataStore);
  };
}