api_error_handler 0.1.0.rc → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 136050d80b1f4f57e427a7b6d626d898824a09ab
4
- data.tar.gz: eef41026be0cf4ec1db00bce32f368d52cda3863
2
+ SHA256:
3
+ metadata.gz: 18b6ef31f241b4845ddbf59be8721e55a218a84fb4d39fad730b2c6941e616fe
4
+ data.tar.gz: 9881362b337b651e73c2472996f6e4c959660bec85fe9856d7a8c54f2b4206e8
5
5
  SHA512:
6
- metadata.gz: d1e3dad2f964a603860c5cbd8d106d47060e2c43735653dd5fda161daff4b2f9708ea77985abf29efea4af14fda20f7c369192edcd749a1f48a6a5b3beedfd85
7
- data.tar.gz: ee295410c74e4dcf66f6e6dc4a34a485f342a6fc35ddacb37667e05ba2a01350a9dfda2bb2ec450310b707f4f371028e2b4e41771de6d4d3105bca6429c55021
6
+ metadata.gz: e4c5ed604f7295e4b1e54738aafd43d3288ce98a8f93b5e18897077f4691b713b08db3db1d1cb06c988ec62aae5f51f02aa8d4bfed3ed795e89de9cf360ed2fd
7
+ data.tar.gz: 8cc44416b4ecadc12b17ab728f1ce30e067d53582fae1b2891e6a5b7a6acacab2ed12be1e33ed93250f2bdd7110e5becc76069f36698b74f8ee1571fef53c06c
data/.gitignore CHANGED
@@ -7,8 +7,20 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
 
10
+ /Gemfile.lock
11
+
10
12
  # rspec failure tracking
11
13
  .rspec_status
12
14
 
