diff options
| author | Murilo Ijanc <[email protected]> | 2026-07-27 20:33:37 -0300 |
|---|---|---|
| committer | Murilo Ijanc <[email protected]> | 2026-07-27 20:33:37 -0300 |
| commit | 487b4c5628c02130580c675925a1d64b09a5f6cb (patch) | |
| tree | 240338af0c2c246bb2c689464f63d70b1f27f240 /src/client.rs | |
| parent | 4a8c3b675df4b1a6b29904fc2af60605002a7f9e (diff) | |
| download | rust-eeaee4cece87d9dfe4ae0bea390f24a01a5f185b.tar.gz | |
A 404 from the API now brings a "faixa" naming the municipality the CEP
would have belonged to, and NotFound carries it. A CEP that fails is
rarely a random number: it is usually someone who knows the city and
guessed the digits, so naming the city turns a dead end into an answer
the caller can show.
It stays an error. The range it carries is the CEPs that actually exist
there, not the bounds the Correios publish — those start at the very CEP
that just failed — and it is sparse, which is what "ceps" measures. The
field is decoration on a failure, so parsing it is entirely defensive:
absent, null or malformed all yield None rather than costing a clean
not-found.
Breaking: NotFound gained a field, hence 0.2.0.
Diffstat (limited to 'src/client.rs')
| -rw-r--r-- | src/client.rs | 27 |
1 files changed, 24 insertions, 3 deletions
diff --git a/src/client.rs b/src/client.rs index 8d73f06..4e6afcd 100644 --- a/src/client.rs +++ b/src/client.rs @@ -12,7 +12,7 @@ use serde_json::{Map, Value}; use crate::csv; use crate::error::{Error, Result}; -use crate::model::{Address, Health}; +use crate::model::{Address, Faixa, Health}; use crate::retry::{jitter, parse_retry_after}; /// Where the public API lives. @@ -270,7 +270,9 @@ impl Client { /// # Errors /// /// - [`Error::InvalidCep`] — not 8 digits (no request is made). - /// - [`Error::NotFound`] — well-formed, but not in the database. + /// - [`Error::NotFound`] — well-formed, but not in the database. It carries + /// a [`Faixa`] naming the municipality the CEP would belong to, whenever + /// the API can place it in one. /// - [`Error::RateLimited`] — 429 after every attempt. /// - [`Error::Server`] — 5xx after every attempt. /// - [`Error::Network`] — the request never completed. @@ -280,7 +282,10 @@ impl Client { let (status, body) = self.request(&format!("/{number}"), None, &[200])?; if status == 404 { - return Err(Error::NotFound { cep: number }); + return Err(Error::NotFound { + cep: number, + faixa: faixa(&body), + }); } if status == 400 { return Err(Error::invalid_cep(error_message( @@ -548,6 +553,22 @@ fn error_message(body: &[u8], fallback: &str) -> String { .unwrap_or_else(|| fallback.to_owned()) } +/// Pull the `faixa` block out of a 404 body, if there is a readable one. +/// +/// Every step is allowed to fail into `None`: a body that is not JSON, a +/// missing or null `faixa`, an object that names no municipality. The caller is +/// on its way to returning [`Error::NotFound`] either way, and an unparseable +/// extra field must not turn a correct not-found into an +/// [`Error::InvalidResponse`]. +fn faixa(body: &[u8]) -> Option<Box<Faixa>> { + serde_json::from_slice::<Value>(body) + .ok() + .as_ref() + .and_then(|v| v.get("faixa")) + .and_then(Faixa::from_json) + .map(Box::new) +} + fn preview(body: &[u8]) -> String { let text = String::from_utf8_lossy(body); let cut = text |