FrankfromDenmark
FrankfromDenmark

Reputation: 170

How to raise exception, send it as a parameter to another method, and rescue the exception in the other method?

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

Answers (2)

Todd A. Jacobs
Todd A. Jacobs

Reputation: 84343

TL;DR

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.

Analysis and Recommendations

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

Yakov
Yakov

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

Related Questions