<|start_filename|>lib/helpers/json-schema.js<|end_filename|>
"use strict";

module.exports = JsonSchema;

const tv4 = require("tv4");
const ono = require("@jsdevtools/ono");
const _ = require("lodash");

// Supported data types
let dataTypes = ["string", "number", "integer", "boolean", "array", "object", "file"];

// Valid patterns for each data type
let dataTypePatterns = {
  integer: /^[+-]?(\d+|0x[\dA-F]+)$/i,

  date: /^\d{4}-\d{2}-\d{2}$/,

  "date-time": /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})$/i
};

// Some older versions of Node don't define these constants
let MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
let MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
let MAX_VALUE = Number.MAX_VALUE || 1.7976931348623157e+308;
let MIN_VALUE = -MAX_VALUE;
let EPSILON = Number.EPSILON || 2.220446049250313e-16;

// Numeric type ranges
let ranges = {
  int32: {
    min: -2147483648,
    max: 2147483647
  },

  int64: {
    min: MIN_SAFE_INTEGER,
    max: MAX_SAFE_INTEGER
  },

  byte: {
    min: 0,
    max: 255
  },

  float: {
    min: -3.402823e38,
    max: 3.402823e38
  },

  double: {
    min: MIN_VALUE,
    max: MAX_VALUE
  }
};

/**
 * Parses and validates values against JSON schemas.
 *
 * @constructor
 */
function JsonSchema (schema) {
  if (!schema) {
    throw ono({ status: 500 }, "Missing JSON schema");
  }
  if (schema.type !== undefined && dataTypes.indexOf(schema.type) === -1) {
    throw ono({ status: 500 }, "Invalid JSON schema type: %s", schema.type);
  }

  this.schema = schema;
}

/**
 * Parses the given value according to the schema.
 * An error is thrown if the value is invalid.
 *
 * @param   {*}         value      - The value to be parsed against the schema
 * @param   {string}    [propPath] - Used only for logging and error messages (e.g. "person.address.city")
 * @returns {*}                    - The valid, parsed value
 */
JsonSchema.prototype.parse = function (value, propPath) {
  switch (this.schema.type) {
    case "number":
      return parseNumber(this.schema, value, propPath);
    case "integer":
      return parseInteger(this.schema, value, propPath);
    case "boolean":
      return parseBoolean(this.schema, value, propPath);
    case "array":
      return parseArray(this.schema, value, propPath);
    case "object":
    case undefined:
      return parseObject(this.schema, value, propPath);
    case "file":
      return parseFile(this.schema, value, propPath);
    case "string":
      switch (this.schema.format) {
        case "byte":
          return parseInteger(this.schema, value, propPath);
        case "date":
        case "date-time":
          return parseDate(this.schema, value, propPath);
        default:
          return parseString(this.schema, value, propPath);
      }
  }
};

/**
 * Serializes the given value according to the schema.
 * An error is thrown if the value is invalid.
 *
 * @param   {*}         value      - The value to be serialized.
 * @param   {string}    [propPath] - Used only for logging and error messages (e.g. "person.address.city")
 * @returns {*}                    - The serialized value, suitable for persisting or transmitting
 */
JsonSchema.prototype.serialize = function (value, propPath) {
  switch (this.schema.type) {
    case "number":
      return serializeNumber(this.schema, value, propPath);
    case "integer":
      return serializeInteger(this.schema, value, propPath);
    case "boolean":
      return serializeBoolean(this.schema, value, propPath);
    case "array":
      return serializeArray(this.schema, value, propPath);
    case "object":
    case undefined:
      return serializeObject(this.schema, value, propPath);
    case "file":
      return serializeFile(this.schema, value, propPath);
    case "string":
      switch (this.schema.format) {
        case "byte":
          return serializeInteger(this.schema, value, propPath);
        case "date":
        case "date-time":
          return serializeDate(this.schema, value, propPath);
        default:
          return serializeString(this.schema, value, propPath);
      }
  }
};

/**
 * Generates sample data from the schema.
 *
 * @returns {*}
 */
