JSONError

Gem Version Code Climate

Installation

Add this line to your application's Gemfile:

gem 'json_error'

And then execute:

$ bundle

Introduction

What is JSONError gem?

It provides parent class with simple DSL to define API errors. It is based on concept that error is something that went wrong (what? = key) under certain circumstances (when? = context).

Why would I use it?

To design, manage and maintain API's errors JSON responses with ease.

How to do it?

Using locale file (e.g. json_errors.en.yml).

# json_errors.en.yml example
en:
  json_errors:
    resource_not_found:
      message: 'Resource %{name} (%{id}) was not found.'
      description: 'Blah blah blah.'
      href: 'https://developers.api.pl/doc/errors/resource_not_found'
    batmans:
      resource_not_found:
        message: 'Resource Batman (%{id}) was not found.'
        description: "Cuz I'm Batman"

Usage

Understading JSONError

Let's take a look at example error class definition.

class Error < JSONError.base
  key :error
  status 500

  translated_properties :message, :href
  custom_properties :details, :some_dependency
  visible_properties :key, :status, :message, :href, :details
end

Key is the error identifier. It's not obligatory to specify its value here - you can also and pass it to the Error initializer when instantiating error object:

Error.new(key: :error)

however, json errors must have a key. Key is not inherited.

Status is the error http status. If not specified will default to 500. It may be expressed by integer or string.

Default properties are properties included to JSON output by default (:key, status: 500).

  • You can alias key property like in example below: ruby class SomeError < JSONError.base # There will be "error_code" within .to_json output instead of "key" key :error, as: :error_code end
  • You can change status in locale.

Translated properties are error attributes which values will be loaded from locale. Translated properties can be inherited.

Custom properties are attributes that won't be loaded from locale but passed to JSON output as they stand. It can be for exammple hash of ActiveRecord object validation error messages. Custom properties can be inherited.

Visible properties is optional whitelist for output JSON properties. By default all default, translated and custom properties are included in output. You can use visible_properties method to include only demanded subset. Visible properties are not inherited.

Defining JSONError

Let's introduce two example error classes now. They will be used in further examples.

module Errors
  class ResourceNotFound < JSONError.base
    key :resource_not_found
    status 404
    translated_properties :message, :href, :description
  end

  class OtherError < JSONError.base
    key :other_error
    status 500

    translated_properties :message
  end
end

Defining JSONError Properties

Just add entries to locale.

# config/locales/json_errors.en.yml
en:
  json_errors:
    resource_not_found:
      message: 'Resource %{name} (%{id}) was not found.'
      description: 'Blah blah blah.'
      href: 'https://developers.api.pl/doc/errors/resource_not_found'

Rendering JSONError

Rails

# app/controllers/batmans_controller.rb
class BatmansController < ApplicationController
  def index
    error = Errors::ResourceNotFound.new(name: :Batman, id: 2)
    render json: error, status: error.status
  end
end

# or

class BatmansController < ApplicationController
  def index
    raise Errors::ResourceNotFound, name: :Batman, id: 2
  rescue JSONError.base => e
    render json: e, status: e.status
  end
end

Sinatra

# Remember to load dependencies
class Application < Sinatra::Base
  before { content_type :json }

  get '/batmans' do
    error = Errors::ResourceNotFound.new(name: :Batman, id: 2)
    halt error.status, json(error)
  end
end

Responses

For ResourceNotFound

{
  "status": 404,
  "key": "resource_not_found",
  "message": "Resource Batman (2) was not found.",
  "description": "Blah blah blah.",
  "href": "https://developers.api.pl/doc/errors/resource_not_found"
}

For OtherError

{
  "status": 500,
  "key": "other_error",
  "message": null
}

Using Contexts

You can specify different contexts for the same error key.

# In locale

en:
  json_errors:
    resource_not_found:
      message: 'Resource %{name} (%{id}) was not found.'
      description: 'Blah blah blah.'
      href: 'https://developers.api.pl/doc/errors/resource_not_found'
    batmans:
      resource_not_found:
        message: 'Resource Batman (%{id}) was not found.'
        description: "Cuz I'm Batman"
# In constructor

Errors::ResourceNotFound.new(context: 'batmans', id: 2)
// And then response will look like below

{
  "status": 404,
  "key": "resource_not_found",
  "message": "Resource Batman (2) was not found.",
  "description": "Cuz I'm Batman",
  "href": null
}

Contributing

Questions, propositions, bug reports and pull requests are welcome.

License

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