23

I am using Ember 1.13.7 and Ember Data 1.13.8, which by default use the JSON-API standard to format the payloads sent to and received from the API.

I would like to use Ember Data's built-in error handling in order to display red "error" form fields to the user. I have formatted my API error responses as per the JSON-API standard, e.g.

{"errors":[
    {
        "title":"The included.1.attributes.street name field is required.", 
        "code":"API_ERR", 
        "status":"400", 
    }
]}

and when I attempt to save my model the error callback is being correctly executed. If I look within the Ember Inspector I can see that the model's "isError" value is set to true but I can't see how Ember Data is supposed to know which field within the model is the one in an error state? I see from the official JSON-API pages (http://jsonapi.org/format/#errors) that you can include a "source" object within the error response:

source: an object containing references to the source of the error, optionally including any of the following members:

pointer: a JSON Pointer [RFC6901] to the associated entity in the request document [e.g. "/data" for a primary data object, or "/data/attributes/title" for a specific attribute].

parameter: a string indicating which query parameter caused the error.

but is this what I should be doing in order to tell Ember Data which fields it should mark as being in an error state?

If anyone can help shed some light on this I'd be grateful.

Thanks.

danr1979
  • 461
  • 1
  • 3
  • 10
  • I'm still having issues with errors and the new JSON API format but according to this page, you are expected to provide a `source/pointer` property that matches the property name with the error. http://emberjs.com/api/data/classes/DS.InvalidError.html – Sarus Aug 17 '15 at 18:20
  • Yes, I found that out last week but I haven't had a chance to try it out yet. Thanks for your response though. – danr1979 Aug 17 '15 at 18:21
  • No problem, if you manage to get it all working please update your question. I'm sure it would be helpful to a lot of people. (I know it would be helpful to me haha!) – Sarus Aug 17 '15 at 18:26
  • Yeah sure no problem. – danr1979 Aug 17 '15 at 18:27

1 Answers1

89

Note the answer below is based on the following versions:

DEBUG: -------------------------------
ember.debug.js:5442DEBUG: Ember                     : 1.13.8
ember.debug.js:5442DEBUG: Ember Data                : 1.13.9
ember.debug.js:5442DEBUG: jQuery                    : 1.11.3
DEBUG: -------------------------------

The error handling documentation is unfortunately scattered around at the moment as the way you handle errors for the different adapters (Active, REST, JSON) are all a bit different.

In your case you want to handle errors for your form which probably means validation errors. The format for errors as specified by the JSON API can be found here: http://jsonapi.org/format/#error-objects

You'll notice that the API only specifies that errors are returned in a top level array keyed by errors and all other error attributes are optional. So seemingly all that JSON API requires is the following:

{
    "errors": [
     {}
    ]
}  

Of course that won't really do anything so for errors to work out of the box with Ember Data and the JSONAPIAdapter you will need to include at a minimum the detail attribute and the source.pointer attribute. The detail attribute is what gets set as the error message and the source.pointer attribute lets Ember Data figure out which attribute in the model is causing the problem. So a valid JSON API error object as required by Ember Data (if you're using the JSONAPI which is now the default) is something like this:

{
    "errors": [
     {
        "detail": "The attribute `is-admin` is required",
        "source": {
             "pointer": "data/attributes/is-admin"
         }
     }
    ]
}  

Note that detail is not plural (a common mistake for me), the value for source.pointer should not include a leading forward slash, and the attribute name should be dasherized.

Finally, you must return your validation error using the HTTP Code 422 which means "Unprocessable Entity". If you do not return a 422 code then by default Ember Data will return an AdapterError and will not set the error messages on the model's errors hash. This stumped me for a while because I was using the HTTP Code 400 (Bad Request) to return validation errors to the client.

The way ember data differentiates the two types of errors is that a validation error returns an InvalidError object (http://emberjs.com/api/data/classes/DS.InvalidError.html). This will cause the errors hash on the model to be set but will not set the isError flag to true (not sure why this is the case but it is documented here: http://emberjs.com/api/data/classes/DS.Model.html#property_isError). By default an HTTP error code other than 422 will result in an AdapterError being returned and the isError flag set to true. In both cases, the promise's reject handler will be called.

model.save().then(function(){
    // yay! it worked
}, function(){
    // it failed for some reason possibly a Bad Request (400)
    // possibly a validation error (422)
}

By default if the HTTP code returned is a 422 and you have the correct JSON API error format then you can access the error messages by accessing the model's errors hash where the hash keys are your attribute names. The hash is keyed on the attribute name in the camelcase format.

For example, in our above json-api error example, if there is an error on is-admin you would access that error like this:

model.get('errors.isAdmin');

This will return an array containing error objects where the format is like this:

[
   {
      "attribute": "isAdmin",
      "message": "The attribute `is-admin` is required"
    }
]

Essentially detail is mapped to message and source.pointer is mapped to attribute. An array is returned in case you have multiple validation errors on a single attribute (JSON API allows you to return multiple validation errors rather than returning just the first validation to fail). You can use the error values directly in a template like this:

{{#each model.errors.isAdmin as |error|}}
    <div class="error">
      {{error.message}}
    </div>
{{/each}}

If there are no errors then the above won't display anything so it works nicely for doing form validation messages.

If you API does not use the HTTP 422 code for validation errors (e.g., if it uses 400) then you can change the default behavior of the JSONAPIAdapter by overriding the handleResponse method in your custom adapter. Here is an example that returns a new InvalidError object for any HTTP response status code that is 400.

import DS from "ember-data";
import Ember from "ember";

export default DS.JSONAPIAdapter.extend({
  handleResponse: function(status, headers, payload){
    if(status === 400 && payload.errors){
      return new DS.InvalidError(payload.errors);
    }
    return this._super(...arguments);
  }
});

In the above example I'm checking to see if the HTTP status is 400 and making sure an errors property exists. If it does, then I create a new DS.InvalidError and return that. This will result in the same behavior as the default behavior that expects a 422 HTTP status code (i.e., your JSON API error will be processed and the message put into the errors hash on the model).

Sarus
  • 3,303
  • 1
  • 23
  • 27
  • Ok, errors on attributes located within related models is now my problem. I can see the errors on these related models by doing model.errors.content or model.errors.messages but if I want to check a particular attribute on a related model in order to highlight field errors I get null or undefined values. – danr1979 Aug 18 '15 at 08:20
  • 1
    Could you make another question with more details on how your models are setup, how you're saving the model and what response you're sending back? I haven't tried using relationships yet so can't easily try it out on my own project. – Sarus Aug 18 '15 at 13:19
  • 4
    You might not need to override the `handleResponse` method... instead, you could simply redefine the `isInvalid` method to look for a 400 status as well as the default 422. See here: https://github.com/emberjs/data/blob/v1.13.10/packages/ember-data/lib/adapters/rest-adapter.js#L879 – Eli Dupuis Oct 21 '15 at 16:54
  • 3
    I wish I could upvote this multiple times. Thank you so much! – mwp Nov 18 '15 at 19:42
  • Excellent answer. The Ember-Data documentation should incorporate this, or a distilled version of it. – blueFast Dec 25 '15 at 12:22
  • Thanks for this answer! Just solved my problem. This source/pointer stuff was the problem. – Bruno Paulino Jan 22 '16 at 03:06
  • I am almost losing my hope here plus hating the short offical documentation. Thank you so much! – Hao Jan 26 '16 at 14:59
  • 20
    How this SO answer, 8 months after its writing, is still the longest and most concise documentation on error handling in Ember.js routes with the default adapter is beyond me. – Craig Otis Mar 21 '16 at 22:19
  • Excellent answer that helped me a lot, thank you. Particularly the difference between an error and invalid state and how it used status code 422 to decide (by default). The API I was using was using 420 and this was a bit part of my problems. – howard10 Jul 19 '16 at 09:21
  • Worth noting that `normalizeErrorResponse` in the REST adapter is only called for errors and not invalid responses so if you need to normalize your errors payload your only option is to override `handleResponse` then modify payload.errors before calling the super method. – howard10 Jul 19 '16 at 10:24
  • 1
    Give this person a job writing documentation!! – Gurnzbot Oct 03 '16 at 20:09
  • In Ember 2.10 I had to access errors in controller with: model.get('errors').get('isActive') – pusle Jan 28 '17 at 09:28
  • Awesome answer! saved my day! People connecting Ember to Django, refer to https://stackoverflow.com/a/34611548/7027427 to change error code 400 to 422. Be sure to import from rest_framework.exceptions not from rest_framework_json_api. – Gevorg Hakobyan Jan 29 '22 at 19:25