diff options
| author | Murilo Ijanc <[email protected]> | 2026-07-27 20:37:09 -0300 |
|---|---|---|
| committer | Murilo Ijanc <[email protected]> | 2026-07-27 20:37:09 -0300 |
| commit | a891657aa43d2aba06a6347dd2f94edd19afd6ba (patch) | |
| tree | b6369ed2c8485455f9c1aa707c1cd287cb8e7065 /source/cepis/client.d | |
| parent | 794f28199a4dde9d18ec711864336c96c58bb422 (diff) | |
| download | d-7cfbca977f267904ebcac7e10956876df780daf8.tar.gz | |
A 404 is rarely a random number: it is usually someone who knows the city
and guessed the digits, and the classic guess is the -000 that 957
municipalities never received. The API now attaches a faixa to the
not-found body naming the municipality that owns the range, so the caller
can say "no such CEP, but that range is Areal/RJ" instead of only "no".
It stays an error. Faixa hangs off CepNotFoundException, never off
Address, and parsing is deliberately lenient: an absent, malformed or
unnamed block yields a null Nullable, so reading the extra context can
never cost the caller the answer it already had.
cep_ini and cep_fim are the smallest and largest CEP that actually exist
there, not the bounds the Correios publish, and the range between them is
sparse — hence ceps, so nobody mistakes the pair for a list.
Diffstat (limited to 'source/cepis/client.d')
| -rw-r--r-- | source/cepis/client.d | 37 |
1 files changed, 34 insertions, 3 deletions
diff --git a/source/cepis/client.d b/source/cepis/client.d index a93af7f..ef2f10e 100644 --- a/source/cepis/client.d +++ b/source/cepis/client.d @@ -37,7 +37,7 @@ import cepis.retry; @safe: /// The version of this library, as sent in the `User-Agent`. -enum string VERSION = "0.1.0"; +enum string VERSION = "0.2.0"; /// Where the public API lives. enum string DEFAULT_URL = "https://cep.is"; @@ -232,11 +232,16 @@ struct Client * API's own message: the format already passed local validation, so the * server knows something this client does not. * + * A 404 may carry a `faixa` — the municipality the CEP would have belonged + * to — which lands on $(D CepNotFoundException.faixa). It is still a 404: + * the block is context for the error and never becomes an $(D Address). + * * Throws: * $(UL * $(LI $(D InvalidCepException) — not 8 digits (no request is made).) * $(LI $(D CepNotFoundException) — well-formed, but not in the - * database.) + * database; carries $(D faixa) when the API named a + * municipality.) * $(LI $(D RateLimitedException) — 429 after every attempt.) * $(LI $(D ServerException) — 5xx after every attempt.) * $(LI $(D NetworkException) — the request never completed.) @@ -250,7 +255,7 @@ struct Client const response = request("/" ~ number, [ushort(200)]); if (response.status == 404) - throw new CepNotFoundException(number); + throw new CepNotFoundException(number, notFoundFaixa(response.content)); if (response.status == 400) throw new InvalidCepException(errorMessage(response.content, "CEP " ~ number ~ " rejected")); @@ -540,6 +545,32 @@ private string errorMessage(string content, string fallback) return fallback; } +/** + * Pull the `faixa` block out of a 404 body. + * + * Every failure here — a body that is not JSON, a `faixa` that is missing, + * empty or malformed — is null, not an exception. The answer to the question + * asked was "this CEP does not exist"; failing to read the extra context the + * API attached to it must never cost the caller that answer. + */ +private Nullable!Faixa notFoundFaixa(string content) +{ + try + { + const value = parseJSON(content); + if (value.type == JSONType.object) + { + const object = value.objectNoRef; + if (auto block = "faixa" in object) + return Faixa.fromJSON(*block); + } + } + catch (Exception) + { + } + return Nullable!Faixa.init; +} + private string preview(string content) { return `"` ~ sanitize(content).byDchar.take(120).array.to!string ~ `"`; |