Skip to content

Error Handling

David Copeland edited this page Oct 14, 2017 · 5 revisions

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.

Creating Errors from an Active Record

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.

Creating Errors from an Exception

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

Having Rich Error Messages via Exceptions

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.

Creating Errors by hand

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