summaryrefslogtreecommitdiffstats
path: root/README.md
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-27 20:37:05 -0300
committerMurilo Ijanc <[email protected]>2026-07-27 20:44:20 -0300
commiteb15ec9b2715783fcd2bf4f834595d1bd549c09d (patch)
treecd9c6e80892b409db346fde3fe8334f3c37a1130 /README.md
parente2c6ae13af335b311da2429c903032124f926df2 (diff)
downloadc-a90051dad5efd24e867396ad840afa4d01a4d3ef.tar.gz
Report the municipality behind a not-found CEPHEADv0.2.0main
A CEP that fails is rarely a random number. It is usually someone who knows the city and guessed the last digits, and the classic guess is the -000 that 957 municipalities never received. The API now answers those 404s with a faixa block naming the municipality the CEP would have belonged to, and telling a caller "that CEP does not exist, did you mean somewhere in Areal/RJ?" is worth far more than telling them "no". It rides on cepis_error_t rather than on cepis_address_t because it is context for a failure, not an address: the lookup still returns CEPIS_ERR_NOT_FOUND and still hands back no address. Embedded by value with a has_faixa flag, so the error keeps owning nothing and keeps living on the stack with nothing to free; cepis_error_faixa gives the absent case the same NULL-or-not shape as cepis_address_t::geo, which is what the API's omit-never-null encoding calls for. Parsing is total on purpose. A body that is not JSON, a faixa that is null, a string, an array, or an object with no localidade all mean "the API named no municipality". A 404 is already the answer, and failing to read the garnish must never turn it into a different one. The bounds need reading with care, hence the length of the comments: cep_ini and cep_fim are the smallest and largest CEP that actually exist, not the range the Correios publish -- the DNE's own start for Areal is 25845000, exactly the CEP that just failed -- and the range is sparse, 118 CEPs scattered over the ~4900 numbers up to 25849899. That is what ceps is for.
Diffstat (limited to 'README.md')
-rw-r--r--README.md62
1 files changed, 61 insertions, 1 deletions
diff --git a/README.md b/README.md
index 2c47a6e..c49e25c 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,13 @@ make build
sudo make install # PREFIX=/usr/local by default
```
+> **0.2.0 changed `sizeof(cepis_error_t)`** — it grew a `faixa` member. The
+> source API is unchanged and existing code compiles untouched, but a program
+> built against 0.1.0 must be **recompiled**, not just relinked: it declares
+> the struct on its own stack at the old size, and the new library would write
+> past the end of it. The soname went from `libcepis.so.0` to `libcepis.so.1`
+> so the loader refuses that pairing instead of corrupting it silently.
+
Then compile against it:
```sh
@@ -191,7 +198,7 @@ config.max_attempts = 3; /* counting the first; 1 disables retry */
config.backoff_base = 0.5; /* base of the exponential wait */
config.backoff_max = 8.0; /* cap on each wait */
config.total_timeout = 0.0; /* cap on the whole call; 0 = none */
-config.user_agent = NULL; /* NULL uses cepis-c/0.1.0 */
+config.user_agent = NULL; /* NULL uses cepis-c/0.2.0 */
cepis_client_new(&config, &client, &err);
```
@@ -251,6 +258,59 @@ default:
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.
+### A CEP that does not exist
+
+A `CEPIS_ERR_NOT_FOUND` often knows more than "no". A CEP that fails is rarely
+a random number — it is usually someone who knows the city and guessed the
+last digits, and the classic guess is the `-000` that 957 municipalities never
+received. So the error can name the municipality the CEP *would* have belonged
+to:
+
+```c
+const cepis_faixa_t *faixa;
+
+/* Still CEPIS_ERR_NOT_FOUND, still a 404. */
+cepis_lookup(client, "25845-000", &address, &err);
+
+faixa = cepis_error_faixa(&err);
+if (faixa != NULL) {
+ faixa->localidade; /* "Areal" */
+ faixa->uf; /* "RJ" */
+ faixa->estado; /* "Rio de Janeiro" */
+ faixa->regiao; /* "Sudeste" */
+ faixa->ibge; /* "3300225" */
+ faixa->cep_ini; /* "25845001" */
+ faixa->cep_fim; /* "25849899" */
+ faixa->ceps; /* 118 */
+}
+```
+
+Three things this is not:
+
+- **Not an address.** The CEP asked for still does not exist and the call
+ still failed. Never promote `faixa` to a delivery address, and never let it
+ turn a not-found into a success. Use it to say "that CEP does not exist —
+ did you mean somewhere in Areal/RJ?".
+- **Not the range the Correios publish.** `cep_ini` and `cep_fim` are the
+ smallest and largest CEP that *actually exist* there. The DNE's own start
+ for Areal is `25845000` — exactly the CEP that just failed. `cep_ini` is
+ `25845001`, the first one really in the database.
+- **Not a list.** The range is sparse: Areal's 118 CEPs are scattered across
+ the ~4900 numbers between `25845001` and `25849899`. Iterating the interval
+ mostly produces CEPs that do not exist either. That is what `ceps` is for —
+ say "118 CEPs in Areal", never "4899".
+
+`faixa` is **omitted, never null**, when the CEP falls in a band no
+municipality owns, so `cepis_error_faixa` returning `NULL` — equivalently
+`err.has_faixa == false` — is how you tell absent from present. A missing,
+malformed or empty block leaves it absent and never changes the status: a 404
+is already the answer, and failing to read the garnish must not turn it into a
+different one.
+
+`cepis_error_t` still owns nothing and still lives on the stack: `faixa` is
+embedded by value, so there is nothing extra to free. The pointer
+`cepis_error_faixa` returns is borrowed from the error and dies with it.
+
## Development
```sh