From eb15ec9b2715783fcd2bf4f834595d1bd549c09d Mon Sep 17 00:00:00 2001 From: Murilo Ijanc Date: Mon, 27 Jul 2026 20:37:05 -0300 Subject: Report the municipality behind a not-found CEP 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. --- src/internal.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src/internal.h') diff --git a/src/internal.h b/src/internal.h index 57487c2..700b1ee 100644 --- a/src/internal.h +++ b/src/internal.h @@ -71,4 +71,19 @@ cepis_address_t *cepis_address_from_csv(const csv_row_t *header, /** @brief Build a health report from a parsed JSON object. */ cepis_health_t *cepis_health_from_json(const json_value_t *object); +/** + * @brief Read the `faixa` block of a 404 body into @p out. + * @param object The decoded body, which may be `NULL`. + * @param out Filled and returning `true`, or untouched and returning + * `false`. + * @return `true` only when the block is present, is an object and names a + * municipality. + * + * Deliberately total: a body that is not JSON, a `faixa` that is null, a + * string, an array or an object without `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. + */ +bool cepis_faixa_from_json(const json_value_t *object, cepis_faixa_t *out); + #endif -- cgit v1.3