JsonSchema.prototype.sample = function () {
  switch (this.schema.type) {
    case "number":
      return sampleNumber(this.schema);
    case "integer":
      return sampleInteger(this.schema);
    case "boolean":
      return sampleBoolean(this.schema);
    case "array":
      return sampleArray(this.schema);
    case "object":
    case undefined:
      return sampleObject(this.schema);
    case "string":
      switch (this.schema.format) {
        case "byte":
          return sampleInteger(this.schema);
        case "date":
        case "date-time":
          return sampleDate(this.schema);
        default:
          return sampleString(this.schema);
      }
  }
};

/**
 * Returns the given value, or the default value if the given value is empty.
 */
function getValueToValidate (schema, value) {
  // Is the value empty?
  if (value === undefined || value === "" ||
    (schema.type === "object" && _.isObject(value) && _.isEmpty(value))) {

    // It's blank, so return the default/example value (if there is one)
    if (schema.default !== undefined) {
      value = schema.default;
    }
    else if (schema.example !== undefined) {
      value = schema.example;
    }
  }

  // Special case for Buffers
  if (value && value.type === "Buffer" && _.isArray(value.data)) {
    value = new Buffer(value.data);
  }

  // It's not empty, so return the existing value
  return value;
}

function jsonValidate (schema, value, propPath) {
  if (tv4.validate(value, schema)) {
    return true;
  }
  else if (propPath !== undefined) {
    let dataPath = propPath ? propPath + "." + tv4.error.dataPath : tv4.error.dataPath;
    throw ono(tv4.error, { status: 400 }, 'JSON Schema validation error. \nData path: "%s" \nSchema path: "%s"',
      dataPath, tv4.error.schemaPath);
  }
  else {
    throw ono(tv4.error, { status: 400 }, "JSON Schema validation error.");
  }
}

function parseInteger (schema, value, propPath) {
  // Handle missing, required, and default
  value = getValueToValidate(schema, value);

  // Make sure it's a properly-formatted integer
  let parsedValue = parseInt(value);
  if (_.isNaN(parsedValue) || !_.isFinite(parsedValue) || !dataTypePatterns.integer.test(value)) {
    throw ono({ status: 400 }, '"%s" is not a properly-formatted whole number', propPath || value);
  }

  // Force the schema to be validated as an integer
  let originalType = schema.type;
  schema.type = "integer";

  // Validate against the schema
  try {
    jsonValidate(schema, parsedValue, propPath);
  }
  finally {
    // Restore the original schema type
    schema.type = originalType;
  }

  // Validate the format
  let range = ranges[schema.format];
  if (range) {
    if (parsedValue < range.min || parsedValue > range.max) {
      throw ono({ status: 400 }, '"%s" is not a valid %s. Must be between %d and %d',
        propPath || parsedValue, schema.format, range.min, range.max);
    }
  }

  return parsedValue;
}

function serializeInteger (schema, value, propPath) {
  value = getValueToValidate(schema, value);
  if (value !== undefined) {
    return parseInt(value);
  }
}

function sampleInteger (schema) {
  let min, max;
  if (schema.minimum !== undefined) {
    min = parseInt(schema.minimum) + (schema.exclusiveMinimum ? 1 : 0);
  }
  else {
    min = Math.min(1, schema.maximum - 1) || 1;
  }

  if (schema.maximum !== undefined) {
    max = parseInt(schema.maximum) - (schema.exclusiveMaximum ? 1 : 0);
  }
  else {
    max = (ranges[schema.format] || ranges.int64).max;
  }

  return _.random(min, max);
}

function parseNumber (schema, value, propPath) {
  // Handle missing, required, and default
  value = getValueToValidate(schema, value);

  // Make sure it's a properly-formatted number
  let parsedValue = value === "" ? NaN : Number(value);
  if (_.isNaN(parsedValue) || !_.isFinite(parsedValue)) {
    throw ono({ status: 400 }, '"%s" is not a valid numeric value', propPath || value);
  }

  // Validate against the schema
  jsonValidate(schema, parsedValue, propPath);

  // Validate the format
  let range = ranges[schema.format];
  if (range) {
    if (parsedValue < range.min || parsedValue > range.max) {
      throw ono({ status: 400 }, '"%s" is not a valid %s. Must be between %d and %d',
        propPath || value, schema.format, range.min, range.max);
    }
  }

  return parsedValue;
}

