From d7ca2d6f4b1c5c1f5f1b6ce6ffad35aaffee5b0f Mon Sep 17 00:00:00 2001 From: Murilo Ijanc Date: Mon, 27 Jul 2026 20:33:24 -0300 Subject: Carry the municipality on a not-found CEP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- README.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 7d75a9c..f92d208 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Add the package to `Package.swift`: ```swift dependencies: [ - .package(url: "https://git.cep.is/swift.git", from: "0.1.0") + .package(url: "https://git.cep.is/swift.git", from: "0.2.0") ] ``` @@ -169,7 +169,7 @@ make**, not by the HTTP status that caused them: | case | when | does retrying help? | | --- | --- | --- | | `invalidCEP` | not 8 digits | no | -| `cepNotFound` | well-formed, not in the database | no | +| `cepNotFound` | well-formed, not in the database; carries `faixa` | no | | `rateLimited` | 429; carries `retryAfter` | yes, later | | `serverError` | 5xx; carries `status` | yes, later | | `networkError` | DNS, connection, timeout, TLS | yes, later | @@ -190,6 +190,52 @@ do { 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. +### Which city the missing CEP belonged to + +A CEP that fails is rarely a random number. It is usually someone who knows the +city and guessed the digits — and the classic guess is the `-000`, which 957 +municipalities never received. + +So `cepNotFound` carries a `Faixa`: the municipality the CEP *would* have +belonged to, when the API can say. + +```swift +do { + let address = try await Client.shared.lookup("25845-000") +} catch CepisError.cepNotFound(let message, let faixa) { + if let faixa { + print("\(message) — that range is \(faixa.localidadeUF)") + // … does not exist in the DNE database — that range is Areal/RJ + faixa.ibge // "3300225" + faixa.cepIni // "25845001" + faixa.cepFim // "25849899" + faixa.ceps // 118 + } +} +``` + +`error.faixa` reaches the same value without pattern-matching, and +`error.description` (and `errorDescription`) already names the municipality when +there is one. `error.message` stays exactly what it was. + +Three things to keep straight: + +- **It is still a 404.** The CEP does not exist. A `Faixa` is context for the + error, never an address — it can turn *"not found"* into *"not a CEP, but you + are looking at Areal/RJ"*, and nothing more. +- **`cepIni`/`cepFim` are not the Correios' published bounds.** They are the + smallest and largest CEP that *actually exist* there. The DNE's own start for + Areal is `25845000` — precisely the CEP that just failed. `cepIni` is + `25845001`. +- **The range is sparse.** It is not a list of valid CEPs and iterating it is + wrong: Areal's 118 CEPs are scattered across the ~4,900 numbers between the + two bounds, about one in forty. That is what `ceps` is for. + +The field is **omitted, never null**, when the CEP falls in a band no +municipality owns, so `faixa == nil` is ordinary and must be handled. It is also +`nil` when the response could not be understood: a malformed `faixa` never +escalates a clean not-found into a decoding failure. + ## Development ```sh -- cgit v1.3