summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-27 20:33:51 -0300
committerMurilo Ijanc <[email protected]>2026-07-27 20:33:51 -0300
commit2fb251996798798af38903fe535b8d147084a50a (patch)
tree9bcbb6ead9c7c4c1910495186707dc0f8859169d /src
parentb71faa6e95a0736f1ff171e006b171467b65db90 (diff)
downloadcpp-0.2.0.tar.gz
Expose the municipality behind a not-found CEPHEADv0.2.0main
A CEP that fails a lookup is rarely random: it is usually someone who knows the city and guessed the number, and the classic guess is the -000 that 957 municipalities never received. The API now names, on the 404 itself, the municipality that CEP would have belonged to, so a caller can say "not found — did you mean somewhere in Areal/RJ?" instead of just "not found". CepNotFound carries it as an optional Faixa. It stays an error: the block is context, not an address, and the range it describes is sparse, which is why it reports a count alongside the first and last CEP that actually exist there rather than the bounds the Correios publish. Parsing it is best-effort on purpose — a missing, empty or malformed block leaves the optional empty, since a 404 is already the answer and reading the hint attached to it must never cost the diagnosis.
Diffstat (limited to 'src')
-rw-r--r--src/client.cpp21
-rw-r--r--src/models.cpp19
2 files changed, 39 insertions, 1 deletions
diff --git a/src/client.cpp b/src/client.cpp
index ef19da8..84011a3 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -170,6 +170,20 @@ std::string message_from_error_body(const std::string& body, const std::string&
return fallback;
}
+/// Reads the `faixa` block a 404 may carry, never failing.
+///
+/// A body that is not JSON, or carries no `faixa`, or carries one that cannot
+/// be read, all give `std::nullopt`. The 404 is the answer; the hint attached
+/// to it is a bonus and must never cost the diagnosis.
+std::optional<Faixa> faixa_from_error_body(const std::string& body) {
+ const std::optional<Json> parsed = Json::parse(body);
+ if (!parsed) {
+ return std::nullopt;
+ }
+ const Json* block = parsed->find("faixa");
+ return block == nullptr ? std::nullopt : Faixa::from_json(*block);
+}
+
std::string preview(const std::string& body) {
constexpr std::size_t kLimit = 120;
return body.size() <= kLimit ? body : body.substr(0, kLimit) + "...";
@@ -232,7 +246,12 @@ Address Client::lookup(std::string_view cep) const {
const Response response = request("/" + number, {200});
if (response.status == 404) {
- throw CepNotFound("CEP " + number + " does not exist in the DNE database");
+ std::optional<Faixa> faixa = faixa_from_error_body(response.body);
+ std::string message = "CEP " + number + " does not exist in the DNE database";
+ if (faixa) {
+ message += " (the range it falls in belongs to " + faixa->localidade + "/" + faixa->uf + ")";
+ }
+ throw CepNotFound(message, std::move(faixa));
}
if (response.status == 400) {
throw InvalidCep(message_from_error_body(response.body, "CEP " + number + " rejected"));
diff --git a/src/models.cpp b/src/models.cpp
index 84e03f9..088283b 100644
--- a/src/models.cpp
+++ b/src/models.cpp
@@ -124,6 +124,25 @@ Address Address::from_json(const Json& data) {
return address;
}
+std::optional<Faixa> Faixa::from_json(const Json& data) {
+ if (!data.is_object()) {
+ return std::nullopt;
+ }
+ Faixa faixa;
+ faixa.localidade = as_field(data.find("localidade"));
+ faixa.uf = as_field(data.find("uf"));
+ if (faixa.localidade.empty() || faixa.uf.empty()) {
+ return std::nullopt;
+ }
+ faixa.estado = as_field(data.find("estado"));
+ faixa.regiao = as_field(data.find("regiao"));
+ faixa.ibge = as_field(data.find("ibge"));
+ faixa.cep_ini = as_field(data.find("cep_ini"));
+ faixa.cep_fim = as_field(data.find("cep_fim"));
+ faixa.ceps = as_integer(data.find("ceps")).value_or(0);
+ return faixa;
+}
+
Health Health::from_json(const Json& data) {
Health health;
health.status = as_field(data.find("status"));