13
- # Stuff to ignore in the test_app
14
- test_app/tmp
15
+ # Stuff to ignore in the test_apps
16
+ rails_5_test_app/tmp
17
+ rails_5_test_app/log
18
+ rails_5_test_app/.byebug_history
19
+
20
+ rails_4_test_app/tmp
21
+ rails_4_test_app/log
22
+ rails_4_test_app/.byebug_history
23
+
24
+ rails_6_test_app/tmp
25
+ rails_6_test_app/log
26
+ rails_6_test_app/.byebug_history
data/.rubocop.yml ADDED
@@ -0,0 +1,30 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ AllCops:
4
+ TargetRubyVersion: 2.5
5
+ Exclude:
6
+ - 'vendor/**/*'
7
+ - 'rails_*/**/*'
8
+
9
+ Style/StringLiterals:
10
+ EnforcedStyle: double_quotes
11
+
12
+ Metrics/LineLength:
13
+ Max: 100
14
+
15
+ Style/MethodCallWithoutArgsParentheses:
16
+ Enabled: false
17
+
18
+ Style/TrailingCommaInArrayLiteral:
19
+ EnforcedStyleForMultiline: comma
20
+
21
+ Style/TrailingCommaInHashLiteral:
22
+ EnforcedStyleForMultiline: comma
23
+
24
+ Metrics/BlockLength:
25
+ Exclude:
26
+ - 'spec/**/*.rb'
27
+ - 'api_error_handler.gemspec'
28
+
29
+ Style/Documentation:
30
+ Enabled: false
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,85 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 100`
3
+ # on 2019-08-25 13:23:25 +0100 using RuboCop version 0.74.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ # Cop supports --auto-correct.
11
+ # Configuration parameters: TreatCommentsAsGroupSeparators, Include.
12
+ # Include: **/*.gemspec
13
+ Gemspec/OrderedDependencies:
14
+ Exclude:
15
+ - 'api_error_handler.gemspec'
16
+
17
+ # Offense count: 1
18
+ # Cop supports --auto-correct.
19
+ Layout/ClosingParenthesisIndentation:
20
+ Exclude:
21
+ - 'spec/api_error_handler/serializers/xml_spec.rb'
22
+
23
+ # Offense count: 2
24
+ # Configuration parameters: Max.
25
+ Metrics/AbcSize:
26
+ Exclude:
27
+ - 'lib/api_error_handler.rb'
28
+ - 'lib/api_error_handler/error_reporter.rb'
29
+
30
+ # Offense count: 1
31
+ # Configuration parameters: Max.
32
+ Metrics/CyclomaticComplexity:
33
+ Exclude:
34
+ - 'lib/api_error_handler/error_reporter.rb'
35
+
36
+ # Offense count: 3
37
+ # Configuration parameters: CountComments, Max, ExcludedMethods.
38
+ Metrics/MethodLength:
39
+ Exclude:
40
+ - 'lib/api_error_handler.rb'
41
+ - 'lib/api_error_handler/error_reporter.rb'
42
+ - 'lib/api_error_handler/error_id_generator.rb'
43
+ - 'lib/api_error_handler/serializers/json_api.rb'
44
+
45
+ # Offense count: 1
46
+ # Configuration parameters: Max.
47
+ Metrics/PerceivedComplexity:
48
+ Exclude:
49
+ - 'lib/api_error_handler/error_reporter.rb'
50
+
51
+ # Offense count: 2
52
+ # Cop supports --auto-correct.
53
+ Style/ExpandPathArguments:
54
+ Exclude:
55
+ - 'api_error_handler.gemspec'
56
+
57
+ # Offense count: 4
58
+ # Cop supports --auto-correct.
59
+ # Configuration parameters: EnforcedStyle.
60
+ # SupportedStyles: implicit, explicit
61
+ Style/RescueStandardError:
62
+ Exclude:
63
+ - 'lib/api_error_handler.rb'
64
+ - 'spec/api_error_handler/serializers/json_api_spec.rb'
65
+ - 'spec/api_error_handler/serializers/json_spec.rb'
66
+ - 'spec/api_error_handler/serializers/xml_spec.rb'
67
+
68
+ # Offense count: 2
69
+ # Cop supports --auto-correct.
70
+ # Configuration parameters: EnforcedStyleForMultiline.
71
+ # SupportedStylesForMultiline: comma, consistent_comma, no_comma
72
+ Style/TrailingCommaInArrayLiteral:
73
+ Exclude:
74
+ - 'lib/api_error_handler/serializers/json_api.rb'
75
+ - 'spec/api_error_handler/serializers/json_api_spec.rb'
76
+
77
+ # Offense count: 3
78
+ # Cop supports --auto-correct.
79
+ # Configuration parameters: EnforcedStyleForMultiline.
80
+ # SupportedStylesForMultiline: comma, consistent_comma, no_comma
81
+ Style/TrailingCommaInHashLiteral:
82
+ Exclude:
83
+ - 'lib/api_error_handler.rb'
84
+ - 'lib/api_error_handler/serializers/json.rb'
85
+ - 'lib/api_error_handler/serializers/json_api.rb'
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.8
data/.travis.yml CHANGED
@@ -1,7 +1,36 @@
1
1
  ---
2
- sudo: false
3
2
  language: ruby
3
+ before_install:
4
+ - gem update --system
5
+ - gem install bundler
6
+ install: bundle install --jobs=3 --retry=3
4
7
  cache: bundler
5
- rvm:
6
- - 2.6.3
7
- before_install: gem install bundler -v 2.0.1
8
+ branches:
9
+ only:
10
+ - master
11
+ jobs:
12
+ include:
13
+ - rvm: 2.5.1
14
+ gemfile: ./rails_5_test_app/Gemfile
15
+ script: cd rails_5_test_app && bundle exec rspec
16
+ - rvm: 2.5.1
17
+ gemfile: ./rails_6_test_app/Gemfile
18
+ script: cd rails_6_test_app && bundle exec rspec
19
+ - rvm: 2.7.2
20
+ gemfile: ./rails_6_test_app/Gemfile
21
+ script: cd rails_6_test_app && bundle exec rspec
22
+ - rvm: 3.0.0
23
+ gemfile: ./rails_6_test_app/Gemfile
24
+ script: cd rails_6_test_app && bundle exec rspec
25
+ - rvm: 2.7.2
26
+ gemfile: Gemfile
27
+ script: bundle exec rubocop
28
+ - rvm: 2.7.2
29
+ gemfile: Gemfile
30
+ script: bundle exec rspec
31
+ - rvm: 3.0.0
32
+ gemfile: Gemfile
33
+ script: bundle exec rubocop
34
+ - rvm: 3.0.0
35
+ gemfile: Gemfile
36
+ script: bundle exec rspec
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
 
3
5
  # Specify your gem's dependencies in api_error_handler.gemspec
data/README.md CHANGED
@@ -1,8 +1,26 @@
1
1
  # ApiErrorHandler
2
+ [![Build Status](https://travis-ci.org/jamesstonehill/api_error_handler.svg?branch=master)](https://travis-ci.org/jamesstonehill/api_error_handler)
2
3
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/api_error_handler`. To experiment with that code, run `bin/console` for an interactive prompt.
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.
4
8
 