function serializeNumber (schema, value, propPath) {
  value = getValueToValidate(schema, value);
  if (value !== undefined) {
    return parseFloat(value);
  }
}

function sampleNumber (schema) {
  let min, max;
  if (schema.minimum !== undefined) {
    min = parseFloat(schema.minimum) + (schema.exclusiveMinimum ? EPSILON : 0);
  }
  else {
    min = Math.min(0, schema.maximum) || 0;
  }

  if (schema.maximum !== undefined) {
    max = parseFloat(schema.maximum) - (schema.exclusiveMaximum ? EPSILON : 0);
  }
  else {
    max = (ranges[schema.format] || ranges.double).max;
  }

  return _.random(min, max);
}

function parseBoolean (schema, value, propPath) {
  // Handle missing, required, and default
  value = getValueToValidate(schema, value);

  // "Parse" the value
  let parsedValue = value;
  let stringValue = _(value).toString().toLowerCase();
  if (stringValue === "true") {
    parsedValue = true;
  }
  else if (stringValue === "false") {
    parsedValue = false;
  }

  // Validate against the schema
  jsonValidate(schema, parsedValue, propPath);

  return !!parsedValue;
}

function serializeBoolean (schema, value, propPath) {
  value = getValueToValidate(schema, value);
  if (value !== undefined) {
    return !!value;
  }
}

function sampleBoolean (schema) {
  return _.random() % 2 === 0;
}

function parseString (schema, value, propPath) {
  // Handle missing, required, and default
  value = getValueToValidate(schema, value);

  // Validate against the schema
  jsonValidate(schema, value, propPath);

  return value;
}

function serializeString (schema, value, propPath) {
  value = getValueToValidate(schema, value);
  if (value !== undefined) {
    return _(value).toString();
  }
}

function sampleString (schema) {
  let minLength, maxLength;
  if (schema.minLength !== undefined) {
    minLength = parseInt(schema.minLength);
  }
  else {
    minLength = Math.min(1, schema.maxLength) || 1;
  }

  if (schema.maxLength !== undefined) {
    maxLength = parseInt(schema.maxLength);
  }
  else {
    maxLength = Math.max(50, minLength);
  }

  let charSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  let str = "";
  let length = _.random(minLength, maxLength);
  for (let i = 0; i < length; i++) {
    str += charSet[_.random(0, charSet.length - 1)];
  }

  return str;
}

function parseArray (schema, value, propPath) {
  // Handle missing, required, and default
  value = getValueToValidate(schema, value);

  let parsedValue = value;

  if (_.isString(value) && value.length) {
    // Parse the string to an array
    switch (schema.collectionFormat) {
      case "ssv":
        parsedValue = value.split(" ");
        break;
      case "tsv":
        parsedValue = value.split("\t");
        break;
      case "pipes":
        parsedValue = value.split("|");
        break;
      default: // csv
        parsedValue = value.split(",");
    }
  }

  if (schema.items.type === "string") {
    // Validate the array against the schema BEFORE parsing the items
    jsonValidate(schema, parsedValue, propPath);
  }

  // Parse the items in the array
  parseArrayItems(schema, parsedValue, propPath);

  if (schema.items.type !== "string") {
    // Validate the array against the schema AFTER parsing the items
    jsonValidate(schema, parsedValue, propPath);
  }

  return parsedValue;
}

function parseArrayItems (schema, array, propPath) {
  let itemSchema = new JsonSchema(schema.items);

  for (let i = 0; i < array.length; i++) {
    let item = array[i];
    try {
      array[i] = itemSchema.parse(item, propPath + "[" + i + "]");
    }
    catch (e) {
      throw ono(e, { status: 400 }, "Unable to parse %s item at index %d (%j).", propPath || "array", i, item);
    }
  }
}

function serializeArray (schema, value, propPath) {
  value = getValueToValidate(schema, value);

  if (_.isArray(value) && schema.items) {
    let itemSchema = new JsonSchema(schema.items);

    for (let i = 0; i < value.length; i++) {
      value[i] = itemSchema.serialize(value[i], propPath + "[" + i + "]");
    }
  }

  return value;
}

