From c9dc95cc3f18ef92cb2c001436434fb6093003c5 Mon Sep 17 00:00:00 2001 From: jsdevel Date: Fri, 5 Feb 2016 01:56:25 -0700 Subject: [PATCH] Vendor extension for scoped middleware (closes #7) * The extension name is x-express-openapi-additional-middleware and it's value is an array of functions (middleware). --- README.md | 9 ++- index.js | 28 ++++++- test/sample-projects.js | 10 ++- .../with-additional-middleware/api-doc.js | 35 +++++++++ .../api-routes/users/{id}.js | 75 +++++++++++++++++++ .../with-additional-middleware/app.js | 26 +++++++ 6 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 test/sample-projects/with-additional-middleware/api-doc.js create mode 100644 test/sample-projects/with-additional-middleware/api-routes/users/{id}.js create mode 100644 test/sample-projects/with-additional-middleware/app.js diff --git a/README.md b/README.md index 017e0cef..8ebc6166 100644 --- a/README.md +++ b/README.md @@ -68,14 +68,19 @@ For more examples see the [sample projects](https://github.com/kogosoftwarellc/e You can directly control what middleware `express-openapi` adds to your express app by using the following vendor extension properties. These properties are scoped, so -if you use one as a root property of your API Document, all paths and operations will be affected. Similarly if you just want to disable middleware for an operation, you can -use these properties in said operation's apiDoc. See full examples in the +if you use one as a root property of your API Document, all paths and operations will +be affected. Similarly if you just want to configure middleware for an operation, +you can use these properties in said operation's apiDoc. See full examples in the [./test/sample-projects/]( https://github.com/kogosoftwarellc/express-openapi/tree/master/test/sample-projects) directory. ### Supported vendor extensions +* `'x-express-openapi-additional-middleware': [myMiddleware]` - Adds the provided +middleware _after_ defaults, coercion, and validation middleware (added by +`express-openapi`) but _before_ middleware defined in operations. This property +inherits from all previous properties. * `'x-express-openapi-disable-middleware': true` - Disables all middleware. * `'x-express-openapi-disable-coercion-middleware': true` - Disables coercion middleware. * `'x-express-openapi-disable-defaults-middleware': true` - Disables diff --git a/index.js b/index.js index 61bbe229..d0edeb70 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,4 @@ +var ADDITIONAL_MIDDLEWARE_PROPERTY = 'x-express-openapi-additional-middleware'; var buildDefaultsMiddleware = require('express-openapi-defaults'); var buildCoercionMiddleware = require('express-openapi-coercion'); var fsRoutes = require('fs-routes'); @@ -60,6 +61,8 @@ function initialize(args) { } var app = args.app; + // Do not make modifications to this. + var originalApiDoc = args.apiDoc; // Make a copy of the apiDoc that we can safely modify. var apiDoc = copy(args.apiDoc); var docsPath = args.docsPath || '/api-docs'; @@ -73,6 +76,8 @@ function initialize(args) { // express path pargumentarams start with :paramName // openapi path params use {paramName} var openapiPath = route; + // Do not make modifications to this. + var originalPathItem = originalApiDoc.paths[openapiPath] || {}; var pathItem = apiDoc.paths[openapiPath] || {}; var pathParameters = Array.isArray(pathModule.parameters) ? [].concat(pathModule.parameters) : @@ -84,7 +89,8 @@ function initialize(args) { // methodHandler may be an array or a function. var methodHandler = pathModule[methodName]; var methodDoc = methodHandler.apiDoc; - var middleware = [].concat(methodHandler); + var middleware = [].concat(getAdditionalMiddleware(originalApiDoc, originalPathItem, + pathModule, methodDoc), methodHandler); if (methodDoc && allowsMiddleware(apiDoc, pathModule, pathItem, methodDoc)) {// add middleware @@ -207,6 +213,26 @@ function copy(obj) { return JSON.parse(JSON.stringify(obj)); } +function getAdditionalMiddleware() { + var additionalMiddleware = []; + + [].slice.call(arguments).forEach(function(doc) { + if (doc && Array.isArray(doc[ADDITIONAL_MIDDLEWARE_PROPERTY])) { + [].push.apply(additionalMiddleware, doc[ADDITIONAL_MIDDLEWARE_PROPERTY]); + } + }); + + return additionalMiddleware.filter(function(middleware) { + if (typeof middleware === 'function') { + return true; + } else { + console.warn(loggingKey, 'Ignoring ' + middleware + ' as middleware in ' + + ADDITIONAL_MIDDLEWARE_PROPERTY + ' array.'); + return false; + } + }); +} + function toExpressParams(part) { return part.replace(/^\{([^\}]+)\}$/, ':$1'); } diff --git a/test/sample-projects.js b/test/sample-projects.js index 201839d4..f6be76a0 100644 --- a/test/sample-projects.js +++ b/test/sample-projects.js @@ -101,7 +101,7 @@ describe(require('../package.json').name + 'sample-projects', function() { }); }); - describe('disabling middleware', function() { + describe('configuring middleware', function() { var coercionMissingBody = { errors: [ { @@ -115,6 +115,14 @@ describe(require('../package.json').name + 'sample-projects', function() { }; [ + // adding additional middleware + {name: 'with-additional-middleware', url: '/v3/users/34?name=fred', + expectedStatus: 200, expectedBody: { + apiDocAdded: true, + pathDocAdded: true, + pathModuleAdded: true + }}, + // disable coercion {name: 'with-coercion-middleware-disabled-in-methodDoc', url: '/v3/users/34?name=fred', expectedStatus: 400, expectedBody: coercionMissingBody}, diff --git a/test/sample-projects/with-additional-middleware/api-doc.js b/test/sample-projects/with-additional-middleware/api-doc.js new file mode 100644 index 00000000..6f884005 --- /dev/null +++ b/test/sample-projects/with-additional-middleware/api-doc.js @@ -0,0 +1,35 @@ +// args.apiDoc needs to be a js object. This file could be a json file, but we can't add +// comments in json files. +module.exports = { + 'x-express-openapi-additional-middleware': [/* generate a warning */ null, + function(req, res, next) { + req.apiDocAdded = true; + next(); + }], + + swagger: '2.0', + + // all routes will now have /v3 prefixed. + basePath: '/v3', + + info: { + title: 'express-openapi sample project', + version: '3.0.0' + }, + + definitions: {}, + + // paths are derived from args.routes. These are filled in by fs-routes. + paths: { + '/users/{id}': { + 'x-express-openapi-additional-middleware': [function(req, res, next) { + req.pathDocAdded = true; + next(); + }] + } + }, + + tags: [ + {name: 'users'} + ] +}; diff --git a/test/sample-projects/with-additional-middleware/api-routes/users/{id}.js b/test/sample-projects/with-additional-middleware/api-routes/users/{id}.js new file mode 100644 index 00000000..02c5a413 --- /dev/null +++ b/test/sample-projects/with-additional-middleware/api-routes/users/{id}.js @@ -0,0 +1,75 @@ +module.exports = { + 'x-express-openapi-additional-middleware': [function(req, res, next) { + req.pathModuleAdded = true; + next(); + }], + + // parameters for all operations in this path + parameters: [ + { + name: 'id', + in: 'path', + type: 'string', + required: true, + description: 'Fred\'s age.' + } + ], + get: get +}; + +function get(req, res) { + res.status(200).json({ + apiDocAdded: req.apiDocAdded, + pathDocAdded: req.pathDocAdded, + pathModuleAdded: req.pathModuleAdded + }); +} + +get.apiDoc = { + description: 'Retrieve a user.', + operationId: 'getUser', + tags: ['users'], + parameters: [ + { + name: 'name', + in: 'query', + type: 'string', + pattern: '^fred$', + description: 'The name of this person. It may only be "fred".' + }, + // showing that operation parameters override path parameters + { + name: 'id', + in: 'path', + type: 'integer', + required: true, + description: 'Fred\'s age.' + }, + { + name: 'age', + in: 'query', + type: 'integer', + description: 'Fred\'s age.', + default: 80 + } + ], + + responses: { + default: { + description: 'showing that additional middleware should have been added at all levels.', + schema: { + properties: { + apiDocAdded: { + type: 'boolean' + }, + pathDocAdded: { + type: 'boolean' + }, + pathModuleAdded: { + type: 'boolean' + } + } + } + } + } +}; diff --git a/test/sample-projects/with-additional-middleware/app.js b/test/sample-projects/with-additional-middleware/app.js new file mode 100644 index 00000000..f86c75b4 --- /dev/null +++ b/test/sample-projects/with-additional-middleware/app.js @@ -0,0 +1,26 @@ +var app = require('express')(); +var bodyParser = require('body-parser'); +// normally you'd just do require('express-openapi'), but this is for test purposes. +var openapi = require('../../../'); +var path = require('path'); +var cors = require('cors'); + +app.use(cors()); +app.use(bodyParser.json()); + +openapi.initialize({ + apiDoc: require('./api-doc.js'), + app: app, + routes: path.resolve(__dirname, 'api-routes') +}); + +app.use(function(err, req, res, next) { + res.status(err.status).json(err); +}); + +module.exports = app; + +var port = parseInt(process.argv[2]); +if (port) { + app.listen(port); +}