@openapi-mock/swagger-node-runner
Version:
Swagger loader and middleware utilities
188 lines (153 loc) • 5.73 kB
JavaScript
;
module.exports = init;
var debug = require('debug')('swagger');
var debugContent = require('debug')('swagger:content');
var _ = require('lodash');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
function init(runner) {
return new Middleware(runner);
}
function Middleware(runner) {
this.runner = runner;
this.middleware = function middleware() {
return function middleware(req, res, next) { // flow back to connect pipe
var operation = runner.getOperation(req);
if (!operation) {
var path = runner.getPath(req);
if (!path) { return next(); }
if (!path['x-swagger-pipe'] && req.method !== 'OPTIONS') {
var msg = util.format('Path [%s] defined in Swagger, but %s operation is not.', path.path, req.method);
var err = new Error(msg);
err.statusCode = 405;
err.status = err.statusCode; // for Sails, see: https://github.com/theganyo/swagger-node-runner/pull/31
var allowedMethods = _.map(path.operationObjects, function(operation) {
return operation.method.toUpperCase();
});
err.allowedMethods = allowedMethods;
res.setHeader('Allow', allowedMethods.sort().join(', '));
return next(err);
}
}
runner.applyMetadata(req, operation, function(err) {
if (err) { /* istanbul ignore next */ return next(err); }
var pipe = runner.getPipe(req);
if (!pipe) {
var err = new Error('No implementation found for this path.');
err.statusCode = 405;
return next(err);
}
var context = {
// system values
_errorHandler: runner.defaultErrorHandler(),
request: req,
response: res,
// user-modifiable values
input: undefined,
statusCode: undefined,
headers: {},
output: undefined
};
context._finish = function finishConnect(ignore1, ignore2) { // must have arity of 2
debugContent("exec", context.error);
if (context.error) { return next(context.error); }
try {
var response = context.response;
if (context.statusCode) {
debug('setting response statusCode: %d', context.statusCode);
response.statusCode = context.statusCode;
}
if (context.headers) {
debugContent('setting response headers: %j', context.headers);
_.each(context.headers, function(value, name) {
response.setHeader(name, value);
});
}
if (undefined === context.output) { return next(); }
var contentType = response.getHeader('content-type');
if (!contentType) {
contentType = request.headers.accept;
if ("*/*" == contentType) contentType = operation.produces[0];
if (contentType) response.setHeader("content-type", contentType);
}
var body = translate(context.output, contentType);
debugContent('sending response body: %s', body);
response.end(body);
}
catch (err) {
/* istanbul ignore next */
next(err);
}
};
/* istanbul ignore next */
var listenerCount = (runner.listenerCount) ?
runner.listenerCount('responseValidationError') : // Node >= 4.0
EventEmitter.listenerCount(runner, 'responseValidationError'); // Node < 4.0
if (listenerCount) {
hookResponseForValidation(context, runner);
}
runner.bagpipes.play(pipe, context);
});
};
};
this.register = function register(app) {
app.use(this.middleware());
};
}
function translate(output, mimeType) {
if (typeof output !== 'object') { return output; }
switch(true) {
case /json/.test(mimeType):
return JSON.stringify(output);
default:
return util.inspect(output)
}
}
function hookResponseForValidation(context, eventEmitter) {
debug('add response validation hook');
var res = context.response;
var end = res.end;
var write = res.write;
var written;
res.write = function hookWrite(chunk, encoding, callback) {
if (written) {
written = '';
res.write = write;
res.end = end;
debug('multiple writes, will not validate response');
} else {
written = chunk;
}
write.apply(res, arguments);
};
res.end = function hookEnd(data, encoding, callback) {
res.write = write;
res.end = end;
if (written && data) {
debug('multiple writes, will not validate response');
} else if (!context.request.swagger.operation) {
debug('not a swagger operation, will not validate response');
} else {
debug('validating response');
try {
var headers = res._headers || res.headers || {};
var body = data || written;
debugContent('response body type: %s value: %s', typeof body, body);
var validateResult = context.request.swagger.operation.validateResponse({
statusCode: res.statusCode,
headers: headers,
body: body
});
debug('validation result:', validateResult);
if (validateResult.errors.length || validateResult.warnings.length) {
debug('emitting responseValidationError');
eventEmitter.emit('responseValidationError', validateResult, context.request, res);
}
} catch (err) {
/* istanbul ignore next */
console.error(err.stack);
}
}
end.apply(res, arguments);
};
}