Skip to content

Commit

Permalink
Vendor extension to control scoped middleware inheritance.
Browse files Browse the repository at this point in the history
* The extension name is x-express-openapi-inherit-additional-middleware and it's value is false.
  • Loading branch information
jsdevel committed Feb 6, 2016
1 parent 9f8a10b commit 1af2dc9
Show file tree
Hide file tree
Showing 14 changed files with 482 additions and 9 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 18 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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') {
Expand All @@ -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) {
Expand Down
21 changes: 21 additions & 0 deletions test/sample-projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
14 changes: 13 additions & 1 deletion test/sample-projects/with-additional-middleware/api-doc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}],
Expand All @@ -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();
}]
Expand Down
Original file line number Diff line number Diff line change
@@ -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: [
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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'}
]
};
Original file line number Diff line number Diff line change
@@ -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'
}
}
}
}
}
};
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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'}
]
};
Loading

0 comments on commit 1af2dc9

Please sign in to comment.