UNPKG

10.7 kBJavaScriptView Raw
1import { assert } from '@ember/debug';
2import EmberError from '@ember/error';
3
4/**
5 @module @ember-data/adapter
6*/
7
8/**
9 A `AdapterError` is used by an adapter to signal that an error occurred
10 during a request to an external API. It indicates a generic error, and
11 subclasses are used to indicate specific error states. The following
12 subclasses are provided:
13
14 - `InvalidError`
15 - `TimeoutError`
16 - `AbortError`
17 - `UnauthorizedError`
18 - `ForbiddenError`
19 - `NotFoundError`
20 - `ConflictError`
21 - `ServerError`
22
23 To create a custom error to signal a specific error state in communicating
24 with an external API, extend the `AdapterError`. For example, if the
25 external API exclusively used HTTP `503 Service Unavailable` to indicate
26 it was closed for maintenance:
27
28 ```app/adapters/maintenance-error.js
29 import AdapterError from '@ember-data/adapter/error';
30
31 export default AdapterError.extend({ message: "Down for maintenance." });
32 ```
33
34 This error would then be returned by an adapter's `handleResponse` method:
35
36 ```app/adapters/application.js
37 import JSONAPIAdapter from '@ember-data/adapter/json-api';
38 import MaintenanceError from './maintenance-error';
39
40 export default JSONAPIAdapter.extend({
41 handleResponse(status) {
42 if (503 === status) {
43 return new MaintenanceError();
44 }
45
46 return this._super(...arguments);
47 }
48 });
49 ```
50
51 And can then be detected in an application and used to send the user to an
52 `under-maintenance` route:
53
54 ```app/routes/application.js
55 import Route from '@ember/routing/route';
56 import MaintenanceError from '../adapters/maintenance-error';
57
58 export default Route.extend({
59 actions: {
60 error(error, transition) {
61 if (error instanceof MaintenanceError) {
62 this.transitionTo('under-maintenance');
63 return;
64 }
65
66 // ...other error handling logic
67 }
68 }
69 });
70 ```
71
72 @class AdapterError
73*/
74function AdapterError(errors, message = 'Adapter operation failed') {
75 this.isAdapterError = true;
76 let error = EmberError.call(this, message);
77
78 // in ember 3.8+ Error is a Native Error and we don't
79 // gain these automatically from the EmberError.call
80 if (error) {
81 this.stack = error.stack;
82 this.description = error.description;
83 this.fileName = error.fileName;
84 this.lineNumber = error.lineNumber;
85 this.message = error.message;
86 this.name = error.name;
87 this.number = error.number;
88 }
89
90 this.errors = errors || [
91 {
92 title: 'Adapter Error',
93 detail: message,
94 },
95 ];
96}
97
98export default AdapterError;
99
100function extendFn(ErrorClass) {
101 return function({ message: defaultMessage } = {}) {
102 return extend(ErrorClass, defaultMessage);
103 };
104}
105
106function extend(ParentErrorClass, defaultMessage) {
107 let ErrorClass = function(errors, message) {
108 assert('`AdapterError` expects json-api formatted errors array.', Array.isArray(errors || []));
109 ParentErrorClass.call(this, errors, message || defaultMessage);
110 };
111 ErrorClass.prototype = Object.create(ParentErrorClass.prototype);
112 ErrorClass.extend = extendFn(ErrorClass);
113
114 return ErrorClass;
115}
116
117AdapterError.prototype = Object.create(EmberError.prototype);
118AdapterError.prototype.code = 'AdapterError';
119AdapterError.extend = extendFn(AdapterError);
120
121/**
122 A `InvalidError` is used by an adapter to signal the external API
123 was unable to process a request because the content was not
124 semantically correct or meaningful per the API. Usually, this means a
125 record failed some form of server-side validation. When a promise
126 from an adapter is rejected with a `InvalidError` the record will
127 transition to the `invalid` state and the errors will be set to the
128 `errors` property on the record.
129
130 For Ember Data to correctly map errors to their corresponding
131 properties on the model, Ember Data expects each error to be
132 a valid JSON-API error object with a `source/pointer` that matches
133 the property name. For example, if you had a Post model that
134 looked like this.
135
136 ```app/models/post.js
137 import Model, { attr } from '@ember-data/model';
138
139 export default Model.extend({
140 title: attr('string'),
141 content: attr('string')
142 });
143 ```
144
145 To show an error from the server related to the `title` and
146 `content` properties your adapter could return a promise that
147 rejects with a `InvalidError` object that looks like this:
148
149 ```app/adapters/post.js
150 import RSVP from 'RSVP';
151 import RESTAdapter from '@ember-data/adapter/rest';
152 import { InvalidError } from '@ember-data/adapter/error';
153
154 export default RESTAdapter.extend({
155 updateRecord() {
156 // Fictional adapter that always rejects
157 return RSVP.reject(new InvalidError([
158 {
159 detail: 'Must be unique',
160 source: { pointer: '/data/attributes/title' }
161 },
162 {
163 detail: 'Must not be blank',
164 source: { pointer: '/data/attributes/content'}
165 }
166 ]));
167 }
168 });
169 ```
170
171 Your backend may use different property names for your records the
172 store will attempt to extract and normalize the errors using the
173 serializer's `extractErrors` method before the errors get added to
174 the model. As a result, it is safe for the `InvalidError` to
175 wrap the error payload unaltered.
176
177 @class InvalidError
178 @extends AdapterError
179*/
180export const InvalidError = extend(AdapterError, 'The adapter rejected the commit because it was invalid');
181InvalidError.prototype.code = 'InvalidError';
182
183/**
184 A `TimeoutError` is used by an adapter to signal that a request
185 to the external API has timed out. I.e. no response was received from
186 the external API within an allowed time period.
187
188 An example use case would be to warn the user to check their internet
189 connection if an adapter operation has timed out:
190
191 ```app/routes/application.js
192 import Route from '@ember/routing/route';
193 import { TimeoutError } from '@ember-data/adapter/error';
194 import { action } from '@ember/object';
195
196 export default class ApplicationRoute extends Route {
197 @action
198 error(error, transition) {
199 if (error instanceof TimeoutError) {
200 // alert the user
201 alert('Are you still connected to the Internet?');
202 return;
203 }
204
205 // ...other error handling logic
206 }
207 }
208 ```
209
210 @class TimeoutError
211 @extends AdapterError
212*/
213export const TimeoutError = extend(AdapterError, 'The adapter operation timed out');
214TimeoutError.prototype.code = 'TimeoutError';
215
216/**
217 A `AbortError` is used by an adapter to signal that a request to
218 the external API was aborted. For example, this can occur if the user
219 navigates away from the current page after a request to the external API
220 has been initiated but before a response has been received.
221
222 @class AbortError
223 @extends AdapterError
224*/
225export const AbortError = extend(AdapterError, 'The adapter operation was aborted');
226AbortError.prototype.code = 'AbortError';
227
228/**
229 A `UnauthorizedError` equates to a HTTP `401 Unauthorized` response
230 status. It is used by an adapter to signal that a request to the external
231 API was rejected because authorization is required and has failed or has not
232 yet been provided.
233
234 An example use case would be to redirect the user to a login route if a
235 request is unauthorized:
236
237 ```app/routes/application.js
238 import Route from '@ember/routing/route';
239 import { UnauthorizedError } from '@ember-data/adapter/error';
240 import { action } from '@ember/object';
241
242 export default class ApplicationRoute extends Route {
243 @action
244 error(error, transition) {
245 if (error instanceof UnauthorizedError) {
246 // go to the login route
247 this.transitionTo('login');
248 return;
249 }
250
251 // ...other error handling logic
252 }
253 }
254 ```
255
256 @class UnauthorizedError
257 @extends AdapterError
258*/
259export const UnauthorizedError = extend(AdapterError, 'The adapter operation is unauthorized');
260UnauthorizedError.prototype.code = 'UnauthorizedError';
261
262/**
263 A `ForbiddenError` equates to a HTTP `403 Forbidden` response status.
264 It is used by an adapter to signal that a request to the external API was
265 valid but the server is refusing to respond to it. If authorization was
266 provided and is valid, then the authenticated user does not have the
267 necessary permissions for the request.
268
269 @class ForbiddenError
270 @extends AdapterError
271*/
272export const ForbiddenError = extend(AdapterError, 'The adapter operation is forbidden');
273ForbiddenError.prototype.code = 'ForbiddenError';
274
275/**
276 A `NotFoundError` equates to a HTTP `404 Not Found` response status.
277 It is used by an adapter to signal that a request to the external API
278 was rejected because the resource could not be found on the API.
279
280 An example use case would be to detect if the user has entered a route
281 for a specific model that does not exist. For example:
282
283 ```app/routes/post.js
284 import Route from '@ember/routing/route';
285 import { NotFoundError } from '@ember-data/adapter/error';
286 import { inject as service } from '@ember/service';
287 import { action } from '@ember/object';
288
289 export default class PostRoute extends Route {
290 @service store;
291 model(params) {
292 return this.get('store').findRecord('post', params.post_id);
293 }
294 @action
295 error(error, transition) {
296 if (error instanceof NotFoundError) {
297 // redirect to a list of all posts instead
298 this.transitionTo('posts');
299 } else {
300 // otherwise let the error bubble
301 return true;
302 }
303 }
304 }
305 ```
306
307 @class NotFoundError
308 @extends AdapterError
309*/
310export const NotFoundError = extend(AdapterError, 'The adapter could not find the resource');
311NotFoundError.prototype.code = 'NotFoundError';
312
313/**
314 A `ConflictError` equates to a HTTP `409 Conflict` response status.
315 It is used by an adapter to indicate that the request could not be processed
316 because of a conflict in the request. An example scenario would be when
317 creating a record with a client-generated ID but that ID is already known
318 to the external API.
319
320 @class ConflictError
321 @extends AdapterError
322*/
323export const ConflictError = extend(AdapterError, 'The adapter operation failed due to a conflict');
324ConflictError.prototype.code = 'ConflictError';
325
326/**
327 A `ServerError` equates to a HTTP `500 Internal Server Error` response
328 status. It is used by the adapter to indicate that a request has failed
329 because of an error in the external API.
330
331 @class ServerError
332 @extends AdapterError
333*/
334export const ServerError = extend(AdapterError, 'The adapter operation failed due to a server error');
335ServerError.prototype.code = 'ServerError';
336
337export { errorsHashToArray, errorsArrayToHash } from '@ember-data/store/-private';