Skip to content

Commit

Permalink
Adds more detailed ("friendly") RESTAdapter Error Messages
Browse files Browse the repository at this point in the history
  • Loading branch information
nikz committed Dec 4, 2015
1 parent dc0be67 commit ef406a2
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 8 deletions.
48 changes: 43 additions & 5 deletions addon/adapters/rest-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -743,18 +743,20 @@ export default Adapter.extend(BuildURLMixin, {
@param {Number} status
@param {Object} headers
@param {Object} payload
@param {Object} requestData - the original request information
@return {Object | DS.AdapterError} response
*/
handleResponse(status, headers, payload) {
handleResponse(status, headers, payload, requestData) {
if (this.isSuccess(status, headers, payload)) {
return payload;
} else if (this.isInvalid(status, headers, payload)) {
return new InvalidError(payload.errors);
}

let errors = this.normalizeErrorResponse(status, headers, payload);
let errors = this.normalizeErrorResponse(status, headers, payload);
let detailedMessage = this.generatedDetailedMessage(status, headers, payload, requestData);

return new AdapterError(errors);
return new AdapterError(errors, detailedMessage);
},

/**
Expand Down Expand Up @@ -812,6 +814,11 @@ export default Adapter.extend(BuildURLMixin, {
ajax(url, type, options) {
var adapter = this;

var requestData = {
url: url,
method: type
};

return new Ember.RSVP.Promise(function(resolve, reject) {
var hash = adapter.ajaxOptions(url, type, options);

Expand All @@ -820,7 +827,8 @@ export default Adapter.extend(BuildURLMixin, {
let response = adapter.handleResponse(
jqXHR.status,
parseResponseHeaders(jqXHR.getAllResponseHeaders()),
payload
payload,
requestData
);

if (response instanceof AdapterError) {
Expand All @@ -844,7 +852,8 @@ export default Adapter.extend(BuildURLMixin, {
error = adapter.handleResponse(
jqXHR.status,
parseResponseHeaders(jqXHR.getAllResponseHeaders()),
adapter.parseErrorResponse(jqXHR.responseText) || errorThrown
adapter.parseErrorResponse(jqXHR.responseText) || errorThrown,
requestData
);
}
}
Expand Down Expand Up @@ -922,6 +931,35 @@ export default Adapter.extend(BuildURLMixin, {
}
];
}
},

/**
Generates a detailed ("friendly") error message, with plenty
of information for debugging (good luck!)
@method generatedDetailedMessage
@private
@param {Number} status
@param {Object} headers
@param {Object} payload
@return {Object} request information
*/
generatedDetailedMessage: function(status, headers, payload, requestData) {
var shortenedPayload;
var payloadContentType = headers["Content-Type"] || "Empty Content-Type";

if (payloadContentType === "text/html" && payload.length > 250) {
shortenedPayload = "[Omitted Lengthy HTML]";
} else {
shortenedPayload = payload;
}

var requestDescription = requestData.method + ' ' + requestData.url;
var payloadDescription = 'Payload (' + payloadContentType + ')';

return ['Ember Data Request ' + requestDescription + ' returned a ' + status,
payloadDescription,
shortenedPayload].join('\n');
}
});

Expand Down
66 changes: 63 additions & 3 deletions tests/integration/adapter/rest-adapter-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2194,22 +2194,28 @@ test("calls adapter.handleResponse with the jqXHR and json", function(assert) {
}
});

test('calls handleResponse with jqXHR, jqXHR.responseText', function(assert) {
assert.expect(3);
test('calls handleResponse with jqXHR, jqXHR.responseText, and requestData', function(assert) {
assert.expect(4);
var originalAjax = Ember.$.ajax;
var jqXHR = {
status: 400,
responseText: 'Nope lol',
getAllResponseHeaders() { return ''; }
};

var expectedRequestData = {
method: "GET",
url: "/posts/1"
};

Ember.$.ajax = function(hash) {
hash.error(jqXHR, jqXHR.responseText, 'Bad Request');
};

adapter.handleResponse = function(status, headers, json) {
adapter.handleResponse = function(status, headers, json, requestData) {
assert.deepEqual(status, 400);
assert.deepEqual(json, jqXHR.responseText);
assert.deepEqual(requestData, expectedRequestData);
return new DS.AdapterError('nope!');
};

Expand Down Expand Up @@ -2312,6 +2318,60 @@ test('on error wraps the error string in an DS.AdapterError object', function(as
}
});

test('error handling includes a detailed message from the server', (assert) => {
assert.expect(2);

let originalAjax = Ember.$.ajax;
let jqXHR = {
status: 500,
responseText: 'An error message, perhaps generated from a backend server!',
getAllResponseHeaders: function() { return 'Content-Type: text/plain'; }
};

Ember.$.ajax = function(hash) {
hash.error(jqXHR, 'error');
};

try {
run(function() {
store.find('post', '1').catch(function(err) {
assert.equal(err.message, "Ember Data Request GET /posts/1 returned a 500\nPayload (text/plain)\nAn error message, perhaps generated from a backend server!");
assert.ok(err, 'promise rejected');
});
});
} finally {
Ember.$.ajax = originalAjax;
}

});

test('error handling with a very long HTML-formatted payload truncates the friendly message', (assert) => {
assert.expect(2);

let originalAjax = Ember.$.ajax;
let jqXHR = {
status: 500,
responseText: new Array(100).join("<blink />"),
getAllResponseHeaders: function() { return 'Content-Type: text/html'; }
};

Ember.$.ajax = function(hash) {
hash.error(jqXHR, 'error');
};

try {
run(function() {
store.find('post', '1').catch(function(err) {
assert.equal(err.message, "Ember Data Request GET /posts/1 returned a 500\nPayload (text/html)\n[Omitted Lengthy HTML]");
assert.ok(err, 'promise rejected');
});
});
} finally {
Ember.$.ajax = originalAjax;
}

});

test('findAll resolves with a collection of DS.Models, not DS.InternalModels', (assert) => {
assert.expect(4);

Expand Down
51 changes: 51 additions & 0 deletions tests/unit/adapters/rest-adapter/detailed-message-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import setupStore from 'dummy/tests/helpers/store';

import {module, test} from 'qunit';

import DS from 'ember-data';

let adapter, env;

module("unit/adapters/rest_adapter/detailed_message_test - DS.RESTAdapter#generatedDetailedMessage", {

beforeEach() {
env = setupStore({ adapter: DS.RESTAdapter });
adapter = env.adapter;
}

});

test("generating a wonderfully friendly error message should work", (assert) => {
assert.expect(1);

let friendlyMessage = adapter.generatedDetailedMessage(
418,
{ "Content-Type": "text/plain" },
"I'm a little teapot, short and stout",
{
url: "/teapots/testing",
method: "GET"
}
);

assert.equal(friendlyMessage, ["Ember Data Request GET /teapots/testing returned a 418",
"Payload (text/plain)",
"I'm a little teapot, short and stout"].join("\n"));
});

test("generating a friendly error message with a missing content-type header should work", (assert) => {

let friendlyMessage = adapter.generatedDetailedMessage(
418,
{},
"I'm a little teapot, short and stout",
{
url: "/teapots/testing",
method: "GET"
}
);

assert.equal(friendlyMessage, ["Ember Data Request GET /teapots/testing returned a 418",
"Payload (Empty Content-Type)",
"I'm a little teapot, short and stout"].join("\n"));
});

0 comments on commit ef406a2

Please sign in to comment.