diff --git a/README.md b/README.md index 8ebc6166..d497150d 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,12 @@ directory. 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-inherit-additional-middleware': false` - Prevents middleware +added in a parent scope with `x-express-openapi-additional-middleware`. This extension +works from the methodDoc up to the apiDoc, as opposed to the apiDoc down to the methodDoc. +The effect is that using this extension in the methodDoc would prevent that method +from receiving any additional middleware defined in parent scopes. You can use this +extension in any scope (methodDoc, pathDoc, or apiDoc) and the result i the same. * `'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 d0edeb70..5602c949 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ 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'); +var INHERIT_ADDITIONAL_MIDDLEWARE_PROPERTY = 'x-express-openapi-inherit-additional-middleware'; var isDir = require('is-dir'); var loggingKey = require('./package.json').name + ': '; var path = require('path'); @@ -215,12 +216,19 @@ function copy(obj) { function getAdditionalMiddleware() { var additionalMiddleware = []; + var index = arguments.length - 1; - [].slice.call(arguments).forEach(function(doc) { - if (doc && Array.isArray(doc[ADDITIONAL_MIDDLEWARE_PROPERTY])) { - [].push.apply(additionalMiddleware, doc[ADDITIONAL_MIDDLEWARE_PROPERTY]); + while (index > 0) { + --index; + var currentDoc = arguments[index + 1]; + var parentDoc = arguments[index]; + + if (currentDoc && currentDoc[INHERIT_ADDITIONAL_MIDDLEWARE_PROPERTY] === false) { + break; + } else { + [].unshift.apply(additionalMiddleware, getDocMiddleware(parentDoc)); } - }); + } return additionalMiddleware.filter(function(middleware) { if (typeof middleware === 'function') { @@ -231,6 +239,12 @@ function getAdditionalMiddleware() { return false; } }); + + function getDocMiddleware(doc) { + if (doc && Array.isArray(doc[ADDITIONAL_MIDDLEWARE_PROPERTY])) { + return doc[ADDITIONAL_MIDDLEWARE_PROPERTY]; + } + } } function toExpressParams(part) { diff --git a/test/sample-projects.js b/test/sample-projects.js index f6be76a0..224d7b8e 100644 --- a/test/sample-projects.js +++ b/test/sample-projects.js @@ -118,11 +118,32 @@ describe(require('../package.json').name + 'sample-projects', function() { // adding additional middleware {name: 'with-additional-middleware', url: '/v3/users/34?name=fred', expectedStatus: 200, expectedBody: { + orderingApiDoc: 'pathModule', apiDocAdded: true, pathDocAdded: true, pathModuleAdded: true }}, + // not inheriting additional middleware + {name: 'with-inherit-additional-middleware-false-at-methodDoc', url: '/v3/users/34?name=fred', + expectedStatus: 200, expectedBody: { + apiDocAdded: null, + pathDocAdded: null, + pathModuleAdded: null + }}, + {name: 'with-inherit-additional-middleware-false-at-pathDoc', url: '/v3/users/34?name=fred', + expectedStatus: 200, expectedBody: { + apiDocAdded: null, + pathDocAdded: true, + pathModuleAdded: true + }}, + {name: 'with-inherit-additional-middleware-false-at-pathModule', url: '/v3/users/34?name=fred', + expectedStatus: 200, expectedBody: { + apiDocAdded: null, + pathDocAdded: null, + 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 index 6f884005..54534700 100644 --- a/test/sample-projects/with-additional-middleware/api-doc.js +++ b/test/sample-projects/with-additional-middleware/api-doc.js @@ -3,6 +3,12 @@ module.exports = { 'x-express-openapi-additional-middleware': [/* generate a warning */ null, function(req, res, next) { + // assure ordering of middleware is preserved + req.apiDocAdded = false; + next(); + }, + function(req, res, next) { + req.orderingApiDoc = 'apiDoc'; req.apiDocAdded = true; next(); }], @@ -22,7 +28,13 @@ module.exports = { // 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) { + 'x-express-openapi-additional-middleware': [ + function(req, res, next) { + req.pathDocAdded = false; + req.orderingApiDoc = 'pathDoc'; + next(); + }, + function(req, res, next) { req.pathDocAdded = true; next(); }] 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 index 02c5a413..c6d8c2e3 100644 --- a/test/sample-projects/with-additional-middleware/api-routes/users/{id}.js +++ b/test/sample-projects/with-additional-middleware/api-routes/users/{id}.js @@ -1,8 +1,16 @@ module.exports = { - 'x-express-openapi-additional-middleware': [function(req, res, next) { - req.pathModuleAdded = true; - next(); - }], + 'x-express-openapi-additional-middleware': [ + function(req, res, next) { + req.orderingApiDoc = 'pathModule'; + // assuring that ordering is preserved + req.pathModuleAdded = false; + next(); + }, + function(req, res, next) { + req.pathModuleAdded = true; + next(); + } + ], // parameters for all operations in this path parameters: [ @@ -19,6 +27,7 @@ module.exports = { function get(req, res) { res.status(200).json({ + orderingApiDoc: req.orderingApiDoc, apiDocAdded: req.apiDocAdded, pathDocAdded: req.pathDocAdded, pathModuleAdded: req.pathModuleAdded diff --git a/test/sample-projects/with-inherit-additional-middleware-false-at-methodDoc/api-doc.js b/test/sample-projects/with-inherit-additional-middleware-false-at-methodDoc/api-doc.js new file mode 100644 index 00000000..6f884005 --- /dev/null +++ b/test/sample-projects/with-inherit-additional-middleware-false-at-methodDoc/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-inherit-additional-middleware-false-at-methodDoc/api-routes/users/{id}.js b/test/sample-projects/with-inherit-additional-middleware-false-at-methodDoc/api-routes/users/{id}.js new file mode 100644 index 00000000..d2719f6e --- /dev/null +++ b/test/sample-projects/with-inherit-additional-middleware-false-at-methodDoc/api-routes/users/{id}.js @@ -0,0 +1,76 @@ +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 || null, + pathDocAdded: req.pathDocAdded || null, + pathModuleAdded: req.pathModuleAdded || null + }); +} + +get.apiDoc = { + 'x-express-openapi-inherit-additional-middleware': false, + 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-inherit-additional-middleware-false-at-methodDoc/app.js b/test/sample-projects/with-inherit-additional-middleware-false-at-methodDoc/app.js new file mode 100644 index 00000000..f86c75b4 --- /dev/null +++ b/test/sample-projects/with-inherit-additional-middleware-false-at-methodDoc/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); +} diff --git a/test/sample-projects/with-inherit-additional-middleware-false-at-pathDoc/api-doc.js b/test/sample-projects/with-inherit-additional-middleware-false-at-pathDoc/api-doc.js new file mode 100644 index 00000000..db5778e0 --- /dev/null +++ b/test/sample-projects/with-inherit-additional-middleware-false-at-pathDoc/api-doc.js @@ -0,0 +1,36 @@ +// 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-inherit-additional-middleware': false, + 'x-express-openapi-additional-middleware': [function(req, res, next) { + req.pathDocAdded = true; + next(); + }] + } + }, + + tags: [ + {name: 'users'} + ] +}; diff --git a/test/sample-projects/with-inherit-additional-middleware-false-at-pathDoc/api-routes/users/{id}.js b/test/sample-projects/with-inherit-additional-middleware-false-at-pathDoc/api-routes/users/{id}.js new file mode 100644 index 00000000..6b0285ea --- /dev/null +++ b/test/sample-projects/with-inherit-additional-middleware-false-at-pathDoc/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 || null, + pathDocAdded: req.pathDocAdded || null, + pathModuleAdded: req.pathModuleAdded || null + }); +} + +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-inherit-additional-middleware-false-at-pathDoc/app.js b/test/sample-projects/with-inherit-additional-middleware-false-at-pathDoc/app.js new file mode 100644 index 00000000..f86c75b4 --- /dev/null +++ b/test/sample-projects/with-inherit-additional-middleware-false-at-pathDoc/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); +} diff --git a/test/sample-projects/with-inherit-additional-middleware-false-at-pathModule/api-doc.js b/test/sample-projects/with-inherit-additional-middleware-false-at-pathModule/api-doc.js new file mode 100644 index 00000000..6f884005 --- /dev/null +++ b/test/sample-projects/with-inherit-additional-middleware-false-at-pathModule/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-inherit-additional-middleware-false-at-pathModule/api-routes/users/{id}.js b/test/sample-projects/with-inherit-additional-middleware-false-at-pathModule/api-routes/users/{id}.js new file mode 100644 index 00000000..894f0e08 --- /dev/null +++ b/test/sample-projects/with-inherit-additional-middleware-false-at-pathModule/api-routes/users/{id}.js @@ -0,0 +1,76 @@ +module.exports = { + 'x-express-openapi-inherit-additional-middleware': false, + '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 || null, + pathDocAdded: req.pathDocAdded || null, + pathModuleAdded: req.pathModuleAdded || null + }); +} + +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-inherit-additional-middleware-false-at-pathModule/app.js b/test/sample-projects/with-inherit-additional-middleware-false-at-pathModule/app.js new file mode 100644 index 00000000..f86c75b4 --- /dev/null +++ b/test/sample-projects/with-inherit-additional-middleware-false-at-pathModule/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); +}