function sampleArray (schema) {
  let minItems, maxItems;
  if (schema.minItems !== undefined) {
    minItems = parseInt(schema.minItems);
  }
  else {
    minItems = Math.min(1, schema.maxItems || 1);
  }

  if (schema.maxItems !== undefined) {
    maxItems = parseInt(schema.maxItems);
  }
  else {
    maxItems = Math.max(50, minItems);
  }

  let array = [];
  let itemSchema = new JsonSchema(schema.items);
  let length = _.random(minItems, maxItems);
  for (let i = 0; i < length; i++) {
    array.push(itemSchema.sample());
  }

  return array;
}

function parseObject (schema, value, propPath) {
  // Handle missing, required, and default
  value = getValueToValidate(schema, value);

  // Parse the value
  let parsedValue = value;
  if (_.isString(value) && value.length) {
    parsedValue = JSON.parse(value);
  }

  // Validate against the schema
  jsonValidate(schema, parsedValue, propPath || "");

  // Recursively parse the object's properties
  _.forEach(schema.properties, (prop, propName) => {
    if (parsedValue[propName] !== undefined || prop.default !== undefined) {
      let propSchema = new JsonSchema(prop);
      let fullPropPath = propPath ? propPath + "." + propName : propName;
      parsedValue[propName] = propSchema.parse(parsedValue[propName], fullPropPath);
    }
  });

  return parsedValue;
}

function serializeObject (schema, value, propPath) {
  value = getValueToValidate(schema, value);

  if (value) {
    _.forEach(schema.properties, (prop, propName) => {
      let propSchema = new JsonSchema(prop);
      let fullPropPath = propPath ? propPath + "." + propName : propName;
      let propValue = propSchema.serialize(value[propName], fullPropPath);
      if (propValue !== undefined) {
        value[propName] = propValue;
      }
    });
  }

  return value;
}

function sampleObject (schema) {
  let obj = {};
  _.keys(schema.properties).forEach((propName) => {
    let propSchema = new JsonSchema(schema.properties[propName]);
    obj[propName] = propSchema.sample();
  });

  return obj;
}

function parseFile (schema, value, propPath) {
  // Handle missing, required, and default
  value = getValueToValidate(schema, value);

  // Parse the value
  let parsedValue = value;
  if (!_.isObject(parsedValue) || !_.has(parsedValue, "size")) {
    throw ono({ status: 400 }, "%s is invalid or corrupted", propPath || "File");
  }

  // Validate minLength and maxLength
  if (schema.minLength) {
    let minLength = parseInt(schema.minLength);
    if (isNaN(minLength)) {
      throw ono({ status: 500 }, 'The "minLength" value in the Swagger API is invalid (%j)', schema.minLength);
    }

    if (parsedValue.size < minLength) {
      throw ono({ status: 400 }, '%s "%s" is only %d bytes. The minimum is %d bytes',
        propPath || "File", parsedValue.originalname, parsedValue.size, minLength);
    }
  }

  if (schema.maxLength) {
    let maxLength = parseInt(schema.maxLength);
    if (isNaN(maxLength)) {
      throw ono({ status: 500 }, 'The "maxLength" value in the Swagger API is invalid (%j)', schema.maxLength);
    }

    if (parsedValue.size > maxLength) {
      throw ono({ status: 413 }, '%s "%s" is %d bytes. The maximum is %d bytes',
        propPath || "File", parsedValue.originalname, parsedValue.size, maxLength);
    }
  }

  return parsedValue;
}

function serializeFile (schema, value, propPath) {
  value = getValueToValidate(schema, value);

  // Search for a property that looks like a File object
  let keys = _.keys(value);
  for (let i = 0; i < keys.length; i++) {
    let file = value[keys[i]];
    if (file && file.path && _.isString(file.path)) {
      // This is the File object, so return it
      return file;
    }
  }

  // None of the properties was a File object, so assume that the value itself is the file object
  return value;
}

