diff options
Diffstat (limited to 'lib/cepis/errors.rb')
| -rw-r--r-- | lib/cepis/errors.rb | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/lib/cepis/errors.rb b/lib/cepis/errors.rb new file mode 100644 index 0000000..7a15841 --- /dev/null +++ b/lib/cepis/errors.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module Cepis + # Base of every error raised by this library. Rescue it to handle any + # failure with one handler. + # + # Below it, the split is by **the decision the caller has to make**, not by + # the HTTP status that caused it: + # + # - {InvalidCEP} and {CEPNotFound} are problems with the *request*. Retrying + # is pointless — the same CEP will fail again. + # - {NetworkError}, {ServerError} and {RateLimited} are problems with + # *transport*. Retrying may help, and the client already retried on its own + # before raising any of them. + # + # That split is why {CEPNotFound} is not "the HTTP 404 error": {InvalidCEP} + # is raised without any request being made at all, when the CEP is malformed + # before it ever leaves the machine. + class Error < StandardError; end + + # The CEP is not 8 digits. + # + # Usually raised **without** a request ever being made — the client checks + # the format before going to the network. It is also raised when the API + # answers 400 to a CEP that passed local validation, in which case it carries + # the API's own message. + class InvalidCEP < Error; end + + # The CEP is well-formed but does not exist in the DNE database. + class CEPNotFound < Error; end + + # The request never completed: DNS, refused connection, timeout, TLS. + # + # Raised only after every configured attempt has failed. + class NetworkError < Error; end + + # The API answered 5xx on every attempt. + class ServerError < Error + # HTTP status seen on the last attempt. + attr_reader :status + + def initialize(message, status:) + super(message) + @status = status + end + end + + # The API (or the edge in front of it) answered 429 on every attempt. + class RateLimited < Error + # What the server asked for, in seconds, or `nil` if it did not say. The + # client already honoured this across the attempts it made; it is exposed + # for callers who want to reschedule the work. + attr_reader :retry_after + + def initialize(message, retry_after: nil) + super(message) + @retry_after = retry_after + end + end + + # A success status came back with a body that cannot be parsed. + # + # In practice this means something between you and the API is answering + # instead of it — a captive Wi-Fi portal, a corporate proxy, an HTML error + # page. Useful as a diagnosis: the problem is not in your code. + class InvalidResponse < Error; end +end |