0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-03 23:00:14 -05:00

Disambiguate between error code & status code

refs #6526

- Change our errors to use `statusCode` for the status code (like res.statusCode)
- Use statusCode for anything that's supposed to be the statusCode, rather than an error idenfier/code
- Update all the tests that check the key
- Route tests don't need fixing as the status codes are still returned correctly
This commit is contained in:
Hannah Wolfe 2016-02-17 14:51:57 +00:00
parent 39b326aa6d
commit 02199c6b02
20 changed files with 79 additions and 49 deletions

View file

@ -4,7 +4,7 @@
function BadRequestError(message) { function BadRequestError(message) {
this.message = message; this.message = message;
this.stack = new Error().stack; this.stack = new Error().stack;
this.code = 400; this.statusCode = 400;
this.errorType = this.name; this.errorType = this.name;
} }

View file

@ -4,7 +4,7 @@
function DataImportError(message, offendingProperty, value) { function DataImportError(message, offendingProperty, value) {
this.message = message; this.message = message;
this.stack = new Error().stack; this.stack = new Error().stack;
this.code = 500; this.statusCode = 500;
this.errorType = this.name; this.errorType = this.name;
this.property = offendingProperty || undefined; this.property = offendingProperty || undefined;
this.value = value || undefined; this.value = value || undefined;

View file

@ -4,7 +4,7 @@
function EmailError(message) { function EmailError(message) {
this.message = message; this.message = message;
this.stack = new Error().stack; this.stack = new Error().stack;
this.code = 500; this.statusCode = 500;
this.errorType = this.name; this.errorType = this.name;
} }

View file

@ -34,6 +34,29 @@ function getConfigModule() {
return config; return config;
} }
function isValidErrorStatus(status) {
return _.isNumber(status) && status >= 400 && status < 600;
}
function getStatusCode(error) {
if (error.statusCode) {
return error.statusCode;
}
if (error.status && isValidErrorStatus(error.status)) {
error.statusCode = error.status;
return error.statusCode;
}
if (error.code && isValidErrorStatus(error.code)) {
error.statusCode = error.code;
return error.statusCode;
}
error.statusCode = 500;
return error.statusCode;
}
/** /**
* Basic error handling helpers * Basic error handling helpers
*/ */
@ -197,7 +220,7 @@ errors = {
var errorContent = {}; var errorContent = {};
// TODO: add logic to set the correct status code // TODO: add logic to set the correct status code
statusCode = errorItem.code || 500; statusCode = getStatusCode(errorItem);
errorContent.message = _.isString(errorItem) ? errorItem : errorContent.message = _.isString(errorItem) ? errorItem :
(_.isObject(errorItem) ? errorItem.message : i18n.t('errors.errors.unknownApiError')); (_.isObject(errorItem) ? errorItem.message : i18n.t('errors.errors.unknownApiError'));
@ -227,7 +250,7 @@ errors = {
if (error.code && (error.errno || error.detail)) { if (error.code && (error.errno || error.detail)) {
error.db_error_code = error.code; error.db_error_code = error.code;
error.errorType = 'DatabaseError'; error.errorType = 'DatabaseError';
error.code = 500; error.statusCode = 500;
return this.rejectError(error); return this.rejectError(error);
} }
@ -243,7 +266,7 @@ errors = {
res.status(httpErrors.statusCode).json({errors: httpErrors.errors}); res.status(httpErrors.statusCode).json({errors: httpErrors.errors});
}, },
renderErrorPage: function (code, err, req, res, next) { renderErrorPage: function (statusCode, err, req, res, next) {
/*jshint unused:false*/ /*jshint unused:false*/
var self = this, var self = this,
defaultErrorTemplatePath = path.resolve(getConfigModule().paths.adminViews, 'user-error.hbs'); defaultErrorTemplatePath = path.resolve(getConfigModule().paths.adminViews, 'user-error.hbs');
@ -281,17 +304,22 @@ errors = {
function renderErrorInt(errorView) { function renderErrorInt(errorView) {
var stack = null; var stack = null;
if (code !== 404 && process.env.NODE_ENV !== 'production' && err.stack) { if (statusCode !== 404 && process.env.NODE_ENV !== 'production' && err.stack) {
stack = parseStack(err.stack); stack = parseStack(err.stack);
} }
res.status(code).render((errorView || 'error'), { res.status(statusCode).render((errorView || 'error'), {
message: err.message || err, message: err.message || err,
code: code, // We have to use code here, as it's the variable passed to the template
// And error templates can be customised... therefore this constitutes API
// In future I recommend we make this be used for a combo-version of statusCode & errorCode
code: statusCode,
// Adding this as being distinctly, the status code, as opposed to any other code see #6526
statusCode: statusCode,
stack: stack stack: stack
}, function (templateErr, html) { }, function (templateErr, html) {
if (!templateErr) { if (!templateErr) {
return res.status(code).send(html); return res.status(statusCode).send(html);
} }
// There was an error trying to render the error page, output the error // There was an error trying to render the error page, output the error
self.logError(templateErr, i18n.t('errors.errors.errorWhilstRenderingError'), i18n.t('errors.errors.errorTemplateHasError')); self.logError(templateErr, i18n.t('errors.errors.errorWhilstRenderingError'), i18n.t('errors.errors.errorTemplateHasError'));
@ -303,12 +331,12 @@ errors = {
'<p>' + i18n.t('errors.errors.encounteredError') + '</p>' + '<p>' + i18n.t('errors.errors.encounteredError') + '</p>' +
'<pre>' + hbs.handlebars.Utils.escapeExpression(templateErr.message || templateErr) + '</pre>' + '<pre>' + hbs.handlebars.Utils.escapeExpression(templateErr.message || templateErr) + '</pre>' +
'<br ><p>' + i18n.t('errors.errors.whilstTryingToRender') + '</p>' + '<br ><p>' + i18n.t('errors.errors.whilstTryingToRender') + '</p>' +
code + ' ' + '<pre>' + hbs.handlebars.Utils.escapeExpression(err.message || err) + '</pre>' statusCode + ' ' + '<pre>' + hbs.handlebars.Utils.escapeExpression(err.message || err) + '</pre>'
); );
}); });
} }
if (code >= 500) { if (statusCode >= 500) {
this.logError(err, i18n.t('errors.errors.renderingErrorPage'), i18n.t('errors.errors.caughtProcessingError')); this.logError(err, i18n.t('errors.errors.renderingErrorPage'), i18n.t('errors.errors.caughtProcessingError'));
} }
@ -334,10 +362,13 @@ errors = {
}, },
error500: function (err, req, res, next) { error500: function (err, req, res, next) {
var statusCode = getStatusCode(err),
returnErrors = [];
// 500 errors should never be cached // 500 errors should never be cached
res.set({'Cache-Control': 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'}); res.set({'Cache-Control': 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'});
if (err.status === 404 || err.code === 404) { if (statusCode === 404) {
return this.error404(req, res, next); return this.error404(req, res, next);
} }
@ -345,11 +376,8 @@ errors = {
if (!err || !(err instanceof Error)) { if (!err || !(err instanceof Error)) {
next(); next();
} }
errors.renderErrorPage(err.status || err.code || 500, err, req, res, next); errors.renderErrorPage(statusCode, err, req, res, next);
} else { } else {
var statusCode = 500,
returnErrors = [];
if (!_.isArray(err)) { if (!_.isArray(err)) {
err = [].concat(err); err = [].concat(err);
} }
@ -357,8 +385,6 @@ errors = {
_.each(err, function (errorItem) { _.each(err, function (errorItem) {
var errorContent = {}; var errorContent = {};
statusCode = errorItem.code || 500;
errorContent.message = _.isString(errorItem) ? errorItem : errorContent.message = _.isString(errorItem) ? errorItem :
(_.isObject(errorItem) ? errorItem.message : i18n.t('errors.errors.unknownError')); (_.isObject(errorItem) ? errorItem.message : i18n.t('errors.errors.unknownError'));
errorContent.errorType = errorItem.errorType || 'InternalServerError'; errorContent.errorType = errorItem.errorType || 'InternalServerError';

View file

@ -4,7 +4,7 @@
function InternalServerError(message) { function InternalServerError(message) {
this.message = message; this.message = message;
this.stack = new Error().stack; this.stack = new Error().stack;
this.code = 500; this.statusCode = 500;
this.errorType = this.name; this.errorType = this.name;
} }

View file

@ -4,7 +4,7 @@
function MethodNotAllowedError(message) { function MethodNotAllowedError(message) {
this.message = message; this.message = message;
this.stack = new Error().stack; this.stack = new Error().stack;
this.code = 405; this.statusCode = 405;
this.errorType = this.name; this.errorType = this.name;
} }

View file

@ -4,7 +4,7 @@
function NoPermissionError(message) { function NoPermissionError(message) {
this.message = message; this.message = message;
this.stack = new Error().stack; this.stack = new Error().stack;
this.code = 403; this.statusCode = 403;
this.errorType = this.name; this.errorType = this.name;
} }

View file

@ -4,7 +4,7 @@
function NotFoundError(message) { function NotFoundError(message) {
this.message = message; this.message = message;
this.stack = new Error().stack; this.stack = new Error().stack;
this.code = 404; this.statusCode = 404;
this.errorType = this.name; this.errorType = this.name;
} }

View file

@ -4,7 +4,7 @@
function RequestEntityTooLargeError(message) { function RequestEntityTooLargeError(message) {
this.message = message; this.message = message;
this.stack = new Error().stack; this.stack = new Error().stack;
this.code = 413; this.statusCode = 413;
this.errorType = this.name; this.errorType = this.name;
} }

View file

@ -4,7 +4,7 @@
function TooManyRequestsError(message) { function TooManyRequestsError(message) {
this.message = message; this.message = message;
this.stack = new Error().stack; this.stack = new Error().stack;
this.code = 429; this.statusCode = 429;
this.errorType = this.name; this.errorType = this.name;
} }

View file

@ -4,7 +4,7 @@
function UnauthorizedError(message) { function UnauthorizedError(message) {
this.message = message; this.message = message;
this.stack = new Error().stack; this.stack = new Error().stack;
this.code = 401; this.statusCode = 401;
this.errorType = this.name; this.errorType = this.name;
} }

View file

@ -4,7 +4,7 @@
function UnsupportedMediaTypeError(message) { function UnsupportedMediaTypeError(message) {
this.message = message; this.message = message;
this.stack = new Error().stack; this.stack = new Error().stack;
this.code = 415; this.statusCode = 415;
this.errorType = this.name; this.errorType = this.name;
} }

View file

@ -4,7 +4,7 @@
function ValidationError(message, offendingProperty) { function ValidationError(message, offendingProperty) {
this.message = message; this.message = message;
this.stack = new Error().stack; this.stack = new Error().stack;
this.code = 422; this.statusCode = 422;
if (offendingProperty) { if (offendingProperty) {
this.property = offendingProperty; this.property = offendingProperty;
} }

View file

@ -73,7 +73,7 @@ function getAjaxHelper(clientId, clientSecret) {
function ghost_head(options) { function ghost_head(options) {
// if error page do nothing // if error page do nothing
if (this.code >= 400) { if (this.statusCode >= 400) {
return; return;
} }
var metaData = getMetaData(this, options.data.root), var metaData = getMetaData(this, options.data.root),

View file

@ -97,7 +97,7 @@ describe('Authentication API', function () {
should.exist(err); should.exist(err);
err.name.should.equal('NoPermissionError'); err.name.should.equal('NoPermissionError');
err.code.should.equal(403); err.statusCode.should.equal(403);
done(); done();
}); });
@ -110,7 +110,7 @@ describe('Authentication API', function () {
should.exist(err); should.exist(err);
err.name.should.equal('NoPermissionError'); err.name.should.equal('NoPermissionError');
err.code.should.equal(403); err.statusCode.should.equal(403);
done(); done();
}); });
@ -123,7 +123,7 @@ describe('Authentication API', function () {
should.exist(err); should.exist(err);
err.name.should.equal('NoPermissionError'); err.name.should.equal('NoPermissionError');
err.code.should.equal(403); err.statusCode.should.equal(403);
done(); done();
}); });
@ -156,7 +156,7 @@ describe('Authentication API', function () {
should.exist(err); should.exist(err);
err.name.should.equal('NoPermissionError'); err.name.should.equal('NoPermissionError');
err.code.should.equal(403); err.statusCode.should.equal(403);
done(); done();
}); });
@ -169,7 +169,7 @@ describe('Authentication API', function () {
should.exist(err); should.exist(err);
err.name.should.equal('UnauthorizedError'); err.name.should.equal('UnauthorizedError');
err.code.should.equal(401); err.statusCode.should.equal(401);
err.message.should.equal('Invalid token structure'); err.message.should.equal('Invalid token structure');
done(); done();
}); });
@ -191,7 +191,7 @@ describe('Authentication API', function () {
should.exist(err); should.exist(err);
err.name.should.equal('UnauthorizedError'); err.name.should.equal('UnauthorizedError');
err.code.should.equal(401); err.statusCode.should.equal(401);
err.message.should.equal('Invalid token structure'); err.message.should.equal('Invalid token structure');
done(); done();
}); });
@ -226,7 +226,7 @@ describe('Authentication API', function () {
should.exist(err); should.exist(err);
err.name.should.equal('NoPermissionError'); err.name.should.equal('NoPermissionError');
err.code.should.equal(403); err.statusCode.should.equal(403);
done(); done();
}); });
@ -259,7 +259,7 @@ describe('Authentication API', function () {
should.exist(err); should.exist(err);
err.name.should.equal('NoPermissionError'); err.name.should.equal('NoPermissionError');
err.code.should.equal(403); err.statusCode.should.equal(403);
done(); done();
}); });

