How to Deal with Not Knowing What Exceptions Can Be Raised by a Library Method in Ruby

How to deal with not knowing what exceptions can be raised by a library method in Ruby?


(And yes I did look at the code of the library method, and can get some idea of what exceptions are raised but I cannot be 100% sure and if it is not documented I feel uncomfortable relying on it.)

I suggest having a look at the tests, as they will show some of the "likely" scenarios and what might be raised. Don't forget that good tests are documentation, too.

What exceptions can be raised by HttpClient?

Base class for Net::Http exceptions is Net::HTTPExceptions
We can look up its descendants like was described in this question.

require 'net/http'

puts ObjectSpace.each_object(Class).select { |x| x < Net::HTTPExceptions }

It outputs 4 class names:

Net::HTTPFatalError
Net::HTTPServerException
Net::HTTPRetriableError
Net::HTTPError

There are also some class names for compatibility, like

# for compatibility
Net::HTTPClientException = Net::HTTPServerException

Source file can be viewed in ruby repository on in local file (change Ruby version to yours)

C:\Ruby26-x64\lib\ruby\2.6.0\net\http\exceptions.rb

Raising an exception for system library dependencies

require will raise an error if it's not found and you can rescue it to specify the error message if you'd like:

begin
require 'wmctrl'
rescue LoadError
# error handling code here
end

Fail vs. raise in Ruby : Should we really believe the style guide?


use 'raise' for exceptions to be caught, and 'fail' for serious errors which are not meant to be handled

This is not what the official style guide or the link you provided say on the matter.

What is meant here is use raise only in rescue blocks. Aka use fail when you want to say something is failing and use raise when rethrowing an exception.

As for the "does it matter" part - it is not one of the most hardcore strictly followed rules, but you could make the same argument for any convention. You should follow in that order:

  1. Your project style guide
  2. Your company style guide
  3. The community style guide

Ideally, the three should be the same.


Update: As of this PR (December 2015), the convention is to always use raise.

ruby when/how to use rescue/ensure?

In general exceptions should be used for exceptional events. Not regular application flow.

When catching exceptions always be specific and only rescue exceptions that you know what to do with.

begin 
pats "hello world"
rescue
nil
end

This example shows a serious flaw in the code in yout question - you created a black hole that swallows the NoMethodError that would have told us that there is a typo in the code. This makes debugging extremely difficult. This anti-pattern is known as Pokémon Exception Handling (Gotta catch em' all).

ensure just ensures that the code is run no matter if the code raised an exception or not. Its used to for example guarantee that the method closes a file handler that it has opened or rolls back a transaction. Its a really big hammer that should be used very sparingly.

HTTParty does not actually raise exceptions when the response code is a "error" code - because its not an exceptional event. Its a part of normal application flow when dealing with HTTP requests. Shit happens. HTTParty raises exceptions if you can't reach the server at all or you can't even reach the network. Those are exceptional events.

class GeolocationClient
include HTTParty
base_uri "https://www.googleapis.com/geolocation/v1"
format :json
attr_accessor :options

def initialize(api_key:)
@options = {
api_key: api_key
}
end

def geolocate
begin
response = self.class.get("/geolocate", options)
if response.successful?
response
else
logger.info("Geolocation API call was unsuccessful. Status code: #{response.code}")
handle_unsuccessful_request
end
rescue HTTParty::Error => e
logger.warn(e.message)
handle_unsuccessful_request
end
end

private

def handle_unsuccessful_request
{ "message" : "unable to find location" }
end
end

response.successful? tests if the response is in the 2xx "happy range". Use a switch statement if you want handle each code or range of codes separately.

What’s the best way to handle exceptions from Net::HTTP?

I agree it is an absolute pain to handle all the potential exceptions. Look at this to see an example:

Working with Net::HTTP can be a pain. It's got about 40 different ways
to do any one task, and about 50 exceptions it can throw.

Just for the love of google, here's what I've got for the "right way"
of catching any exception that Net::HTTP can throw at you:

begin
response = Net::HTTP.post_form(...) # or any Net::HTTP call
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
...
end

Why not just rescue Exception => e? That's a bad habit to get into, as
it hides any problems in your actual code (like SyntaxErrors, whiny
nils, etc). Of course, this would all be much easier if the possible
errors had a common ancestor.

The issues I've been seeing in dealing with Net::HTTP have made me
wonder if it wouldn't be worth it to write a new HTTP client library.
One that was easier to mock out in tests, and didn't have all these
ugly little facets.

What I've done, and seen most people do, is move away from Net::HTTP and move to 3rd party HTTP libraries such as:

httparty and faraday



Related Topics



Leave a reply



Submit