5
- TODO: Delete this and the text above, and describe your gem
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.
6
24
 
7
25
  ## Installation
8
26
 
@@ -14,25 +32,284 @@ gem 'api_error_handler'
14
32
 
15
33
  And then execute:
16
34
 
17
- $ bundle
35
+ $ bundle install
18
36
 
19
- Or install it yourself as:
37
+ ## Usage
20
38
 
21
- $ gem install api_error_handler
39
+ To get started, all you need to do is invoke `handle_api_errors` inside your
40
+ controller like so:
22
41
 
23
- ## Usage
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
24
163
 
25
- TODO: Write usage instructions here
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" => :unauthorized
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.
26
229
 
27
- ## Development
230
+ ### Error IDs
231
+ Sometimes it's helpful to include IDs with your error responses so that you can
232
+ correlate a specific error with a record in your logs or bug tracking software.
233
+ For this you can use the `error_id` option.
28
234
 
29
- 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.
235
+ You can either use the UUID error strategy
236
+ ```ruby
237
+ handle_api_errors(error_id: :uuid)
238
+ ```
30
239
 
31
- 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](https://rubygems.org).
240
+ Or pass a Proc if you need to do something custom.
241
+ ```ruby
242
+ handle_api_errors(error_id: Proc.new { |error| SecureRandom.uuid })
243
+ ```
32
244
 
33
- ## Contributing
245
+ These will result in:
246
+ ```json
247
+ {
248
+ "error": {
249
+ "title": "Internal Server Error",
250
+ "detail": "Something is very very wrong!",
251
+ "id": "4ab520f2-ae33-4539-9371-ea21aada5582"
252
+ }
253
+ }
254
+ ```
34
255
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/api_error_handler.
256
+ ### Error Reporting
257
+ If you use an external error tracking software like Sentry or Honeybadger, you'll
258
+ want to report all errors to that service.
259
+
260
+ #### Out of the Box Error Reporting
261
+ There are a few supported error reporter options that you can select.
262
+
263
+ ##### Raven/Sentry
264
+ ```ruby
265
+ handle_api_errors(error_reporter: :raven)
266
+ # Or
267
+ handle_api_errors(error_reporter: :sentry)
268
+ ```
269
+
270
+ ##### Honeybadger
271
+ ```ruby
272
+ handle_api_errors(error_reporter: :honeybadger)
273
+ ```
274
+
275
+ __NOTE:__ If you use the `:error_id` option, the error error reporter will tag
276
+ the exception with the error ID when reporting the error.
277
+
278
+ #### Custom Reporting
279
+ If none of the out of the box options work for you, you can pass in a proc which
280
+ will receive the error and the error_id as arguments.
281
+
282
+ ```ruby
283
+ handle_api_errors(
284
+ error_reporter: Proc.new do |error, error_id|
285
+ # Do something with the `error` here.
286
+ end
287
+ )
288
+ ```
289
+
290
+ ### Setting Content Type
291
+ The api_error_handler will set the content type of your error based on the
292
+ `format` option you pick. However, you can override this by setting the
293
+ `content_type` option if you wish.
294
+
295
+ ```ruby
296
+ handle_api_errors(
297
+ format: :json,
298
+ content_type: 'application/vnd.api+json'
299
+ )
300
+ ```
301
+
302
+ ```json
303
+ HTTP/1.1 500 Internal Server Error
304
+ Content-Type: application/vnd.api+json
305
+
306
+ {
307
+ "error": {
308
+ "title":"Internal Server Error",
309
+ "detail":"Something is very very wrong!"
310
+ }
311
+ }
312
+ ```
36
313
 
37
314
  ## License
38
315
 
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require "rspec/core/rake_task"
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  lib = File.expand_path("../lib", __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
@@ -7,27 +8,40 @@ Gem::Specification.new do |spec|
7
8
  spec.name = "api_error_handler"
8
9
  spec.version = ApiErrorHandler::VERSION
9
10
  spec.authors = ["James Stonehill"]
10
- spec.email = ["jamesstonehill@gmail.com"]
11
+ spec.email = ["james.stonehill@gmail.com"]
12
+ spec.required_ruby_version = ">= 2.5"
13
+
14
+ spec.summary = <<~SUMMARY
15
+ A gem that helps you easily handle exceptions in your Rails API and return
16
+ informative responses to the client.
17
+ SUMMARY
18
+
19
+ spec.description = <<~DESCRIPTION
20
+ A gem that helps you easily handle exceptions in your Ruby on Rails API and
21
+ return informative responses to the client by serializing exceptions into JSON
22
+ and other popular API formats and returning a response with a status code that
23
+ makes sense based on the exception.
24
+ DESCRIPTION
11
25
 
12
- spec.summary = %q{A gem that helps you easily handle exptions in your Rails API and return informative responses to the client.}
13
- spec.description = %q{A gem that helps you easily handle exptions 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.}
14
26
  spec.homepage = "https://github.com/jamesstonehill/api_error_handler"
15
27
  spec.license = "MIT"
16
28
 
17
29
  # Specify which files should be added to the gem when it is released.
18
30
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|test_app)/}) }
31
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
32
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|rails_.+)/}) }
21
33
  end