View file

@ -263,7 +263,7 @@ describe('Post API', function () {
}).catch(function (err) { }).catch(function (err) {
should.exist(err); should.exist(err);
err.message.should.eql('Validation (isSlug) failed for tag'); err.message.should.eql('Validation (isSlug) failed for tag');
err.code.should.eql(422); err.statusCode.should.eql(422);
done(); done();
}); });
}); });
@ -274,7 +274,7 @@ describe('Post API', function () {
}).catch(function (err) { }).catch(function (err) {
should.exist(err); should.exist(err);
err.message.should.eql('Validation (isSlug) failed for author'); err.message.should.eql('Validation (isSlug) failed for author');
err.code.should.eql(422); err.statusCode.should.eql(422);
done(); done();
}); });
}); });

View file

@ -42,7 +42,7 @@ describe('Upload API', function () {
UploadAPI.add({uploadimage: uploadimage}).then(function () { UploadAPI.add({uploadimage: uploadimage}).then(function () {
done(new Error('Upload suceeded with invalid file.')); done(new Error('Upload suceeded with invalid file.'));
}, function (result) { }, function (result) {
result.code.should.equal(415); result.statusCode.should.equal(415);
result.errorType.should.equal('UnsupportedMediaTypeError'); result.errorType.should.equal('UnsupportedMediaTypeError');
done(); done();
}); });
@ -59,7 +59,7 @@ describe('Upload API', function () {
UploadAPI.add({uploadimage: uploadimage}).then(function () { UploadAPI.add({uploadimage: uploadimage}).then(function () {
done(new Error('Upload suceeded with invalid file.')); done(new Error('Upload suceeded with invalid file.'));
}, function (result) { }, function (result) {
result.code.should.equal(415); result.statusCode.should.equal(415);
result.errorType.should.equal('UnsupportedMediaTypeError'); result.errorType.should.equal('UnsupportedMediaTypeError');
done(); done();
}); });
@ -88,7 +88,7 @@ describe('Upload API', function () {
UploadAPI.add({uploadimage: uploadimage}).then(function () { UploadAPI.add({uploadimage: uploadimage}).then(function () {
done(new Error('Upload suceeded with invalid file.')); done(new Error('Upload suceeded with invalid file.'));
}, function (result) { }, function (result) {
result.code.should.equal(415); result.statusCode.should.equal(415);
result.errorType.should.equal('UnsupportedMediaTypeError'); result.errorType.should.equal('UnsupportedMediaTypeError');
done(); done();
}); });

View file

@ -97,7 +97,7 @@ describe('Channels', function () {
function testChannel404(props, done) { function testChannel404(props, done) {
testChannelError(props, function (error) { testChannelError(props, function (error) {
error.errorType.should.eql('NotFoundError'); error.errorType.should.eql('NotFoundError');
error.code.should.eql(404); error.statusCode.should.eql(404);
}, done); }, done);
} }

View file

@ -358,6 +358,7 @@ describe('Error handling', function () {
// Test that the message is correct // Test that the message is correct
options.message.should.equal('Page not found'); options.message.should.equal('Page not found');
// Template variable
options.code.should.equal(404); options.code.should.equal(404);
this.statusCode.should.equal(404); this.statusCode.should.equal(404);
@ -389,6 +390,7 @@ describe('Error handling', function () {
// Test that the message is correct // Test that the message is correct
options.message.should.equal('Page not found'); options.message.should.equal('Page not found');
// Template variable
options.code.should.equal(404); options.code.should.equal(404);
this.statusCode.should.equal(404); this.statusCode.should.equal(404);
@ -421,6 +423,7 @@ describe('Error handling', function () {
// Test that the message is correct // Test that the message is correct
options.message.should.equal('I am a big bad error'); options.message.should.equal('I am a big bad error');
// Template variable
options.code.should.equal(500); options.code.should.equal(500);
this.statusCode.should.equal(500); this.statusCode.should.equal(500);
@ -452,6 +455,7 @@ describe('Error handling', function () {
// Test that the message is correct // Test that the message is correct
options.message.should.equal('I am a big bad error'); options.message.should.equal('I am a big bad error');
// Template variable
options.code.should.equal(500); options.code.should.equal(500);
this.statusCode.should.equal(500); this.statusCode.should.equal(500);
@ -469,18 +473,18 @@ describe('Error handling', function () {
return res; return res;
}); });
err.code = 500; err.statusCode = 500;
errors.error500(err, req, res, null); errors.error500(err, req, res, null);
}); });
it('Renders custom error template if one exists', function (done) { it('Renders custom error template if one exists', function (done) {
var code = 404, var statusCode = 404,
error = {message: 'Custom view test'}, error = {message: 'Custom view test'},
req = { req = {
session: null session: null
}, },
res = { res = {
status: function (code) { status: function (statusCode) {
/*jshint unused:false*/ /*jshint unused:false*/
return this; return this;
}, },
@ -493,7 +497,7 @@ describe('Error handling', function () {
}, },
next = null; next = null;
errors.updateActiveTheme('theme-with-error'); errors.updateActiveTheme('theme-with-error');
errors.renderErrorPage(code, error, req, res, next); errors.renderErrorPage(statusCode, error, req, res, next);
}); });
}); });
}); });

View file

@ -417,7 +417,7 @@ describe('RSS', function () {
rss(req, res, function (err) { rss(req, res, function (err) {
should.exist(err); should.exist(err);
err.code.should.eql(404); err.statusCode.should.eql(404);
res.redirect.called.should.be.false(); res.redirect.called.should.be.false();
res.render.called.should.be.false(); res.render.called.should.be.false();
done(); done();
@ -434,7 +434,7 @@ describe('RSS', function () {
rss(req, res, function (err) { rss(req, res, function (err) {
should.exist(err); should.exist(err);
err.code.should.eql(404); err.statusCode.should.eql(404);
res.redirect.called.should.be.false(); res.redirect.called.should.be.false();
res.render.called.should.be.false(); res.render.called.should.be.false();
done(); done();