Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,13 +246,19 @@ handle_api_errors(
### Error IDs
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
```ruby
handle_api_errors(
error_id: Proc.new { |error| SecureRandom.uuid }
)
handle_api_errors(error_id: :uuid)
```

Or pass a Proc if you need to do something custom.
```ruby
handle_api_errors(error_id: Proc.new { |error| SecureRandom.uuid })
```

These will result in:
```json
{
"error": {
Expand Down
8 changes: 3 additions & 5 deletions lib/api_error_handler.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require_relative "./api_error_handler/version"
require_relative "./api_error_handler/action_controller"
require_relative "./api_error_handler/error_id_generator"
Dir[File.join(__dir__, 'api_error_handler', 'serializers', "*.rb")].each { |file| require file }

module ApiErrorHandler
Expand All @@ -19,21 +20,18 @@ module ApiErrorHandler

def handle_api_errors(options = {})
format = options.fetch(:format, :json)
status_mapping = ActionDispatch::ExceptionWrapper.rescue_responses
error_reporter = options[:error_reporter]
serializer_options = SERIALIZER_OPTIONS.merge(
options.slice(*SERIALIZER_OPTIONS.keys)
)

serializer_class = options[:serializer] || SERIALIZERS_BY_FORMAT.fetch(format)
content_type = options[:content_type] || CONTENT_TYPE_BY_FORMAT[format]

rescue_from StandardError do |error|
begin
status = status_mapping[error.class.to_s]
status = ActionDispatch::ExceptionWrapper.rescue_responses[error.class.to_s]

error_id = nil
error_id = options[:error_id].call(error) if options[:error_id]
error_id = ErrorIdGenerator.run(options[:error_id])
error_reporter.call(error, error_id) if error_reporter

serializer = serializer_class.new(error, status)
Expand Down
18 changes: 18 additions & 0 deletions lib/api_error_handler/error_id_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require "securerandom"
require_relative "./errors"

module ApiErrorHandler
class ErrorIdGenerator
def self.run(error_id_option)
if error_id_option.instance_of?(Proc)
error_id_option.call
elsif error_id_option == :uuid
SecureRandom.uuid
elsif error_id_option.nil?
nil
else
raise(InvalidOptionError, "Unable to handle `#{error_id_option}` as argument for the `:error_id` option.")
end
end
end
end
4 changes: 3 additions & 1 deletion lib/api_error_handler/errors.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module ApiErrorHandler
# class UnknownSerializerError < ArgumentError; end
class Error < StandardError; end

class InvalidOptionError < Error; end
end
23 changes: 23 additions & 0 deletions spec/api_error_handler/error_id_generator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require_relative "../../lib/api_error_handler/error_id_generator"

RSpec.describe ApiErrorHandler::ErrorIdGenerator do
it "Returns the result of the proc if you git it a proc" do
expect(described_class.run(proc { "Result!" })).to eq("Result!")
end

it "Returns the result of the lambda if you git it a lambda" do
expect(described_class.run(-> { "Result!" })).to eq("Result!")
end

it "Returns a UUID if you give it :uuid" do
allow(SecureRandom).to receive(:uuid).and_return("Result!")

expect(described_class.run(:uuid)).to eq("Result!")
end

it "Raises an error if you give it something it doesn't recognise" do
expect do
described_class.run(:foo)
end.to raise_error(ApiErrorHandler::InvalidOptionError)
end
end