From dc20d6ef02e0302d3272ce5e362017e0b8ab195e Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 17 Jun 2020 12:12:32 -0400 Subject: [PATCH] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20convenience=20ide?= =?UTF-8?q?ntifiers=20to=20error=20objects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/adapters/application.js | 49 +++++++++++++++++++ .../tests/unit/adapters/application-test.js | 24 +++++++-- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/addons/api/addon/adapters/application.js b/addons/api/addon/adapters/application.js index ace0b9669..cef50d39e 100644 --- a/addons/api/addon/adapters/application.js +++ b/addons/api/addon/adapters/application.js @@ -4,6 +4,7 @@ import { get } from '@ember/object'; import { InvalidError } from '@ember-data/adapter/error'; import { dasherize } from '@ember/string'; import { pluralize } from 'ember-inflector'; +import { isArray } from '@ember/array'; export default class ApplicationAdapter extends RESTAdapter { // =attributes @@ -91,6 +92,53 @@ export default class ApplicationAdapter extends RESTAdapter { return super.handleResponse(status, headers, payload, requestData); } + /** + * Add convenience booleans to each error within the errors list indicating + * the type of error that occurred. This is useful in downstream templates, + * e.g. `{{if error.isForbidden}}`. + * + * Error booleans are attached based on the error status code: + * + * - `401 isUnauthenticated`: the session isn't authenticated + * - `403 isForbidden`: the session is authenticated but does not have + * permission to perform the requested action + * - `404 isNotFound`: the requested resource could not be found + * - `500 isServer`: an internal server error occurred + * - `isUnknown`: an error occurred, but we don't know which or + * we don't distinguish it yet + * + * @override + * @method normalizeErrorResponse + * @param {Number} status + * @param {Object} headers + * @param {Object} payload + * @return {Array} errors payload + */ + normalizeErrorResponse(/*status, headers, payload*/) { + const errors = super.normalizeErrorResponse(...arguments); + if (isArray(errors)) { + errors.forEach(error => { + switch (Number(error.status)) { + case 401: + error.isUnauthenticated = true; + break; + case 403: + error.isForbidden = true; + break; + case 404: + error.isNotFound = true; + break; + case 500: + error.isServer = true; + break; + default: + error.isUnknown = true; + } + }); + } + return errors; + } + /** * Normalizes the errors found in a payload from our API into JSON API * error format. @@ -103,6 +151,7 @@ export default class ApplicationAdapter extends RESTAdapter { // Normalize the primary error message into "base", which is an error // that applies to the whole model instance const baseError = { + isInvalid: true, status: payload.source, code: payload.code, detail: payload.message, diff --git a/addons/api/tests/unit/adapters/application-test.js b/addons/api/tests/unit/adapters/application-test.js index 8464a7513..8c58bafec 100644 --- a/addons/api/tests/unit/adapters/application-test.js +++ b/addons/api/tests/unit/adapters/application-test.js @@ -38,23 +38,37 @@ module('Unit | Adapter | application', function (hooks) { }); test('it returns an proper InvalidError from handleResponse', function (assert) { - assert.expect(3); + assert.expect(4); const adapter = this.owner.lookup('adapter:application'); - let payload = { + const payload = { status: 400, code: 'invalid_argument', message: 'The request was invalid.', }; - let handledResponse = adapter.handleResponse(400, {}, payload); + const handledResponse = adapter.handleResponse(400, {}, payload); assert.ok(handledResponse instanceof InvalidError); assert.equal(handledResponse.errors.length, 1); assert.equal(handledResponse.message, 'The request was invalid.'); + assert.ok(handledResponse.errors[0].isInvalid); + }); + + test('it assigns convenience booleans for error types', function (assert) { + assert.expect(6); + const adapter = this.owner.lookup('adapter:application'); + const getResponse = (status) => + adapter.handleResponse(status, {}, { status }); + assert.ok(getResponse(401).errors[0].isUnauthenticated); + assert.ok(getResponse(403).errors[0].isForbidden); + assert.ok(getResponse(404).errors[0].isNotFound); + assert.ok(getResponse(500).errors[0].isServer); + assert.ok(getResponse(0).errors[0].isUnknown); + assert.ok(getResponse(false).errors[0].isUnknown); }); test('it returns field-level errors in InvalidError from handleResponse', function (assert) { assert.expect(1); const adapter = this.owner.lookup('adapter:application'); - let payload = { + const payload = { status: 400, code: 'invalid_argument', message: 'The request was invalid.', @@ -62,7 +76,7 @@ module('Unit | Adapter | application', function (hooks) { fields: [{ name: 'name', message: 'Name is wrong.' }], }, }; - let handledResponse = adapter.handleResponse(400, {}, payload); + const handledResponse = adapter.handleResponse(400, {}, payload); assert.equal( handledResponse.errors.length, 2,