# 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