Reputation: 170
I have two methods as shown below, one tries to make a connection, if it fails it raises an exception. That exception should then be rescued by another method, who has been called by the method who raised the exception.
Currently the conn
method raises the error, breaks the application and it's
never rescued. How do I rescue the exception, and is it possible to send the
exception as the response parameter?
def conn
begin
reponse = parse_response(
conn.post(
..
).tap do |res|
unless res.status == 200
raise Error::AuthenticationError.new
end
end
)
..
end
end
def parse_response(response)
body =
begin
JSON.parse(response.body)
rescue JSON::ParseError
end
rescue Error::AuthenticationError => e
LOGGER.error e
end
end
Upvotes: 0
Views: 2079
Reputation: 84343
You can pass exceptions around with some refactoring, but I'm taking the approach here of looking at passing exceptions as an anti-pattern for expected (but not necessarily desirable) results. If you really need the overhead of passing around exceptions, other answers will likely give you suggestions about how to do so through closures or refactoring your call stack.
There are certainly ways to pass exceptions around, but there's a bigger problem here, and that's the use of an exception to handle a non-exceptional use case. Exceptions should be...well, exceptional. An HTTP call that returns a non-200 status code is usually not a completely unexpected result. In fact, your code specifically checks for this state of affairs, so it's actually an expected code path in your application.
However, note that checking for "not 200 OK" isn't really the same as explictly checking for a 401 Unauthorized. In such cases, treating an expected condition as such is generally faster and less error prone than raising or passing around exceptions.
One way to approach this (but certainly not the only way) is to refactor your application to handle different expected outcomes sensibly. For example:
def conn
response = conn.post
# Pass through 200; handle everything else.
case response.status
when 200
when 401 then handle_auth_error(response)
when 403 then handle_forbidden_error(response)
else raise "unexpected HTTP status code: #{response.status}"
end
parse_response(response)
end
The point of doing this is less about embedding the logic into #conn; it may certainly be worth extracting it to another method. The point is that you should be leaving exceptions for unanticipated/uncaught errors that can't be handled within the running application.
In this example, we only raise an exception on status codes we didn't anticipate. This leads to a much simpler solution, and likely a more robust application.
Upvotes: 2
Reputation: 3191
This happens because of this part of your code executes before parse_respose
method, as if it was written like this::
def conn
begin
response = conn.post(
..
).tap do |res|
unless res.status == 200
raise Error::AuthenticationError.new
end
end
parse_response(response)
end
end
You can move the rescue block to conn
method:
def conn
begin
reponse = parse_response(
conn.post(
..
).tap do |res|
unless res.status == 200
raise Error::AuthenticationError.new
end
end
)
..
rescue Error::AuthenticationError => e
LOGGER.error e
end
end
def parse_response(response)
body =
begin
JSON.parse(response.body)
rescue JSON::ParseError
end
end
You can pass conn.post
thing as a block to parse_response
method or create a Proc
or lambda
object and pass it as a parameter. But I believe that better to deal with Error::AuthenticationError
inside conn
method.
Upvotes: 2