diff options
| author | Murilo Ijanc <[email protected]> | 2026-07-27 20:42:22 -0300 |
|---|---|---|
| committer | Murilo Ijanc <[email protected]> | 2026-07-27 20:44:20 -0300 |
| commit | c3b525537e944064869a2cd6495733388144ce64 (patch) | |
| tree | a30fa16568ffe470876f670e6bd75873c201df1a /include | |
| parent | b7b08a7d29d84de187dd8a55e0c94de66b07a572 (diff) | |
| download | asm-c2a632111bdee923e7cb6a1eb9f2ab352b8d5c70.tar.gz | |
A CEP that fails is almost never a random number. It is someone who knows
the city and guessed the last digits, and the guess is usually the -000 that
957 municipalities never received. The API answers those 404s with a faixa
object naming the municipality that owns the band, which turns "not found"
from a dead end into something the caller can act on - and the caller cannot
reach it through this library, because the 404 path never looked at the body
at all.
cepis_error is where a 404 already reports itself, so faixa goes there, as
inline storage rather than a pointer: the struct is documented as owning
nothing and declared on the caller's stack, and that stays true. It costs
sizeof(cepis_error), 272 bytes to 384, so 0.2.0 needs a recompile.
The parser needed one new primitive. cepis__json_field could only pull
scalars out of a flat object; splitting it over cepis__json_find, which
returns a pointer to a value of any shape, reaches one level down without
building a document tree - the same depth cepis__geo_parse already needs for
geo. Absence is the case that matters: no faixa, an empty one, or one that
is not an object all leave the error byte-for-byte what it was before.
cep_ini and cep_fim are deliberately not presented as a range. They are the
lowest and highest CEP that exist, the band is sparse - Areal's 118 CEPs are
scattered over ~4900 numbers - and ceps is the only honest count.
Diffstat (limited to 'include')
| -rw-r--r-- | include/cepis.h | 68 |
1 files changed, 62 insertions, 6 deletions
diff --git a/include/cepis.h b/include/cepis.h index ea8d378..a00982a 100644 --- a/include/cepis.h +++ b/include/cepis.h @@ -18,7 +18,7 @@ extern "C" { #endif -#define CEPIS_VERSION "0.1.0" +#define CEPIS_VERSION "0.2.0" /** Largest number of CEPs the API accepts in one /csv request. */ #define CEPIS_MAX_BATCH 500 @@ -26,6 +26,12 @@ extern "C" { /** Size of cepis_error::message, including the terminating NUL. */ #define CEPIS_MESSAGE_MAX 256 +/** Size of cepis_faixa::localidade, including the terminating NUL. The + * longest municipality name in the IBGE list is 32 characters; this leaves + * room for it in UTF-8 twice over. A longer name is truncated, never + * overflowed. */ +#define CEPIS_FAIXA_LOCALIDADE_MAX 72 + /** * Outcome of a call. Split by the decision the caller has to make, not by the * HTTP status that caused it: CEPIS_ERR_INVALID_CEP and CEPIS_ERR_NOT_FOUND @@ -145,10 +151,54 @@ struct cepis_health { }; /** + * The municipality a CEP that does not exist would have belonged to. + * + * 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 API answers a 404 with the + * band the number falls in, when a municipality owns that band. This is + * context for an error and **not an address**: the lookup still failed, and + * cepis_lookup() still returns CEPIS_ERR_NOT_FOUND. + * + * Everything here is inline storage inside cepis_error - nothing to free, + * nothing that can be NULL. Read it only when `present` is non-zero; when it + * is zero the strings are `""` and `ceps` is 0. + * + * `cep_ini` and `cep_fim` are the **smallest and largest CEP that actually + * exist** in that municipality, not the band the Correios publish. For Areal + * the DNE's own band starts at 25845000 - exactly the CEP that just failed - + * while `cep_ini` is 25845001. + * + * The band is sparse. Areal's 118 CEPs are scattered over the ~4900 numbers + * between 25845001 and 25849899, so `ceps` is a count and the pair is a pair + * of endpoints. Never present the two as a range of valid CEPs, and never + * iterate it. + */ +typedef struct cepis_faixa { + /** Non-zero when the API named a municipality for this CEP. Zero means it + * did not - the field is omitted, never null, for a number in a band no + * municipality owns. */ + int present; + /** Municipality name, e.g. "Areal". Truncated at + * CEPIS_FAIXA_LOCALIDADE_MAX - 1 bytes. */ + char localidade[CEPIS_FAIXA_LOCALIDADE_MAX]; + /** Two-letter state, e.g. "RJ". */ + char uf[3]; + /** Smallest CEP that exists there, 8 digits unmasked. */ + char cep_ini[9]; + /** Largest CEP that exists there, 8 digits unmasked. */ + char cep_fim[9]; + /** How many CEPs exist there. Far smaller than cep_fim - cep_ini. */ + long long ceps; +} cepis_faixa; + +/** * Detail about a failure. Optional: pass NULL to any call that takes one. * * Caller-allocated, no ownership: declare one on the stack and reuse it. It is - * fully overwritten at the start of every call that receives it. + * fully overwritten at the start of every call that receives it, `faixa` + * included, so a band left over from a previous 404 cannot be mistaken for + * this one's. */ typedef struct cepis_error { /** Same value the call returned. */ @@ -159,8 +209,12 @@ typedef struct cepis_error { /** Seconds the server asked for via Retry-After, or 0 if it did not say. * Only meaningful for CEPIS_ERR_RATE_LIMITED. */ double retry_after; - /** Human-readable description, NUL-terminated, empty on success. */ + /** Human-readable description, NUL-terminated, empty on success. On a 404 + * with a `faixa` it names the municipality too. */ char message[CEPIS_MESSAGE_MAX]; + /** Only ever filled by CEPIS_ERR_NOT_FOUND, and only when the API sent a + * band. See cepis_faixa. */ + cepis_faixa faixa; } cepis_error; /** @@ -192,7 +246,7 @@ typedef struct cepis_config { * 0 leaves max_attempts as the only bound. */ double total_timeout; /** Overrides the User-Agent. Default - * "cepis-asm/0.1.0 (+https://cep.is)". */ + * "cepis-asm/0.2.0 (+https://cep.is)". */ const char *user_agent; } cepis_config; @@ -201,7 +255,7 @@ typedef struct cepis_config { * between requests, so one instance is safe to share across threads. */ typedef struct cepis_client cepis_client; -/** Library version, "0.1.0". Static storage; do not free. */ +/** Library version, "0.2.0". Static storage; do not free. */ const char *cepis_version(void); /** One-line description of a cepis_status. Static storage; do not free. */ @@ -263,7 +317,9 @@ cepis_status cepis_normalize_cep(const char *value, char *out); * The CEP is validated locally first: a 7-digit CEP returns * CEPIS_ERR_INVALID_CEP without a request being made. A 400 from the API is * also CEPIS_ERR_INVALID_CEP, carrying the API's own message. A 404 is - * CEPIS_ERR_NOT_FOUND. Neither is retried; 429, 5xx and network failures are. + * CEPIS_ERR_NOT_FOUND, and fills cepis_error::faixa when the API says which + * municipality owns that band. Neither is retried; 429, 5xx and network + * failures are. */ cepis_status cepis_lookup(cepis_client *client, const char *cep, cepis_address **out, cepis_error *err); |