Skip to content

Commit ed5688f

Browse files
First release
0 parents  commit ed5688f

File tree

107 files changed

+2251
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+2251
-0
lines changed

.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/.bundle/
2+
/.yardoc
3+
/_yardoc/
4+
/coverage/
5+
/doc/
6+
/pkg/
7+
/spec/reports/
8+
/tmp/
9+
10+
/Gemfile.lock
11+
12+
# rspec failure tracking
13+
.rspec_status
14+
15+
# Stuff to ignore in the test_app
16+
test_app/tmp
17+
test_app/log
18+
test_app/.byebug_history
19+
test_app/log/development.log

.rspec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--format documentation
2+
--color
3+
--require spec_helper

.travis.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
sudo: false
3+
language: ruby
4+
rvm:
5+
- 2.4
6+
cache: bundler
7+
before_install:
8+
- gem update --system
9+
- gem install bundler
10+
script: ./bin/test.sh

Gemfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
source "https://rubygems.org"
2+
3+
# Specify your gem's dependencies in api_error_handler.gemspec
4+
gemspec

LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2019 James Stonehill
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# ApiErrorHandler
2+
[![Build Status](https://travis-ci.org/jamesstonehill/api_error_handler.svg?branch=master)](https://travis-ci.org/jamesstonehill/api_error_handler)
3+
4+
Are your API error responses not all that you want them to be? If so, you've
5+
found the right gem! `api_error_handler` handles all aspects of returning
6+
informative, spec-compliant responses to clients when your application
7+
encounters an error in the course of processing a response.
8+
9+
This "handling" includes:
10+
- __Error serialization__: each response will include a response body that
11+
gives some information on the type of error that your application
12+
encountered. See the [Responses Body Options](#response-body-options)
13+
section for details and configuration options.
14+
- __Status code setting__: `api_error_handler` will set the HTTP status code of
15+
the response based on the type of error that is raised. For example, when an
16+
`ActiveRecord::RecordNotFound` error is raised, it will set the response
17+
status to 404. See the [HTTP Status Mapping](#http-status-mapping) section
18+
for details and configuration options.
19+
- __Error reporting__: If you use a 3rd party bug tracking
20+
tool like Honeybadger or Sentry, `api_error_handler` will notify this
21+
service of the error for you so you don't have to!
22+
- __Content type setting__: `api_error_handler` will set the content type of the
23+
response based on the format of response body.
24+
25+
## Installation
26+
27+
Add this line to your application's Gemfile:
28+
29+
```ruby
30+
gem 'api_error_handler'
31+
```
32+
33+
And then execute:
34+
35+
$ bundle install
36+
37+
## Usage
38+
39+
To get started, all you need to do is invoke `handle_api_errors` inside your
40+
controller like so:
41+
42+
```ruby
43+
class MyController < ActionController::API
44+
handle_api_errors()
45+
46+
def index
47+
raise "Something is very very wrong!"
48+
end
49+
end
50+
```
51+
52+
Now when you go to `MyController#index`, your API will return the following
53+
response:
54+
55+
```json
56+
HTTP/1.1 500 Internal Server Error
57+
Content-Type: application/json
58+
59+
{
60+
"error": {
61+
"title":"Internal Server Error",
62+
"detail":"Something is very very wrong!"
63+
}
64+
}
65+
```
66+
67+
### Error handling options
68+
69+
`handle_api_errors` implements a bunch of (hopefully) sensible defaults so that
70+
all you need to do is invoke `handle_api_errors()` in your controller to get
71+
useful error handling! However, in all likelihood you'll want to override some
72+
of these options. This section gives details on the various options available
73+
for configuring the `api_error_handler`.
74+
75+
#### Response Body Options
76+
By default, `handle_api_errors` picks the `:json` format for serializing errors.
77+
However, this gem comes with a number of other formats for serializing your
78+
errors.
79+
80+
##### JSON (the default)
81+
```ruby
82+
handle_api_errors(format: :json)
83+
# Or
84+
handle_api_errors()
85+
```
86+
87+
```json
88+
HTTP/1.1 500 Internal Server Error
89+
Content-Type: application/json
90+
91+
{
92+
"error": {
93+
"title":"Internal Server Error",
94+
"detail":"Something is very very wrong!"
95+
}
96+
}
97+
```
98+
99+
##### JSON:API
100+
If your API follows the `JSON:API` spec, you'll want to use the `:json_api`
101+
format option.
102+
103+
```ruby
104+
handle_api_errors(format: :json_api)
105+
```
106+
107+
Responses with this format will follow the `JSON:API` [specification for error
108+
objects](https://jsonapi.org/format/#error-objects). This will look something
109+
like this:
110+
111+
```json
112+
HTTP/1.1 500 Internal Server Error
113+
Content-Type: application/vnd.api+json
114+
115+
{
116+
"errors": [
117+
{
118+
"status":"500",
119+
"title":"Internal Server Error",
120+
"detail":"Something is very very wrong!"
121+
}
122+
]
123+
}
124+
```
125+
126+
##### XML
127+
```ruby
128+
handle_api_errors(format: :xml)
129+
```
130+
131+
```xml
132+
<?xml version="1.0" encoding="UTF-8"?>
133+
<Error>
134+
<Title>Internal Server Error</title>
135+
<Detail>Something is very very wrong!</detail>
136+
</Error>
137+
```
138+
139+
##### Custom Error Responses
140+
If none of the out-of-the-box options suit you then you can pass in your own
141+
error serializer like so:
142+
143+
```ruby
144+
handle_api_errors(serializer: MyCustomErrorSerializer)
145+
```
146+
147+
The custom serializer must implement two instance methods, `serialize` and
148+
`render_format`. The `serialize` method should return the body of the response
149+
you want to render. The `render_format` should be the format that you want to
150+
render the response in (e.g `:json`, `:xml`, `:plain`), which will be passed to
151+
Rails' `render` method.
152+
153+
It is recommended you inherit your serializer from
154+
`ApiErrorHandler::Serializers::BaseSerializer` to gain some helpful instance
155+
methods and defaults.
156+
157+
```ruby
158+
class MyCustomErrorSerializer < ApiErrorHandler::Serializers::BaseSerializer
159+
def serialize(serializer_options)
160+
# The `title` and `status_code` come from the BaseSerializer.
161+
"Error! Title: #{title} Status Code: #{status_code}"
162+
end
163+
164+
def render_format
165+
:plain
166+
end
167+
end
168+
```
169+
##### Backtraces
170+
If you want to include the error's backtrace in the response body:
171+
172+
```ruby
173+
handle_api_errors(backtrace: true)
174+
```
175+
176+
```json
177+
{
178+
"error": {
179+
"title":"Internal Server Error",
180+
"detail":"Something is very very wrong!",
181+
"backtrace": [
182+
# The backtrace
183+
]
184+
}
185+
}
186+
```
187+
188+
### HTTP Status Mapping
189+
190+
Most of the time, you'll want to set the HTTP status code based on the type of
191+
error being raised. To determine which errors map to which status codes,
192+
`api_error_handler` uses `ActionDispatch::ExceptionWrapper.rescue_responses`. If
193+
you're using Rails with ActiveRecord, by default this includes:
194+
195+
```ruby
196+
{
197+
"ActionController::RoutingError" => :not_found,
198+
"AbstractController::ActionNotFound" => :not_found,
199+
"ActionController::MethodNotAllowed" => :method_not_allowed,
200+
"ActionController::UnknownHttpMethod" => :method_not_allowed,
201+
"ActionController::NotImplemented" => :not_implemented,
202+
"ActionController::UnknownFormat" => :not_acceptable,
203+
"Mime::Type::InvalidMimeType" => :not_acceptable,
204+
"ActionController::MissingExactTemplate" => :not_acceptable,
205+
"ActionController::InvalidAuthenticityToken" => :unprocessable_entity,
206+
"ActionController::InvalidCrossOriginRequest" => :unprocessable_entity,
207+
"ActionDispatch::Http::Parameters::ParseError" => :bad_request,
208+
"ActionController::BadRequest" => :bad_request,
209+
"ActionController::ParameterMissing" => :bad_request,
210+
"Rack::QueryParser::ParameterTypeError" => :bad_request,
211+
"Rack::QueryParser::InvalidParameterError" => :bad_request
212+
"ActiveRecord::RecordNotFound" => :not_found,
213+
"ActiveRecord::StaleObjectError" => :conflict,
214+
"ActiveRecord::RecordInvalid" => :unprocessable_entity,
215+
"ActiveRecord::RecordNotSaved" => :unprocessable_entity
216+
}
217+
```
218+
- https://guides.rubyonrails.org/configuring.html#configuring-action-dispatch
219+
220+
You can add to this mapping on an application level by doing the following:
221+
```ruby
222+
config.action_dispatch.rescue_responses.merge!(
223+
"AuthenticationError" => :not_authorized
224+
)
225+
```
226+
227+
Now when an you raise an `AuthenticationError` in one of your actions, the
228+
status code of the response will be 401.
229+
230+
### Error Reporting
231+
If you use an external error tracking software like Sentry or Honeybadger you'll
232+
want to report all errors to that service.
233+
234+
You can do so by passing in a `Proc` to the `error_reporter` option. The first
235+
argument provided to the Proc will be the error. The second argument will be the
236+
`error_id` if you have one. See the section below for more details on error IDs.
237+
238+
```ruby
239+
handle_api_errors(
240+
error_reporter: Proc.new do |error, error_id|
241+
Raven.capture_exception(error, error_id: error_id)
242+
end
243+
)
244+
```
245+
246+
### Error IDs
247+
Sometimes it's helpful to include IDs with your error responses so that you can
248+
correlate a specific error with a record in your logs or bug tracking software.
249+
250+
```ruby
251+
handle_api_errors(
252+
error_id: Proc.new { |error| SecureRandom.uuid }
253+
)
254+
```
255+
256+
```json
257+
{
258+
"error": {
259+
"title": "Internal Server Error",
260+
"detail": "Something is very very wrong!",
261+
"id": "4ab520f2-ae33-4539-9371-ea21aada5582"
262+
}
263+
}
264+
```
265+
266+
### Setting Content Type
267+
The api_error_handler will set the content type of your error based on the
268+
`format` option you pick. However, you can override this by setting the
269+
`content_type` option if you wish.
270+
271+
```ruby
272+
handle_api_errors(
273+
format: :json,
274+
content_type: 'application/vnd.api+json'
275+
)
276+
```
277+
278+
```json
279+
HTTP/1.1 500 Internal Server Error
280+
Content-Type: application/vnd.api+json
281+
282+
{
283+
"error": {
284+
"title":"Internal Server Error",
285+
"detail":"Something is very very wrong!"
286+
}
287+
}
288+
```
289+
290+
## License
291+
292+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).

Rakefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
require "bundler/gem_tasks"
2+
require "rspec/core/rake_task"
3+
4+
RSpec::Core::RakeTask.new(:spec)
5+
6+
task :default => :spec

api_error_handler.gemspec

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
lib = File.expand_path("../lib", __FILE__)
3+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4+
require "api_error_handler/version"
5+
6+
Gem::Specification.new do |spec|
7+
spec.name = "api_error_handler"
8+
spec.version = ApiErrorHandler::VERSION
9+
spec.authors = ["James Stonehill"]
10+
spec.email = ["james.stonehill@gmail.com"]
11+
spec.required_ruby_version = '~> 2.2'
12+
13+
spec.summary = %q{A gem that helps you easily handle exceptions in your Rails API and return informative responses to the client.}
14+
spec.description = %q{A gem that helps you easily handle exceptions in your Ruby on Rails API and return informative responses to the client by serializing exceptions into JSON and other popular API formats and returning a response with a status code that makes sense based on the exception.}
15+
spec.homepage = "https://github.com/jamesstonehill/api_error_handler"
16+
spec.license = "MIT"
17+
18+
# Specify which files should be added to the gem when it is released.
19+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|test_app)/}) }
22+
end
23+
spec.bindir = "exe"
24+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25+
spec.require_paths = ["lib"]
26+
27+
spec.add_dependency "activesupport", ">= 4.0"
28+
spec.add_dependency "actionpack", ">= 4.0"
29+
spec.add_dependency "rack", ">= 1.0"
30+
31+
spec.add_development_dependency "bundler", "~> 2.0"
32+
spec.add_development_dependency "rake", "~> 10.0"
33+
spec.add_development_dependency "rspec-rails", "~> 3.0"
34+
end

0 commit comments

Comments
 (0)