You're Invited: Meet the Socket team at BSidesSF and RSAC - April 27 - May 1.RSVP

swagger-node-express

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

swagger-node-express - npm Package Compare versions

Comparing version

to
1.3.1

@@ -30,3 +30,3 @@ /**

exports.path = function(name, description, dataType, allowableValues) {
exports.path = function(name, description, dataType, allowableValues, defaultValue) {
return {

@@ -39,11 +39,15 @@ "name" : name,

"allowableValues" : allowableValues,
"paramType" : "path"
"paramType" : "path",
"defaultValue" : defaultValue
};
};
exports.post = function(dataType, description, defaultValue) {
exports.body = function(name, description, dataType, defaultValue) {
return {
"name" : name,
"description" : description,
"dataType" : dataType,
"required" : true,
"allowMultiple" : false,
"paramType" : "body",

@@ -54,2 +58,14 @@ "defaultValue" : defaultValue

exports.form = function(name, description, dataType, defaultValue) {
return {
"name" : name,
"description" : description,
"dataType" : "string",
"required" : true,
"allowMultiple" : false,
"paramType" : "form",
"defaultValue" : defaultValue
};
};
exports.header = function(name, description, dataType, required) {

@@ -60,6 +76,6 @@ return {

"dataType" : dataType,
"required" : true,
"required" : required,
"allowMultiple" : false,
"paramType" : "header"
};
};
};

@@ -16,3 +16,3 @@ /**

*/
var _ = require('lodash');
var formatString = ".{format}";

@@ -27,3 +27,3 @@ var resourcePath = "/api-docs" + formatString;

var appHandler = null;
var allowedMethods = ['get', 'post', 'put', 'delete'];
var allowedMethods = ['get', 'post', 'put', 'patch', 'delete'];
var allowedDataTypes = ['string', 'int', 'long', 'double', 'boolean', 'date', 'array'];

@@ -33,2 +33,15 @@ var params = require(__dirname + '/paramTypes.js');

