Skip to content

Commit

Permalink
Merge pull request #19 from collectiveidea/rescue_from
Browse files Browse the repository at this point in the history
Allow handlers to use rescue_from in a way familiar to ActionController users
  • Loading branch information
danielmorrison authored Oct 4, 2024
2 parents 4ba7392 + a4ba369 commit a84c47a
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 1 deletion.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,22 @@ TODO: Give more examples of handlers

Use `before_action`, `around_action`, and other callbacks you're used to, as we build on [AbstractController::Callbacks](https://api.rubyonrails.org/classes/AbstractController/Callbacks.html).

### rescue_from

Use `rescue_from` just like you would in a controller:

```ruby
class HaberdasherServiceHandler < Twirp::Rails::Handler
rescue_from "ArgumentError" do |error|
Twirp::Error.invalid_argument(error.message)
end

rescue_from "Pundit::NotAuthorizedError", :not_authorized

...
end
```

### DRY Service Hooks

Apply [Service Hooks](https://github.com/twitchtv/twirp-ruby/wiki/Service-Hooks) one time across multiple services.
Expand Down
1 change: 1 addition & 0 deletions lib/twirp/rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Error < StandardError; end
require_relative "rails/configuration"
require_relative "rails/dispatcher"
require_relative "rails/engine"
require_relative "rails/rescuable"
require_relative "rails/handler"

module Twirp
Expand Down
8 changes: 7 additions & 1 deletion lib/twirp/rails/handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module Twirp
module Rails
class Handler
include Twirp::Rails::Callbacks
include ActiveSupport::Rescuable
using Twirp::Rails::Rescuable

attr_reader :request, :env
attr_reader :action_name
Expand Down Expand Up @@ -34,7 +36,11 @@ def process_action(name)
ActiveSupport::Notifications.instrument("handler_run_callbacks.twirp_rails", handler: self.class.name, action: action_name, env: @env, request: @request) do
run_callbacks(:process_action) do
ActiveSupport::Notifications.instrument("handler_run.twirp_rails", handler: self.class.name, action: action_name, env: @env, request: @request) do |payload|
payload[:response] = send_action(name)
payload[:response] = begin
send_action(name)
rescue => exception
rescue_with_handler_and_return(exception) || raise
end
end
end
end
Expand Down
31 changes: 31 additions & 0 deletions lib/twirp/rails/rescuable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

module Twirp
module Rails
module Rescuable
refine ::ActiveSupport::Rescuable::ClassMethods do
# A slightly altered version of ActiveSupport::Rescuable#rescue_with_handler
# that returns the result rather than the handled exception
def rescue_with_handler_and_return(exception, object: self, visited_exceptions: [])
visited_exceptions << exception

if (handler = handler_for_rescue(exception, object: object))
handler.call exception
elsif exception
if visited_exceptions.include?(exception.cause)
nil
else
rescue_with_handler(exception.cause, object: object, visited_exceptions: visited_exceptions)
end
end
end
end

refine ::ActiveSupport::Rescuable do
def rescue_with_handler_and_return(exception)
self.class.rescue_with_handler_and_return exception, object: self
end
end
end
end
end
4 changes: 4 additions & 0 deletions spec/rails_app/app/handlers/haberdasher_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ class HaberdasherHandler < Twirp::Rails::Handler
before_action :track_request_ip
before_action :reject_giant_hats

rescue_from "ArgumentError" do |error|
Twirp::Error.invalid_argument(error.message)
end

def make_hat
# We can return a Twirp::Error when appropriate
if request.inches < 12
Expand Down
19 changes: 19 additions & 0 deletions spec/requests/haberdasher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,23 @@ def make_hat_success_request
expect(response.status).to eq(304)
end
end

describe "Rescuable" do
it "rescues from ArgumentError" do
size = Twirp::Example::Haberdasher::Size.new(inches: 100)

# Fake an exception
expect(Twirp::Example::Haberdasher::Hat).to receive(:new).and_raise(ArgumentError.new("is way too large"))

post "/twirp/twirp.example.haberdasher.Haberdasher/MakeHat",
params: size.to_proto, headers: {
:accept => "application/protobuf",
"Content-Type" => "application/protobuf"
}

expect(response.status).to eq(400)
expect(response.content_type).to eq("application/json")
expect(response.body).to eq('{"code":"invalid_argument","msg":"is way too large"}')
end
end
end

0 comments on commit a84c47a

Please sign in to comment.