Renderror

Renderror makes it easy to render JSON:Api compliant error messages from your Rails API.

Installation

Add this line to your application's Gemfile:

gem 'renderror'

And then execute:

$ bundle

Or install it yourself as:

$ gem install renderror

Then run the installer:

$ rails g renderror:install

Basic Usage

Out of the box Renderror allows you to call render_errors(errors) from anywhere in your controllers to render a simple json array of those errors. The only requirement is that the errors respond to .to_json and to .status. Out of the box they will not be converted to JSON:Api Syntax, to do that see the Custom Errors section of this Readme.

Auto Rescue

Renderror has the ability to automatically rescue from common exceptions and render the correct error messages. It just requires a small amount of setup.

class ApiController < ApplicationController
  renderror_auto_rescue :bad_request, :not_found, :invalid_document,
                        :cancan
end

We recommend including this in a base controller of your API to ensure that these exceptions are rescued everywhere. You can turn on an off as many of the auto rescue flags as you wish. More details on each of these options can be found below.

:bad_request

Including this will automatically rescue from ActionController::BadRequest and give the following response.

{
  "errors": [
    {
      "title": "Bad Request",
      "status": "400",
      "detail": "Bad Request"
    }
  ]
}

:not_found

Including this will automatically rescue from ActiveRecord::RecordNotFound and render a response formatted similiar to:

{
  "errors": [
    {
      "title": "Not Found",
      "status": "404",
      "detail": "Could not find User with id 1"
    }
  ]
}

:invalid_document

Ties into Active Model Serializers JsonApi::Deserialization::InvalidDocument method, which is used to deserialize request params. If the request params are determined to be invalid, Renderror will rescue and respond with a Bad Request status and error.

:cancan

Ties into the CanCanCan gem to allow your app to auto rescue from CanCan::AccessDenied and respond with a Forbidden error.

{
  "errors": [
    {
      "title": "Forbidden",
      "status": "403",
      "detail": "You are not authorized to access this resource"
    }
  ]
}

:conflict

This ties into renderror_validate mentioned below, and allows auto rescuing from Renderror::Conflict errors, responding with a Conflict error

{
  "errors": [
    {
      "title": "Conflict",
      "status": "409",
      "detail": "Incorrect type specified"
    }
  ]
}

Renderror Validate

As well as providing an auto rescue functionality, Renderror can also validate request params to ensure they have the correct type and id as specified by JSON:Api.

You can enable this in a similar way to auto rescue:

class ApiController < ApplicationController
  renderror_validate :jsonapi_type, :jsonapi_id
end

This will enable automatic type validate for all create and update actions, as well as validating that the id matches the query param id for all update requests. If this validation fails a Renderror::Conflict will be raised, which you can auto rescue from (as mentioned above).

Validating Custom Actions

Sometimes you may want to validate jsonapi_type or jsonapi_id for actions other than just create and update. If this is the case then you can simply pass them in as a before action and they will be handled the same way:

class UsersController < ApiController
  before_action :validate_jsonapi_type, only: :change_status
  before_action :validate_jsonapi_id,   only: :change_status

  def change_status
    # Your code here
  end
end

Additional Methods

There is also a number of situations where Renderror can be triggered manually, such as when wanting to render model errors or invalid authentication errors. You can do this using the following methods.

render_unprocessable(resource)

This can be called inside a controller to render validation errors

def create
  user = User.new(user_params)

  if user.save
    respond_with user
  else
    render_unprocessable user 
  end
end  

This would return the following JSON if the user failed validation

{
  "errors": [
    {
      "status": "422",
      "title": "Unprocessable Entity",
      "detail": "Name can't be blank",
      "source": {
        "pointer": "/data/attributes/name"  
      }
    },
    {
      "status": "422",
      "title": "Unprocessable Entity",
      "detail": "Email can't be blank",
      "source": {
        "pointer": "/data/attributes/email"  
      }
    }
  ]
}

render_invalid_authentication

This addon can be used to render errors when a user fails signin validation (either from Devise or a custom signin method)

def create
  use_case = ::Sessions::CreateSession.perform(session_attributes)

  if use_case.success?
    respond_with use_case.session
  else
    raise_invalid_authentication User
  end
end

render_reform_unprocessable(form)

Renderror also works with the Reform gem from Trailblazer. Similar to render_unprocessable, however takes an instance of a Reform::Form and renders the errors from there:

def create
  form = UserForm.new(User.new)

  if form.validate(form_attributes) && form.save
    respond_with form.model
  else
    render_reform_unprocessable form
  end
end

Customising Responses

After running the install command you can simply open the renderror.en.yml file to change any of the default titles and details for each message.

Custom Errors

If you with to make your own errors, you can do so by simply inheriting from the Renderror::BaseError class.

class ImATeapot < Renderror::BaseError

  def status
    '418'
  end

  private

  def default_title
    "I'm a teapot"
  end

  def default_detail
    "Short and Stout"
  end
end

From there you can either choose to raise the error, or render it. Both these methods can take title:, detail:, and pointer: keyword arguments if you wish to override the defaults

Example Raise

raise ImATeapot, title: "I'm a little teapot", detail: "Tip me over, pour me out"

Example Render

render_errors(ImATeapot.new)
{
  "errors": [
    {
      "title": "I'm a teapot",
      "status": "418",
      "detail": "Tip me over, pour me out"
    }
  ]
}

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/Papercloud/renderror. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

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

Code of Conduct

Everyone interacting in the Renderror project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.