konduto/types/address/
country_code.rs

1use crate::types::validation_errors::ValidationError;
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::fmt;
4use std::str::FromStr;
5
6/// Código do país (ISO 3166-2)
7///
8/// Enum com os países mais comuns e suporte para códigos personalizados.
9/// Fornece autocompletar e type-safety enquanto mantém flexibilidade.
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub enum Country {
12    // América do Sul
13    /// Brasil
14    BR,
15    /// Argentina
16    AR,
17    /// Chile
18    CL,
19    /// Colômbia
20    CO,
21    /// Peru
22    PE,
23    /// Uruguai
24    UY,
25    /// Paraguai
26    PY,
27    /// Venezuela
28    VE,
29    /// Equador
30    EC,
31    /// Bolívia
32    BO,
33
34    // América do Norte
35    /// Estados Unidos
36    US,
37    /// Canadá
38    CA,
39    /// México
40    MX,
41
42    // Europa
43    /// Reino Unido
44    GB,
45    /// Alemanha
46    DE,
47    /// França
48    FR,
49    /// Itália
50    IT,
51    /// Espanha
52    ES,
53    /// Portugal
54    PT,
55    /// Holanda
56    NL,
57    /// Suíça
58    CH,
59    /// Bélgica
60    BE,
61    /// Áustria
62    AT,
63    /// Suécia
64    SE,
65    /// Noruega
66    NO,
67    /// Dinamarca
68    DK,
69    /// Finlândia
70    FI,
71    /// Polônia
72    PL,
73    /// Irlanda
74    IE,
75
76    // Ásia
77    /// China
78    CN,
79    /// Japão
80    JP,
81    /// Índia
82    IN,
83    /// Coreia do Sul
84    KR,
85    /// Singapura
86    SG,
87    /// Hong Kong
88    HK,
89    /// Taiwan
90    TW,
91    /// Tailândia
92    TH,
93    /// Malásia
94    MY,
95    /// Indonésia
96    ID,
97    /// Filipinas
98    PH,
99    /// Vietnã
100    VN,
101
102    // Oceania
103    /// Austrália
104    AU,
105    /// Nova Zelândia
106    NZ,
107
108    // África
109    /// África do Sul
110    ZA,
111
112    // Oriente Médio
113    /// Israel
114    IL,
115    /// Emirados Árabes Unidos
116    AE,
117
118    /// Outro país (código ISO 3166-2 personalizado)
119    Other(String),
120}
121
122impl Country {
123    /// Cria um país a partir do código ISO 3166-2
124    ///
125    /// # Exemplos
126    /// ```
127    /// #  use konduto::Country;
128    /// let br = Country::from_code("BR").unwrap();
129    /// let us = Country::from_code("us").unwrap(); // case-insensitive
130    /// let custom = Country::from_code("XX").unwrap(); // Aceita códigos não listados
131    /// ```
132    pub fn from_code(code: impl Into<String>) -> Result<Self, ValidationError> {
133        let input = code.into();
134        let trimmed = input.trim().to_uppercase();
135
136        if trimmed.is_empty() {
137            return Err(ValidationError::EmptyField("country".to_string()));
138        }
139
140        if trimmed.len() != 2 {
141            return Err(ValidationError::InvalidFormat {
142                field: "country".to_string(),
143                message: format!(
144                    "country code must be exactly 2 characters (ISO 3166-2), received {}",
145                    trimmed.len()
146                ),
147            });
148        }
149
150        if !trimmed.chars().all(|c| c.is_ascii_alphabetic()) {
151            return Err(ValidationError::InvalidFormat {
152                field: "country".to_string(),
153                message: "country code must contain only alphabetic characters".to_string(),
154            });
155        }
156
157        Ok(match trimmed.as_str() {
158            // América do Sul
159            "BR" => Country::BR,
160            "AR" => Country::AR,
161            "CL" => Country::CL,
162            "CO" => Country::CO,
163            "PE" => Country::PE,
164            "UY" => Country::UY,
165            "PY" => Country::PY,
166            "VE" => Country::VE,
167            "EC" => Country::EC,
168            "BO" => Country::BO,
169
170            // América do Norte
171            "US" => Country::US,
172            "CA" => Country::CA,
173            "MX" => Country::MX,
174
175            // Europa
176            "GB" => Country::GB,
177            "DE" => Country::DE,
178            "FR" => Country::FR,
179            "IT" => Country::IT,
180            "ES" => Country::ES,
181            "PT" => Country::PT,
182            "NL" => Country::NL,
183            "CH" => Country::CH,
184            "BE" => Country::BE,
185            "AT" => Country::AT,
186            "SE" => Country::SE,
187            "NO" => Country::NO,
188            "DK" => Country::DK,
189            "FI" => Country::FI,
190            "PL" => Country::PL,
191            "IE" => Country::IE,
192
193            // Ásia
194            "CN" => Country::CN,
195            "JP" => Country::JP,
196            "IN" => Country::IN,
197            "KR" => Country::KR,
198            "SG" => Country::SG,
199            "HK" => Country::HK,
200            "TW" => Country::TW,
201            "TH" => Country::TH,
202            "MY" => Country::MY,
203            "ID" => Country::ID,
204            "PH" => Country::PH,
205            "VN" => Country::VN,
206
207            // Oceania
208            "AU" => Country::AU,
209            "NZ" => Country::NZ,
210
211            // África
212            "ZA" => Country::ZA,
213
214            // Oriente Médio
215            "IL" => Country::IL,
216            "AE" => Country::AE,
217
218            // Outros
219            code => Country::Other(code.to_string()),
220        })
221    }
222
223    /// Retorna o código ISO 3166-2 do país
224    pub fn code(&self) -> &str {
225        match self {
226            Country::BR => "BR",
227            Country::AR => "AR",
228            Country::CL => "CL",
229            Country::CO => "CO",
230            Country::PE => "PE",
231            Country::UY => "UY",
232            Country::PY => "PY",
233            Country::VE => "VE",
234            Country::EC => "EC",
235            Country::BO => "BO",
236            Country::US => "US",
237            Country::CA => "CA",
238            Country::MX => "MX",
239            Country::GB => "GB",
240            Country::DE => "DE",
241            Country::FR => "FR",
242            Country::IT => "IT",
243            Country::ES => "ES",
244            Country::PT => "PT",
245            Country::NL => "NL",
246            Country::CH => "CH",
247            Country::BE => "BE",
248            Country::AT => "AT",
249            Country::SE => "SE",
250            Country::NO => "NO",
251            Country::DK => "DK",
252            Country::FI => "FI",
253            Country::PL => "PL",
254            Country::IE => "IE",
255            Country::CN => "CN",
256            Country::JP => "JP",
257            Country::IN => "IN",
258            Country::KR => "KR",
259            Country::SG => "SG",
260            Country::HK => "HK",
261            Country::TW => "TW",
262            Country::TH => "TH",
263            Country::MY => "MY",
264            Country::ID => "ID",
265            Country::PH => "PH",
266            Country::VN => "VN",
267            Country::AU => "AU",
268            Country::NZ => "NZ",
269            Country::ZA => "ZA",
270            Country::IL => "IL",
271            Country::AE => "AE",
272            Country::Other(code) => code,
273        }
274    }
275
276    pub fn name(&self) -> &str {
277        match self {
278            Country::BR => "Brasil",
279            Country::AR => "Argentina",
280            Country::CL => "Chile",
281            Country::CO => "Colômbia",
282            Country::PE => "Peru",
283            Country::UY => "Uruguai",
284            Country::PY => "Paraguai",
285            Country::VE => "Venezuela",
286            Country::EC => "Equador",
287            Country::BO => "Bolívia",
288            Country::US => "Estados Unidos",
289            Country::CA => "Canadá",
290            Country::MX => "México",
291            Country::GB => "Reino Unido",
292            Country::DE => "Alemanha",
293            Country::FR => "França",
294            Country::IT => "Itália",
295            Country::ES => "Espanha",
296            Country::PT => "Portugal",
297            Country::NL => "Holanda",
298            Country::CH => "Suíça",
299            Country::BE => "Bélgica",
300            Country::AT => "Áustria",
301            Country::SE => "Suécia",
302            Country::NO => "Noruega",
303            Country::DK => "Dinamarca",
304            Country::FI => "Finlândia",
305            Country::PL => "Polônia",
306            Country::IE => "Irlanda",
307            Country::CN => "China",
308            Country::JP => "Japão",
309            Country::IN => "Índia",
310            Country::KR => "Coreia do Sul",
311            Country::SG => "Singapura",
312            Country::HK => "Hong Kong",
313            Country::TW => "Taiwan",
314            Country::TH => "Tailândia",
315            Country::MY => "Malásia",
316            Country::ID => "Indonésia",
317            Country::PH => "Filipinas",
318            Country::VN => "Vietnã",
319            Country::AU => "Austrália",
320            Country::NZ => "Nova Zelândia",
321            Country::ZA => "África do Sul",
322            Country::IL => "Israel",
323            Country::AE => "Emirados Árabes Unidos",
324            Country::Other(code) => code,
325        }
326    }
327}
328
329impl FromStr for Country {
330    type Err = ValidationError;
331
332    fn from_str(s: &str) -> Result<Self, Self::Err> {
333        Country::from_code(s)
334    }
335}
336
337impl fmt::Display for Country {
338    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339        write!(f, "{}", self.code())
340    }
341}
342
343impl Serialize for Country {
344    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
345    where
346        S: Serializer,
347    {
348        serializer.serialize_str(self.code())
349    }
350}
351
352impl<'de> Deserialize<'de> for Country {
353    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
354    where
355        D: Deserializer<'de>,
356    {
357        let s = String::deserialize(deserializer)?;
358        Country::from_code(s).map_err(serde::de::Error::custom)
359    }
360}
361
362// Mantém alias para compatibilidade
363pub type CountryCode = Country;
364
365#[cfg(test)]
366mod tests {
367    use super::*;
368
369    #[test]
370    fn test_brazil() {
371        let br = Country::from_code("BR").unwrap();
372        assert_eq!(br, Country::BR);
373        assert_eq!(br.code(), "BR");
374        assert_eq!(br.name(), "Brasil");
375    }
376
377    #[test]
378    fn test_lowercase_conversion() {
379        let us = Country::from_code("us").unwrap();
380        assert_eq!(us, Country::US);
381        assert_eq!(us.code(), "US");
382    }
383
384    #[test]
385    fn test_mixed_case() {
386        let gb = Country::from_code("Gb").unwrap();
387        assert_eq!(gb, Country::GB);
388    }
389
390    #[test]
391    fn test_with_whitespace() {
392        let fr = Country::from_code("  FR  ").unwrap();
393        assert_eq!(fr, Country::FR);
394    }
395
396    #[test]
397    fn test_other_country() {
398        let xx = Country::from_code("XX").unwrap();
399        assert_eq!(xx, Country::Other("XX".to_string()));
400        assert_eq!(xx.code(), "XX");
401    }
402
403    #[test]
404    fn test_invalid_length() {
405        let result = Country::from_code("USA");
406        assert!(result.is_err());
407    }
408
409    #[test]
410    fn test_invalid_chars() {
411        let result = Country::from_code("B1");
412        assert!(result.is_err());
413    }
414
415    #[test]
416    fn test_empty() {
417        let result = Country::from_code("");
418        assert!(result.is_err());
419    }
420
421    #[test]
422    fn test_serialization() {
423        let br = Country::BR;
424        let json = serde_json::to_string(&br).unwrap();
425        assert_eq!(json, "\"BR\"");
426    }
427
428    #[test]
429    fn test_deserialization() {
430        let json = "\"US\"";
431        let country: Country = serde_json::from_str(json).unwrap();
432        assert_eq!(country, Country::US);
433    }
434
435    #[test]
436    fn test_from_str() {
437        let br: Country = "BR".parse().unwrap();
438        assert_eq!(br, Country::BR);
439    }
440
441    #[test]
442    fn test_display() {
443        let mx = Country::MX;
444        assert_eq!(format!("{}", mx), "MX");
445    }
446
447    #[test]
448    fn test_common_countries() {
449        let countries = vec![
450            ("BR", "Brasil"),
451            ("US", "Estados Unidos"),
452            ("GB", "Reino Unido"),
453            ("FR", "França"),
454            ("DE", "Alemanha"),
455            ("IT", "Itália"),
456            ("ES", "Espanha"),
457            ("JP", "Japão"),
458            ("CN", "China"),
459        ];
460
461        for (code, expected_name) in countries {
462            let country = Country::from_code(code).unwrap();
463            assert_eq!(country.code(), code);
464            assert_eq!(country.name(), expected_name);
465        }
466    }
467
468    #[test]
469    fn test_equality() {
470        let br1 = Country::BR;
471        let br2 = Country::from_code("BR").unwrap();
472        assert_eq!(br1, br2);
473    }
474
475    #[test]
476    fn test_hash() {
477        use std::collections::HashSet;
478
479        let mut set = HashSet::new();
480        set.insert(Country::BR);
481        set.insert(Country::US);
482        set.insert(Country::BR); // Duplicado
483
484        assert_eq!(set.len(), 2);
485    }
486}