diff --git a/lib/rest-adapter.js b/lib/rest-adapter.js index e116597..d39b3ec 100644 --- a/lib/rest-adapter.js +++ b/lib/rest-adapter.js @@ -100,7 +100,7 @@ RestAdapter.prototype.connect = function(url) { RestAdapter.prototype.invoke = function(method, ctorArgs, args, callback) { assert(this.connection, 'Cannot invoke method without a connection. See RemoteObjects#connect().'); assert(typeof method === 'string', 'method is required when calling invoke()'); - + var lastArg = arguments[arguments.length - 1]; callback = typeof lastArg === 'function' ? lastArg : undefined; @@ -305,29 +305,46 @@ RestAdapter.urlNotFoundHandler = function() { RestAdapter.errorHandler = function(options) { options = options || {}; return function restErrorHandler(err, req, res, next) { - if(typeof err === 'string') { - err = new Error(err); - err.status = err.statusCode = 500; + if (typeof options.handler === 'function') { + try { + options.handler(err, req, res, defaultHandler); + } catch(e) { + defaultHandler(e); + } + } else { + return defaultHandler(); } - res.statusCode = err.statusCode || err.status || 500; + function defaultHandler(handlerError) { + if(handlerError) { + // ensure errors that occurred during + // the handler are reported + err = handlerError; + } + if(typeof err === 'string') { + err = new Error(err); + err.status = err.statusCode = 500; + } - debug('Error in %s %s: %s', req.method, req.url, err.stack); - var data = { - name: err.name, - status: res.statusCode, - message: err.message || 'An unknown error occurred' - }; + res.statusCode = err.statusCode || err.status || 500; - for (var prop in err) { - data[prop] = err[prop]; - } + debug('Error in %s %s: %s', req.method, req.url, err.stack); + var data = { + name: err.name, + status: res.statusCode, + message: err.message || 'An unknown error occurred' + }; + + for (var prop in err) { + data[prop] = err[prop]; + } - data.stack = err.stack; - if (process.env.NODE_ENV === 'production' || options.disableStackTrace) { - delete data.stack; + data.stack = err.stack; + if (process.env.NODE_ENV === 'production' || options.disableStackTrace) { + delete data.stack; + } + res.send({ error: data }); } - res.send({ error: data }); }; }; diff --git a/test/rest-adapter.test.js b/test/rest-adapter.test.js index b10d813..6830508 100644 --- a/test/rest-adapter.test.js +++ b/test/rest-adapter.test.js @@ -73,7 +73,7 @@ describe('RestAdapter', function() { return new RestAdapter(remotes).getClasses(); } }); - + describe('path normalization', function() { it('fills `routes`', function() { remotes.exports.testClass = factory.createSharedClass(); @@ -307,8 +307,6 @@ describe('RestAdapter', function() { ]); }); - - }); }); diff --git a/test/rest.test.js b/test/rest.test.js index de3b168..9a66a53 100644 --- a/test/rest.test.js +++ b/test/rest.test.js @@ -13,7 +13,7 @@ describe('strong-remoting-rest', function(){ var objects; var remotes; var adapterName = 'rest'; - + before(function(done) { app = express(); app.disable('x-powered-by'); @@ -32,7 +32,7 @@ describe('strong-remoting-rest', function(){ objects = RemoteObjects.create({json: {limit: '1kb'}, errorHandler: {disableStackTrace: false}}); remotes = objects.exports; - + // connect to the app objects.connect('http://localhost:' + server.address().port, adapterName); }); @@ -75,6 +75,30 @@ describe('strong-remoting-rest', function(){ .expect(413, done); }); + it('should allow custom error handlers', function(done) { + var called = false; + var method = givenSharedStaticMethod( + function(cb) { + cb(new Error('foo')); + } + ); + + objects.options.errorHandler.handler = function(err, req, res, next) { + expect(err.message).to.contain('foo'); + var err = new Error('foobar'); + called = true; + next(err); + } + + request(app).get(method.url) + .expect('Content-Type', /json/) + .expect(500) + .end(expectErrorResponseContaining({message: 'foobar'}, function(err) { + expect(called).to.eql(true); + done(err); + })); + }); + it('should disable stack trace', function(done) { objects.options.errorHandler.disableStackTrace = true; var method = givenSharedStaticMethod( @@ -1022,7 +1046,7 @@ describe('strong-remoting-rest', function(){ returns: { arg: 'msg', type: 'string' } } ); - + var msg = 'hello'; objects.invoke(method.name, [msg], function(err, resMsg) { assert.equal(resMsg, msg); @@ -1048,7 +1072,7 @@ describe('strong-remoting-rest', function(){ objects.invoke(method.name, [1, 2], function(err, n) { assert.equal(n, 3); done(); - }); + }); }); it('should allow arguments in the query', function(done) { @@ -1065,7 +1089,7 @@ describe('strong-remoting-rest', function(){ http: { path: '/' } } ); - + objects.invoke(method.name, [1, 2], function(err, n) { assert.equal(n, 3); done(); @@ -1086,7 +1110,7 @@ describe('strong-remoting-rest', function(){ ] } ); - + objects.invoke(method.name, [], function(err) { assert(called); done(); @@ -1106,7 +1130,7 @@ describe('strong-remoting-rest', function(){ http: { path: '/' } } ); - + var obj = { foo: 'bar' }; @@ -1152,7 +1176,7 @@ describe('strong-remoting-rest', function(){ http: { path: '/' } } ); - + objects.invoke(method.name, [1, 2], function(err, n) { assert.equal(n, 3); done(); @@ -1182,7 +1206,7 @@ describe('strong-remoting-rest', function(){ done(); }); }); - + describe('uncaught errors', function () { it('should return 500 if an error object is thrown', function (done) { var errMsg = 'an error'; @@ -1191,7 +1215,7 @@ describe('strong-remoting-rest', function(){ throw new Error(errMsg); } ); - + objects.invoke(method.name, function(err) { assert(err instanceof Error); assert.equal(err.message, errMsg);