1use crate::types::validation_errors::ValidationError;
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::fmt;
4use std::str::FromStr;
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub enum Country {
12 BR,
15 AR,
17 CL,
19 CO,
21 PE,
23 UY,
25 PY,
27 VE,
29 EC,
31 BO,
33
34 US,
37 CA,
39 MX,
41
42 GB,
45 DE,
47 FR,
49 IT,
51 ES,
53 PT,
55 NL,
57 CH,
59 BE,
61 AT,
63 SE,
65 NO,
67 DK,
69 FI,
71 PL,
73 IE,
75
76 CN,
79 JP,
81 IN,
83 KR,
85 SG,
87 HK,
89 TW,
91 TH,
93 MY,
95 ID,
97 PH,
99 VN,
101
102 AU,
105 NZ,
107
108 ZA,
111
112 IL,
115 AE,
117
118 Other(String),
120}
121
122impl Country {
123 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 "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 "US" => Country::US,
172 "CA" => Country::CA,
173 "MX" => Country::MX,
174
175 "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 "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 "AU" => Country::AU,
209 "NZ" => Country::NZ,
210
211 "ZA" => Country::ZA,
213
214 "IL" => Country::IL,
216 "AE" => Country::AE,
217
218 code => Country::Other(code.to_string()),
220 })
221 }
222
223 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
362pub 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); assert_eq!(set.len(), 2);
485 }
486}