Skip to content
This repository was archived by the owner on Jan 30, 2023. It is now read-only.
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
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ env:
- EMBER_TRY_SCENARIO=ember-release
- EMBER_TRY_SCENARIO=ember-beta
- EMBER_TRY_SCENARIO=ember-canary
- EMBER_TRY_SCENARIO=ember-1-13

matrix:
fast_finish: true
Expand Down
40 changes: 18 additions & 22 deletions addon/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@ const { Error: EmberError } = Ember;

/**
* @class AjaxError
* @private
* @public
* @extends Ember.Error
*/
export function AjaxError(errors, message = 'Ajax operation failed') {
export function AjaxError(payload, message = 'Ajax operation failed') {
EmberError.call(this, message);

this.errors = errors || [
{
title: 'Ajax Error',
detail: message
}
];
this.payload = payload;
}

AjaxError.prototype = Object.create(EmberError.prototype);
Expand All @@ -24,8 +20,8 @@ AjaxError.prototype = Object.create(EmberError.prototype);
* @public
* @extends AjaxError
*/
export function InvalidError(errors) {
AjaxError.call(this, errors, 'Request was rejected because it was invalid');
export function InvalidError(payload) {
AjaxError.call(this, payload, 'Request was rejected because it was invalid');
}

InvalidError.prototype = Object.create(AjaxError.prototype);
Expand All @@ -35,8 +31,8 @@ InvalidError.prototype = Object.create(AjaxError.prototype);
* @public
* @extends AjaxError
*/
export function UnauthorizedError(errors) {
AjaxError.call(this, errors, 'Ajax authorization failed');
export function UnauthorizedError(payload) {
AjaxError.call(this, payload, 'Ajax authorization failed');
}

UnauthorizedError.prototype = Object.create(AjaxError.prototype);
Expand All @@ -46,8 +42,8 @@ UnauthorizedError.prototype = Object.create(AjaxError.prototype);
* @public
* @extends AjaxError
*/
export function ForbiddenError(errors) {
AjaxError.call(this, errors,
export function ForbiddenError(payload) {
AjaxError.call(this, payload,
'Request was rejected because user is not permitted to perform this operation.');
}

Expand All @@ -58,8 +54,8 @@ ForbiddenError.prototype = Object.create(AjaxError.prototype);
* @public
* @extends AjaxError
*/
export function BadRequestError(errors) {
AjaxError.call(this, errors, 'Request was formatted incorrectly.');
export function BadRequestError(payload) {
AjaxError.call(this, payload, 'Request was formatted incorrectly.');
}

BadRequestError.prototype = Object.create(AjaxError.prototype);
Expand All @@ -69,8 +65,8 @@ BadRequestError.prototype = Object.create(AjaxError.prototype);
* @public
* @extends AjaxError
*/
export function NotFoundError(errors) {
AjaxError.call(this, errors, 'Resource was not found.');
export function NotFoundError(payload) {
AjaxError.call(this, payload, 'Resource was not found.');
}

NotFoundError.prototype = Object.create(AjaxError.prototype);
Expand Down Expand Up @@ -102,8 +98,8 @@ AbortError.prototype = Object.create(AjaxError.prototype);
* @public
* @extends AjaxError
*/
export function ConflictError(errors) {
AjaxError.call(this, errors, 'The ajax operation failed due to a conflict');
export function ConflictError(payload) {
AjaxError.call(this, payload, 'The ajax operation failed due to a conflict');
}

ConflictError.prototype = Object.create(AjaxError.prototype);
Expand All @@ -113,8 +109,8 @@ ConflictError.prototype = Object.create(AjaxError.prototype);
* @public
* @extends AjaxError
*/
export function ServerError(errors) {
AjaxError.call(this, errors, 'Request was rejected due to server error');
export function ServerError(payload) {
AjaxError.call(this, payload, 'Request was rejected due to server error');
}

ServerError.prototype = Object.create(AjaxError.prototype);
Expand Down
68 changes: 52 additions & 16 deletions addon/mixins/ajax-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const {
Mixin,
RSVP: { Promise },
Test,
deprecate,
get,
isArray,
isEmpty,
Expand All @@ -45,6 +46,29 @@ const {
} = Ember;
const JSONAPIContentType = 'application/vnd.api+json';

function defineDeprecatedErrorsProperty(error, errors) {
Object.defineProperty(error, 'errors', {
get() {
deprecate(
'This property will be removed in ember-ajax 3.0.0. Please use `payload` going forward. Note the attached URL for details.',
false,
{
url: 'https://github.com/ember-cli/ember-ajax/issues/175',
until: '3.0.0',
id: 'ember-ajax.errors.normalize-errors'
}
);

let defaultError = {
title: 'Ajax Error',
detail: this.message
};

return errors || [defaultError];
}
});
}

function isJSONAPIContentType(header) {
if (isNone(header)) {
return false;
Expand Down Expand Up @@ -276,8 +300,12 @@ export default Mixin.create({
response = errorThrown;
} else if (textStatus === 'timeout') {
response = new TimeoutError();

defineDeprecatedErrorsProperty(response);
} else if (textStatus === 'abort') {
response = new AbortError();

defineDeprecatedErrorsProperty(response);
} else {
response = this.handleResponse(
jqXHR.status,
Expand Down Expand Up @@ -512,35 +540,43 @@ export default Mixin.create({
*/
handleResponse(status, headers, payload, requestData) {
payload = (payload === null || payload === undefined) ? {} : payload;
const errors = this.normalizeErrorResponse(status, headers, payload);

let error;

if (this.isSuccess(status, headers, payload)) {
return payload;
} else if (this.isUnauthorizedError(status, headers, payload)) {
return new UnauthorizedError(errors);
error = new UnauthorizedError(payload);
} else if (this.isForbiddenError(status, headers, payload)) {
return new ForbiddenError(errors);
error = new ForbiddenError(payload);
} else if (this.isInvalidError(status, headers, payload)) {
return new InvalidError(errors);
error = new InvalidError(payload);
} else if (this.isBadRequestError(status, headers, payload)) {
return new BadRequestError(errors);
error = new BadRequestError(payload);
} else if (this.isNotFoundError(status, headers, payload)) {
return new NotFoundError(errors);
error = new NotFoundError(payload);
} else if (this.isAbortError(status, headers, payload)) {
return new AbortError(errors);
error = new AbortError(payload);
} else if (this.isConflictError(status, headers, payload)) {
return new ConflictError(errors);
error = new ConflictError(payload);
} else if (this.isServerError(status, headers, payload)) {
return new ServerError(errors);
error = new ServerError(payload);
} else {
let detailedMessage = this.generateDetailedMessage(
status,
headers,
payload,
requestData
);

error = new AjaxError(payload, detailedMessage);
}

const detailedMessage = this.generateDetailedMessage(
status,
headers,
payload,
requestData
);
return new AjaxError(errors, detailedMessage);
let errors = this.normalizeErrorResponse(status, headers, payload);

defineDeprecatedErrorsProperty(error, errors);

return error;
},

/**
Expand Down
74 changes: 36 additions & 38 deletions tests/unit/mixins/ajax-request-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -577,40 +577,6 @@ describe('Unit | Mixin | ajax request', function() {
});
});

it('it always returns error objects with status codes as strings', function() {
const response = [404, { 'Content-Type': 'application/json' }, ''];
this.server.get('/posts', () => response);

const service = new AjaxRequest();
return service.request('/posts')
.then(function() {
throw new Error('success handler should not be called');
})
.catch(function(result) {
expect(result.errors[0].status).to.equal('404');
});
});

it('it coerces payload error response status codes to strings', function() {
const body = {
errors: [
{ status: 403, message: 'Permission Denied' }
]
};
const response = [403, { 'Content-Type': 'application/json' }, JSON.stringify(body)];
this.server.get('/posts', () => response);

const service = new AjaxRequest();
return service.request('/posts')
.then(function() {
throw new Error('success handler should not be called');
})
.catch(function(result) {
expect(result.errors[0].status).to.equal('403');
expect(result.errors[0].message).to.equal('Permission Denied');
});
});

it('it throws an error when the user tries to use `.get` to make a request', function() {
const service = new AjaxRequest();
service.set('someProperty', 'foo');
Expand Down Expand Up @@ -881,6 +847,32 @@ describe('Unit | Mixin | ajax request', function() {
});
});

describe('[ISSUE 175] error property deprecation', function() {
beforeEach(function() {
// eslint-disable-next-line
Ember.ENV.RAISE_ON_DEPRECATION = true;
});

afterEach(function() {
// eslint-disable-next-line
Ember.ENV.RAISE_ON_DEPRECATION = false;
});

it('notifies', function() {
this.server.get('/posts', jsonFactory(404, { errors: [{ id: 1, message: 'error description' }] }));
const service = new AjaxRequest();
return service.request('/posts')
.then(function() {
throw new Error('success handler should not be called');
})
.catch(function(reason) {
expect(function() {
reason.errors;
}).to.throws('This property will be removed in ember-ajax 3.0.0. Please use `payload` going forward. Note the attached URL for details.');
});
});
});

describe('error handlers', function() {
it('handles a TimeoutError correctly', function() {
this.server.get('/posts', jsonFactory(200), 2);
Expand All @@ -891,7 +883,7 @@ describe('Unit | Mixin | ajax request', function() {
})
.catch(function(reason) {
expect(isTimeoutError(reason)).to.be.ok;
expect(reason.errors && typeOf(reason.errors) === 'array').to.be.ok;
expect(reason.payload).to.be.null;
});
});

Expand All @@ -905,9 +897,15 @@ describe('Unit | Mixin | ajax request', function() {
})
.catch(function(reason) {
expect(reason instanceof errorClass).to.be.ok;
expect(reason.errors && typeOf(reason.errors) === 'array').to.be.ok;
expect(reason.errors[0].id).to.equal(1);
expect(reason.errors[0].message).to.equal('error description');
expect(reason.payload).to.not.be.undefined;

let {
errors
} = reason.payload;

expect(errors && typeOf(errors) === 'array').to.be.ok;
expect(errors[0].id).to.equal(1);
expect(errors[0].message).to.equal('error description');
});
});
}
Expand Down