Skip to content

Commit

Permalink
Merge branch 'master' into swagger-fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
theninj4 committed May 21, 2016
2 parents dd4bddc + d92bcf5 commit 70ab5b0
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 56 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
- 2016-05-16 - v1.6.0
- 2016-05-16 - Enable authentication errors
- 2016-05-16 - Avoid the HTTP stack for inclusions
- 2016-05-16 - Fix crash when opt-ing out of resource operations
- 2016-05-05 - v1.5.0
- 2016-05-05 - Allow overriding debug output functions
- 2016-04-27 - Static code analysis with Flow and JSCPD
Expand Down
1 change: 1 addition & 0 deletions example/handlers/photoHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
var jsonApi = require("../..");

module.exports = new jsonApi.MemoryHandler();
module.exports.delete = null;
1 change: 1 addition & 0 deletions lib/handlerEnforcer.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ handlerEnforcer._wrapHandler = function(handlers, operation, outCount) {
}

var original = handlers[operation];
if (!original) return null;
return function() {
var argsIn = Array.prototype.slice.call(arguments);
var requestParams = argsIn[0].params;
Expand Down
25 changes: 7 additions & 18 deletions lib/postProcess.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ var postProcess = module.exports = { };

var jsonApi = require("..");
var debug = require("./debugging.js");
var externalRequest = require("request").defaults({
pool: { maxSockets: Infinity }
});
var rerouter = require("./rerouter.js");
var async = require("async");
postProcess._applySort = require("./postProcessing/sort.js").action;
postProcess._applyFilter = require("./postProcessing/filter.js").action;
Expand Down Expand Up @@ -57,23 +55,14 @@ postProcess._fetchRelatedResources = function(request, mainResource, callback) {
async.map(resourcesToFetch, function(related, done) {
debug.include(related);

externalRequest({
rerouter.route({
method: "GET",
uri: related,
headers: request.safeHeaders
}, function(err, externalRes, json) {
if (err || !json) return done(null, [ ]);

try {
json = JSON.parse(json);
} catch(e) {
json = null;
}

if (!json) return done(null, [ ]);

if (externalRes.statusCode >= 400) {
return done(json.errors);
originalRequest: request
}, function(err, json) {
if (err) {
debug.include("!!", JSON.stringify(err));
return done(err.errors);
}

var data = json.data;
Expand Down
30 changes: 7 additions & 23 deletions lib/postProcessing/include.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ var jsonApi = require("../jsonApi.js");
var _ = {
unique: require("lodash.uniq")
};
var externalRequest = require("request").defaults({
pool: { maxSockets: Infinity }
});
var rerouter = require("../rerouter.js");
var async = require("async");
var debug = require("../debugging.js");

Expand Down Expand Up @@ -186,28 +184,14 @@ includePP._fillIncludeTree = function(includeTree, request, callback) {
var parts = related.as.split("~~");
debug.include(related);

externalRequest({
rerouter.route({
method: "GET",
uri: related.url,
headers: request.safeHeaders
}, function(err, res, json) {
// console.log(err, json)
if (err || !json) {
return done(null);
}

try {
json = JSON.parse(json);
} catch(e) {
debug.include("!!", JSON.stringify(json));
json = null;
}

if (!json) return done(null, [ ]);

if (res.statusCode >= 400) {
debug.include("!!", JSON.stringify(json));
return done(json.errors);
originalRequest: request
}, function(err, json) {
if (err) {
debug.include("!!", JSON.stringify(err));
return done(err.errors);
}

var data = json.data;
Expand Down
72 changes: 72 additions & 0 deletions lib/rerouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* @flow weak */
"use strict";
var rerouter = module.exports = { };

var router = require("./router.js");
var jsonApi = require("./jsonApi.js");
var url = require("qs");
var _ = {
omit: require("lodash.omit")
};


rerouter.route = function(newRequest, callback) {
var validRoutes = router._routes[newRequest.method.toLowerCase()];

var path = rerouter._generateSanePath(newRequest);
var route = rerouter._pickFirstMatchingRoute(validRoutes, path);

var req = {
url: newRequest.uri,
headers: newRequest.originalRequest.headers,
cookies: newRequest.originalRequest.cookies,
params: url.parse(newRequest.uri.split("?").pop())
};
rerouter._extendUrlParamsOntoReq(route, path, req);

var res = {
status: function(httpCode) {
res.httpCode = httpCode;
return res;
},
json: function(payload) {
var err = null;
if (res.httpCode >= 400) {
err = payload;
payload = undefined;
}
return callback(err, payload);
}
};
validRoutes[route](req, res, _.omit(newRequest.originalRequest, [ "params", "route" ]));
};

rerouter._generateSanePath = function(newRequest) {
var path = newRequest.uri;
if (path.match(/^https?\:\/\//)) {
path = path.split("/").slice(3).join("/");
}
if (jsonApi._apiConfig.base !== "/") {
if (path[0] !== "/") path = "/" + path;
path = path.split(jsonApi._apiConfig.base);
path.shift();
path = path.join(jsonApi._apiConfig.base);
}
path = path.replace(/^\//, "").split("?")[0].replace(/\/$/, "");
return path;
};

rerouter._pickFirstMatchingRoute = function(validRoutes, path) {
return Object.keys(validRoutes).filter(function(someRoute) {
someRoute = someRoute.replace(/(\:[a-z]+)/g, "[^/]*?");
someRoute = new RegExp("^" + someRoute);
return someRoute.test(path);
}).pop();
};

rerouter._extendUrlParamsOntoReq = function(route, path, req) {
route.split("/").forEach(function(urlPart, i) {
if (urlPart[0] !== ":") return;
req.params[urlPart.substring(1)] = path.split("/")[i];
});
};
22 changes: 19 additions & 3 deletions lib/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ var bodyParser = require("body-parser");
var cookieParser = require("cookie-parser");
var jsonApi = require("./jsonApi.js");
var debug = require("./debugging.js");
var responseHelper = require("./responseHelper.js");
var url = require("url");

app.use(function(req, res, next) {
Expand Down Expand Up @@ -88,15 +89,23 @@ router.close = function() {
server = null;
};

router._routes = { };
router.bindRoute = function(config, callback) {
app[config.verb](jsonApi._apiConfig.base + config.path, function(req, res) {
var path = jsonApi._apiConfig.base + config.path;
var verb = config.verb.toLowerCase();

var routeHandler = function(req, res, extras) {
var request = router._getParams(req);
request = _.assign(request, extras);
var resourceConfig = jsonApi._resources[request.params.type];
request.resourceConfig = resourceConfig;
router.authenticate(request, res, function() {
return callback(request, resourceConfig, res);
});
});
};
router._routes[verb] = router._routes[verb] || { };
router._routes[verb][config.path] = routeHandler;
app[verb](path, routeHandler);
};

router.authenticate = function(request, res, callback) {
Expand All @@ -105,7 +114,14 @@ router.authenticate = function(request, res, callback) {
router._authFunction(request, function(err) {
if (!err) return callback();

res.status(401).end();
var errorWrapper = {
status: "401",
code: "UNAUTHORIZED",
title: "Authentication Failed",
detail: err || "You are not authorised to access this resource."
};
var payload = responseHelper.generateError(request, errorWrapper);
res.status(401).json(payload);
});
};

Expand Down
15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jsonapi-server",
"version": "1.5.0",
"version": "1.6.0",
"description": "A config driven NodeJS framework implementing json:api",
"keywords": [
"jsonapi",
Expand All @@ -24,26 +24,27 @@
"cookie-parser": "1.4.0",
"debug": "2.2.0",
"express": "4.13.3",
"flow-bin": "^0.23.1",
"joi": "6.10.1",
"jscpd": "^0.6.1",
"lodash.assign": "3.2.0",
"lodash.isequal": "3.0.4",
"lodash.omit": "3.1.0",
"lodash.pick": "3.1.0",
"lodash.uniq": "3.2.2",
"node-uuid": "1.4.7",
"qs": "^6.2.0",
"request": "2.67.0"
},
"devDependencies": {
"mocha": "2.2.5",
"eslint": "0.24.1",
"blanket": "1.1.7",
"mocha-lcov-reporter": "0.0.2",
"coveralls": "2.11.2",
"plato": "1.5.0",
"eslint": "0.24.1",
"flow-bin": "^0.23.1",
"jscpd": "^0.6.1",
"mocha": "2.2.5",
"mocha-lcov-reporter": "0.0.2",
"mocha-performance": "0.1.0",
"node-inspector": "0.12.5",
"plato": "1.5.0",
"swagger-tools": "^0.10.1",
"v8-profiler": "5.5.0"
},
Expand Down
10 changes: 5 additions & 5 deletions test/authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
var request = require("request");
var assert = require("assert");
var jsonApiTestServer = require("../example/server.js");

var helpers = require("./helpers.js");

describe("Testing jsonapi-server", function() {
describe("authentication", function() {
Expand All @@ -14,10 +14,10 @@ describe("Testing jsonapi-server", function() {
"blockMe": "please"
}
};
request(data, function(err, res) {
request(data, function(err, res, json) {
assert.equal(err, null);
assert.equal(res.statusCode, "401", "Expecting 401");

helpers.validateError(json);
done();
});
});
Expand All @@ -30,10 +30,10 @@ describe("Testing jsonapi-server", function() {
"cookie": "blockMe=please"
}
};
request(data, function(err, res) {
request(data, function(err, res, json) {
assert.equal(err, null);
assert.equal(res.statusCode, "401", "Expecting 401");

helpers.validateError(json);
done();
});
});
Expand Down
32 changes: 32 additions & 0 deletions test/unavailableFunctions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use strict";
var request = require("request");
var assert = require("assert");
var helpers = require("./helpers.js");
var jsonApiTestServer = require("../example/server.js");


describe("Testing jsonapi-server", function() {
describe("unavailable functions", function() {
it("responds with a clear error", function(done) {
var data = {
method: "delete",
url: "http://localhost:16006/rest/photos/14"
};
request(data, function(err, res, json) {
assert.equal(err, null);
json = helpers.validateError(json);
assert.equal(res.statusCode, "403", "Expecting 403");
assert.equal(json.errors[0].detail, "The requested resource 'photos' does not support 'delete'");

done();
});
});
});

before(function() {
jsonApiTestServer.start();
});
after(function() {
jsonApiTestServer.close();
});
});

0 comments on commit 70ab5b0

Please sign in to comment.