Skip to main content

stdbr_core/
uf.rs

1//! Brazilian states (Unidades Federativas).
2//!
3//! The 26 states plus the Federal District, with their two-letter abbreviations
4//! and region classification.
5
6use core::fmt;
7
8/// The 27 Brazilian federative units.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10#[repr(u8)]
11pub enum State {
12    AC = 0,
13    AL = 1,
14    AM = 2,
15    AP = 3,
16    BA = 4,
17    CE = 5,
18    DF = 6,
19    ES = 7,
20    GO = 8,
21    MA = 9,
22    MG = 10,
23    MS = 11,
24    MT = 12,
25    PA = 13,
26    PB = 14,
27    PE = 15,
28    PI = 16,
29    PR = 17,
30    RJ = 18,
31    RN = 19,
32    RO = 20,
33    RR = 21,
34    RS = 22,
35    SC = 23,
36    SE = 24,
37    SP = 25,
38    TO = 26,
39}
40
41/// All 27 states in alphabetical order by abbreviation.
42pub const ALL: [State; 27] = [
43    State::AC,
44    State::AL,
45    State::AM,
46    State::AP,
47    State::BA,
48    State::CE,
49    State::DF,
50    State::ES,
51    State::GO,
52    State::MA,
53    State::MG,
54    State::MS,
55    State::MT,
56    State::PA,
57    State::PB,
58    State::PE,
59    State::PI,
60    State::PR,
61    State::RJ,
62    State::RN,
63    State::RO,
64    State::RR,
65    State::RS,
66    State::SC,
67    State::SE,
68    State::SP,
69    State::TO,
70];
71
72/// Geographic region.
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
74#[repr(u8)]
75pub enum Region {
76    Norte = 0,
77    Nordeste = 1,
78    CentroOeste = 2,
79    Sudeste = 3,
80    Sul = 4,
81}
82
83impl State {
84    /// Two-letter abbreviation (e.g. `"SP"`, `"RJ"`).
85    pub fn abbreviation(self) -> &'static str {
86        match self {
87            State::AC => "AC",
88            State::AL => "AL",
89            State::AM => "AM",
90            State::AP => "AP",
91            State::BA => "BA",
92            State::CE => "CE",
93            State::DF => "DF",
94            State::ES => "ES",
95            State::GO => "GO",
96            State::MA => "MA",
97            State::MG => "MG",
98            State::MS => "MS",
99            State::MT => "MT",
100            State::PA => "PA",
101            State::PB => "PB",
102            State::PE => "PE",
103            State::PI => "PI",
104            State::PR => "PR",
105            State::RJ => "RJ",
106            State::RN => "RN",
107            State::RO => "RO",
108            State::RR => "RR",
109            State::RS => "RS",
110            State::SC => "SC",
111            State::SE => "SE",
112            State::SP => "SP",
113            State::TO => "TO",
114        }
115    }
116
117    /// Full name of the state.
118    pub fn name(self) -> &'static str {
119        match self {
120            State::AC => "Acre",
121            State::AL => "Alagoas",
122            State::AM => "Amazonas",
123            State::AP => "Amapá",
124            State::BA => "Bahia",
125            State::CE => "Ceará",
126            State::DF => "Distrito Federal",
127            State::ES => "Espírito Santo",
128            State::GO => "Goiás",
129            State::MA => "Maranhão",
130            State::MG => "Minas Gerais",
131            State::MS => "Mato Grosso do Sul",
132            State::MT => "Mato Grosso",
133            State::PA => "Pará",
134            State::PB => "Paraíba",
135            State::PE => "Pernambuco",
136            State::PI => "Piauí",
137            State::PR => "Paraná",
138            State::RJ => "Rio de Janeiro",
139            State::RN => "Rio Grande do Norte",
140            State::RO => "Rondônia",
141            State::RR => "Roraima",
142            State::RS => "Rio Grande do Sul",
143            State::SC => "Santa Catarina",
144            State::SE => "Sergipe",
145            State::SP => "São Paulo",
146            State::TO => "Tocantins",
147        }
148    }
149
150    /// Geographic region this state belongs to.
151    pub fn region(self) -> Region {
152        match self {
153            State::AC | State::AM | State::AP | State::PA | State::RO | State::RR | State::TO => {
154                Region::Norte
155            }
156            State::AL
157            | State::BA
158            | State::CE
159            | State::MA
160            | State::PB
161            | State::PE
162            | State::PI
163            | State::RN
164            | State::SE => Region::Nordeste,
165            State::DF | State::GO | State::MS | State::MT => Region::CentroOeste,
166            State::ES | State::MG | State::RJ | State::SP => Region::Sudeste,
167            State::PR | State::RS | State::SC => Region::Sul,
168        }
169    }
170
171    /// Parse from a two-letter abbreviation (case-insensitive).
172    pub fn from_abbreviation(s: &str) -> Option<Self> {
173        match s.as_bytes() {
174            b"AC" | b"ac" | b"Ac" => Some(State::AC),
175            b"AL" | b"al" | b"Al" => Some(State::AL),
176            b"AM" | b"am" | b"Am" => Some(State::AM),
177            b"AP" | b"ap" | b"Ap" => Some(State::AP),
178            b"BA" | b"ba" | b"Ba" => Some(State::BA),
179            b"CE" | b"ce" | b"Ce" => Some(State::CE),
180            b"DF" | b"df" | b"Df" => Some(State::DF),
181            b"ES" | b"es" | b"Es" => Some(State::ES),
182            b"GO" | b"go" | b"Go" => Some(State::GO),
183            b"MA" | b"ma" | b"Ma" => Some(State::MA),
184            b"MG" | b"mg" | b"Mg" => Some(State::MG),
185            b"MS" | b"ms" | b"Ms" => Some(State::MS),
186            b"MT" | b"mt" | b"Mt" => Some(State::MT),
187            b"PA" | b"pa" | b"Pa" => Some(State::PA),
188            b"PB" | b"pb" | b"Pb" => Some(State::PB),
189            b"PE" | b"pe" | b"Pe" => Some(State::PE),
190            b"PI" | b"pi" | b"Pi" => Some(State::PI),
191            b"PR" | b"pr" | b"Pr" => Some(State::PR),
192            b"RJ" | b"rj" | b"Rj" => Some(State::RJ),
193            b"RN" | b"rn" | b"Rn" => Some(State::RN),
194            b"RO" | b"ro" | b"Ro" => Some(State::RO),
195            b"RR" | b"rr" | b"Rr" => Some(State::RR),
196            b"RS" | b"rs" | b"Rs" => Some(State::RS),
197            b"SC" | b"sc" | b"Sc" => Some(State::SC),
198            b"SE" | b"se" | b"Se" => Some(State::SE),
199            b"SP" | b"sp" | b"Sp" => Some(State::SP),
200            b"TO" | b"to" | b"To" => Some(State::TO),
201            _ => None,
202        }
203    }
204}
205
206impl fmt::Display for State {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        f.write_str(self.abbreviation())
209    }
210}
211
212impl fmt::Display for Region {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        f.write_str(match self {
215            Region::Norte => "Norte",
216            Region::Nordeste => "Nordeste",
217            Region::CentroOeste => "Centro-Oeste",
218            Region::Sudeste => "Sudeste",
219            Region::Sul => "Sul",
220        })
221    }
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227
228    #[test]
229    fn all_states_count() {
230        assert_eq!(ALL.len(), 27);
231    }
232
233    #[test]
234    fn abbreviation_roundtrip() {
235        for state in ALL {
236            let abbr = state.abbreviation();
237            assert_eq!(abbr.len(), 2);
238            assert_eq!(State::from_abbreviation(abbr), Some(state));
239        }
240    }
241
242    #[test]
243    fn from_abbreviation_case_insensitive() {
244        assert_eq!(State::from_abbreviation("sp"), Some(State::SP));
245        assert_eq!(State::from_abbreviation("SP"), Some(State::SP));
246        assert_eq!(State::from_abbreviation("Sp"), Some(State::SP));
247        assert_eq!(State::from_abbreviation("XX"), None);
248        assert_eq!(State::from_abbreviation(""), None);
249    }
250
251    #[test]
252    fn display_shows_abbreviation() {
253        use alloc::string::ToString;
254        assert_eq!(State::SP.to_string(), "SP");
255        assert_eq!(State::RJ.to_string(), "RJ");
256    }
257
258    #[test]
259    fn region_classification() {
260        assert_eq!(State::SP.region(), Region::Sudeste);
261        assert_eq!(State::AM.region(), Region::Norte);
262        assert_eq!(State::BA.region(), Region::Nordeste);
263        assert_eq!(State::DF.region(), Region::CentroOeste);
264        assert_eq!(State::RS.region(), Region::Sul);
265    }
266
267    #[test]
268    fn name_not_empty() {
269        for state in ALL {
270            assert!(!state.name().is_empty());
271        }
272    }
273}