Rescue from errors with a grace
published on NOV 1, 2018
How many times you had to deal with the external API's errors in order to make your application behaves properly? You may end up then with the following code, especially when errors classes are generic:
class SomeAPIService def self.call perform_action rescue SomeAPI::Error => e if e.message.match(/account expired/) send_email_with_notification_about_expired_account elsif e.message.match(/api key invalid/) send_email_with_notification_about_invalid_api_key elsif e.message.match(/unauthorized action performed/) Rollbar.error('log some useful info') else raise(e) end end end
This code is far from being readable, easy-testable and perfect, isn't it? If the external API does not provide custom error messages, we can create them on our side:
class SomeAPIAccountExpired < StandardError def self.===(exception) exception.class == SomeAPI::Error && exception.message.match(/account expired/) end end class SomeAPIInvalidAPIKey < StandardError def self.===(exception) exception.class == SomeAPI::Error && exception.message.match(/api key invalid/) end end class SomeAPIUnauthorizedAction < StandardError def self.===(exception) exception.class == SomeAPI::Error && exception.message.match(/unauthorized action performed/) end end
How does it work? When an error is raised, Ruby is checking the error class against the classes you have defined in the
rescue. If there is a match then the exception gets rescued. Every operator in Ruby is a method so we can define our version of
=== as well.
Having the above error classes defined, allows us to refactor our code in the following way:
class SomeAPIService def self.call perform_action rescue SomeAPIAccountExpired send_email_with_notification_about_expired_account rescue SomeAPIInvalidAPIKey send_email_with_notification_about_invalid_api_key rescue SomeAPIUnauthorizedAction Rollbar.error('log some useful info') end end
Now, we can test each error class separately and easily test the
SomeAPIService. The whole service's code is also readable. We also don't have to re-raise the error when we don't support error message because it happens automatically as such error is not matched. That's it!