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 anActiveRecord::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.
Add this line to your application's Gemfile:
gem 'api_error_handler'
And then execute:
$ bundle install
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!"
}
}
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
.
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.
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!"
}
}
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!"
}
]
}
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>
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
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
]
}
}
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" => :unauthorized
)
Now when an you raise an AuthenticationError
in one of your actions, the
status code of the response will be 401.
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.
For this you can use the error_id
option.
You can either use the UUID error strategy
handle_api_errors(error_id: :uuid)
Or pass a Proc if you need to do something custom.
handle_api_errors(error_id: Proc.new { |error| SecureRandom.uuid })
These will result in:
{
"error": {
"title": "Internal Server Error",
"detail": "Something is very very wrong!",
"id": "4ab520f2-ae33-4539-9371-ea21aada5582"
}
}
If you use an external error tracking software like Sentry or Honeybadger, you'll want to report all errors to that service.
There are a few supported error reporter options that you can select.
handle_api_errors(error_reporter: :raven)
# Or
handle_api_errors(error_reporter: :sentry)
handle_api_errors(error_reporter: :honeybadger)
NOTE: If you use the :error_id
option, the error error reporter will tag
the exception with the error ID when reporting the error.
If none of the out of the box options work for you, you can pass in a proc which will receive the error and the error_id as arguments.
handle_api_errors(
error_reporter: Proc.new do |error, error_id|
# Do something with the `error` here.
end
)
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!"
}
}
The gem is available as open source under the terms of the MIT License.