function parseDate (schema, value, propPath) {
  let parsedValue;

  // Handle missing, required, and default
  value = getValueToValidate(schema, value);

  // If the value is already a Date, then we can skip some validation
  if (_.isDate(value)) {
    parsedValue = value;
  }
  else {
    // Validate against the schema
    jsonValidate(schema, value, propPath);

    // Validate the format
    let formatPattern = dataTypePatterns[schema.format];
    if (!formatPattern.test(value)) {
      throw ono({ status: 400 }, '"%s" is not a properly-formatted %s', propPath || value, schema.format);
    }

    // Parse the date
    parsedValue = new Date(value);
    if (!parsedValue || isNaN(parsedValue.getTime())) {
      throw ono({ status: 400 }, '"%s" is an invalid %s', propPath || value, schema.format);
    }
  }

  if (schema.minimum) {
    let minDate = new Date(schema.minimum);
    if (isNaN(minDate.getTime())) {
      throw ono({ status: 500 }, 'The "minimum" value in the Swagger API is invalid (%j)', schema.minimum);
    }

    if (parsedValue < minDate) {
      throw ono({ status: 400 }, "%s (%j) is less than minimum %j", propPath || "Value", parsedValue, minDate);
    }

    if (schema.exclusiveMinimum === true) {
      if (parsedValue.getTime() === minDate.getTime()) {
        throw ono({ status: 400 }, "%s (%j) is equal to exclusive minimum %j", propPath || "Value", parsedValue, minDate);
      }
    }
  }

  if (schema.maximum) {
    let maxDate = new Date(schema.maximum);
    if (isNaN(maxDate.getTime())) {
      throw ono({ status: 500 }, 'The "maximum" value in the Swagger API is invalid (%j)', schema.maximum);
    }

    if (parsedValue > maxDate) {
      throw ono({ status: 400 }, "%s (%j) is greater than maximum %j", propPath || "Value", parsedValue, maxDate);
    }

    if (schema.exclusiveMaximum === true) {
      if (parsedValue.getTime() === maxDate.getTime()) {
        throw ono({ status: 400 }, "%s (%j) is equal to exclusive maximum %j", propPath || "Value", parsedValue, maxDate);
      }
    }
  }

  return parsedValue;
}

function serializeDate (schema, value, propPath) {
  value = getValueToValidate(schema, value);

  if (schema.format === "date" && value) {
    // This works regardless of whether the value is a Date or an ISO8601 string
    return JSON.stringify(value).substring(1, 11);
  }
  else if (_.isDate(value)) {
    return value.toJSON();
  }
  else if (value) {
    return _(value).toString();
  }
}

function sampleDate (schema) {
  let min, max;
  if (schema.minimum !== undefined) {
    min = parseInt(new Date(schema.minimum).valueOf()) + (schema.exclusiveMinimum ? 1 : 0);
  }
  else {
    min = Date.UTC(1970, 0, 1);
    min = Math.min(min, new Date(schema.maximum).valueOf()) || min;
  }

  if (schema.maximum !== undefined) {
    max = parseInt(new Date(schema.maximum).valueOf()) - (schema.exclusiveMaximum ? 1 : 0);
  }
  else {
    max = Math.max(Date.now(), min);
  }

  let date = new Date(_.random(min, max));

  if (schema.format === "date") {
    // Only return the year/month/day (in UTC)
    date = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
  }

  return date;
}


<|start_filename|>lib/mock/query-resource.js<|end_filename|>
"use strict";

module.exports = {
  GET: queryResource,
  HEAD: queryResource,
  OPTIONS: queryResource
};

const util = require("../helpers/util");
const ono = require("@jsdevtools/ono");
const Resource = require("../data-store/resource");

/**
 * Returns the REST resource at the URL.
 * If there's no resource that matches the URL, then a 404 error is returned.
 *
 * @param   {Request}   req
 * @param   {Response}  res
 * @param   {function}  next
 * @param   {DataStore} dataStore
 */
