Skip to content
This repository has been archived by the owner on Oct 19, 2021. It is now read-only.

Dealing with errors #62

Open
plexus opened this issue Apr 25, 2015 · 2 comments
Open

Dealing with errors #62

plexus opened this issue Apr 25, 2015 · 2 comments

Comments

@plexus
Copy link
Owner

plexus commented Apr 25, 2015

Taking the discussion from #13 to the top level.

Once an API reaches a certain complexity it becomes necessary to communicate errors back to the client. We'll have to decide how to represent errors as part of a Yaks::Resource, so formats can take that error info and represent it. This needs to be able to carry enough info so formats with explicit support for error info can use that. On the other hand formats that don't have explicit specifications for errors should still be able to render them using (sub)resources and attributes.

In Collection+JSON the situation is pretty simple, it is allowed to add an extra "error" key at the top level, which MAY contain "error", "code" and "message" (see the spec)

{
  "error" :
  {
    "title" : STRING,
    "code" : STRING,
    "message" : STRING
  }
}

For JSON-API it's still under discussion. Long thread but some good stuff there.

For Siren, people also haven't decided yet, but there's a proposal gist

Mason does have it specified in their spec

They have an optional "@error" key, similar to CJ, but more extensive: it can also contain "@code" and "@message", but also "@messages" (list of string), "@controls" (hypermedia controls), "@HttpStatusCode". Unfortunately they don't have forms (with fields) (more about why that matters in a sec), it's a format that's in the "response template" school of hypermedia controls.

For Ticketsolve what we do is we have a separate ErrorMapper. Any exception that's raised in our API endpoints (~= controllers) gets caught and passed to the ErrorMapper. This renders a resource with http_code, type, and message attributes, e.g. (in HAL)

{
  "http_code": 404,
  "message": "Couldn't find Show with id=8735283249999",
  "type": "record_not_found",
  "_links": {
    "profile": [
      {
        "href": "http://api.ticketsolve.com/profile/error"
      }
    ]
  }
}

If a form embedded in an API response is submitted, and this causes a validation error, then we raise a special exception, which holds a reference to the form object. In that case an error response like above is rendered, but it also includes the form, with error messages embedded in the form fields (you'll notice that Yaks::Resource::Form::Field has an error attribute).

All of that as a bit of background. I still want to spend some more time going over the discussion happening over at JSON-API, cause there are some good pointers there, but from what I've seen so far...

Summarizing

Errors can either be represented as a top-level entity (Siren, last example above), or as part of another resource (CJ, Mason). The latter I find a bit odd, how can you still return a proper resource if the operation that was requested didn't succeed, but I'm sure there are use cases.

Secondly the error itself seems to commonly have

  • a string "type" / "code" to identify the error
  • a single string "message"
  • some formats repeat the HTTP code. This may seem redundant but I know from experience this can be useful

Then you have things like these

    1. ["name can't be empty", "email address is invalid"] (Mason "@messages")
    1. {"first_name": ["can't be empty"], "email": ["email is invalid"]} (allows mapping back to UI fields, some examples of that in the JSON API thread)
    1. {"type": "text", "name": "first_name", "error": ["can't be empty"]} (i.e. what we are currently doing, embed the form-field specific errors inside a form representation).

Note that formats that do 1) or 2) could take 3) and collapse it into what they need.

So... with all that in mind, I would suggest we represent errors as their own Yaks::Resource (or possibly a subclass Yaks::ErrorResource), where we standardize which attribute names to use for a top level message string, error code, http code, maybe others. You can either serialize this at the top level, or include it as a subresource under the error link relation.

Formats can do a couple different things with this

  • just render it like any other resource, in case the format has no explicit support for it
  • map the attributes to whatever is the standardized name in a given format
  • render embedded forms with fields errors as is, or
  • extract the messages and represent them in isolation.

Finally a remark about including extra error information on an otherwise "regular" response. This I would do by passing it in the Rack environment. If you have all your mappers inherit from a BaseMapper, then you can do something like this

class BaseMapper < Yaks::Mapper
  has_one :error, if: -> { env['api.error'] }, mapper: ErrorMapper
end

So any controller can set "api.error" to sideload that information into the response that is being rendered.

@janko janko mentioned this issue May 20, 2015
@danelowe
Copy link
Collaborator

@plexus Just listing some of the options and potential issues with each.

Subclass e.g. Yaks::ErrorResource

  • Are standard resources and error resources the only two types of Yaks resource we would ever need?
  • Using this approach, it would be expected that we are rendering an error, and only an error. No sideloading errors.

Tags on a resource

E.g. The mapper has a tags method, where you would pass tags (e.g. :error) to the resource that gets built

  • Same issue with no sideloading
  • Allows more possibilities than just errors, but that may not be necessary (and not as clear what tags are supported, or even what they are for)

A convention for attributes.

E.g. by convention, store error-related data in a hash under the attribute _errors

  • Simple, and would just require implementing by the formatters.
  • Possibility of clashing with actual attribute names
  • Might be a little bit more work for one to set up the mapper given they'd have to construct the hash.

error_attributes

Similar to using convention for attributes, but we have a method in the mappers that works just like attributes, but maps to a specific attribute in the resource.

attributes :id, :name, :data, tags: :errors

A mix of tags and error_attributes

@plexus
Copy link
Owner Author

plexus commented Sep 25, 2015

@danelowe I wrote up a concrete proposal in #112, please have a look if you can and see if you have any feedback or input.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants