diff options
| author | Murilo Ijanc <[email protected]> | 2026-07-27 20:33:24 -0300 |
|---|---|---|
| committer | Murilo Ijanc <[email protected]> | 2026-07-27 20:33:24 -0300 |
| commit | d7ca2d6f4b1c5c1f5f1b6ce6ffad35aaffee5b0f (patch) | |
| tree | 21aca875c28f4d7d9ec3b0a807e7c7f15a6766d8 /Sources/Cepis/Errors.swift | |
| parent | 72a4b451c9d06f7a97cac2f43f1b7f52fafcd3b7 (diff) | |
| download | swift-0.2.0.tar.gz | |
A CEP that fails a lookup is rarely a random number: it is almost always
someone who knows the city and guessed the digits. The classic guess is
the -000, which 957 municipalities never received. Answering that with a
bare "not found" throws away what the API knows — that the number is in
Areal/RJ's band — and leaves the caller unable to tell a typo apart from
a city that has no such CEP.
The 404 now carries an optional faixa, so cepNotFound carries it too. It
decorates the error and never replaces it: the CEP still does not exist
and the call still throws. Parsing is deliberately incapable of
escalating — a faixa that is absent, null, empty or malformed becomes
nil, because losing the decoration must never cost the clean not-found
underneath it.
Faixa offers no contains(): cep_ini and cep_fim are the smallest and
largest CEP that actually exist, not the bounds the Correios publish,
and the range between them is sparse — Areal's 118 CEPs are spread over
some 4,900 numbers. Only a lookup can answer whether a CEP exists, and
an API that appeared to answer it from the pair would be lying.
Widening cepNotFound's payload breaks anyone matching the case with a
single binding, hence 0.2.0.
Diffstat (limited to 'Sources/Cepis/Errors.swift')
| -rw-r--r-- | Sources/Cepis/Errors.swift | 44 |
1 files changed, 36 insertions, 8 deletions
diff --git a/Sources/Cepis/Errors.swift b/Sources/Cepis/Errors.swift index 1472f81..089121d 100644 --- a/Sources/Cepis/Errors.swift +++ b/Sources/Cepis/Errors.swift @@ -6,15 +6,15 @@ import Foundation /// wrong. The cases split by **the decision the caller has to make**, not by /// the HTTP status that caused them: /// -/// - ``invalidCEP(_:)`` and ``cepNotFound(_:)`` are problems with the -/// *request*. Retrying is pointless — the same CEP will fail again. +/// - ``invalidCEP(_:)`` and ``cepNotFound(message:faixa:)`` are problems with +/// the *request*. Retrying is pointless — the same CEP will fail again. /// - ``networkError(message:)``, ``serverError(message:status:)`` and /// ``rateLimited(message:retryAfter:)`` are problems with *transport*. /// Retrying may help, and the client already retried on its own before /// throwing any of them. /// -/// That split is why ``cepNotFound(_:)`` is not "the HTTP 404 case": nothing -/// about it is tied to a status line. +/// That split is why ``cepNotFound(message:faixa:)`` is not "the HTTP 404 +/// case": nothing about it is tied to a status line. public enum CepisError: Error, Sendable { /// The CEP is not 8 digits. /// @@ -25,7 +25,13 @@ public enum CepisError: Error, Sendable { case invalidCEP(String) /// The CEP is well-formed but does not exist in the DNE database. - case cepNotFound(String) + /// + /// `faixa` names the municipality the CEP *would* have belonged to, when + /// the API can say — see ``Faixa``. It is `nil` whenever the API omits it, + /// which happens for a CEP in a band no municipality owns, and whenever + /// what came back could not be understood. It never changes the meaning of + /// the case: the CEP does not exist either way. + case cepNotFound(message: String, faixa: Faixa?) /// The API (or the edge in front of it) answered 429 on every attempt. /// @@ -53,7 +59,7 @@ public enum CepisError: Error, Sendable { public var message: String { switch self { case .invalidCEP(let message): return message - case .cepNotFound(let message): return message + case .cepNotFound(let message, _): return message case .rateLimited(let message, _): return message case .serverError(let message, _): return message case .networkError(let message): return message @@ -61,6 +67,15 @@ public enum CepisError: Error, Sendable { } } + /// The municipality the missing CEP would have belonged to, when known. + /// + /// `nil` for every case other than ``cepNotFound(message:faixa:)``, so a + /// caller can reach for it without pattern-matching the enum first. + public var faixa: Faixa? { + guard case .cepNotFound(_, let faixa) = self else { return nil } + return faixa + } + /// Whether making the same call again later stands a chance of succeeding. /// /// `true` for rate limiting, server errors and network failures; `false` @@ -74,9 +89,22 @@ public enum CepisError: Error, Sendable { } extension CepisError: CustomStringConvertible { - public var description: String { message } + /// ``message``, plus the municipality when a ``Faixa`` came with the + /// not-found. + /// + /// The addition is the whole point of the faixa: *"CEP 25845000 does not + /// exist in the DNE database (that range is Areal/RJ)"* tells the person at + /// the keyboard they typed the right city and the wrong number, which + /// *"does not exist"* on its own does not. + /// + /// ``message`` stays exactly what it was, for callers that log or match on + /// it. + public var description: String { + guard let faixa else { return message } + return "\(message) (that range is \(faixa.localidadeUF))" + } } extension CepisError: LocalizedError { - public var errorDescription: String? { message } + public var errorDescription: String? { description } } |