Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 15 additions & 23 deletions lib/data-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@
var cloneAllProperties = require('../lib/clone.js');
var httpStatus = require('http-status');

module.exports = function buildResponseData(err, options) {
module.exports = buildResponseData;

function buildResponseData(err, options) {
// Debugging mode is disabled by default. When turned on (in dev),
// all error properties (including) stack traces are sent in the response
var isDebugMode = options.debug;
const isDebugMode = options.debug;

if (Array.isArray(err) && isDebugMode) {
err = serializeArrayOfErrors(err);
if (Array.isArray(err)) {
return serializeArrayOfErrors(err, options);
}

var data = Object.create(null);
const data = Object.create(null);
fillStatusCode(data, err);

if (typeof err !== 'object') {
Expand All @@ -29,35 +31,25 @@ module.exports = function buildResponseData(err, options) {

if (isDebugMode) {
fillDebugData(data, err);
} else if (data.statusCode >= 400 && data.statusCode <= 499) {
return data;
}

if (data.statusCode >= 400 && data.statusCode <= 499) {
fillBadRequestError(data, err);
} else {
fillInternalError(data, err);
}

var safeFields = options.safeFields || [];
const safeFields = options.safeFields || [];
fillSafeFields(data, err, safeFields);

return data;
};

function serializeArrayOfErrors(errors) {
var details = [];
for (var ix in errors) {
var err = errors[ix];
if (typeof err !== 'object') {
details.push('' + err);
continue;
}

var data = {};
cloneAllProperties(data, err);
delete data.statusCode;
details.push(data);
}

function serializeArrayOfErrors(errors, options) {
const details = errors.map(e => buildResponseData(e, options));
return {
name: 'ArrayOfErrors',
statusCode: 500,
message: 'Failed with multiple errors, ' +
'see `details` for more information.',
details: details,
Expand Down
91 changes: 72 additions & 19 deletions test/handler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,43 +397,96 @@ describe('strong-error-handler', function() {

requestJson().expect(500).end(function(err, res) {
if (err) return done(err);
expect(res.body).to.eql({
error: {
statusCode: 500,
message: 'Internal Server Error',
},
});
const data = res.body.error;
expect(data).to.have.property('message').that.match(/multiple errors/);
expect(data).to.have.property('details').eql([
{statusCode: 500, message: 'Internal Server Error'},
{statusCode: 500, message: 'Internal Server Error'},
{statusCode: 500, message: 'Internal Server Error'},
]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit ugly. Before, we would return a short error response:

{
  error: {
    statusCode: 500,
    message: 'Internal Server Error',
  },
});

Now we are returning more details, but these details are not really helpful:

{
  error: {
    statusCode: 500,
    message: 'Internal Server Error',
    details: [
      {statusCode: 500, message: 'Internal Server Error'},
      {statusCode: 500, message: 'Internal Server Error'},
      {statusCode: 500, message: 'Internal Server Error'},
    ],
  }
}

It may be possible to implement some heuristics to omit these "generic" entries from the output, but don't think it's worth the effort and additional complexity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

done();
});
});

it('returns all array items when debug=true', function(done) {
var testError = new ErrorWithProps({
const testError = new ErrorWithProps({
message: 'expected test error',
statusCode: 400,
});
var anotherError = new ErrorWithProps({
const anotherError = new ErrorWithProps({
message: 'another expected error',
statusCode: 500,
});
var errors = [testError, anotherError, 'ERR STRING'];
const errors = [testError, anotherError, 'ERR STRING'];
givenErrorHandlerForError(errors, {debug: true});

requestJson().expect(500).end(function(err, res) {
if (err) return done(err);

var data = res.body.error;
const data = res.body.error;
expect(data).to.have.property('message').that.match(/multiple errors/);
var expectTestError = getExpectedErrorData(testError);
delete expectTestError.statusCode;
var expectAnotherError = getExpectedErrorData(anotherError);
delete expectAnotherError.statusCode;

var expectedDetails = [
expectTestError,
expectAnotherError,
'ERR STRING',

const expectedDetails = [
getExpectedErrorData(testError),
getExpectedErrorData(anotherError),
{message: 'ERR STRING', statusCode: 500},
];
expect(data).to.have.property('details').to.eql(expectedDetails);
done();
});
});

it('includes safeFields of array items when debug=false', (done) => {
const internalError = new ErrorWithProps({
message: 'a test error message',
code: 'MACHINE_READABLE_CODE',
details: 'some details',
extra: 'sensitive data',
});
const validationError = new ErrorWithProps({
name: 'ValidationError',
message: 'The model instance is not valid.',
statusCode: 422,
code: 'VALIDATION_ERROR',
details: 'some details',
extra: 'sensitive data',
});

const errors = [internalError, validationError, 'ERR STRING'];
givenErrorHandlerForError(errors, {
debug: false,
safeFields: ['code'],
});

requestJson().end(function(err, res) {
if (err) return done(err);
const data = res.body.error;

const expectedInternalError = {
statusCode: 500,
message: 'Internal Server Error',
code: 'MACHINE_READABLE_CODE',
// notice the property "extra" is not included
};
const expectedValidationError = {
statusCode: 422,
message: 'The model instance is not valid.',
name: 'ValidationError',
code: 'VALIDATION_ERROR',
details: 'some details',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please notice that for 4xx errors, we always include additional fields beyond safeFields. If we are going to list individual errors in production (debug=false) mode, then I think we should apply the same rules.

// notice the property "extra" is not included
};
const expectedErrorFromString = {
message: 'Internal Server Error',
statusCode: 500,
};
const expectedDetails = [
expectedInternalError,
expectedValidationError,
expectedErrorFromString,
];

expect(data).to.have.property('message').that.match(/multiple errors/);
expect(data).to.have.property('details').to.eql(expectedDetails);
done();
});
Expand Down