summaryrefslogtreecommitdiffstats
path: root/README.md
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-27 20:42:18 -0300
committerMurilo Ijanc <[email protected]>2026-07-27 20:42:18 -0300
commit218bc8fbfbbdc2520130ef43652a6cd22a6e67fb (patch)
tree4291a5bb3a235a21b44cfbc51bf1d0c6b9af0c3e /README.md
parent5598ea80735a51cb7b3690837b2b7d4240331b2d (diff)
downloadocaml-1e24c8acae5286ca3d6a5aa35efaf61508b57b0f.tar.gz
Carry the municipality range on a not-found CEPHEADv0.2.0main
A CEP that fails a lookup 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 answers those 404s with a faixa naming the municipality whose band the number falls into, so a caller can ask "did you mean somewhere in Areal/RJ?" instead of returning a bare "not found". Cep_not_found becomes an inline record so the context rides along with the message, matching how Rate_limited and Server_error already carry theirs. It is still a 404: faixa is context on the failure and never promotes it to an address, and its bounds are the smallest and largest CEP that actually exist there — sparse across the interval, which is what the ceps count is for — not the bounds the Correios publish. Parsing is deliberately lenient. A missing, null, malformed or municipality-less faixa yields None rather than an error: a 404 is a clean answer, and no shape of this field should turn it into an Invalid_response.
Diffstat (limited to 'README.md')
-rw-r--r--README.md36
1 files changed, 35 insertions, 1 deletions
diff --git a/README.md b/README.md
index 4e1a18f..94b5daa 100644
--- a/README.md
+++ b/README.md
@@ -157,7 +157,7 @@ decision you have to make**, not by the HTTP status that caused it:
| error | when | does retrying help? |
| --- | --- | --- |
| `Invalid_cep` | not 8 digits | no |
-| `Cep_not_found` | well-formed, not in the database | no |
+| `Cep_not_found` | well-formed, not in the database; may carry `faixa` | no |
| `Rate_limited` | 429; carries `retry_after` | yes, later |
| `Server_error` | 5xx; carries `status` | yes, later |
| `Network_error` | DNS, connection, timeout, TLS | yes, later |
@@ -170,6 +170,40 @@ match Cepis.lookup cep_from_form with
| Error _ -> reschedule ()
```
+### Not found: which city it would have been
+
+A failing CEP is rarely random. It is usually someone who knows the city and
+guessed the number — and the classic guess is the `-000` that 957
+municipalities never received. So `Cep_not_found` carries a `faixa` naming the
+municipality whose band that number falls into, when it falls into one:
+
+```ocaml
+match Cepis.lookup "25845000" with
+| Error (Cepis.Cep_not_found { faixa = Some f; _ }) ->
+ Printf.printf "not found — did you mean somewhere in %s/%s? (%d CEPs, %s to %s)\n"
+ f.Cepis.localidade f.Cepis.uf f.Cepis.ceps f.Cepis.cep_ini f.Cepis.cep_fim
+ (* not found — did you mean somewhere in Areal/RJ? (118 CEPs, 25845001 to 25849899) *)
+| Error (Cepis.Cep_not_found { faixa = None; _ }) -> ask_for_another_cep ()
+| _ -> ()
+```
+
+Three things this is not:
+
+- **It is still not found.** The lookup failed; `faixa` is context on the
+ error, never an address. Nothing here says the CEP exists.
+- **`cep_ini`/`cep_fim` are not the Correios' published bounds.** They are the
+ smallest and largest CEP that *actually exist* in that municipality. The
+ DNE's own start for Areal is `25845000` — exactly the CEP that just failed.
+- **The pair is not a list of valid CEPs.** 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. Rendering it as "valid from … to …"
+ would be wrong for about 98% of the interval.
+
+`faixa` is `None` when the CEP lands in a band no municipality owns — the API
+omits the field rather than sending null — and also whenever it cannot be read.
+A 404 is a clean answer, and no shape of `faixa` turns it into an
+`Invalid_response`.
+
`Invalid_response` almost always 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.