22
34
  spec.bindir = "exe"
23
35
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
36
  spec.require_paths = ["lib"]
25
37
 
26
- spec.add_dependency "activesupport", ">= 4.1.0"
27
- spec.add_dependency "actionpack"
38
+ spec.add_dependency "activesupport", ">= 5.0"
39
+ spec.add_dependency "actionpack", ">= 5.0"
40
+ spec.add_dependency "rack", ">= 1.0"
28
41
 
29
42
  spec.add_development_dependency "bundler", "~> 2.0"
30
- spec.add_development_dependency "rake", "~> 10.0"
31
- spec.add_development_dependency "rspec-rails", "~> 3.0"
32
- spec.add_development_dependency "pry"
43
+ spec.add_development_dependency "rake", "~> 13.0"
44
+ spec.add_development_dependency "rspec-rails", "~> 3.9"
45
+ spec.add_development_dependency "rubocop", "~> 0.80.0"
46
+ spec.add_development_dependency "pry-byebug"
33
47
  end
data/bin/console CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require "bundler/setup"
4
5
  require "api_error_handler"
@@ -1,7 +1,9 @@
1
- require 'active_support/lazy_load_hooks'
2
- require 'action_controller'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/lazy_load_hooks"
4
+ require "action_controller"
3
5
 
4
6
  ActiveSupport.on_load :action_controller do