// Default error handler
var errorHandler = function (req, res, error) {
if (error.code && error.reason)
res.send(JSON.stringify(error), error.code);
else {
console.error(req.method + " failed for path '" + require('url').parse(req.url).href + "': " + error);
res.send(JSON.stringify({
"reason": "unknown error",
"code": 500
}), 500);
}
};
function configureSwaggerPaths(format, path, suffix) {

@@ -42,2 +55,3 @@ formatString = format;

// subdocuments. It should only be done once, and during bootstrap of the app
function configure(bp, av) {

@@ -52,13 +66,13 @@ basePath = bp;

for(key in resources) {
var r = resources[key];
r.apiVersion = av;
r.basePath = bp;
}
_.forOwn(resources, function (resource) {
resource.apiVersion = av;
resource.basePath = bp;
});
}
// Convenience to set default headers in each response.
function setHeaders(res) {
res.header('Access-Control-Allow-Origin', "*");
res.header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE");
res.header("Access-Control-Allow-Headers", "Content-Type, api_key");

@@ -69,8 +83,10 @@ res.header("Content-Type", "application/json; charset=utf-8");

// creates declarations for each resource path.
function setResourceListingPaths(app) {
for (var key in resources) {
_.forOwn(resources, function (resource, key) {
// pet.json => api-docs.json/pet
var path = baseApiFromPath(key);
app.get(path, function(req, res) {
app.get(path, function (req, res) {
// find the api base path from the request URL

@@ -86,5 +102,7 @@ // /api-docs.json/pet => /pet.json

console.error("unable to find listing");
return stopWithError(res, {'description': 'internal error', 'code': 500});
}
else {
return stopWithError(res, {
'reason': 'internal error',
'code': 500
});
} else {
exports.setHeaders(res);

@@ -94,4 +112,4 @@ var data = filterApiListing(req, res, r);

if (data.code) {
res.send(data, data.code); }
else {
res.send(data, data.code);
} else {
res.send(JSON.stringify(filterApiListing(req, res, r)));

@@ -101,3 +119,3 @@ }

});
}
});
}

@@ -118,47 +136,46 @@

// methods and models that the user actually has access to.
function filterApiListing(req, res, r) {
var route = req.route;
var excludedPaths = [];
if (!r || !r.apis) {
return stopWithError(res, {'description': 'internal error', 'code': 500});
return stopWithError(res, {
'reason': 'internal error',
'code': 500
});
}
for (var key in r.apis) {
var api = r.apis[key];
_.forOwn(r.apis, function (api) {
for (var opKey in api.operations) {
if (!api.operations.hasOwnProperty(opKey)) {
continue;
}
var op = api.operations[opKey];
var path = api.path.replace(formatString, "").replace(/{.*\}/, "*");
if (!canAccessResource(req, path, op.httpMethod)) {
excludedPaths.push(op.httpMethod + ":" + api.path); }
excludedPaths.push(op.httpMethod + ":" + api.path);
}
}
}
});
// clone attributes in the resource
var output = shallowClone(r);
// models required in the api listing
var requiredModels = [];
// clone methods that user can access
output.apis = [];
var apis = JSON.parse(JSON.stringify(r.apis));
for (var i in apis) {
var api = apis[i];
_.forOwn(apis, function (api) {
var clonedApi = shallowClone(api);
clonedApi.operations = [];
var shouldAdd = true;
for (var o in api.operations) {
var operation = api.operations[o];
if (excludedPaths.indexOf(operation.httpMethod + ":" + api.path) >= 0) {
break;
}
else {
_.forOwn(api.operations, function (operation) {
if (!excludedPaths.indexOf(operation.httpMethod + ":" + api.path) >= 0) {
clonedApi.operations.push(JSON.parse(JSON.stringify(operation)));
addModelsFromPost(operation, requiredModels);
addModelsFromBody(operation, requiredModels);
addModelsFromResponse(operation, requiredModels);
}
}
});
// only add cloned api if there are operations

@@ -168,24 +185,23 @@ if (clonedApi.operations.length > 0) {

}
}
});
// add required models to output
output.models = {};
for (var i in requiredModels){
var modelName = requiredModels[i];
var model = allModels.models[modelName];
if(model){
output.models[requiredModels[i]] = model;
_.forOwn(requiredModels, function (modelName) {
var model = allModels[modelName];
if (model) {
output.models[modelName] = model;
}
}
});
// look in object graph
for (key in output.models) {
var model = output.models[key];
_.forOwn(output.models, function (model) {
if (model && model.properties) {
for (var key in model.properties) {
var t = model.properties[key].type;
_.forOwn(model.properties, function (property) {
var type = property.type;
switch (t){
switch (type) {
case "array":
case "Array":
if (model.properties[key].items) {
var ref = model.properties[key].items.$ref;
if (property.items) {
var ref = property.items.$ref;
if (ref && requiredModels.indexOf(ref) < 0) {

@@ -200,19 +216,18 @@ requiredModels.push(ref);

default:
if (requiredModels.indexOf(t) < 0) {
requiredModels.push(t);
if (requiredModels.indexOf(type) < 0) {
requiredModels.push(type);
}
break;
}
}
});
}
}
for (var i in requiredModels){
var modelName = requiredModels[i];
if(!output[modelName]) {
var model = allModels.models[modelName];
if(model){
output.models[requiredModels[i]] = model;
});
_.forOwn(requiredModels, function (modelName) {
if (!output[modelName]) {
var model = allModels[modelName];
if (model) {
output.models[modelName] = model;
}
}
}
});
return output;

@@ -222,33 +237,22 @@ }

// Add model to list and parse List[model] elements
function addModelsFromPost(operation, models){
if(operation.parameters) {
for(var i in operation.parameters) {
var param = operation.parameters[i];
if(param.paramType == "body" && param.dataType) {
var model = param.dataType.replace(/^List\[/,"").replace(/\]/,"");
if(models.indexOf(model) < 0) {
models.push(responseModel);
}
models.push(param.dataType);
function addModelsFromBody(operation, models) {
if (operation.parameters) {
_.forOwn(operation.parameters, function (param) {
if (param.paramType == "body" && param.dataType) {
var model = param.dataType.replace(/^List\[/, "").replace(/\]/, "");
models.push(model);
}
}
});
}
var responseModel = operation.responseClass;
if (responseModel) {
responseModel = responseModel.replace(/^List\[/,"").replace(/\]/,"");
if (models.indexOf(responseModel) < 0) {
models.push(responseModel);
}
}
}
// Add model to list and parse List[model] elements
// Add model to list and parse List[model] elements
function addModelsFromResponse(operation, models){
function addModelsFromResponse(operation, models) {
var responseModel = operation.responseClass;
if (responseModel) {
responseModel = responseModel.replace(/^List\[/,"").replace(/\]/,"");
responseModel = responseModel.replace(/^List\[/, "").replace(/\]/, "");
if (models.indexOf(responseModel) < 0) {
models.push(responseModel);
models.push(responseModel);
}

@@ -259,5 +263,9 @@ }

// clone anything but objects to avoid shared references
function shallowClone(obj) {
var cloned = {};
for (var i in obj) {
if (!obj.hasOwnProperty(i)) {
continue;
}
if (typeof (obj[i]) != "object") {

@@ -272,6 +280,9 @@ cloned[i] = obj[i];

// if consumer can access the resource, method returns true.
function canAccessResource(req, path, httpMethod) {
for (var i in validators) {
if (!validators[i](req,path,httpMethod))
for (var i = 0; i < validators.length; i++) {
var validator = validators[i];
if (_.isFunction(validator) && !validator(req, path, httpMethod)) {
return false;
}
}

@@ -283,18 +294,22 @@ return true;

* returns the json representation of a resource
*
*
* @param request
* @param response
*/
function resourceListing(req, res) {
var r = {
"apiVersion" : apiVersion,
"swaggerVersion" : swaggerVersion,
"basePath" : basePath,
"apis" : []
"apiVersion": apiVersion,
"swaggerVersion": swaggerVersion,
"basePath": basePath,
"apis": []
};
for (var key in resources) {
var p = resourcePath + "/" + key.replace(formatString,"");
r.apis.push({"path": p, "description": "none"});
}
_.forOwn(resources, function (value, key) {
var p = resourcePath + "/" + key.replace(formatString, "");
r.apis.push({
"path": p,
"description": "none"
});
});

@@ -307,2 +322,3 @@ exports.setHeaders(res);

// Adds a method to the api along with a spec. If the spec fails to validate, it won't be added
function addMethod(app, callback, spec) {

@@ -314,4 +330,3 @@ var apiRootPath = spec.path.split("/")[1];

// this path already exists in swagger resources
for (var key in root.apis) {
var api = root.apis[key];
_.forOwn(root.apis, function (api) {
if (api && api.path == spec.path && api.method == spec.method) {

@@ -322,12 +337,19 @@ // add operation & return

}
}
});
}
var api = {"path" : spec.path};
var api = {
"path": spec.path
};
if (!resources[apiRootPath]) {
if (!root) {
//
var resourcePath = "/" + apiRootPath.replace(formatString, "");
var resourcePath = "/" + apiRootPath.replace(formatString, "");
root = {
"apiVersion" : apiVersion, "swaggerVersion": swaggerVersion, "basePath": basePath, "resourcePath": resourcePath, "apis": [], "models" : []
"apiVersion": apiVersion,
"swaggerVersion": swaggerVersion,
"basePath": basePath,
"resourcePath": resourcePath,
"apis": [],
"models": []
};

@@ -342,6 +364,6 @@ }

// convert .{format} to .json, make path params happy
var fullPath = spec.path.replace(formatString, jsonSuffix).replace(/\/{/g, "/:").replace(/\}/g,"");
var fullPath = spec.path.replace(formatString, jsonSuffix).replace(/\/{/g, "/:").replace(/\}/g, "");
var currentMethod = spec.method.toLowerCase();
if (allowedMethods.indexOf(currentMethod)>-1) {
app[currentMethod](fullPath, function(req,res) {
if (allowedMethods.indexOf(currentMethod) > -1) {
app[currentMethod](fullPath, function (req, res) {
exports.setHeaders(res);

@@ -352,19 +374,20 @@

if (!canAccessResource(req, path, req.method)) {
res.send(JSON.stringify({"description":"forbidden", "code":403}), 403);
} else {
res.send(JSON.stringify({
"reason": "forbidden",
"code": 403
}), 403);
} else {
try {
callback(req,res);
}
catch (ex) {
if (ex.code && ex.description)
res.send(JSON.stringify(ex), ex.code);
else {
console.error(spec.method + " failed for path '" + require('url').parse(req.url).href + "': " + ex);
res.send(JSON.stringify({"description":"unknown error","code":500}), 500);
callback(req, res);
} catch (error) {
if (typeof errorHandler === "function") {
errorHandler(req, res, error);
} else {
throw error;
}
}
}
});
});
} else {
console.error('unable to add ' + currentMethod.toUpperCase() + ' handler');
console.error('unable to add ' + currentMethod.toUpperCase() + ' handler');
return;

@@ -375,2 +398,3 @@ }

// Set expressjs app handler
function setAppHandler(app) {

@@ -380,23 +404,31 @@ appHandler = app;

// Change error handler
// Error handler should be a function that accepts parameters req, res, error
function setErrorHandler(handler) {
errorHandler = handler;
}
// Add swagger handlers to express
function addHandlers(type, handlers) {
for (var i = 0; i < handlers.length; i++) {
var handler = handlers[i];
_.forOwn(handlers, function (handler) {
handler.spec.method = type;
addMethod(appHandler, handler.action, handler.spec);
}
});
}
// Discover swagger handler from resource
function discover(resource) {
for (var key in resource) {
if (resource[key].spec && resource[key].spec.method && allowedMethods.indexOf(resource[key].spec.method.toLowerCase())>-1) {
addMethod(appHandler, resource[key].action, resource[key].spec);
}
else
console.error('auto discover failed for: ' + key);
}
_.forOwn(resource, function (handler, key) {
if (handler.spec && handler.spec.method && allowedMethods.indexOf(handler.spec.method.toLowerCase()) > -1) {
addMethod(appHandler, handler.action, handler.spec);
} else
console.error('auto discover failed for: ' + key);
});
}
// Discover swagger handler from resource file path
function discoverFile(file) {

@@ -407,2 +439,3 @@ return discover(require(file));

// adds get handler
function addGet() {

@@ -414,2 +447,3 @@ addHandlers('GET', arguments);

// adds post handler
function addPost() {

@@ -421,3 +455,4 @@ addHandlers('POST', arguments);

// adds delete handler
function addDelete() {
function addDelete() {
addHandlers('DELETE', arguments);

@@ -428,2 +463,3 @@ return this;

// adds put handler
function addPut() {

@@ -434,10 +470,33 @@ addHandlers('PUT', arguments);

// adds patch handler
function addPatch() {
addHandlers('PATCH', arguments);
return this;
}
// adds models to swagger
function addModels(models) {
if(!allModels['models']) {
models = _.cloneDeep(models);
if (!allModels) {
allModels = models;
} else {
for(k in models['models']) {
allModels['models'][k] = models['models'][k];
}
_.forOwn(models, function (model, key) {
var required = model.required;
_.forOwn(model.properties, function (property, propertyKey) {
// convert enum to allowableValues
if (typeof property.enum !== 'undefined') {
property.allowableValues = {
"valueType": "LIST",
"values": property.enum
}
}
// convert existence in v4 required array to required attribute
if (required && required.indexOf(propertyKey) > -1) {
property.required = true;
}
});
allModels[key] = model;
});
}

@@ -447,48 +506,70 @@ return this;

function wrap(callback, req, resp){
callback(req,resp);
function wrap(callback, req, resp) {
callback(req, resp);
}
// appends a spec to an existing operation
function appendToApi(rootResource, api, spec) {
if (!api.description) {
api.description = spec.description;
api.description = spec.description;
}
var validationErrors = [];
if(!spec.nickname || spec.nickname.indexOf(" ")>=0){
if (!spec.nickname || spec.nickname.indexOf(" ") >= 0) {
// nicknames don't allow spaces
validationErrors.push({"path": api.path, "error": "invalid nickname '" + spec.nickname + "'"});
}
validationErrors.push({
"path": api.path,
"error": "invalid nickname '" + spec.nickname + "'"
});
}
// validate params
for ( var paramKey in spec.params) {
var param = spec.params[paramKey];
if(param.allowableValues) {
_.forOwn(spec.params, function (param) {
if (param.allowableValues) {
var avs = param.allowableValues.toString();
var type = avs.split('[')[0];
if(type == 'LIST'){
var values = avs.match(/\[(.*)\]/g).toString().replace('\[','').replace('\]', '').split(',');
param.allowableValues = {valueType: type, values: values};
if (type == 'LIST') {
var values = avs.match(/\[(.*)\]/g).toString().replace('\[', '').replace('\]', '').split(',');
param.allowableValues = {
valueType: type,
values: values
};
} else if (type == 'RANGE') {
var values = avs.match(/\[(.*)\]/g).toString().replace('\[', '').replace('\]', '').split(',');
param.allowableValues = {
valueType: type,
min: values[0],
max: values[1]
};
}
else if (type == 'RANGE') {
var values = avs.match(/\[(.*)\]/g).toString().replace('\[','').replace('\]', '').split(',');
param.allowableValues = {valueType: type, min: values[0], max: values[1]};
}
}
switch (param.paramType) {
case "path":
if (api.path.indexOf("{" + param.name + "}") < 0) {
validationErrors.push({"path": api.path, "name": param.name, "error": "invalid path"});
}
break;
case "query":
break;
case "body":
break;
default:
validationErrors.push({"path": api.path, "name": param.name, "error": "invalid param type " + param.paramType});
break;
case "path":
if (api.path.indexOf("{" + param.name + "}") < 0) {
validationErrors.push({
"path": api.path,
"name": param.name,
"error": "invalid path"
});
}
break;
case "query":
break;
case "body":
break;
case "form":
break;
case "header":
break;
default:
validationErrors.push({
"path": api.path,
"name": param.name,
"error": "invalid param type " + param.paramType
});
break;
}
}
});

@@ -499,20 +580,25 @@ if (validationErrors.length > 0) {

}
if (!api.operations) {
api.operations = []; }
api.operations = [];
}
// TODO: replace if existing HTTP operation in same api path
var op = {
"parameters" : spec.params,
"httpMethod" : spec.method,
"notes" : spec.notes,
"errorResponses" : spec.errorResponses,
"nickname" : spec.nickname,
"summary" : spec.summary
"parameters": spec.params,
"httpMethod": spec.method,
"notes": spec.notes,
"errorResponses": spec.errorResponses,
"nickname": spec.nickname,
"summary": spec.summary,
"consumes" : spec.consumes,
"produces" : spec.produces
};
// Add custom fields.
op = _.extend({}, spec, op);
if (spec.responseClass) {
op.responseClass = spec.responseClass;
}
else {
op.responseClass = spec.responseClass;
} else {
op.responseClass = "void";

@@ -523,3 +609,3 @@ }

if (!rootResource.models) {
rootResource.models = {};
rootResource.models = {};
}

@@ -533,13 +619,21 @@ }

// Create Error JSON by code and text
function error(code, description) {
return {"code" : code, "description" : description};
return {
"code": code,
"reason": description
};
}
// Stop express ressource with error code
function stopWithError(res, error) {
exports.setHeaders(res);
if (error && error.description && error.code)
if (error && error.reason && error.code)
res.send(JSON.stringify(error), error.code);
else
res.send(JSON.stringify({'description': 'internal error', 'code': 500}), 500);
res.send(JSON.stringify({
'reason': 'internal error',
'code': 500
}), 500);
}

@@ -549,19 +643,40 @@

exports.errors = {
'notFound': function(field, res) {
if (!res) {
return {"code": 404, "description": field + ' not found'}; }
else {
res.send({"code": 404, "description": field + ' not found'}, 404); }
'notFound': function (field, res) {
if (!res) {
return {
"code": 404,
"reason": field + ' not found'
};
} else {
res.send({
"code": 404,
"reason": field + ' not found'
}, 404);
}
},
'invalid': function(field, res) {
if (!res) {
return {"code": 400, "description": 'invalid ' + field}; }
else {
res.send({"code": 400, "description": 'invalid ' + field}, 404); }
'invalid': function (field, res) {
if (!res) {
return {
"code": 400,
"reason": 'invalid ' + field
};
} else {
res.send({
"code": 400,
"reason": 'invalid ' + field
}, 404);
}
},
'forbidden': function(res) {
if (!res) {
return {"code": 403, "description": 'forbidden' }; }
else {
res.send({"code": 403, "description": 'forbidden'}, 403); }
'forbidden': function (res) {
if (!res) {
return {
"code": 403,
"reason": 'forbidden'
};
} else {
res.send({
"code": 403,
"reason": 'forbidden'
}, 403);
}
}

@@ -573,3 +688,4 @@ };

exports.pathParam = exports.params.path;
exports.postParam = exports.params.post;
exports.bodyParam = exports.params.body;
exports.formParam = exports.params.form;
exports.getModels = allModels;

@@ -589,2 +705,3 @@

exports.addPut = addPut;
exports.addPatch = addPatch;
exports.addDelete = addDelete;

@@ -594,8 +711,10 @@ exports.addGET = addGet;

exports.addPUT = addPut;
exports.addPATCH = addPatch;
exports.addDELETE = addDelete;
exports.addModels = addModels;
exports.setAppHandler = setAppHandler;
exports.setErrorHandler = setErrorHandler;
exports.discover = discover;
exports.discoverFile = discoverFile;
exports.configureSwaggerPaths = configureSwaggerPaths;
exports.setHeaders = setHeaders;
exports.setHeaders = setHeaders;
{
"name": "swagger-node-express",
"version": "1.2.3",
"version": "1.3.1",
"author": {

@@ -30,5 +30,6 @@ "name": "Tony Tam",

"express": "3.x",
"docco": "0.4.x"
"docco": "0.4.x",
"lodash": "1.3.1"
},
"license": "apache 2.0"
}

@@ -22,3 +22,3 @@ This is the Wordnik Swagger code for the express framework. For more on Swagger, please visit http://swagger.wordnik.com. For more on express, please visit https://github.com/visionmedia/express

or from [swagger UI], mounted at `/docs`: [http://localhost:8002/docs](http://localhost:8002/docs).
or from [swagger UI](https://github.com/wordnik/swagger-ui), mounted at `/docs`: [http://localhost:8002/docs](http://localhost:8002/docs).

@@ -34,13 +34,9 @@ ### How it works

For the sample app, the models are defined here:
For the sample app, the models are defined here: [Apps/petstore/models.js](https://github.com/wordnik/swagger-node-express/blob/master/Apps/petstore/models.js)
(Apps/petstore/models.js)[https://github.com/wordnik/swagger-node-express/blob/master/Apps/petstore/models.js]
You could load this from a static file or generate them programatically as in the
sample.
The operations and the callback functions are defined in this file:
The operations and the callback functions are defined in this file: [Apps/petstore/petResources.js](https://github.com/wordnik/swagger-node-express/blob/master/Apps/petstore/petResources.js)
(Apps/petstore/petResources.js)[https://github.com/wordnik/swagger-node-express/blob/master/Apps/petstore/petResources.js]
Each spec defines input/output params with helper functions to generate the swagger

@@ -47,0 +43,0 @@ metadata.

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet