Active_model_serializers: Serializing errors

Created on 1 Jun 2015  Â·  23Comments  Â·  Source: rails-api/active_model_serializers

The JSON API docs currently state (I'm not sure if they said this before JSON API 1.0):

A document MUST contain at least one of the following top-level members:

data: containing the document's "primary data"
errors: containing an array of error objects
meta: non-standard meta-information.

The members data and errors MUST NOT coexist in the same document.

Is there a way to render the errors member in master / v0.10.0.rc1?

JSON API Ready for PR 0.10.x 0.10.x Mandatory

Most helpful comment

This is what I use in my app:

      render json: model, status: 422, serializer: ActiveModel::Serializer::ErrorSerializer

All 23 comments

@remear it's a implementation of 1.0
We haven't updated AMS yet, the actual supported version is RC4, we might update it this week but don't take it for granted :smile:

Great! Would it be possible to leave this issue open until it's resolved and reference it in commits so others can follow along. I'd love to help test this as soon as it becomes available.

I think what you want here is pseudo-code

if success
  render json: model
else
  render json: model.errors, root: "errors" # or maybe roll-your own error serializer and model.  Just mix in ActiveModel::Serialization
end

right?

That's one way. For a rough example, what if I wanted to do this from rack with:

def respond_400(errors)
  serializer = ActiveModel::Serializer::ArraySerializer.new(errors, serializer: ErrorSerializer)
  response = ActiveModel::Serializer::Adapter.create(serializer).to_json
  [400, {'Content-Type' => 'application/vnd.api+json'}, [response]]
end

I wasn't sure how to get root in there.

Oh. you probably don't want to do that for a couple of reasons

  1. it's not the intended usage and will only cause pain (in my experience)
  2. it might be more happy path to have an error controller that you call directly here, e.g. ErrorsController.action(:show).call(env.merge(something)) (this will skip the rest of the call stack, see actioncontroller::metal
  3. If you follow the code in https://github.com/rails-api/active_model_serializers/blob/460150fef28b298a38bb2bd0ef424cf968c5692f/lib/action_controller/serialization.rb#L38-L48 you'll have a happier time

Following @bf4's suggestion the structure still isn't in accordance with the JSON API specifications example and suggested structure. Here's an example structure from jsonapi.org:

{
  "errors": [
    {
      "status": "422",
      "source": { "pointer": "/data/attributes/first-name" },
      "title":  "Invalid Attribute",
      "detail": "First name must contain at least three characters."
    }
  ]
}

Using the following code I can still follow the JSON API requirements but it still isn't even close to the suggested structure above, which would be much more powerful:

if object.save
  render json: object, status: :created, location: object
else
  render json: { errors: object.errors }, status: :unprocessable_entity
end

Example error output:

{
  "errors": {
    "first_name": ["can't be blank", "is too short (minimum is 3 characters)", "is invalid"]
  }
}

I'd love to see this be part of this gem before Release of 0.10! :+1:

@bf4: Thank you, looks like good progress. I'm currently using the https://github.com/joaomdmoura/active_model_serializers/tree/deserializer-implementation branch though since I really need the deserializers, so I will have to wait until both come to complete fruition and get merged.

I might be mistaken but my feeling when reading through the other threads was that this issue is either already solved or a duplicate, isn't it?

@Dschee I would say it can be done, just not explicitly supported, esp JsonApi, that's more work. In fact, it led me to create a json api errors examples on their examples page.. which is also why so many of the features are still unchecked in that pr :)

For rehydrating, take a look at some of the libs on http://jsonapi.org/implementations/

Wanted to copy the PR description from #1004 to track further changes / remaining TODOs

Courtesy of @bf4

Usage

Requires explicit passing of Error serializer

# POST /api/{plural_resource_name}
def create  
  set_resource(resource_class.new(resource_params))

  if get_resource.save
    render :show, status: :created, adapter: :json_api
  else
    render json: get_resource.errors, status: :unprocessable_entity, adapter: :json_api, serializer: ActiveModel::Serializer::ErrorSerializer
  end
end

PR TODOS:

  • DOCS

    • [x] Usage and requirements jsonapi/errors.md

    • [x] data and errors cannot co-exist. http://jsonapi.org/format/#document-top-level ref

    • [x] requests are transactional, so any part that succeeds needs to be rolled back if any part fails. http://jsonapi.org/format/#crud ref

    • [x] examples http://jsonapi.org/examples/#error-objects ref

  • [ ] changelog

Error object:

Serializing errors is different from resources.

  • Errors may be an instance of ActiveModel::Error, which we'd want to call model.errors.messages on.

    • Resolution: _true_

  • Errors may be given as a hash.

    • Resolution: _undecided_

  • Errors may be given by the end-user as a special 'error' object.

    • Resolution _undecided_

  • ActiveModel::Error is necessarily a collection of error objects, each with it's own 'source' and 'detail'

    • Resolution: that's how we'll use it.

  • [ ] Errors may be automatically generated from the controller.

    • Resolution: _deferred_

    • [ ] [422 for errors](https://github.com/rails-api/active_model_serializers/pull/1004#issuecomment-181708926)..

    • [ ] but see https://github.com/rails-api/active_model_serializers/pull/1004#discussion_r46471845 for concerns and alternative implementations

Full Error API Implementation:

  • [x] error root key to collection of error objects

    • [x] The members data and errors MUST NOT coexist in the same document.

  • [x] error objects from ActiveModel::Error
  • [ ] id: a unique identifier for this particular occurrence of the problem.
  • [ ] links: a links object containing the following members:

    • [ ] about: a link that leads to further details about this particular occurrence of the problem.

  • [ ] status: the HTTP status code applicable to this problem, expressed as a string value.
  • [ ] code: an application-specific error code, expressed as a string value.
  • [ ] title: a short, human-readable summary of the problem that SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization.
  • [x] detail: a human-readable explanation specific to this occurrence of the problem.
  • [x] source: an object containing references to the source of the error, optionally including any of the following members:

    • [x] 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].

    • [x] parameter: a string indicating which query parameter caused the error.

  • [ ] meta: a meta object containing non-standard meta-information about the error.

Implementation tests:

  • [x] multiple error objects
  • [x] serializable resource?
  • [ ] Controller

    • [x] test/action_controller/json_api/errors_test.rb

    • [ ] test/action_controller/rescue_from_test.rb

    • [ ] [rescue_with_handler, handle_exceptions](https://github.com/rails-api/active_model_serializers/pull/1004#discussion_r46471845)

Other implementations

ref:

  • ams issue: https://github.com/rails-api/active_model_serializers/issues/933
  • http://jsonapi.org/format/#error-object
  • http://jsonapi.org/examples/
  • https://github.com/json-api/json-api/pull/828
  • other implementations

    • https://github.com/cerebris/jsonapi-resources/blob/42a3c6409964a0f91544813e9a0437d248832ef4/lib/jsonapi/exceptions.rb

    • https://github.com/cerebris/jsonapi-resources/blob/28ac3971f8b9e73073575d41439ffe263d7e56f5/test/controllers/controller_test.rb#L452-L462

    • https://github.com/cerebris/jsonapi-resources/blob/28ac3971f8b9e73073575d41439ffe263d7e56f5/test/controllers/controller_test.rb#L2412-L2429

    • https://github.com/chingor13/json_api_client/blob/cf79aba96cbc9ff66a793d07f43b025fd8f70b38/lib/json_api_client/error_collector.rb#L34-L48

    • https://github.com/manyminds/api2go/blob/3023e50d563e6ec642b46a4f044812cb232661a1/error.go

    • https://github.com/emberjs/data/blob/1f866da3b4a4edcf70fdf346cd6e6be6cfaecfab/packages/ember-data/lib/adapters/errors.js#L3-L154

    • Usage in Ember

    • http://emberjs.com/blog/2015/06/18/ember-data-1-13-released.html#toc_using-json-api-error-object-format

    • ember-data


  • libs to review for ideas

    • http://jsonapi.org/implementations/#server-libraries-ruby

    • https://github.com/endpoints/endpoints/blob/master/src/format-jsonapi/index.js

    • https://github.com/chingor13/json_api_client

    • https://github.com/jsmestad/jsonapi-consumer

    • https://github.com/ember-data/active-model-adapter

  • libs to consider

Something I'll probably work on soon is automatically detecting the existence of errors of swapping out what was going to be used to render the object, with the errors serializer. This should probably be an opt-in feature

I implemented that in the pr and backed it out. We don't have enough
control in the controller to do it well, esp the status code

Better to focus elsewhere I think

You can see it in the commit history
On Mon, Mar 7, 2016 at 6:31 AM L. Preston Sego III [email protected]
wrote:

Something I'll probably work on soon is automatically detecting the
existence of errors of swapping out what was going to be used to render the
object, with the errors serializer. This should probably be an opt-in
feature

—
Reply to this email directly or view it on GitHub
https://github.com/rails-api/active_model_serializers/issues/933#issuecomment-193232069
.

can the errors objects have added data to them? like by default, 422, but if we want to add a custom error to :base, maybe add 401 to some property on that particular error object? that way the error codes are still managed outside of AMS

It's a goal but current impl ia sufficient. Doing it right will require lots of new objects and APIs and tighter rails integration. I think of it as phase two or so

B mobile phone

On Mar 7, 2016, at 1:16 PM, L. Preston Sego III [email protected] wrote:

can the errors objects have added data to them? like by default, 422, but if we want to add a custom error to :base, maybe add 401 to some property on that particular error object? that way the error codes are still managed outside of AMS

—
Reply to this email directly or view it on GitHub.

Any update on the remaining features?

there's this:

ActiveModel::Serializer::ErrorSerializer

This is what I use in my app:

      render json: model, status: 422, serializer: ActiveModel::Serializer::ErrorSerializer

I use the same render approach but that doesn't include the HTTP status code inside the body as JSONAPI suggests, only in the header.

ah, I see. in the examples, jsonapi gives a different status code per error. http://jsonapi.org/examples/#error-objects (or the capability to).

I'm not sure how this would work with the default ErrorSerializer.

You could probably have a request-level errors object to keep track of errors during a request and append to that list as things happen, and then if the list contains errors, render that instead of the resource?

That would add a lot of extra workload both on the implementation and on the actual use of serializers.

Another idea would be to make use of the new feature in Rails 5.0.1 . We could assign http statuses to the new details attribute of error and display them per error.

That would add a lot of extra workload both on the implementation and on the actual use of serializers.

Yeah, I don't think adding per-error status adds a whole lot of value anyway.
I think a request should short-circuit immediately when the first error is hit.

Another idea would be to make use of the new feature in Rails 5.0.1 .

If anything, I think that would just clean up the existing error serializer.

doesn't include the HTTP status code inside the body as JSONAPI suggests, only in the header.

suggests, not require

I couldn't figure out a clean way to do it and it's been a while and no one has felt the need to add more error serializing features, or fix some bugs in my lame pointer code.

In brief, in order to provide richer errors, we need tighter integration with the rendering context like jsonapi-resources has.

I'd be happy to start a new issue to discuss specific feature requests. Otherwise, this is really just issue necromancy.

Arguably the issue could be a placeholder for all errors discussions, but I'm going to close the issue now, and not lock it in case anyone has a good reason to continue discussion in this issue specifically.

Was this page helpful?
0 / 5 - 0 ratings