5
- ::ActionController::Base.send :extend, ApiErrorHandler
6
- ::ActionController::API.send :extend, ApiErrorHandler
7
+ ::ActionController::Base.extend ApiErrorHandler
8
+ ::ActionController::API.extend ApiErrorHandler
7
9
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require_relative "./errors"
5
+
6
+ module ApiErrorHandler
7
+ class ErrorIdGenerator
8
+ def self.run(error_id_option)
9
+ if error_id_option.instance_of?(Proc)
10
+ error_id_option.call
11
+ elsif error_id_option == :uuid
12
+ SecureRandom.uuid
13
+ elsif error_id_option.nil?
14
+ nil
15
+ else
16
+ raise(
17
+ InvalidOptionError,
18
+ "Unable to handle `#{error_id_option}` as argument for the `:error_id` option."
19
+ )
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require_relative "./errors"
5
+
6
+ module ApiErrorHandler
7
+ class ErrorReporter
8
+ def initialize(strategy)
9
+ @strategy = strategy
10
+ end
11
+
12
+ def report(error, error_id: nil)
13
+ if @strategy.nil?
14
+ true
15
+ elsif @strategy.instance_of?(Proc)
16
+ @strategy.call(error, error_id)
17
+ elsif @strategy == :honeybadger
18
+ raise_dependency_error(missing_constant: "Honeybadger") unless defined?(Honeybadger)
19
+
20
+ context = error_id ? { error_id: error_id } : {}
21
+ Honeybadger.notify(error, context: context)
22
+ elsif @strategy == :raven
23
+ raise_dependency_error(missing_constant: "Raven") unless defined?(Raven)
24
+
25
+ extra = error_id ? { error_id: error_id } : {}
26
+ Raven.capture_exception(error, extra: extra)
27
+ elsif @strategy == :sentry
28
+ raise_dependency_error(missing_constant: "Sentry") unless defined?(Sentry)
29
+
30
+ extra = error_id ? { error_id: error_id } : {}
31
+ Sentry.capture_exception(error, extra: extra)
32
+ else
33
+ raise(
34
+ InvalidOptionError,
35
+ "`#{@strategy.inspect}` is an invalid argument for the `:error_id` option."
36
+ )
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def raise_dependency_error(missing_constant:)
43
+ raise MissingDependencyError, <<~MESSAGE
44
+ You selected the #{@strategy.inspect} error reporter option but the
45
+ #{missing_constant} constant is not defined. If you wish to use this
46
+ error reporting option you must have the #{@strategy} client gem
47
+ installed.
48
+ MESSAGE
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApiErrorHandler
4
+ class Error < StandardError; end
5
+
6
+ class InvalidOptionError < Error; end
7
+ class MissingDependencyError < Error; end
8
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/utils"
4
+
5
+ module ApiErrorHandler
6
+ module Serializers
7
+ class BaseSerializer
8
+ DEFAULT_STATUS_CODE = "500"
9
+
10
+ def initialize(error, status)
11
+ @error = error
12
+ @status = status
13
+ end
14
+
15
+ def status_code
16
+ Rack::Utils::SYMBOL_TO_STATUS_CODE.fetch(@status, DEFAULT_STATUS_CODE).to_s
17
+ end
18
+
19
+ def title
20
+ Rack::Utils::HTTP_STATUS_CODES.fetch(status_code.to_i)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,6 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base_serializer"
4
+
1
5
  module ApiErrorHandler
2
6
  module Serializers
3
- class Json
7
+ class Json < BaseSerializer
8
+ # There is no official spec that governs this error response format so
9
+ # this serializer is just trying to impliment a simple response with
10
+ # sensible defaults.
11
+ #
12
+ # I borrowed heavily from Facebook's error response format since it seems
13
+ # to be a reasonable approach for a simple light-weight error response.
14
+
15
+ def serialize(options = {})
16
+ body = {
17
+ error: {
18
+ title: title,
19
+ detail: @error.message,
20
+ }
21
+ }
22
+
23
+ body[:error][:id] = options[:error_id] if options[:error_id]
24
+ body[:error][:backtrace] = @error.backtrace if options[:backtrace]
25
+
26
+ body.to_json
27
+ end
28
+
29
+ def render_format
30
+ :json
31
+ end
4
32
  end
5
33
  end
6
34
  end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base_serializer"