function queryResource (req, res, next, dataStore) {
  let resource = new Resource(req.path);

  dataStore.get(resource, (err, result) => {
    if (err) {
      next(err);
    }
    else if (!result) {
      let defaultValue = getDefaultValue(res);

      if (defaultValue === undefined) {
        util.debug("ERROR! 404 - %s %s does not exist", req.method, req.path);
        err = ono({ status: 404 }, "%s Not Found", resource.toString());
        next(err);
      }
      else {
        // There' a default value, so use it instead of sending a 404
        util.debug(
          "%s %s does not exist, but the response schema defines a fallback value.  So, using the fallback value",
          req.method, req.path
        );
        res.swagger.lastModified = new Date();
        res.body = defaultValue;
        next();
      }
    }
    else {
      res.swagger.lastModified = result.modifiedOn;

      // Set the response body (unless it's already been set by other middleware)
      res.body = res.body || result.data;
      next();
    }
  });
}

/**
 * Returns the default/example value for this request.
 */
function getDefaultValue (res) {
  if (res.body) {
    return res.body;
  }
  else if (res.swagger.schema) {
    return res.swagger.schema.default || res.swagger.schema.example;
  }
}


<|start_filename|>lib/request-validator.js<|end_filename|>
"use strict";

module.exports = requestValidator;

const util = require("./helpers/util");
const ono = require("@jsdevtools/ono");
const _ = require("lodash");

/**
 * Validates the HTTP request against the Swagger API.
 * An error is sent downstream if the request is invalid for any reason.
 *
 * @param   {MiddlewareContext}    context
 * @returns {function[]}
 */
function requestValidator (context) {
  return [http500, http401, http404, http405, http406, http413, http415];

  /**
   * Throws an HTTP 500 error if the Swagger API is invalid.
   * Calling {@link Middleware#init} again with a valid Swagger API will clear the error.
   */
  function http500 (req, res, next) {
    if (context.error) {
      context.error.status = 500;
      throw context.error;
    }

    next();
  }
}

/**
 * If the Swagger API requires security for the request, and the request doesn't contain the necessary security info,
 * then an HTTP 401 (Unauthorized) is thrown, and the WWW-Authenticate header is set.
 * NOTE: This does not perform any authentication or authorization. It simply verifies that authentication info is present.
 */
function http401 (req, res, next) {
  if (util.isSwaggerRequest(req) && req.swagger.security.length > 0) {
    let securityTypes = [];

    util.debug("Validating security requirements");

    // Loop through each Security Requirement (if ANY of them are met, then the request is valid)
    let isValid = req.swagger.security.some((requirement) => {
      let securityDefinitions = _.map(requirement, (scopes, name) => {
        return req.swagger.api.securityDefinitions[name];
      });

      // Loop through each Security Definition (if ALL of them are met, then the request is valid)
      return securityDefinitions.every((securityDef) => {
        if (securityTypes.indexOf(securityDef.type) === -1) {
          securityTypes.push(securityDef.type);
        }

        if (securityDef.type === "basic") {
          return _.startsWith(req.header("Authorization"), "Basic ");
        }
        else if (securityDef.type === "apiKey" && securityDef.in === "header") {
          return req.header(securityDef.name) !== undefined;
        }
        else if (securityDef.type === "apiKey" && securityDef.in === "query") {
          return req.query[securityDef.name] !== undefined;
        }
        else {
          // For any other type of security, just assume it's valid.
          // TODO: Is there a way to validate OAuth2 here?
          return true;
        }
      });
    });

    if (!isValid) {
      securityTypes = securityTypes.join(", ");
      util.debug(
        "The client didn't provide authentication information for any of the required authentication types (%s). " +
        "Returning HTTP 401 (Unauthorized)", securityTypes
      );
      res.set("WWW-Authenticate", 'Basic realm="' + (req.hostname || "server") + '"');
      throw ono({ status: 401 }, "%s %s requires authentication (%s)", req.method, req.path, securityTypes);
    }
  }

  next();
}

/**
 * If the request is under the Swagger API's basePath, but no matching Path was found,
 * then an HTTP 404 (Not Found) error is thrown
 */
function http404 (req, res, next) {
  if (req.swagger && req.swagger.api && !req.swagger.path) {
    util.debug(
      'Client requested path "%s", which is not defined in the Swagger API. Returning HTTP 404 (Not Found)',
      req.path
    );
    throw ono({ status: 404 }, "Resource not found: %s", req.path);
  }

  next();
}

