Skip to content

Commit d4235f7

Browse files
committed
Merge pull request #69 from pixelhandler/add-code-to-custom-errors
Add code property to custom errors
2 parents 7af61bb + d09efa0 commit d4235f7

File tree

4 files changed

+99
-45
lines changed

4 files changed

+99
-45
lines changed

addon/mixins/fetch.js

Lines changed: 74 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,11 @@ export default Ember.Mixin.create({
7070
@param {Function} reject - Promise reject handler
7171
*/
7272
fetchServerErrorHandler(response, reject) {
73-
let msg = 'The Service responded with a '+ response.status +' error.';
74-
reject(new ServerError(msg, response));
73+
response.text().then(function(respText) {
74+
let msg = parseFetchErrorMessage(response);
75+
let json = parseFetchErrorText(respText, response);
76+
reject(new ServerError(msg, json));
77+
});
7578
},
7679

7780
/**
@@ -82,14 +85,9 @@ export default Ember.Mixin.create({
8285
@param {Function} reject - Promise reject handler
8386
*/
8487
fetchClientErrorHandler(response, reject) {
85-
response.text().then(function(resp) {
86-
let json, msg = 'The API responded with a '+ response.status +' error.';
87-
try {
88-
json = JSON.parse(resp);
89-
} catch (e) {
90-
Ember.Logger.error(e);
91-
json = { "errors": [ { "status": response.status } ] };
92-
}
88+
response.text().then(function(respText) {
89+
let msg = parseFetchErrorMessage(response);
90+
let json = parseFetchErrorText(respText, response);
9391
reject(new ClientError(msg, json));
9492
});
9593
},
@@ -98,12 +96,22 @@ export default Ember.Mixin.create({
9896
Fetch generic error handler
9997
10098
@method fetchErrorHandler
101-
@param {Response} response - Fetch response
99+
@param {Error|Response} error - Fetch error or response object
102100
@param {Function} reject - Promise reject handler
103101
*/
104102
fetchErrorHandler(error, reject) {
105-
let msg = (error && error.message) ? error.message : 'Unable to Fetch resource(s)';
106-
reject(new FetchError(msg, error));
103+
let msg = 'Unable to Fetch resource(s)';
104+
if (error instanceof Error) {
105+
msg = (error && error.message) ? error.message : msg;
106+
reject(new FetchError(msg, error));
107+
} else if (typeof error.text === 'function') {
108+
error.text().then(function(respText) {
109+
msg = parseFetchErrorMessage(error);
110+
reject(new FetchError(msg, parseFetchErrorText(respText, error)));
111+
});
112+
} else {
113+
reject(new FetchError(msg, error));
114+
}
107115
},
108116

109117
/**
@@ -219,7 +227,8 @@ export default Ember.Mixin.create({
219227
*/
220228
ajaxServerErrorHandler(jqXHR, textStatus, errorThrown, reject) {
221229
let msg = 'The Service responded with ' + textStatus + ' ' + jqXHR.status;
222-
reject(new ServerError(msg, jqXHR.responseJSON || jqXHR.responseText || errorThrown));
230+
let json = parseXhrErrorResponse(jqXHR, errorThrown);
231+
reject(new ServerError(msg, json));
223232
},
224233

225234
/**
@@ -233,18 +242,7 @@ export default Ember.Mixin.create({
233242
*/
234243
ajaxClientErrorHandler(jqXHR, textStatus, errorThrown, reject) {
235244
let msg = 'The API responded with a '+ jqXHR.status +' error.';
236-
let json = jqXHR.responseJSON;
237-
if (!json) {
238-
json = {
239-
"errors": [
240-
{
241-
"status": jqXHR.status,
242-
"detail": jqXHR.responseText,
243-
"message": errorThrown
244-
}
245-
]
246-
};
247-
}
245+
let json = parseXhrErrorResponse(jqXHR, errorThrown);
248246
reject(new ClientError(msg, json));
249247
},
250248

@@ -259,12 +257,8 @@ export default Ember.Mixin.create({
259257
*/
260258
ajaxErrorHandler(jqXHR, textStatus, errorThrown, reject) {
261259
let msg = (errorThrown) ? errorThrown : 'Unable to Fetch resource(s)';
262-
reject(new FetchError(msg, {
263-
code: jqXHR.status,
264-
message: errorThrown,
265-
'status': textStatus,
266-
response: jqXHR.responseText
267-
}));
260+
let json = parseXhrErrorResponse(jqXHR, errorThrown);
261+
reject(new FetchError(msg, json));
268262
},
269263

270264
/**
@@ -326,3 +320,51 @@ export default Ember.Mixin.create({
326320
}
327321

328322
});
323+
324+
function parseFetchErrorMessage(response) {
325+
return [
326+
'The API responded with a ',
327+
response.status,
328+
(response.statusText) ? ' (' + response.statusText + ') ' : '',
329+
' error.'
330+
].join('');
331+
}
332+
333+
function parseFetchErrorText(text, response) {
334+
let json;
335+
try {
336+
json = JSON.parse(text);
337+
} catch (err) {
338+
Ember.Logger.warn(err);
339+
json = {
340+
"errors": [{
341+
"status": response.status,
342+
"detail": text
343+
}]
344+
};
345+
}
346+
json = json || {};
347+
json.status = response.status;
348+
return json;
349+
}
350+
351+
function parseXhrErrorResponse(jqXHR, errorThrown) {
352+
let json = jqXHR.responseJSON;
353+
if (!json) {
354+
try {
355+
if (jqXHR.responseText) {
356+
json = JSON.parse(jqXHR.responseText);
357+
}
358+
} catch(err) {
359+
Ember.Logger.warn(err);
360+
}
361+
}
362+
json = json || {};
363+
json.status = jqXHR.status;
364+
json.errors = json.errors || [{
365+
status: jqXHR.status,
366+
detail: jqXHR.responseText,
367+
message: errorThrown
368+
}];
369+
return json;
370+
}

addon/utils/errors.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export function ServerError(message = 'Server Error', response = null) {
1818
this.name = 'ServerError';
1919
this.response = response;
2020
this.errors = (response) ? response.errors || null : null;
21+
this.code = (response) ? response.status || null : null;
2122
}
2223
ServerError.prototype = errorProtoFactory(ServerError);
2324

@@ -36,25 +37,27 @@ export function ClientError(message = 'Client Error', response = null) {
3637
this.name = 'ClientError';
3738
this.response = response;
3839
this.errors = (response) ? response.errors || null : null;
40+
this.code = (response) ? response.status || null : null;
3941
}
4042
ClientError.prototype = errorProtoFactory(ClientError);
4143

4244
/**
4345
@class FetchError
4446
@constructor
4547
@param {String} message
46-
@param {Error} error
47-
@param {Object} response
48+
@param {Error|Object} error or response object
4849
@return {Error}
4950
*/
50-
export function FetchError(message = 'Fetch Error', error = null, response = null) {
51+
export function FetchError(message = 'Fetch Error', response = null) {
5152
let _error = Error.prototype.constructor.call(this, message);
5253
_error.name = this.name = 'FetchError';
53-
this.stack = (error && error.stack) ? error.stack : _error.stack;
54-
this.message = (error && error.message) ? error.message : _error.message;
54+
this.stack = (response && response.stack) ? response.stack : _error.stack;
55+
this.message = (response && response.message) ? response.message : _error.message;
5556
this.name = 'FetchError';
56-
this.response = response;
57-
this.error = error || _error;
57+
this.error = (response instanceof Error) ? response : _error;
58+
this.response = (response instanceof Error) ? null : response;
59+
this.errors = (this.response) ? response.errors : null;
60+
this.code = (response) ? response.status || null : null;
5861
}
5962
FetchError.prototype = errorProtoFactory(FetchError);
6063

tests/unit/adapters/application-test.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -395,22 +395,28 @@ test('serializer#deserializeIncluded called after successful fetch', function(as
395395

396396

397397
test('#fetch handles 5xx (ServerError) response status', function(assert) {
398-
assert.expect(2);
398+
assert.expect(3);
399399
const done = assert.async();
400400
const adapter = this.subject({type: 'posts', url: '/posts'});
401401
sandbox.stub(window, 'fetch', function () {
402-
return Ember.RSVP.Promise.resolve({ "status": 500 });
402+
return Ember.RSVP.Promise.resolve({
403+
"status": 500,
404+
"text": function() {
405+
return Ember.RSVP.Promise.resolve('');
406+
}
407+
});
403408
});
404409
let promise = adapter.fetch('/posts', { method: 'POST', body: 'json string here' });
405410
assert.ok(typeof promise.then === 'function', 'returns a thenable');
406411
promise.catch(function(error) {
407412
assert.equal(error.name, 'ServerError', '5xx response throws a custom error');
413+
assert.equal(error.code, 500, 'error code 500');
408414
done();
409415
});
410416
});
411417

412418
test('#fetch handles 4xx (Client Error) response status', function(assert) {
413-
assert.expect(4);
419+
assert.expect(5);
414420
const done = assert.async();
415421
const adapter = this.subject({type: 'posts', url: '/posts'});
416422
sandbox.stub(adapter, 'fetchUrl', function () {});
@@ -428,6 +434,7 @@ test('#fetch handles 4xx (Client Error) response status', function(assert) {
428434
assert.ok(error.name, 'Client Error', '4xx response throws a custom error');
429435
assert.ok(Array.isArray(error.errors), '4xx error includes errors');
430436
assert.equal(error.errors[0].status, 404, '404 error status is in errors list');
437+
assert.equal(error.code, 404, 'error code 404');
431438
done();
432439
});
433440
});

tests/unit/mixins/fetch-test.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,20 @@ test('#useFetch default value is true', function(assert) {
4141
});
4242

4343
test('#_ajax handles 5xx (Server Error)', function(assert) {
44-
assert.expect(2);
44+
assert.expect(3);
4545
const done = assert.async();
4646
this.server.respondWith('GET', '/posts', [500, {}, ""]);
4747
let promise = this.subject._ajax('/posts', { method: 'GET' }, false);
4848
assert.ok(typeof promise.then === 'function', 'returns a thenable');
4949
promise.catch(function(error) {
5050
assert.equal(error.name, 'ServerError', '5xx response throws a custom error');
51+
assert.equal(error.code, 500, 'error code 500');
5152
done();
5253
});
5354
});
5455

5556
test('#_ajax handles 4xx (Client Error)', function(assert) {
56-
assert.expect(4);
57+
assert.expect(5);
5758
const done = assert.async();
5859
this.server.respondWith('GET', '/posts/101', [
5960
404,
@@ -73,6 +74,7 @@ test('#_ajax handles 4xx (Client Error)', function(assert) {
7374
assert.ok(error.name, 'Client Error', '4xx response throws a custom error');
7475
assert.ok(Array.isArray(error.errors), '4xx error includes errors');
7576
assert.equal(error.errors[0].code, 404, '404 error code is in errors list');
77+
assert.equal(error.code, 404, 'error code 404');
7678
done();
7779
});
7880
});
@@ -85,7 +87,7 @@ test('#_ajax handles 3xx error', function(assert) {
8587
assert.ok(typeof promise.then === 'function', 'returns a thenable');
8688
promise.catch(function(error) {
8789
assert.equal(error.name, 'FetchError', 'unknown error response throws a custom error');
88-
assert.equal(error.error.code, 302, '302 error code');
90+
assert.equal(error.code, 302, '302 error code');
8991
done();
9092
});
9193
});

0 commit comments

Comments
 (0)