4
+
5
+ module ApiErrorHandler
6
+ module Serializers
7
+ class JsonApi < BaseSerializer
8
+ def serialize(options = {})
9
+ body = {
10
+ errors: [
11
+ {
12
+ status: status_code,
13
+ title: title,
14
+ detail: @error.message,
15
+ }
16
+ ]
17
+ }
18
+
19
+ body[:errors].first[:id] = options[:error_id] if options[:error_id]
20
+ body[:errors].first[:meta] = { backtrace: @error.backtrace } if options[:backtrace]
21
+
22
+ body.to_json
23
+ end
24
+
25
+ def render_format
26
+ :json
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/conversions"
4
+ require_relative "./base_serializer"
5
+
6
+ module ApiErrorHandler
7
+ module Serializers
8
+ class Xml < BaseSerializer
9
+ def serialize(options = {})
10
+ body = {
11
+ Title: title,
12
+ Detail: @error.message,
13
+ }
14
+
15
+ body[:Id] = options[:error_id] if options[:error_id]
16
+ body[:Backtrace] = @error.backtrace if options[:backtrace]
17
+
18
+ body.to_xml(root: "Error")
19
+ end
20
+
21
+ def render_format
22
+ :xml
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ApiErrorHandler
2
- VERSION = "0.1.0.rc"
4
+ VERSION = "0.2.1"
3
5
  end
@@ -1,11 +1,55 @@
1
- require "api_error_handler/version"
2
- require "api_error_handler/action_controller"
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./api_error_handler/version"
4
+ require_relative "./api_error_handler/action_controller"
5
+ require_relative "./api_error_handler/error_id_generator"
6
+ require_relative "./api_error_handler/error_reporter"
7
+ Dir[File.join(__dir__, "api_error_handler", "serializers", "*.rb")].sort.each do |file|
8
+ require file
9
+ end
3
10
 
4
11
  module ApiErrorHandler
5
- def handle_api_errors(format: :json)
12
+ SERIALIZERS_BY_FORMAT = {
13
+ json: Serializers::Json,
14
+ json_api: Serializers::JsonApi,
15
+ xml: Serializers::Xml,
16
+ }.freeze
17
+
18
+ SERIALIZER_OPTIONS = {
19
+ backtrace: false,
20
+ }.freeze
21
+
22
+ CONTENT_TYPE_BY_FORMAT = {
23
+ json_api: "application/vnd.api+json"
24
+ }.freeze
25
+
26
+ def handle_api_errors(options = {})
27
+ format = options.fetch(:format, :json)
28
+ error_reporter = ErrorReporter.new(options[:error_reporter])
29
+ serializer_options = SERIALIZER_OPTIONS.merge(
30
+ options.slice(*SERIALIZER_OPTIONS.keys)
31
+ )
32
+
33
+ serializer_class = options[:serializer] || SERIALIZERS_BY_FORMAT.fetch(format)
34
+ content_type = options[:content_type] || CONTENT_TYPE_BY_FORMAT[format]
35
+ rescue_from StandardError do |error|
36
+ status = ActionDispatch::ExceptionWrapper.rescue_responses[error.class.to_s]
37
+
38
+ error_id = ErrorIdGenerator.run(options[:error_id])
39
+ error_reporter.report(error, error_id: error_id)
40
+
41
+ serializer = serializer_class.new(error, status)
42
+ response_body = serializer.serialize(
43
+ serializer_options.merge(error_id: error_id)
44
+ )
6
45
 
7
- rescue_from StandardError do |exception|
8
- render json: "caught!"
46
+ render(
47
+ serializer.render_format => response_body,
48
+ content_type: content_type,
49
+ status: status
50
+ )
51
+ rescue
52
+ raise error
9
53
  end
10
54
  end
11
55
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: api_error_handler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.rc
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Stonehill
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-07-20 00:00:00.000000000 Z
11
+ date: 2022-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,28 +16,42 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 4.1.0
19
+ version: '5.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 4.1.0
26
+ version: '5.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: actionpack
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '5.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -58,30 +72,44 @@ dependencies:
58
72
  requirements:
59
73
  - - "~>"
60
74
  - !ruby/object:Gem::Version
61
- version: '10.0'
75
+ version: '13.0'
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - "~>"
67
81
  - !ruby/object:Gem::Version