/**
 * If the Swagger Path was matched, but the HTTP method doesn't match any of the Swagger Operations,
 * then an HTTP 405 (Method Not Allowed) error is thrown, and the "Allow" header is set to the list of valid methods
 */
function http405 (req, res, next) {
  if (req.swagger && req.swagger.path && !req.swagger.operation) {
    util.debug(
      'Client attempted a %s operation on "%s", which is not allowed by the Swagger API. ' +
      "Returning HTTP 405 (Method Not Allowed)",
      req.method, req.path
    );

    // Let the client know which methods are allowed
    let allowedList = util.getAllowedMethods(req.swagger.path);
    res.set("Allow", allowedList);

    throw ono({ status: 405 }, "%s does not allow %s. \nAllowed methods: %s",
      req.path, req.method, allowedList || "NONE");
  }

  next();
}

/**
 * If the Swagger API specifies the MIME types that this operation produces,
 * and the HTTP Accept header does not match any of those MIME types, then an HTTP 406 (Not Acceptable) is thrown.
 */
function http406 (req, res, next) {
  if (util.isSwaggerRequest(req)) {
    // Get the MIME types that this operation produces
    let produces = req.swagger.operation.produces || req.swagger.api.produces || [];

    if (produces.length > 0) {
      util.debug("Validating Accept header (%s)", req.get("Accept"));

      if (!req.accepts(produces)) {
        let accepts = req.accepts();
        util.debug(
          'The %s operation on "%s" only produces %j content, but the client requested %j. ' +
          "Returning HTTP 406 (Not Acceptable)",
          req.method, req.path, produces, accepts
        );
        throw ono({ status: 406 }, "%s %s cannot produce any of the requested formats (%s). \nSupported formats: %s",
          req.method, req.path, accepts.join(", "), produces.join(", "));
      }
    }
  }

  next();
}

/**
 * Throws an HTTP 413 (Request Entity Too Large) if the HTTP request includes
 * body content that is not allowed by the Swagger API.
 */
function http413 (req, res, next) {
  if (util.isSwaggerRequest(req)) {
    // Determine if the request allows body content
    let bodyAllowed = req.swagger.params.some((param) => {
      return param.in === "body" || param.in === "formData";
    });

    if (!bodyAllowed) {
      // NOTE: We used to also check the Transfer-Encoding header, but that fails in Node 0.10.x
      // TODO: Once we drop support for Node 0.10.x, add a Transfer-Encoding check (via typeIs.hasBody())
      let length = req.get("Content-Length");
      util.debug("Validating Content-Length header (%d)", length);

      // NOTE: Even a zero-byte file attachment will have a Content-Length > 0
      if (length > 0) {
        util.debug(
          'The HTTP request contains body content, but the %s operation on "%s" does not allow a request body. ' +
          "Returning HTTP 413 (Request Entity Too Large)",
          req.method, req.path
        );
        throw ono({ status: 413 }, "%s %s does not allow body content", req.method, req.path);
      }
    }
  }

  next();
}

/**
 * Validates the HTTP Content-Type header against the Swagger API's "consumes" MIME types,
 * and throws an HTTP 415 (Unsupported Media Type) if there's a conflict.
 */
function http415 (req, res, next) {
  if (util.isSwaggerRequest(req)) {
    // Only validate the Content-Type if there's body content
    if (!_.isEmpty(req.body)) {
      // Get the MIME types that this operation consumes
      let consumes = req.swagger.operation.consumes || req.swagger.api.consumes || [];

      if (consumes.length > 0) {
        util.debug("Validating Content-Type header (%s)", req.get("Content-Type"));

        if (!req.is(consumes)) {
          let contentType = req.header("Content-Type");
          util.debug(
            'Client attempted to send %s data to the %s operation on "%s", which is not allowed by the Swagger API. ' +
            "Returning HTTP 415 (Unsupported Media Type)",
            contentType, req.method, req.path
          );
          throw ono({ status: 415 }, '%s %s does not allow Content-Type "%s". \nAllowed Content-Types: %s',
            req.method, req.path, contentType, consumes.join(", "));
        }
      }
    }
  }

  next();
}
