ApiErrorHandler

Build Status

Are your API error responses not all that you want them to be? If so, you've found the right gem! api_error_handler handles all aspects of returning informative, spec-compliant responses to clients when your application encounters an error in the course of processing a response.

This "handling" includes:

  • Error serialization: each response will include a response body that gives some information on the type of error that your application encountered. See the Responses Body Options section for details and configuration options.
  • Status code setting: api_error_handler will set the HTTP status code of the response based on the type of error that is raised. For example, when an ActiveRecord::RecordNotFound error is raised, it will set the response status to 404. See the HTTP Status Mapping section for details and configuration options.
  • Error reporting: If you use a 3rd party bug tracking tool like Honeybadger or Sentry, api_error_handler will notify this service of the error for you so you don't have to!
  • Content type setting: api_error_handler will set the content type of the response based on the format of response body.

Installation

Add this line to your application's Gemfile:

gem 'api_error_handler'

And then execute:

$ bundle install

Usage

To get started, all you need to do is invoke handle_api_errors inside your controller like so:

class MyController < ActionController::API
  handle_api_errors()

  def index
    raise "Something is very very wrong!"
  end
end

Now when you go to MyController#index, your API will return the following response:

HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{
  "error": {
    "title":"Internal Server Error",
    "detail":"Something is very very wrong!"
  }
}

Error handling options

handle_api_errors implements a bunch of (hopefully) sensible defaults so that all you need to do is invoke handle_api_errors() in your controller to get useful error handling! However, in all likelihood you'll want to override some of these options. This section gives details on the various options available for configuring the api_error_handler.

Response Body Options

By default, handle_api_errors picks the :json format for serializing errors. However, this gem comes with a number of other formats for serializing your errors.

JSON (the default)
  handle_api_errors(format: :json)
  # Or
  handle_api_errors()
HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{
  "error": {
    "title":"Internal Server Error",
    "detail":"Something is very very wrong!"
  }
}
JSON:API

If your API follows the JSON:API spec, you'll want to use the :json_api format option.

  handle_api_errors(format: :json_api)

Responses with this format will follow the JSON:API specification for error objects. This will look something like this:

HTTP/1.1 500 Internal Server Error
Content-Type: application/vnd.api+json

{
  "errors": [
    {
      "status":"500",
      "title":"Internal Server Error",
      "detail":"Something is very very wrong!"
    }
  ]
}
XML
  handle_api_errors(format: :xml)
<?xml version="1.0" encoding="UTF-8"?>
<Error>
  <Title>Internal Server Error</title>
  <Detail>Something is very very wrong!</detail>
</Error>
Custom Error Responses

If none of the out-of-the-box options suit you then you can pass in your own error serializer like so:

  handle_api_errors(serializer: MyCustomErrorSerializer)

The custom serializer must implement two instance methods, serialize and render_format. The serialize method should return the body of the response you want to render. The render_format should be the format that you want to render the response in (e.g :json, :xml, :plain), which will be passed to Rails' render method.

It is recommended you inherit your serializer from ApiErrorHandler::Serializers::BaseSerializer to gain some helpful instance methods and defaults.

class MyCustomErrorSerializer < ApiErrorHandler::Serializers::BaseSerializer
  def serialize(serializer_options)
    # The `title` and `status_code` come from the BaseSerializer.
    "Error! Title: #{title} Status Code: #{status_code}"
  end

  def render_format
    :plain
  end
end
Backtraces

If you want to include the error's backtrace in the response body:

  handle_api_errors(backtrace: true)
{
  "error": {
    "title":"Internal Server Error",
    "detail":"Something is very very wrong!",
    "backtrace": [
      # The backtrace
    ]
  }
}

HTTP Status Mapping

Most of the time, you'll want to set the HTTP status code based on the type of error being raised. To determine which errors map to which status codes, api_error_handler uses ActionDispatch::ExceptionWrapper.rescue_responses. If you're using Rails with ActiveRecord, by default this includes:

{
  "ActionController::RoutingError"               => :not_found,
  "AbstractController::ActionNotFound"           => :not_found,
  "ActionController::MethodNotAllowed"           => :method_not_allowed,
  "ActionController::UnknownHttpMethod"          => :method_not_allowed,
  "ActionController::NotImplemented"             => :not_implemented,
  "ActionController::UnknownFormat"              => :not_acceptable,
  "Mime::Type::InvalidMimeType"                  => :not_acceptable,
  "ActionController::MissingExactTemplate"       => :not_acceptable,
  "ActionController::InvalidAuthenticityToken"   => :unprocessable_entity,
  "ActionController::InvalidCrossOriginRequest"  => :unprocessable_entity,
  "ActionDispatch::Http::Parameters::ParseError" => :bad_request,
  "ActionController::BadRequest"                 => :bad_request,
  "ActionController::ParameterMissing"           => :bad_request,
  "Rack::QueryParser::ParameterTypeError"        => :bad_request,
  "Rack::QueryParser::InvalidParameterError"     => :bad_request
  "ActiveRecord::RecordNotFound"                 => :not_found,
  "ActiveRecord::StaleObjectError"               => :conflict,
  "ActiveRecord::RecordInvalid"                  => :unprocessable_entity,
  "ActiveRecord::RecordNotSaved"                 => :unprocessable_entity
}

You can add to this mapping on an application level by doing the following:

config.action_dispatch.rescue_responses.merge!(
  "AuthenticationError" => :not_authorized
)

Now when an you raise an AuthenticationError in one of your actions, the status code of the response will be 401.

Error Reporting

If you use an external error tracking software like Sentry or Honeybadger you'll want to report all errors to that service.

You can do so by passing in a Proc to the error_reporter option. The first argument provided to the Proc will be the error. The second argument will be the error_id if you have one. See the section below for more details on error IDs.

handle_api_errors(
  error_reporter: Proc.new do |error, error_id|
    Raven.capture_exception(error, error_id: error_id)
  end
)

Error IDs

Sometimes it's helpful to include IDs with your error responses so that you can correlate a specific error with a record in your logs or bug tracking software.

handle_api_errors(
  error_id: Proc.new { |error| SecureRandom.uuid }
)
{
  "error": {
    "title": "Internal Server Error",
    "detail": "Something is very very wrong!",
    "id": "4ab520f2-ae33-4539-9371-ea21aada5582"
  }
}

Setting Content Type

The api_error_handler will set the content type of your error based on the format option you pick. However, you can override this by setting the content_type option if you wish.

handle_api_errors(
  format: :json,
  content_type: 'application/vnd.api+json'
)
HTTP/1.1 500 Internal Server Error
Content-Type: application/vnd.api+json

{
  "error": {
    "title":"Internal Server Error",
    "detail":"Something is very very wrong!"
  }
}

License

The gem is available as open source under the terms of the MIT License.