68
- version: '10.0'
82
+ version: '13.0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: rspec-rails
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - "~>"
74
88
  - !ruby/object:Gem::Version
75
- version: '3.0'
89
+ version: '3.9'
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
- version: '3.0'
96
+ version: '3.9'
83
97
  - !ruby/object:Gem::Dependency
84
- name: pry
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.80.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.80.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry-byebug
85
113
  requirement: !ruby/object:Gem::Requirement
86
114
  requirements:
87
115
  - - ">="
@@ -94,21 +122,24 @@ dependencies:
94
122
  - - ">="
95
123
  - !ruby/object:Gem::Version
96
124
  version: '0'
97
- description: A gem that helps you easily handle exptions in your Ruby on Rails API
98
- and return informative responses to the client by serializing exceptions into JSON
99
- and other popular API formats and returning a response with a status code that makes
100
- sense based on the exception.
125
+ description: |
126
+ A gem that helps you easily handle exceptions in your Ruby on Rails API and
127
+ return informative responses to the client by serializing exceptions into JSON
128
+ and other popular API formats and returning a response with a status code that
129
+ makes sense based on the exception.
101
130
  email:
102
- - jamesstonehill@gmail.com
131
+ - james.stonehill@gmail.com
103
132
  executables: []
104
133
  extensions: []
105
134
  extra_rdoc_files: []
106
135
  files:
107
136
  - ".gitignore"
108
137
  - ".rspec"
138
+ - ".rubocop.yml"
139
+ - ".rubocop_todo.yml"
140
+ - ".ruby-version"
109
141
  - ".travis.yml"
110
142
  - Gemfile
111
- - Gemfile.lock
112
143
  - LICENSE.txt
113
144
  - README.md
114
145
  - Rakefile
@@ -117,10 +148,14 @@ files:
117
148
  - bin/setup
118
149
  - lib/api_error_handler.rb
119
150
  - lib/api_error_handler/action_controller.rb
120
- - lib/api_error_handler/error_serializer.rb
151
+ - lib/api_error_handler/error_id_generator.rb
152
+ - lib/api_error_handler/error_reporter.rb
153
+ - lib/api_error_handler/errors.rb
154
+ - lib/api_error_handler/serializers/base_serializer.rb
121
155
  - lib/api_error_handler/serializers/json.rb
156
+ - lib/api_error_handler/serializers/json_api.rb
157
+ - lib/api_error_handler/serializers/xml.rb
122
158
  - lib/api_error_handler/version.rb
123
- - lib/serializers/json.rb
124
159
  homepage: https://github.com/jamesstonehill/api_error_handler
125
160
  licenses:
126
161
  - MIT
@@ -133,17 +168,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
133
168
  requirements:
134
169
  - - ">="
135
170
  - !ruby/object:Gem::Version
136
- version: '0'
171
+ version: '2.5'
137
172
  required_rubygems_version: !ruby/object:Gem::Requirement
138
173
  requirements:
139
- - - ">"
174
+ - - ">="
140
175
  - !ruby/object:Gem::Version
141
- version: 1.3.1
176
+ version: '0'
142
177
  requirements: []
143
- rubyforge_project:
144
- rubygems_version: 2.6.14.4
178
+ rubygems_version: 3.0.3.1
145
179
  signing_key:
146
180
  specification_version: 4
147
- summary: A gem that helps you easily handle exptions in your Rails API and return
181
+ summary: A gem that helps you easily handle exceptions in your Rails API and return
148
182
  informative responses to the client.
149
183
  test_files: []
