lib/helpers/util.js
"use strict";
const debug = require("debug");
const swaggerMethods = require("@apidevtools/swagger-methods");
const format = require("util").format;
const _ = require("lodash");
/**
* Writes messages to stdout.
* Log messages are suppressed by default, but can be enabled by setting the DEBUG variable.
*
* @param {string} message - The error message. May include format strings (%s, %d, %j)
* @param {...*} [params] - One or more params to be passed to {@link util#format}
* @type {function}
*/
exports.debug = debug("swagger:middleware");
/**
* Writes messages to stderr.
* Warning messages are enabled by default, but can be suppressed by setting the WARN variable to "off".
*
* @param {Error} [err] - The error, if any
* @param {string} message - The warning message. May include format strings (%s, %d, %j)
* @param {...*} [params] - One or more params to be passed to {@link util#format}
*/
exports.warn = function (err, message, params) {
if (process.env.WARN !== "off") {
if (_.isString(err)) {
console.warn(format.apply(null, arguments));
}
else if (arguments.length > 1) {
console.warn(format.apply(null, _.drop(arguments, 1)) + " \n" + err.stack);
}
else {
console.warn(err.stack);
}
}
};
/**
* Determines whether the given value is an Express Application.
* Note: An Express Application is also an Express Router.
*
* @param {*} router
* @returns {boolean}
*/
exports.isExpressApp = function (router) {
return exports.isExpressRouter(router) &&
_.isFunction(router.get) &&
_.isFunction(router.set) &&
_.isFunction(router.enabled) &&
_.isFunction(router.disabled);
};
/**
* Determines whether the given value is an Express Router.
* Note: An Express Application is also an Express Router.
*
* @param {*} router
* @returns {boolean}
*/
exports.isExpressRouter = function (router) {
return _.isFunction(router) &&
_.isFunction(router.param);
};
/**
* Determines whether the given value is an Express routing-options object.
*
* @param {*} router
* @returns {boolean}
*/
exports.isExpressRoutingOptions = function (router) {
return _.isObject(router) &&
("caseSensitive" in router || "strict" in router || "mergeParams" in router);
};
/**
* Normalizes a path according to the given router's case-sensitivity and strict-routing settings.
*
* @param {string} path
* @param {express#Router} router
* @returns {string}
*/
exports.normalizePath = function (path, router) {
let caseSensitive, strict;
if (!path) {
return "";
}
if (exports.isExpressApp(router)) {
caseSensitive = router.enabled("case sensitive routing");
strict = router.enabled("strict routing");
}
else {
// This could be an Express Router, or a POJO
caseSensitive = !!router.caseSensitive;
strict = !!router.strict;
}
if (!caseSensitive) {
path = path.toLowerCase();
}
if (!strict && _.endsWith(path, "/")) {
path = path.substr(0, path.length - 1);
}
return path;
};
/**
* Formats a date as RFC 1123 (e.g. "Tue, 05 Nov 1994 02:09:26 GMT")
*
* @param {Date} date
* @returns {string}
*/
exports.rfc1123 = function (date) {
// jscs:disable maximumLineLength
let dayName = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][date.getUTCDay()];
let monthName = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][date.getUTCMonth()];
return [
dayName, ", ", _.padStart(date.getUTCDate(), 2, "0"), " ", monthName, " ", date.getUTCFullYear(), " ",
_.padStart(date.getUTCHours(), 2, "0"), ":", _.padStart(date.getUTCMinutes(), 2, "0"), ":", _.padStart(date.getUTCSeconds(), 2, "0"), " GMT"
].join("");
// jscs:enable maximumLineLength
};
/**
* Regular Expression that matches Swagger path params.
*/
exports.swaggerParamRegExp = /\{([^/}]+)}/g;
/**
* Determines whether the given HTTP request is a valid Swagger request.
* That is, its `req.swagger.api`, `req.swagger.path`, and `req.swagger.operation` properties are set.
*
* @param {Request} req
* @returns {boolean}
*/
exports.isSwaggerRequest = function (req) {
// If req.swagger.operation is set, then so are req.swagger.api and req.swagger.path
return req.swagger && req.swagger.operation;
};
/**
* Returns a comma-delimited list of allowed HTTP methods for the given Swagger path.
* This is useful for setting HTTP headers such as Allow and Access-Control-Allow-Methods.
*
* @param {object} path - A Path object, from the Swagger API.
* @returns {string}
*/
exports.getAllowedMethods = function (path) {
return swaggerMethods
.filter((method) => { return !!path[method]; })
.join(", ")
.toUpperCase();
};
/**
* Returns the given operation's Response objects that have HTTP response codes between
* the given min and max (inclusive).
*
* @param {object} operation - An Operation object, from the Swagger API.
* @param {integer} min - The minimum HTTP response code to include
* @param {integer} max - The maximum HTTP response code to include
*
* @returns {{code: integer, api: object}[]}
* An array of HTTP response codes and their corresponding Response objects,
* sorted by response code ("default" comes last).
*/
exports.getResponsesBetween = function (operation, min, max) {
return _.map(operation.responses,
(response, responseCode) => {
return {
code: parseInt(responseCode) || responseCode,
api: response
};
})
.sort((a, b) => {
// Sort by response code. "default" comes last.
a = _.isNumber(a.code) ? a.code : 999;
b = _.isNumber(b.code) ? b.code : 999;
return a - b;
})
.filter((response) => {
return (response.code >= min && response.code <= max) || _.isString(response.code);
});
};
/**
* Returns the combined parameters for the given path and operation.
*
* @param {object} path - A Path object, from the Swagger API
* @param {object} operation - An Operation object, from the Swagger API
* @returns {object[]} - An array of Parameter objects
*/
exports.getParameters = function (path, operation) {
let pathParams = [], operationParams = [];
// Get the path and operation parameters
if (path && path.parameters) {
pathParams = path.parameters;
}
if (operation && operation.parameters) {
operationParams = operation.parameters;
}
// Combine the path and operation parameters,
// with the operation params taking precedence over the path params
return _.uniqBy(operationParams.concat(pathParams), (param) => {
return param.name + param.in;
});
};
/**
* Gets the JSON schema for the given operation, based on its "body" or "formData" parameters.
*
* @param {object} path - A Path object, from the Swagger API
* @param {object} operation - An Operation object, from the Swagger API
* @returns {object} - A JSON schema object
*/
exports.getRequestSchema = function (path, operation) {
let params = exports.getParameters(path, operation);
// If there's a "body" parameter, then use its schema
let bodyParam = _.find(params, { in: "body" });
if (bodyParam) {
if (bodyParam.schema.type === "array") {
return bodyParam.schema.items;
}
else {
return bodyParam.schema;
}
}
else {
let schema = { type: "object", required: [], properties: {}};
// If there are "formData" parameters, then concatenate them into a single JSON schema
_.filter(params, { in: "formData" }).forEach((param) => {
schema.properties[param.name] = param;
if (param.required) {
schema.required.push(param.name);
}
});
return schema;
}
};