diff options
| author | Murilo Ijanc <[email protected]> | 2026-07-27 20:37:05 -0300 |
|---|---|---|
| committer | Murilo Ijanc <[email protected]> | 2026-07-27 20:44:20 -0300 |
| commit | eb15ec9b2715783fcd2bf4f834595d1bd549c09d (patch) | |
| tree | cd9c6e80892b409db346fde3fe8334f3c37a1130 /include/cepis.h | |
| parent | e2c6ae13af335b311da2429c903032124f926df2 (diff) | |
| download | c-0.2.0.tar.gz | |
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 'include/cepis.h')
| -rw-r--r-- | include/cepis.h | 105 |
1 files changed, 101 insertions, 4 deletions
diff --git a/include/cepis.h b/include/cepis.h index 6b2dc9a..d639d62 100644 --- a/include/cepis.h +++ b/include/cepis.h @@ -39,6 +39,10 @@ * borrowed for the duration of the call only; the library copies whatever it * needs to keep. * + * A ::cepis_error_t is the exception in the other direction: it owns nothing, + * lives happily on the stack and is never freed. ::cepis_error_faixa therefore + * returns a pointer *into* it, borrowed for as long as the error itself lives. + * * @section fieldnames Field names * * The API surface is English, but the **data fields keep the API's own @@ -63,7 +67,7 @@ extern "C" { #endif /** Library version. */ -#define CEPIS_VERSION "0.1.0" +#define CEPIS_VERSION "0.2.0" /** Default API endpoint. */ #define CEPIS_DEFAULT_BASE_URL "https://cep.is" @@ -77,6 +81,12 @@ extern "C" { /** Size of ::cepis_error_t::message, terminator included. */ #define CEPIS_ERROR_MESSAGE_MAX 256 +/** Size of the longer ::cepis_faixa_t members, terminator included. */ +#define CEPIS_FAIXA_NAME_MAX 80 + +/** Size of the short coded ::cepis_faixa_t members, terminator included. */ +#define CEPIS_FAIXA_CODE_MAX 16 + /** * @brief Result of every fallible call. * @@ -108,11 +118,58 @@ typedef enum cepis_status { } cepis_status_t; /** + * @brief The municipality a CEP that does not exist would have belonged to. + * + * Context for a ::CEPIS_ERR_NOT_FOUND, **not an address**. The CEP asked for + * still does not exist and the lookup still failed with a 404; this only names + * the band it fell inside. Never promote it to a delivery address, and never + * let it turn a not-found into a success. + * + * It exists because 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. + * + * @section faixabounds What the bounds are, and are not + * + * ::cep_ini and ::cep_fim are the smallest and the largest CEP that **actually + * exist** in that municipality -- not the range the Correios publish for it. + * For Areal/RJ the DNE's own start is `25845000`, which is exactly the CEP that + * just failed; ::cep_ini is `25845001`, the first one really there. + * + * The range is **sparse**. Areal's ::ceps is 118, spread over the ~4900 numbers + * between `25845001` and `25849899`. Iterating the interval, or offering the + * pair as a list of valid CEPs, produces mostly CEPs that do not exist either. + * ::ceps is there precisely so that cannot pass unnoticed: use it to say "118 + * CEPs in Areal", never "4899 CEPs". + */ +typedef struct cepis_faixa { + /** Municipality name. Never `NULL`, never empty when present. */ + char localidade[CEPIS_FAIXA_NAME_MAX]; + /** Two-letter state code, e.g. `"RJ"`. */ + char uf[CEPIS_FAIXA_CODE_MAX]; + char estado[CEPIS_FAIXA_NAME_MAX]; + char regiao[CEPIS_FAIXA_CODE_MAX]; + /** IBGE municipality code. */ + char ibge[CEPIS_FAIXA_CODE_MAX]; + /** Smallest CEP that exists there, unmasked. */ + char cep_ini[CEPIS_CEP_LEN + 1]; + /** Largest CEP that exists there, unmasked. */ + char cep_fim[CEPIS_CEP_LEN + 1]; + /** + * How many CEPs exist between ::cep_ini and ::cep_fim. Far smaller + * than the numeric distance between them. `0` when the API did not + * say. + */ + long long ceps; +} cepis_faixa_t; + +/** * @brief Detail about a failure. * - * Owns no memory: the message lives in a fixed buffer inside the struct, so an - * error can be declared on the stack and forgotten. Pass a pointer to one as - * the last argument of any fallible call, or `NULL` to discard the detail. + * Owns no memory: the message and ::faixa live in fixed buffers inside the + * struct, so an error can be declared on the stack and forgotten. Pass a + * pointer to one as the last argument of any fallible call, or `NULL` to + * discard the detail. */ typedef struct cepis_error { /** Same value the call returned. */ @@ -128,6 +185,19 @@ typedef struct cepis_error { * attempts it made; it is exposed for rescheduling the work. */ double retry_after; + /** + * `true` when ::faixa was filled. Only ::CEPIS_ERR_NOT_FOUND from + * ::cepis_lookup ever sets it, and only when the CEP fell inside a + * band some municipality owns -- the API **omits** the block, rather + * than sending null, for a band nobody owns. Always test this before + * reading ::faixa, or use ::cepis_error_faixa. + */ + bool has_faixa; + /** + * The municipality the missing CEP would have belonged to. Meaningful + * only while ::has_faixa is `true`; untouched otherwise. + */ + cepis_faixa_t faixa; } cepis_error_t; /** @@ -345,6 +415,11 @@ const char *cepis_client_user_agent(const cepis_client_t *client); * own message: the format already passed local validation, so the server knows * something this client does not. * + * On ::CEPIS_ERR_NOT_FOUND the API often names the municipality the CEP would + * have belonged to; it arrives in ::cepis_error_t::faixa, guarded by + * ::cepis_error_t::has_faixa. It is context for the failure and never an + * address -- see ::cepis_faixa_t. + * * This is the only call that carries ::cepis_address_t::geo. */ cepis_status_t cepis_lookup(cepis_client_t *client, const char *cep, @@ -458,6 +533,28 @@ bool cepis_geo_is_precise(const cepis_geo_t *geo); /** @brief `true` when the API reports itself healthy. `false` for `NULL`. */ bool cepis_health_ok(const cepis_health_t *health); +/** + * @brief The municipality named by a not-found error, or `NULL`. + * + * @return A pointer borrowed from @p err, valid as long as @p err is, or + * `NULL` when the API named no municipality -- for `NULL`, for any + * status other than ::CEPIS_ERR_NOT_FOUND, and for a CEP that fell in + * a band nobody owns. + * + * Reads better than testing ::cepis_error_t::has_faixa by hand and gives the + * absent case the same shape as ::cepis_address_t::geo: + * + * @code + * const cepis_faixa_t *faixa = cepis_error_faixa(&err); + * + * if (faixa != NULL) + * printf("no such CEP; %s/%s has %lld, from %s to %s\n", + * faixa->localidade, faixa->uf, faixa->ceps, faixa->cep_ini, + * faixa->cep_fim); + * @endcode + */ +const cepis_faixa_t *cepis_error_faixa(const cepis_error_t *err); + /** @brief A short, stable, English name for a status code. */ const char *cepis_status_str(cepis_status_t status); |