-
Notifications
You must be signed in to change notification settings - Fork 21
Error Handling
Stitches provides support for structured error messages. Consumers want these so that they can cleanly handle errors from the service you are building.
The error format you want to send is like so:
{
"errors": [
{
"code": "not_found",
"message": "No Widget with the id 4"
},
{
"code": "required",
"message": "widget-id is required"
}
]
}
This format allows for multiple errors, provides codes to be used for basing logic, and human-readable messages for debugging.
Stitches::Errors
is the class to do this. There are three ways to use it in your controllers.
def create
widget = Widget.create(params)
if widget.valid?
render json: { widget: widget }, status: 201
else
render json: { errors: Stitches::Errors.from_active_record_object(widget)},
status: 422
end
end
This will introspect the errors and format them properly.
rescue_from ActiveRecord::NotFoundError do |ex|
render json: { errors: Stitches::Errors.from_exception(ex) }, status: 404
end
def show
widget = Widget.find(params[:id])
render json: { widget: widget }
end
Doing a lot of manual check and formatting of error messages is a pain. Often, it's easiest to raise an exception when things go wrong and let a rescue_from
in ApiController
(that you would have to add) sort it out.
By creating a hierarchy of exceptions you can have a rich set of structured errors without writing a lot of code.
Inside Stitches::Errors
, stitches determines the "code" of an error via:
code = exception.class.name.underscore.gsub(/_error$/,'')
So, for a SomethingWentWrongError
, the code would be "something_went_wrong"
You can leverage this to create a rich set of exceptions. For example, you could have a base exception ApiError
that has two subclasses, ClientError
and ServerError
, to handle situations where the consumer messed up, or your code messed up, respectively:
rescue_from ClientError do |ex|
render json: { errors: Stitches::Errors.from_exception(ex) }, status: 400
end
rescue_from ServerError do |ex|
render json: { errors: Stitches::Errors.from_exception(ex) }, status: 500
end
Then, subclass those exceptions with specific problems. For example, if your API requires properly formatted addresses and a client sends an ill-formatted one, you could raise AddressInvalidError
, which extends ClientError
. If thrown, the client would get an error code of "address_invalid" and a status of 400. Similarly, if you are integrating with a flaky external API, you could detect this and raise DownstreamApiError
, which extends ServerError
. Callers getting this would see a code of "downstream_api" and a status of 500.
Generally, you shouldn't have to do this because you are either using Active Record validations or exceptions, but you can create errors by hand like so:
render json: {
errors: Stitches::Errors.new([
Stitches::Error.new(code: "name_required", message: "The name is required")
Stitches::Error.new(code: "numeric_age", message: "The age should be a number")
])
}, status: 422