summaryrefslogtreecommitdiffstats
path: root/src/error.rs
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-27 20:33:37 -0300
committerMurilo Ijanc <[email protected]>2026-07-27 20:33:37 -0300
commit487b4c5628c02130580c675925a1d64b09a5f6cb (patch)
tree240338af0c2c246bb2c689464f63d70b1f27f240 /src/error.rs
parent4a8c3b675df4b1a6b29904fc2af60605002a7f9e (diff)
downloadrust-eeaee4cece87d9dfe4ae0bea390f24a01a5f185b.tar.gz
Carry the municipality of a CEP that does not existHEADv0.2.0main
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/error.rs')
-rw-r--r--src/error.rs49
1 files changed, 48 insertions, 1 deletions
diff --git a/src/error.rs b/src/error.rs
index 40a3049..b771ded 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -15,11 +15,17 @@
//! That split is why `NotFound` is not "the HTTP 404 variant": the client also
//! returns `InvalidCep` without making any request at all, when the CEP is
//! malformed before it ever leaves the machine.
+//!
+//! [`Error::NotFound`] may carry a [`Faixa`] — the municipality the CEP would
+//! have belonged to. That is context on the failure and changes nothing about
+//! it: the answer is still 404 and the CEP still does not exist.
use std::error::Error as StdError;
use std::fmt;
use std::time::Duration;
+use crate::model::Faixa;
+
/// The result of every fallible operation in this crate.
pub type Result<T> = std::result::Result<T, Error>;
@@ -45,6 +51,17 @@ pub enum Error {
NotFound {
/// The CEP that was looked up, unmasked.
cep: String,
+ /// The municipality this CEP *would* belong to, when the API knows it.
+ ///
+ /// Still an error, and still a 404 — see [`Faixa`] before showing any
+ /// of it to a user. `None` when the CEP falls in a band no municipality
+ /// owns, and also whenever the field cannot be read: an unparseable
+ /// `faixa` never costs the not-found.
+ ///
+ /// Boxed to keep [`Error`] small: it travels in the `Err` of every
+ /// [`Result`] in this crate, including the ones that never carry a
+ /// `faixa`. [`Error::faixa`] hides the box at the call site.
+ faixa: Option<Box<Faixa>>,
},
/// The API (or the edge in front of it) answered 429 on every attempt.
@@ -102,6 +119,28 @@ impl Error {
)
}
+ /// The municipality of a [`Error::NotFound`], when the API named one.
+ ///
+ /// `None` for every other variant, and for a not-found the API could not
+ /// place. It is context for the failure, not a result: the CEP asked for
+ /// still does not exist.
+ ///
+ /// ```no_run
+ /// # let cep_from_form = "25845000";
+ /// if let Err(err) = cepis::lookup(cep_from_form) {
+ /// match err.faixa() {
+ /// Some(faixa) => println!("no such CEP in {}", faixa.municipality()),
+ /// None => println!("no such CEP"),
+ /// }
+ /// }
+ /// ```
+ pub fn faixa(&self) -> Option<&Faixa> {
+ match self {
+ Error::NotFound { faixa, .. } => faixa.as_deref(),
+ _ => None,
+ }
+ }
+
pub(crate) fn invalid_cep(message: impl Into<String>) -> Self {
Error::InvalidCep {
message: message.into(),
@@ -129,9 +168,17 @@ impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::InvalidCep { message } => f.write_str(message),
- Error::NotFound { cep } => {
+ Error::NotFound { cep, faixa: None } => {
write!(f, "CEP {cep} does not exist in the DNE database")
}
+ Error::NotFound {
+ cep,
+ faixa: Some(faixa),
+ } => write!(
+ f,
+ "CEP {cep} does not exist in the DNE database (it would be in {})",
+ faixa.municipality()
+ ),
Error::RateLimited { message, .. } => f.write_str(message),
Error::Server { message, .. } => f.write_str(message),
Error::Network { message, .. } => f.write_str(message),