summaryrefslogtreecommitdiffstats
path: root/README.md
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-27 20:31:30 -0300
committerMurilo Ijanc <[email protected]>2026-07-27 20:31:30 -0300
commit67276c6444a70b61030ab3ee52b84dc5014af0e8 (patch)
treeac0246ed287c6aec57e335403b20b23214ec4eb0 /README.md
parent64764fb9fac0227f8e0443e7c7519d73aec275b9 (diff)
downloadphp-main.tar.gz
Carry the municipality of a not-found CEP on the exceptionHEADv0.2.0main
The API now sends an optional faixa block on the 404 of GET /{cep}, naming the municipality the CEP would have belonged to. That failing CEP is rarely random: it is usually someone who knows the city and guessed the number, most often the -000 that 957 municipalities never received. Knowing the lookup failed "in Areal/RJ" is the difference between a message a user can act on and a bare "CEP not found". It stays an error. Faixa hangs off CepNotFoundException, never off Address, and parsing is deliberately forgiving: a missing, empty or malformed block yields null rather than turning a clean not-found into a parse failure. Its cep_ini/cep_fim are the CEPs that exist, not the bounds the Correios publish, and the interval between them is sparse -- hence ceps, and hence the docblocks insisting the pair is not a list of valid CEPs.
Diffstat (limited to 'README.md')
-rw-r--r--README.md56
1 files changed, 55 insertions, 1 deletions
diff --git a/README.md b/README.md
index 5d26dbd..e13ed1a 100644
--- a/README.md
+++ b/README.md
@@ -233,7 +233,7 @@ the HTTP 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 |
| `ServerErrorException` | 5xx; carries `status` | yes, later |
| `NetworkException` | DNS, connection, timeout, TLS | yes, later |
@@ -264,6 +264,60 @@ an `\InvalidArgumentException` and `CepNotFoundException` is an
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.
+### A CEP that does not exist, but in a city that does
+
+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`, which 957
+municipalities never received. So `CepNotFoundException` carries a `Faixa` when
+the API can say which municipality the CEP *would* have belonged to:
+
+```php
+try {
+ $address = Client::default()->lookup('25845-000');
+} catch (CepNotFoundException $e) {
+ if ($e->faixa !== null) {
+ printf(
+ "CEP not found in %s — the city has %d CEPs, between %s and %s\n",
+ $e->faixa->label(), // "Areal/RJ"
+ $e->faixa->ceps, // 118
+ $e->faixa->cep_ini, // "25845001"
+ $e->faixa->cep_fim, // "25849899"
+ );
+ }
+}
+```
+
+It is still a 404. **`faixa` is context for an error, never an address**: a CEP
+that has one is just as absent from the database as one that does not, and
+nothing here should turn a not-found into a success. What it buys you is a
+message worth reading — *"we could not find that CEP in Areal/RJ"* instead of
+*"CEP not found"*.
+
+`$e->faixa` is null whenever the API sent nothing, which is what happens for a
+CEP in a band no municipality owns. The field is **omitted, never null**, so
+absence is normal; and a `faixa` this client cannot read is also null, because a
+malformed block must never cost you the exception you came for.
+
+| field | |
+| --- | --- |
+| `localidade`, `uf`, `estado`, `regiao`, `ibge` | the municipality, same keys as on `Address` |
+| `cep_ini`, `cep_fim` | the smallest and largest CEP that **exist** there |
+| `ceps` | how many CEPs the municipality actually has |
+| `label()` | `"Areal/RJ"`, for showing to a person |
+
+Two things about the range, both easy to get wrong:
+
+`cep_ini` and `cep_fim` are **not** the bounds the Correios publish. They are
+the extremes of what is really in the database. The DNE's own start for Areal is
+`25845000` — exactly the CEP that failed above — and it is not `cep_ini`
+precisely because it does not exist.
+
+**The range is sparse.** Areal's 118 CEPs are spread across the ~4,900 numbers
+between `25845001` and `25849899`; almost all of that interval is empty. That is
+what `ceps` is for. Never iterate the interval, never present the pair as the
+list of valid CEPs for the city, and never conclude from
+`cep_ini <= $x <= cep_fim` that `$x` exists.
+
## Development
Everything runs through `make`: