Skip to content

Commit

Permalink
Vendor extension for scoped middleware (closes #7)
Browse files Browse the repository at this point in the history
* The extension name is x-express-openapi-additional-middleware and it's value is an array of functions (middleware).
  • Loading branch information
jsdevel committed Feb 5, 2016
1 parent c4fbc9e commit c9dc95c
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 4 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 27 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down Expand Up @@ -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';
Expand All @@ -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) :
Expand All @@ -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
Expand Down Expand Up @@ -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');
}
Expand Down
10 changes: 9 additions & 1 deletion test/sample-projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ describe(require('../package.json').name + 'sample-projects', function() {
});
});

describe('disabling middleware', function() {
describe('configuring middleware', function() {
var coercionMissingBody = {
errors: [
{
Expand All @@ -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},
Expand Down
35 changes: 35 additions & 0 deletions test/sample-projects/with-additional-middleware/api-doc.js
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,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'
}
}
}
}
}
};
26 changes: 26 additions & 0 deletions test/sample-projects/with-additional-middleware/app.js
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);
}

0 comments on commit c9dc95c

Please sign in to comment.