You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on Oct 19, 2021. It is now read-only.
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)
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
["name can't be empty", "email address is invalid"] (Mason "@messages")
{"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)
{"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
@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.
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)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 theErrorMapper
. This renders a resource withhttp_code
,type
, andmessage
attributes, e.g. (in HAL)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 anerror
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
Then you have things like these
["name can't be empty", "email address is invalid"]
(Mason"@messages"
){"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){"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 subclassYaks::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 theerror
link relation.Formats can do a couple different things with this
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 thisSo any controller can set "api.error" to sideload that information into the response that is being rendered.
The text was updated successfully, but these errors were encountered: