Skip to content
This repository has been archived by the owner on Aug 4, 2023. It is now read-only.

Commit

Permalink
Incremental commit with first pass at 2.0 support for middleware
Browse files Browse the repository at this point in the history
* Missing swagger-validator middleware for 2.0 due to it not being complete
  • Loading branch information
whitlockjc committed Aug 14, 2014
1 parent 0acc168 commit 7428704
Show file tree
Hide file tree
Showing 22 changed files with 1,432 additions and 195 deletions.
9 changes: 5 additions & 4 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ var mocha = require('gulp-mocha');
gulp.task('lint', function () {
return gulp.src([
'./index.js',
'./lib/*.js',
'./middleware/*.js',
'./test/*.js',
'./lib/**/*.js',
'./middleware/**/*.js',
'./test/1.2/*.js',
'./test/2.0/*.js',
'./gulpfile.js'
])
.pipe(jshint())
Expand All @@ -34,7 +35,7 @@ gulp.task('lint', function () {
});

gulp.task('test', function () {
return gulp.src('test/test-*.js')
return gulp.src('test/**/test-*.js')
.pipe(mocha({reporter: 'list'}));
});

Expand Down
6 changes: 1 addition & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@
'use strict';

module.exports = {
middleware: {
swaggerMetadata: require('./middleware/swagger-metadata'),
swaggerRouter: require('./middleware/swagger-router'),
swaggerValidator: require('./middleware/swagger-validator')
},
middleware: require('./middleware'),
specs: require('./lib/specs')
};
4 changes: 4 additions & 0 deletions lib/specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ var path = require('path');
var jjv = require('jjv');
var jjve = require('jjve');

var draft04Json = require('../schemas/json-schema-draft-04.json');
var draft04Url = 'http://json-schema.org/draft-04/schema';
var jjvOptions = {
checkRequired: true,
removeAdditional: false,
Expand All @@ -40,6 +42,8 @@ var createValidator = function createValidator (spec, schemaNames) {
return true;
});

validator.addSchema(draft04Url, draft04Json);

// Compile the necessary schemas
_.each(schemaNames, function (schemaName) {
var clone = _.cloneDeep(spec.schemas[schemaName]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,10 @@
'use strict';

var _ = require('lodash');
var expressStylePath = require('../helpers').expressStylePath;
var parseurl = require('parseurl');
var pathToRegexp = require('path-to-regexp');

var expressStylePath = function expressStylePath (basePath, apiPath) {
basePath = parseurl({url: basePath || '/'}).pathname || '/';

// Make sure the base path starts with '/'
if (basePath.charAt(0) !== '/') {
basePath = '/' + basePath;
}

// Make sure the base path ends with '/'
if (basePath.charAt(basePath.length - 1) !== '/') {
basePath = basePath + '/';
}

// Make sure the api path does not start with '/' since the base path will end with '/'
if (apiPath.charAt(0) === '/') {
apiPath = apiPath.substring(1);
}

// Replace Swagger syntax for path parameters with Express' version (All Swagger path parameters are required)
return (basePath + apiPath).replace(/{/g, ':').replace(/}/g, '');
};

/**
* Middleware for providing Swagger information to downstream middleware and request handlers. 'req.swagger' will be
* added to the request of all routes that match routes defined in your Swagger resources. Here is the structure of
Expand Down
37 changes: 3 additions & 34 deletions middleware/swagger-router.js → middleware/1.2/swagger-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,46 +17,15 @@
'use strict';

var _ = require('lodash');
var fs = require('fs');
var path = require('path');
var helpers = require('../helpers');
var handlerCacheFromDir = helpers.handlerCacheFromDir;
var createStubHandler = helpers.createStubHandler;

var defaultOptions = {
controllers: {},
useStubs: false // Should we set this automatically based on process.env.NODE_ENV?
};

var handlerCacheFromDir = function handlerCacheFromDir (dir) {
var handlerCache = {};
var jsFileRegex = /\.js$/;

_.each(fs.readdirSync(dir), function (file) {
var controllerName = file.replace(jsFileRegex, '');
var controller;

if (file.match(jsFileRegex)) {
controller = require(path.resolve(path.join(dir, controllerName)));

if (!_.isPlainObject(controller)) {
throw new Error('Controller module expected to export an object: ' + path.join(dir, file));
}

_.each(controller, function (value, name) {
if (_.isFunction(value)) {
handlerCache[controllerName + '_' + name] = value;
}
});
}
});

return handlerCache;
};

var createStubHandler = function createStubHandler (req, res, msg) {
return function stubHandler (req, res) {
res.end(msg);
};
};

/**
* Middleware for using Swagger information to route requests to handlers.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
'use strict';

var _ = require('lodash');
var validators = require('../validators');

// http://tools.ietf.org/html/rfc3339#section-5.6
var dateRegExp = /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/;
// http://tools.ietf.org/html/rfc3339#section-5.6
Expand Down Expand Up @@ -124,20 +126,14 @@ var isValid = function isValid (val, type, format) {
exports = module.exports = function swaggerValidatorMiddleware () {

return function swaggerValidator (req, res, next) {
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
var contentType = req.headers['content-type'] || 'application/octet-stream';
var operation = req.swagger ? req.swagger.operation : undefined;

if (!_.isUndefined(operation)) {
// Validate content type (Only for POST/PUT per HTTP spec)
if (!_.isUndefined(operation.consumes) && ['POST', 'PUT'].indexOf(req.method) !== -1) {
if (operation.consumes.indexOf(contentType) === -1) {
return next('Invalid content type (' + contentType + '). These are valid: ' + operation.consumes.join(', '));
}
}

// Validate the parameters
// Validate the request
try {
// Validate the content type
validators.validateContentType(req.swagger.api.consumes, operation.consumes, req);

_.each(operation.parameters || [], function (param) {
var minimum = param.minimum;
var maximum = param.maximum;
Expand All @@ -147,9 +143,7 @@ exports = module.exports = function swaggerValidatorMiddleware () {
var val = req.swagger.params[param.name].value;

// Validate requiredness
if (!_.isUndefined(param.required) && param.required === true && _.isUndefined(val)) {
throw new Error(invalidParamPrefix + 'is required');
}
validators.validateRequiredness(param.name, val, param.required);

// Quick return if the value is not present
if (_.isUndefined(val)) {
Expand Down
151 changes: 151 additions & 0 deletions middleware/2.0/swagger-metadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Copyright 2014 Apigee Corporation
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

var _ = require('lodash');
var expressStylePath = require('../helpers').expressStylePath;
var parseurl = require('parseurl');
var pathToRegexp = require('path-to-regexp');

/**
* Middleware for providing Swagger information to downstream middleware and request handlers. 'req.swagger' will be
* added to the request of all routes that match routes defined in your Swagger resources. Here is the structure of
* 'req.swagger':
*
* * path: The Swagger path the request is associated with
* * operation: The Swagger path operation the request is associated with
* * params: The parameters for the request
* * schema: The resource API operation parameter definition
* * value: The value of the paramter from the request (Not converted to any particular type)
* * swaggerObject: The Swagger object itself
*
* This middleware requires that you use the appropriate middleware to populate req.body and req.query before this
* middleware. This middleware also makes no attempt to work around invalid Swagger documents.
*
* @param {object} swaggerObject - The Swagger object
*
* @returns the middleware function
*/
exports = module.exports = function swaggerMetadataMiddleware (swaggerObject) {
if (_.isUndefined(swaggerObject)) {
throw new Error('swaggerObject is required');
} else if (!_.isPlainObject(swaggerObject)) {
throw new TypeError('swaggerObject must be an object');
}

var paths = {};

// Gather the paths, their path regex patterns and the corresponding operations
_.each(swaggerObject.paths, function (path, pathName) {
var keys = [];
var re = pathToRegexp(expressStylePath(swaggerObject.basePath, pathName), keys);
var reStr = re.toString();

paths[reStr] = {
path: path,
keys: keys,
re: re,
operations: {}
};

_.each(['get', 'put', 'post', 'delete', 'options', 'head', 'patch'], function (method) {
var operation = path[method];

if (!_.isUndefined(operation)) {
paths[reStr].operations[method] = operation;
}
});
});

return function swaggerMetadata (req, res, next) {
var rPath = parseurl(req).pathname;
var match;
var path = _.find(paths, function (path) {
match = path.re.exec(rPath);
return _.isArray(match);
});
var metadata = {
path: path ? path.path : undefined,
operation: path ? path.operations[req.method.toLowerCase()] : undefined,
params: {},
swaggerObject: swaggerObject
};

// Collect the parameter values
if (!_.isUndefined(metadata.operation)) {
try {
// Until Swagger 2.0 documentation comes out, I'm going to assume that you cannot override "path" parameters
// with operation parameters. That's why we start with the path parameters first and then the operation
// parameters. (Note: "path" in this context is a path entry at #/paths in the Swagger Object)
_.each(_.union(metadata.path.parameters, metadata.operation.parameters), function (param) {
var paramType = param.in || 'query';
var val;

// Get the value to validate based on the operation parameter type
switch (paramType) {
case 'body':
case 'formData':
if (!req.body) {
throw new Error('Server configuration error: req.body is not defined but is required');
}

val = req.body[param.name];

break;
case 'header':
val = req.headers[param.name];

break;
case 'path':
_.each(path.keys, function (key, index) {
if (key.name === param.name) {
val = match[index + 1];
}
});

break;
case 'query':
if (!req.query) {
throw new Error('Server configuration error: req.query is not defined but is required');
}

val = req.query[param.name];

break;
}

// Use the default value when necessary
if (_.isUndefined(val) && !_.isUndefined(param.schema) && !_.isUndefined(param.schema.default)) {
val = param.default;
}

metadata.params[param.name] = {
schema: param,
value: val
};
});

// Attach Swagger metadata to the request
req.swagger = metadata;
} catch (err) {
return next(err.message);
}
}

return next();
};
};
Loading

0 comments on commit 7428704

Please sign in to comment.