(in-package #:cepis) (define-condition cepis-error (error) ((message :initarg :message :initform "" :reader error-message :documentation "The human-readable description of the failure.")) (:report (lambda (condition stream) (write-string (error-message condition) stream))) (:documentation "Base of every error this library signals. Handle it to catch any failure. Below it the split is by the decision the caller has to make, not by the HTTP status that caused it. `invalid-cep' and `cep-not-found' are problems with the request: retrying is pointless, the same CEP will fail again. `network-error', `server-error' and `rate-limited' are problems with transport: retrying may help, and the client already retried on its own before signalling any of them.")) (define-condition invalid-cep (cepis-error parse-error) () (:documentation "The CEP is not 8 digits. Deliberately also a `parse-error': turning a string into a CEP is a parse, and code that already handles `parse-error' while validating input keeps working without knowing this library exists. Usually signalled without a request ever being made — the client checks the format before going to the network.")) (define-condition cep-not-found (cepis-error cell-error) ((faixa :initarg :faixa :initform nil :reader cep-not-found-faixa :documentation "The municipality owning the band this CEP falls in, or NIL.")) (:report (lambda (condition stream) (write-string (error-message condition) stream) (let ((faixa (cep-not-found-faixa condition))) (when faixa (format stream " — it falls in the band of ~a/~a" (faixa-localidade faixa) (faixa-uf faixa)))))) (:documentation "The CEP is well-formed but does not exist in the DNE database. Also a `cell-error', which is the closest thing the standard has to a failed key lookup: `cell-error-name' returns the CEP that was not found. `cep-not-found-faixa' is a `faixa' naming the municipality that owns the band the CEP falls in, when the API knows one, and NIL otherwise — either because no municipality owns that band or because an older API is answering. It is context for the error, not an address: this is still a 404, and a caller must not read a municipality here as a CEP that resolved.")) (define-condition network-error (cepis-error) () (:documentation "The request never completed: DNS, refused connection, timeout, TLS. Signalled only after every configured attempt has failed.")) (define-condition server-error (cepis-error) ((status :initarg :status :initform 0 :reader server-error-status :documentation "The HTTP status seen on the last attempt.")) (:documentation "The API answered 5xx on every attempt.")) (define-condition rate-limited (cepis-error) ((retry-after :initarg :retry-after :initform nil :reader rate-limited-retry-after :documentation "What the server asked for, in seconds, or NIL if it did not say.")) (:documentation "The API (or the edge in front of it) answered 429. The client already honoured `rate-limited-retry-after' across the attempts it made; it is exposed here for callers who want to reschedule the work.")) (define-condition invalid-response (cepis-error) () (:documentation "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."))