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        ALL.into_iter()
174            .find(|state| state.abbreviation().eq_ignore_ascii_case(s))
175    }
176}
177
178impl fmt::Display for State {
179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180        f.write_str(self.abbreviation())
181    }
182}
183
184impl fmt::Display for Region {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        f.write_str(match self {
187            Region::Norte => "Norte",
188            Region::Nordeste => "Nordeste",
189            Region::CentroOeste => "Centro-Oeste",
190            Region::Sudeste => "Sudeste",
191            Region::Sul => "Sul",
192        })
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn all_states_count() {
202        assert_eq!(ALL.len(), 27);
203    }
204
205    #[test]
206    fn abbreviation_roundtrip() {
207        for state in ALL {
208            let abbr = state.abbreviation();
209            assert_eq!(abbr.len(), 2);
210            assert_eq!(State::from_abbreviation(abbr), Some(state));
211        }
212    }
213
214    #[test]
215    fn from_abbreviation_case_insensitive() {
216        for state in ALL {
217            let [first, second] = state.abbreviation().as_bytes() else {
218                unreachable!()
219            };
220            for abbreviation in [
221                [*first, *second],
222                [first.to_ascii_lowercase(), *second],
223                [*first, second.to_ascii_lowercase()],
224                [first.to_ascii_lowercase(), second.to_ascii_lowercase()],
225            ] {
226                let abbreviation = core::str::from_utf8(&abbreviation).unwrap();
227                assert_eq!(State::from_abbreviation(abbreviation), Some(state));
228            }
229        }
230        assert_eq!(State::from_abbreviation("XX"), None);
231        assert_eq!(State::from_abbreviation(""), None);
232    }
233
234    #[test]
235    fn display_shows_abbreviation() {
236        use alloc::string::ToString;
237        assert_eq!(State::SP.to_string(), "SP");
238        assert_eq!(State::RJ.to_string(), "RJ");
239    }
240
241    #[test]
242    fn region_classification() {
243        assert_eq!(State::SP.region(), Region::Sudeste);
244        assert_eq!(State::AM.region(), Region::Norte);
245        assert_eq!(State::BA.region(), Region::Nordeste);
246        assert_eq!(State::DF.region(), Region::CentroOeste);
247        assert_eq!(State::RS.region(), Region::Sul);
248    }
249
250    #[test]
251    fn name_not_empty() {
252        for state in ALL {
253            assert!(!state.name().is_empty());
254        }
255    }
256}