From a891657aa43d2aba06a6347dd2f94edd19afd6ba Mon Sep 17 00:00:00 2001 From: Murilo Ijanc Date: Mon, 27 Jul 2026 20:37:09 -0300 Subject: Carry the municipality a missing CEP would belong to MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A 404 is rarely a random number: it is usually someone who knows the city and guessed the digits, and the classic guess is the -000 that 957 municipalities never received. The API now attaches a faixa to the not-found body naming the municipality that owns the range, so the caller can say "no such CEP, but that range is Areal/RJ" instead of only "no". It stays an error. Faixa hangs off CepNotFoundException, never off Address, and parsing is deliberately lenient: an absent, malformed or unnamed block yields a null Nullable, so reading the extra context can never cost the caller the answer it already had. cep_ini and cep_fim are the smallest and largest CEP that actually exist there, not the bounds the Correios publish, and the range between them is sparse — hence ceps, so nobody mistakes the pair for a list. --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 9c76d3d..9ca1fe7 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Or in `dub.json`: ```json "dependencies": { - "cepis": "~>0.1.0" + "cepis": "~>0.2.0" } ``` @@ -166,7 +166,7 @@ status that caused it: | exception | when | does retrying help? | | --- | --- | --- | | `InvalidCepException` | not 8 digits | no | -| `CepNotFoundException` | well-formed, not in the database | no | +| `CepNotFoundException` | well-formed, not in the database; may carry `faixa` | no | | `RateLimitedException` | 429; carries `retryAfter` | yes, later | | `ServerException` | 5xx; carries `status` | yes, later | | `NetworkException` | DNS, connection, timeout, TLS | yes, later | @@ -190,6 +190,49 @@ catch (CepisException error) 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. +### When the CEP does not exist + +A 404 sometimes knows *where* the CEP would have been. `CepNotFoundException` +carries it in `faixa`: the municipality that owns the range the number falls +in. + +```d +try + render(lookup("25845-000")); +catch (CepNotFoundException error) +{ + if (error.faixa.isNull) + askForAnotherCep(); + else + { + auto f = error.faixa.get; + writeln("no such CEP — that range is ", f.localidade, "/", f.uf); + writeln(f.ceps, " CEPs, from ", f.cep_ini, " to ", f.cep_fim); + // 118 CEPs, from 25845001 to 25849899 + } +} +``` + +It is **context for an error, not an address.** The status is still 404 and the +CEP still does not exist; `faixa` never becomes an `Address` and must not be +rendered as if the lookup had worked. + +`cep_ini` and `cep_fim` are the smallest and the largest CEP that **actually +exist** there — not the bounds the Correios publish. The DNE's own start for +Areal is `25845000`, which is exactly the CEP that just failed. + +The range is **sparse**: Areal's 118 CEPs are scattered across the ~4,900 +numbers between `25845001` and `25849899`. That is what `ceps` is for. Never +present the pair as the list of valid CEPs for the city, and do not walk it +expecting the numbers in between to resolve. + +`faixa` is null whenever the API sent none — and it is **omitted, never null**, +when the CEP falls in a band no municipality owns. + +Why it exists: a CEP that fails is rarely a random number, it is someone who +knows the city and guessed the digits — and the classic guess is the `-000` +that 957 municipalities never received. + ## Development Everything runs through `make`, which shells out to `dub`: -- cgit v1.3