1use 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 pub ibge_code: u32,
19 pub name: &'static str,
21 pub state: State,
23}
24
25#[allow(clippy::unreadable_literal)]
27pub const CAPITAL_CODES: [u32; 27] = [
28 1200401, 2704302, 1302603, 1600303, 2927408, 2304400, 5300108, 3205309, 5208707, 2111300, 3106200, 5002704, 5103403, 1501402, 2507507, 2611606, 2211001, 4106902, 3304557, 2408102, 1100205, 1400100, 4314902, 4205407, 2800308, 3550308, 1721000, ];
56
57impl Municipio {
58 pub fn from_ibge_code(code: u32) -> Option<&'static Municipio> {
60 ALL.iter().find(|m| m.ibge_code == code)
61 }
62
63 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 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 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 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 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}