Skip to main content

stdbr_core/
municipio.rs

1//! Municípios brasileiros.
2//!
3//! Dados extraídos da API de Localidades do IBGE:
4//! <https://servicodados.ibge.gov.br/api/v1/localidades/municipios>
5//!
6//! Referência oficial dos códigos:
7//! <https://www.ibge.gov.br/explica/codigos-dos-municipios.php>
8
9use crate::uf::State;
10use core::fmt;
11
12mod generated;
13pub use generated::ALL;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct Municipio {
17    /// IBGE 7-digit code.
18    pub ibge_code: u32,
19    /// Municipality name.
20    pub name: &'static str,
21    /// State this municipality belongs to.
22    pub state: State,
23}
24
25/// IBGE codes of the 27 state capitals, ordered to match [`crate::uf::ALL`].
26#[allow(clippy::unreadable_literal)]
27pub const CAPITAL_CODES: [u32; 27] = [
28    1200401, // Rio Branco - AC
29    2704302, // Maceió - AL
30    1302603, // Manaus - AM
31    1600303, // Macapá - AP
32    2927408, // Salvador - BA
33    2304400, // Fortaleza - CE
34    5300108, // Brasília - DF
35    3205309, // Vitória - ES
36    5208707, // Goiânia - GO
37    2111300, // São Luís - MA
38    3106200, // Belo Horizonte - MG
39    5002704, // Campo Grande - MS
40    5103403, // Cuiabá - MT
41    1501402, // Belém - PA
42    2507507, // João Pessoa - PB
43    2611606, // Recife - PE
44    2211001, // Teresina - PI
45    4106902, // Curitiba - PR
46    3304557, // Rio de Janeiro - RJ
47    2408102, // Natal - RN
48    1100205, // Porto Velho - RO
49    1400100, // Boa Vista - RR
50    4314902, // Porto Alegre - RS
51    4205407, // Florianópolis - SC
52    2800308, // Aracaju - SE
53    3550308, // São Paulo - SP
54    1721000, // Palmas - TO
55];
56
57impl Municipio {
58    /// Find a municipality by its IBGE code.
59    pub fn from_ibge_code(code: u32) -> Option<&'static Municipio> {
60        ALL.iter().find(|m| m.ibge_code == code)
61    }
62
63    /// Get the capital of a given state.
64    pub fn capital_of(state: State) -> &'static Municipio {
65        let code = CAPITAL_CODES
66            .iter()
67            .zip(crate::uf::ALL.iter())
68            .find(|(_, s)| **s == state)
69            .map(|(c, _)| *c)
70            .unwrap();
71        Self::from_ibge_code(code).unwrap()
72    }
73
74    /// Returns all municipalities in a given state.
75    ///
76    /// Efficient: exploits the fact that `ALL` is sorted by state.
77    pub fn by_state(state: State) -> &'static [Municipio] {
78        let start = ALL.iter().position(|m| m.state == state);
79        match start {
80            Some(s) => {
81                let end = ALL[s..]
82                    .iter()
83                    .position(|m| m.state != state)
84                    .map_or(ALL.len(), |e| s + e);
85                &ALL[s..end]
86            }
87            None => &[],
88        }
89    }
90
91    /// Find municipalities whose name contains the given substring (case-insensitive).
92    pub fn search_by_name(query: &str) -> impl Iterator<Item = &'static Municipio> {
93        let query_lower: alloc::string::String =
94            query.chars().flat_map(char::to_lowercase).collect();
95        ALL.iter().filter(move |m| {
96            let name_lower: alloc::string::String =
97                m.name.chars().flat_map(char::to_lowercase).collect();
98            name_lower.contains(query_lower.as_str())
99        })
100    }
101
102    pub fn is_capital(&self) -> bool {
103        CAPITAL_CODES.contains(&self.ibge_code)
104    }
105}
106
107impl fmt::Display for Municipio {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        write!(f, "{}/{}", self.name, self.state)
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use alloc::vec::Vec;
117
118    #[test]
119    fn total_count() {
120        assert_eq!(ALL.len(), 5571);
121    }
122
123    #[test]
124    fn sorted_by_state_then_name() {
125        for pair in ALL.windows(2) {
126            let a = &pair[0];
127            let b = &pair[1];
128            assert!(
129                (a.state as u8) < (b.state as u8)
130                    || ((a.state as u8) == (b.state as u8) && a.name <= b.name),
131                "{a} should come before {b}"
132            );
133        }
134    }
135
136    #[test]
137    fn each_state_has_municipalities() {
138        for &state in &crate::uf::ALL {
139            let munis = Municipio::by_state(state);
140            assert!(!munis.is_empty(), "{state:?} has no municipalities");
141        }
142    }
143
144    #[test]
145    fn capitals_exist_in_all() {
146        for &code in &CAPITAL_CODES {
147            assert!(
148                Municipio::from_ibge_code(code).is_some(),
149                "Capital code {code} not found"
150            );
151        }
152    }
153
154    #[test]
155    fn capital_of_each_state() {
156        for &state in &crate::uf::ALL {
157            let cap = Municipio::capital_of(state);
158            assert_eq!(cap.state, state);
159            assert!(cap.is_capital());
160        }
161    }
162
163    #[test]
164    fn from_ibge_code_sao_paulo() {
165        let sp = Municipio::from_ibge_code(3_550_308).unwrap();
166        assert_eq!(sp.name, "São Paulo");
167        assert_eq!(sp.state, State::SP);
168    }
169
170    #[test]
171    fn from_ibge_code_returns_none() {
172        assert!(Municipio::from_ibge_code(0).is_none());
173        assert!(Municipio::from_ibge_code(9_999_999).is_none());
174    }
175
176    #[test]
177    fn by_state_returns_correct_state() {
178        let sp_munis = Municipio::by_state(State::SP);
179        for m in sp_munis {
180            assert_eq!(m.state, State::SP);
181        }
182        // SP has 645 municipalities
183        assert!(sp_munis.len() > 600);
184    }
185
186    #[test]
187    fn search_by_name_works() {
188        let results: Vec<_> = Municipio::search_by_name("porto").collect();
189        assert!(results.len() >= 2);
190    }
191
192    #[test]
193    fn is_capital_correct() {
194        let sp = Municipio::from_ibge_code(3_550_308).unwrap();
195        assert!(sp.is_capital());
196
197        // Campinas is not a capital
198        let campinas = Municipio::from_ibge_code(3_509_502).unwrap();
199        assert!(!campinas.is_capital());
200    }
201
202    #[test]
203    fn display_format() {
204        use alloc::string::ToString;
205        let sp = Municipio::from_ibge_code(3_550_308).unwrap();
206        assert_eq!(sp.to_string(), "São Paulo/SP");
207    }
208
209    #[test]
210    fn ibge_codes_are_7_digits() {
211        for m in ALL {
212            assert!(
213                m.ibge_code >= 1_000_000 && m.ibge_code <= 9_999_999,
214                "{} has invalid IBGE code: {}",
215                m.name,
216                m.ibge_code
217            );
218        }
219    }
220}