summaryrefslogtreecommitdiffstats
path: root/src/client.c
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 /src/client.c
parente2c6ae13af335b311da2429c903032124f926df2 (diff)
downloadc-0.2.0.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 'src/client.c')
-rw-r--r--src/client.c29
1 files changed, 28 insertions, 1 deletions
diff --git a/src/client.c b/src/client.c
index 58c55b3..27812f2 100644
--- a/src/client.c
+++ b/src/client.c
@@ -513,6 +513,20 @@ static void api_message(const cepis_buf_t *body, char *out, size_t out_size,
json_free(root);
}
+/*
+ * A 404 body may carry a `faixa` block naming the municipality the CEP would
+ * have belonged to. Nothing here can fail: an unparseable body just means the
+ * API named no municipality, which is also what an absent block means.
+ */
+static bool faixa_from_body(const cepis_buf_t *body, cepis_faixa_t *out)
+{
+ json_value_t *root = json_parse(body->data, body->len);
+ bool named = cepis_faixa_from_json(root, out);
+
+ json_free(root);
+ return named;
+}
+
static cepis_status_t build_url(const cepis_client_t *client, const char *path,
cepis_buf_t *out)
{
@@ -558,10 +572,23 @@ cepis_status_t cepis_lookup(cepis_client_t *client, const char *cep,
return status;
if (response.status == 404) {
+ cepis_faixa_t faixa;
+ bool named = faixa_from_body(&response.body, &faixa);
+
cepis_buf_free(&response.body);
- return cepis_set_error(
+ status = cepis_set_error(
err, CEPIS_ERR_NOT_FOUND, 404, -1.0,
"CEP %s does not exist in the DNE database", number);
+ /*
+ * After cepis_set_error, which clears has_faixa: the block is
+ * extra detail about the failure, never a reason to report a
+ * different one.
+ */
+ if (named && err != NULL) {
+ err->faixa = faixa;
+ err->has_faixa = true;
+ }
+ return status;
}
if (response.status == 400) {
snprintf(fallback, sizeof(fallback), "CEP %s rejected", number);