summaryrefslogtreecommitdiffstats
path: root/lib/cepis/errors.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/cepis/errors.rb')
-rw-r--r--lib/cepis/errors.rb67
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