summaryrefslogtreecommitdiffstats
path: root/src/model.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/model.rs')
-rw-r--r--src/model.rs81
1 files changed, 81 insertions, 0 deletions
diff --git a/src/model.rs b/src/model.rs
index 59cacbe..9092239 100644
--- a/src/model.rs
+++ b/src/model.rs
@@ -64,6 +64,87 @@ impl Geo {
}
}
+/// The municipality a CEP that does not exist would have belonged to.
+///
+/// This is **context for a not-found**, never an address. It rides on
+/// [`Error::NotFound`](crate::Error::NotFound), the response is still a 404, and
+/// nothing here says the CEP that was looked up exists — it does not.
+///
+/// It is there because a CEP that fails 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 range is not the Correios range
+///
+/// [`cep_ini`](Faixa::cep_ini) and [`cep_fim`](Faixa::cep_fim) are the smallest
+/// and the largest CEP that **actually exist** in that municipality, not the
+/// bounds the Correios publish for it. For Areal/RJ the published start is
+/// `25845000` — which is precisely the CEP that just failed. Showing the
+/// official bounds would therefore suggest the CEP is valid.
+///
+/// # The range is sparse
+///
+/// The two ends are not a span of usable numbers. Areal's 118 CEPs are spread
+/// over the roughly 4,900 numbers between `25845001` and `25849899`; the vast
+/// majority of that interval does not exist either. That is what
+/// [`ceps`](Faixa::ceps) is for — never present the pair as a list, or as a
+/// count, of valid CEPs.
+#[derive(Debug, Clone, PartialEq)]
+#[non_exhaustive]
+pub struct Faixa {
+ /// Municipality.
+ pub localidade: String,
+ /// Federative unit, two letters: `"RJ"`.
+ pub uf: String,
+ /// Federative unit spelled out: `"Rio de Janeiro"`.
+ pub estado: String,
+ /// Region: `"Sudeste"`, `"Nordeste"`, and so on.
+ pub regiao: String,
+ /// IBGE municipality code — the key to join with public datasets.
+ pub ibge: String,
+ /// Smallest CEP that exists in the municipality, unmasked.
+ pub cep_ini: String,
+ /// Largest CEP that exists in the municipality, unmasked.
+ pub cep_fim: String,
+ /// How many CEPs exist between those two ends, the ends included.
+ ///
+ /// Read next to [`cep_ini`](Faixa::cep_ini) and [`cep_fim`](Faixa::cep_fim)
+ /// it is the measure of how sparse the interval is: 118 across ~4,900
+ /// numbers for Areal/RJ.
+ pub ceps: u64,
+}
+
+impl Faixa {
+ /// `"Areal/RJ"` — the municipality in the shortest form worth showing.
+ pub fn municipality(&self) -> String {
+ format!("{}/{}", self.localidade, self.uf)
+ }
+
+ /// Build from the `faixa` block of a 404 body, or `None`.
+ ///
+ /// Anything short of an object naming a municipality yields `None`:
+ /// absent, null, empty, or malformed. The caller is already returning
+ /// [`Error::NotFound`](crate::Error::NotFound) — turning a clean not-found
+ /// into a parse failure over decoration would be a bad trade.
+ pub(crate) fn from_json(value: &Value) -> Option<Faixa> {
+ value.as_object()?;
+ let localidade = text(value, "localidade");
+ if localidade.is_empty() {
+ return None;
+ }
+ Some(Faixa {
+ localidade,
+ uf: text(value, "uf"),
+ estado: text(value, "estado"),
+ regiao: text(value, "regiao"),
+ ibge: text(value, "ibge"),
+ cep_ini: text(value, "cep_ini"),
+ cep_fim: text(value, "cep_fim"),
+ ceps: count(value, "ceps"),
+ })
+ }
+}
+
/// A CEP and everything the database knows about it.
///
/// Fields the database does not have for that CEP come back as empty strings,