data/Gemfile.lock DELETED
@@ -1,96 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- api_error_handler (0.1.0)
5
- actionpack
6
- activesupport (>= 4.1.0)
7
-
8
- GEM
9
- remote: https://rubygems.org/
10
- specs:
11
- actionpack (5.2.3)
12
- actionview (= 5.2.3)
13
- activesupport (= 5.2.3)
14
- rack (~> 2.0)
15
- rack-test (>= 0.6.3)
16
- rails-dom-testing (~> 2.0)
17
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
18
- actionview (5.2.3)
19
- activesupport (= 5.2.3)
20
- builder (~> 3.1)
21
- erubi (~> 1.4)
22
- rails-dom-testing (~> 2.0)
23
- rails-html-sanitizer (~> 1.0, >= 1.0.3)
24
- activesupport (5.2.3)
25
- concurrent-ruby (~> 1.0, >= 1.0.2)
26
- i18n (>= 0.7, < 2)
27
- minitest (~> 5.1)
28
- tzinfo (~> 1.1)
29
- builder (3.2.3)
30
- coderay (1.1.2)
31
- concurrent-ruby (1.1.5)
32
- crass (1.0.4)
33
- diff-lcs (1.3)
34
- erubi (1.8.0)
35
- i18n (1.6.0)
36
- concurrent-ruby (~> 1.0)
37
- loofah (2.2.3)
38
- crass (~> 1.0.2)
39
- nokogiri (>= 1.5.9)
40
- method_source (0.9.2)
41
- mini_portile2 (2.4.0)
42
- minitest (5.11.3)
43
- nokogiri (1.10.3)
44
- mini_portile2 (~> 2.4.0)
45
- pry (0.12.2)
46
- coderay (~> 1.1.0)
47
- method_source (~> 0.9.0)
48
- rack (2.0.7)
49
- rack-test (1.1.0)
50
- rack (>= 1.0, < 3)
51
- rails-dom-testing (2.0.3)
52
- activesupport (>= 4.2.0)
53
- nokogiri (>= 1.6)
54
- rails-html-sanitizer (1.0.4)
55
- loofah (~> 2.2, >= 2.2.2)
56
- railties (5.2.3)
57
- actionpack (= 5.2.3)
58
- activesupport (= 5.2.3)
59
- method_source
60
- rake (>= 0.8.7)
61
- thor (>= 0.19.0, < 2.0)
62
- rake (10.5.0)
63
- rspec-core (3.8.2)
64
- rspec-support (~> 3.8.0)
65
- rspec-expectations (3.8.4)
66
- diff-lcs (>= 1.2.0, < 2.0)
67
- rspec-support (~> 3.8.0)
68
- rspec-mocks (3.8.1)
69
- diff-lcs (>= 1.2.0, < 2.0)
70
- rspec-support (~> 3.8.0)
71
- rspec-rails (3.8.2)
72
- actionpack (>= 3.0)
73
- activesupport (>= 3.0)
74
- railties (>= 3.0)
75
- rspec-core (~> 3.8.0)
76
- rspec-expectations (~> 3.8.0)
77
- rspec-mocks (~> 3.8.0)
78
- rspec-support (~> 3.8.0)
79
- rspec-support (3.8.2)
80
- thor (0.20.3)
81
- thread_safe (0.3.6)
82
- tzinfo (1.2.5)
83
- thread_safe (~> 0.1)
84
-
85
- PLATFORMS
86
- ruby
87
-
88
- DEPENDENCIES
89
- api_error_handler!
90
- bundler (~> 2.0)
91
- pry
92
- rake (~> 10.0)
93
- rspec-rails (~> 3.0)
94
-
95
- BUNDLED WITH
96
- 2.0.2
@@ -1,25 +0,0 @@
1
- require_relative 'serializers/json'
2
-
3
- module ApiErrorHandler
4
- class ErrorSerializer
5
- SERIALIZERS_BY_FORMAT = {
6
- json: Serializers::Json
7
- }
8
-
9
- def initialize(error, format, status)
10
- @error = error
11
- @format = format
12
- @status = status
13
- end
14
-
15
- def serialize
16
-
17
- end
18
-
19
- private
20
-
21
- def serializer
22
-
23
- end
24
- end
25
- end
@@ -1,9 +0,0 @@
1
- class ApiErrorHandler
2
- module Serializers
3
- class Json
4
- def self.serialize(error)
5
- { message: error.message }.to_json
6
- end
7
- end
8
- end
9
- end