1use std::{borrow::Cow, fmt};
5
6use crate::{
7 into_caveat, json,
8 warning::{self, GatherWarnings as _},
9 IntoCaveat, Verdict,
10};
11
12const RESERVED_PREFIX: u8 = b'x';
13const ALPHA_2_LEN: usize = 2;
14const ALPHA_3_LEN: usize = 3;
15
16#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
17pub enum WarningKind {
18 ContainsEscapeCodes,
20
21 Decode(json::decode::WarningKind),
23
24 InvalidCase,
26
27 InvalidCode,
29
30 InvalidType,
32
33 InvalidLength,
35
36 InvalidReserved,
38}
39
40impl fmt::Display for WarningKind {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match self {
43 WarningKind::ContainsEscapeCodes => write!(f, "The value contains escape codes but it does not need them"),
44 WarningKind::Decode(warning) => fmt::Display::fmt(warning, f),
45 WarningKind::InvalidCase => write!(f, "The value is not a valid ISO 3166-1 country code because it's not uppercase."),
46 WarningKind::InvalidCode => write!(f, "The value field is not a valid ISO 3166 currency code."),
47 WarningKind::InvalidType => write!(f, "The value should be a string."),
48 WarningKind::InvalidLength => write!(f, "The value is not a valid ISO 3166-1 country code because it's not 2 or 3 chars in length."),
49 WarningKind::InvalidReserved => write!(f, "The value is not a valid ISO 3166-1 alpha-2 country code because it's all codes beginning with 'X' are reserved."),
50 }
51 }
52}
53
54impl warning::Kind for WarningKind {
55 fn id(&self) -> Cow<'static, str> {
56 match self {
57 WarningKind::ContainsEscapeCodes => "contains_escape_codes".into(),
58 WarningKind::Decode(kind) => format!("decode.{}", kind.id()).into(),
59 WarningKind::InvalidCase => "invalid_case".into(),
60 WarningKind::InvalidCode => "invalid_code".into(),
61 WarningKind::InvalidType => "invalid_type".into(),
62 WarningKind::InvalidLength => "invalid_length".into(),
63 WarningKind::InvalidReserved => "invalid_reserved".into(),
64 }
65 }
66}
67
68#[derive(Debug)]
72pub(crate) enum CodeSet {
73 Alpha2(Code),
75
76 Alpha3(Code),
78}
79
80into_caveat!(CodeSet);
81into_caveat!(Code);
82
83impl From<json::decode::WarningKind> for WarningKind {
84 fn from(warn_kind: json::decode::WarningKind) -> Self {
85 Self::Decode(warn_kind)
86 }
87}
88
89impl json::FromJson<'_, '_> for CodeSet {
90 type WarningKind = WarningKind;
91
92 fn from_json(elem: &json::Element<'_>) -> Verdict<CodeSet, Self::WarningKind> {
93 let mut warnings = warning::Set::new();
94 let value = elem.as_value();
95
96 let Some(s) = value.as_raw_str() else {
97 warnings.with_elem(WarningKind::InvalidType, elem);
98 return Err(warnings);
99 };
100
101 let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
102
103 let s = match pending_str {
104 json::decode::PendingStr::NoEscapes(s) => s,
105 json::decode::PendingStr::HasEscapes(_) => {
106 warnings.with_elem(WarningKind::ContainsEscapeCodes, elem);
107 return Err(warnings);
108 }
109 };
110
111 let bytes = s.as_bytes();
112
113 if let [a, b, c] = bytes {
114 let triplet: [u8; ALPHA_3_LEN] = [
115 a.to_ascii_uppercase(),
116 b.to_ascii_uppercase(),
117 c.to_ascii_uppercase(),
118 ];
119
120 if triplet != bytes {
121 warnings.with_elem(WarningKind::InvalidCase, elem);
122 }
123
124 if a.eq_ignore_ascii_case(&RESERVED_PREFIX) {
125 warnings.with_elem(WarningKind::InvalidReserved, elem);
126 }
127
128 let Some(code) = Code::from_alpha_3(triplet) else {
129 warnings.with_elem(WarningKind::InvalidCode, elem);
130 return Err(warnings);
131 };
132
133 Ok(CodeSet::Alpha3(code).into_caveat(warnings))
134 } else if let [a, b] = bytes {
135 let pair: [u8; ALPHA_2_LEN] = [a.to_ascii_uppercase(), b.to_ascii_uppercase()];
136
137 if pair != bytes {
138 warnings.with_elem(WarningKind::InvalidCase, elem);
139 }
140
141 if a.eq_ignore_ascii_case(&RESERVED_PREFIX) {
142 warnings.with_elem(WarningKind::InvalidReserved, elem);
143 }
144
145 let Some(code) = Code::from_alpha_2(pair) else {
146 warnings.with_elem(WarningKind::InvalidCode, elem);
147 return Err(warnings);
148 };
149
150 Ok(CodeSet::Alpha2(code).into_caveat(warnings))
151 } else {
152 warnings.with_elem(WarningKind::InvalidLength, elem);
153 Err(warnings)
154 }
155 }
156}
157
158impl Code {
159 pub(crate) fn into_str(self) -> &'static str {
161 self.into_alpha_2_raw()
162 }
163}
164
165impl From<Code> for &'static json::RawValue {
166 fn from(code: Code) -> Self {
167 serde_json::from_str(code.into_alpha_2_raw())
168 .expect("All country codes are valid JSON strings")
169 }
170}
171
172impl fmt::Display for Code {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 f.write_str(self.into_str())
175 }
176}
177
178macro_rules! country_codes {
180 [$(($name:ident, $alph2:literal, $alph3:literal)),*] => {
181 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize)]
185 pub enum Code {
186 $($name),*
187 }
188
189 impl Code {
190 const fn from_alpha_2(code: [u8; 2]) -> Option<Self> {
192 match &code {
193 $($alph2 => Some(Self::$name),)*
194 _ => None
195 }
196 }
197
198 const fn from_alpha_3(code: [u8; 3]) -> Option<Self> {
200 match &code {
201 $($alph3 => Some(Self::$name),)*
202 _ => None
203 }
204 }
205
206 fn into_alpha_2_raw(self) -> &'static str {
208 let bytes = match self {
209 $(Self::$name => $alph2),*
210 };
211 std::str::from_utf8(bytes).expect("The country code bytes are known to be valid UTF8 as they are embedded into the binary")
212 }
213 }
214 };
215}
216
217country_codes![
218 (Ad, b"AD", b"AND"),
219 (Ae, b"AE", b"ARE"),
220 (Af, b"AF", b"AFG"),
221 (Ag, b"AG", b"ATG"),
222 (Ai, b"AI", b"AIA"),
223 (Al, b"AL", b"ALB"),
224 (Am, b"AM", b"ARM"),
225 (Ao, b"AO", b"AGO"),
226 (Aq, b"AQ", b"ATA"),
227 (Ar, b"AR", b"ARG"),
228 (As, b"AS", b"ASM"),
229 (At, b"AT", b"AUT"),
230 (Au, b"AU", b"AUS"),
231 (Aw, b"AW", b"ABW"),
232 (Ax, b"AX", b"ALA"),
233 (Az, b"AZ", b"AZE"),
234 (Ba, b"BA", b"BIH"),
235 (Bb, b"BB", b"BRB"),
236 (Bd, b"BD", b"BGD"),
237 (Be, b"BE", b"BEL"),
238 (Bf, b"BF", b"BFA"),
239 (Bg, b"BG", b"BGR"),
240 (Bh, b"BH", b"BHR"),
241 (Bi, b"BI", b"BDI"),
242 (Bj, b"BJ", b"BEN"),
243 (Bl, b"BL", b"BLM"),
244 (Bm, b"BM", b"BMU"),
245 (Bn, b"BN", b"BRN"),
246 (Bo, b"BO", b"BOL"),
247 (Bq, b"BQ", b"BES"),
248 (Br, b"BR", b"BRA"),
249 (Bs, b"BS", b"BHS"),
250 (Bt, b"BT", b"BTN"),
251 (Bv, b"BV", b"BVT"),
252 (Bw, b"BW", b"BWA"),
253 (By, b"BY", b"BLR"),
254 (Bz, b"BZ", b"BLZ"),
255 (Ca, b"CA", b"CAN"),
256 (Cc, b"CC", b"CCK"),
257 (Cd, b"CD", b"COD"),
258 (Cf, b"CF", b"CAF"),
259 (Cg, b"CG", b"COG"),
260 (Ch, b"CH", b"CHE"),
261 (Ci, b"CI", b"CIV"),
262 (Ck, b"CK", b"COK"),
263 (Cl, b"CL", b"CHL"),
264 (Cm, b"CM", b"CMR"),
265 (Cn, b"CN", b"CHN"),
266 (Co, b"CO", b"COL"),
267 (Cr, b"CR", b"CRI"),
268 (Cu, b"CU", b"CUB"),
269 (Cv, b"CV", b"CPV"),
270 (Cw, b"CW", b"CUW"),
271 (Cx, b"CX", b"CXR"),
272 (Cy, b"CY", b"CYP"),
273 (Cz, b"CZ", b"CZE"),
274 (De, b"DE", b"DEU"),
275 (Dj, b"DJ", b"DJI"),
276 (Dk, b"DK", b"DNK"),
277 (Dm, b"DM", b"DMA"),
278 (Do, b"DO", b"DOM"),
279 (Dz, b"DZ", b"DZA"),
280 (Ec, b"EC", b"ECU"),
281 (Ee, b"EE", b"EST"),
282 (Eg, b"EG", b"EGY"),
283 (Eh, b"EH", b"ESH"),
284 (Er, b"ER", b"ERI"),
285 (Es, b"ES", b"ESP"),
286 (Et, b"ET", b"ETH"),
287 (Fi, b"FI", b"FIN"),
288 (Fj, b"FJ", b"FJI"),
289 (Fk, b"FK", b"FLK"),
290 (Fm, b"FM", b"FSM"),
291 (Fo, b"FO", b"FRO"),
292 (Fr, b"FR", b"FRA"),
293 (Ga, b"GA", b"GAB"),
294 (Gb, b"GB", b"GBR"),
295 (Gd, b"GD", b"GRD"),
296 (Ge, b"GE", b"GEO"),
297 (Gf, b"GF", b"GUF"),
298 (Gg, b"GG", b"GGY"),
299 (Gh, b"GH", b"GHA"),
300 (Gi, b"GI", b"GIB"),
301 (Gl, b"GL", b"GRL"),
302 (Gm, b"GM", b"GMB"),
303 (Gn, b"GN", b"GIN"),
304 (Gp, b"GP", b"GLP"),
305 (Gq, b"GQ", b"GNQ"),
306 (Gr, b"GR", b"GRC"),
307 (Gs, b"GS", b"SGS"),
308 (Gt, b"GT", b"GTM"),
309 (Gu, b"GU", b"GUM"),
310 (Gw, b"GW", b"GNB"),
311 (Gy, b"GY", b"GUY"),
312 (Hk, b"HK", b"HKG"),
313 (Hm, b"HM", b"HMD"),
314 (Hn, b"HN", b"HND"),
315 (Hr, b"HR", b"HRV"),
316 (Ht, b"HT", b"HTI"),
317 (Hu, b"HU", b"HUN"),
318 (Id, b"ID", b"IDN"),
319 (Ie, b"IE", b"IRL"),
320 (Il, b"IL", b"ISR"),
321 (Im, b"IM", b"IMN"),
322 (In, b"IN", b"IND"),
323 (Io, b"IO", b"IOT"),
324 (Iq, b"IQ", b"IRQ"),
325 (Ir, b"IR", b"IRN"),
326 (Is, b"IS", b"ISL"),
327 (It, b"IT", b"ITA"),
328 (Je, b"JE", b"JEY"),
329 (Jm, b"JM", b"JAM"),
330 (Jo, b"JO", b"JOR"),
331 (Jp, b"JP", b"JPN"),
332 (Ke, b"KE", b"KEN"),
333 (Kg, b"KG", b"KGZ"),
334 (Kh, b"KH", b"KHM"),
335 (Ki, b"KI", b"KIR"),
336 (Km, b"KM", b"COM"),
337 (Kn, b"KN", b"KNA"),
338 (Kp, b"KP", b"PRK"),
339 (Kr, b"KR", b"KOR"),
340 (Kw, b"KW", b"KWT"),
341 (Ky, b"KY", b"CYM"),
342 (Kz, b"KZ", b"KAZ"),
343 (La, b"LA", b"LAO"),
344 (Lb, b"LB", b"LBN"),
345 (Lc, b"LC", b"LCA"),
346 (Li, b"LI", b"LIE"),
347 (Lk, b"LK", b"LKA"),
348 (Lr, b"LR", b"LBR"),
349 (Ls, b"LS", b"LSO"),
350 (Lt, b"LT", b"LTU"),
351 (Lu, b"LU", b"LUX"),
352 (Lv, b"LV", b"LVA"),
353 (Ly, b"LY", b"LBY"),
354 (Ma, b"MA", b"MAR"),
355 (Mc, b"MC", b"MCO"),
356 (Md, b"MD", b"MDA"),
357 (Me, b"ME", b"MNE"),
358 (Mf, b"MF", b"MAF"),
359 (Mg, b"MG", b"MDG"),
360 (Mh, b"MH", b"MHL"),
361 (Mk, b"MK", b"MKD"),
362 (Ml, b"ML", b"MLI"),
363 (Mm, b"MM", b"MMR"),
364 (Mn, b"MN", b"MNG"),
365 (Mo, b"MO", b"MAC"),
366 (Mp, b"MP", b"MNP"),
367 (Mq, b"MQ", b"MTQ"),
368 (Mr, b"MR", b"MRT"),
369 (Ms, b"MS", b"MSR"),
370 (Mt, b"MT", b"MLT"),
371 (Mu, b"MU", b"MUS"),
372 (Mv, b"MV", b"MDV"),
373 (Mw, b"MW", b"MWI"),
374 (Mx, b"MX", b"MEX"),
375 (My, b"MY", b"MYS"),
376 (Mz, b"MZ", b"MOZ"),
377 (Na, b"NA", b"NAM"),
378 (Nc, b"NC", b"NCL"),
379 (Ne, b"NE", b"NER"),
380 (Nf, b"NF", b"NFK"),
381 (Ng, b"NG", b"NGA"),
382 (Ni, b"NI", b"NIC"),
383 (Nl, b"NL", b"NLD"),
384 (No, b"NO", b"NOR"),
385 (Np, b"NP", b"NPL"),
386 (Nr, b"NR", b"NRU"),
387 (Nu, b"NU", b"NIU"),
388 (Nz, b"NZ", b"NZL"),
389 (Om, b"OM", b"OMN"),
390 (Pa, b"PA", b"PAN"),
391 (Pe, b"PE", b"PER"),
392 (Pf, b"PF", b"PYF"),
393 (Pg, b"PG", b"PNG"),
394 (Ph, b"PH", b"PHL"),
395 (Pk, b"PK", b"PAK"),
396 (Pl, b"PL", b"POL"),
397 (Pm, b"PM", b"SPM"),
398 (Pn, b"PN", b"PCN"),
399 (Pr, b"PR", b"PRI"),
400 (Ps, b"PS", b"PSE"),
401 (Pt, b"PT", b"PRT"),
402 (Pw, b"PW", b"PLW"),
403 (Py, b"PY", b"PRY"),
404 (Qa, b"QA", b"QAT"),
405 (Re, b"RE", b"REU"),
406 (Ro, b"RO", b"ROU"),
407 (Rs, b"RS", b"SRB"),
408 (Ru, b"RU", b"RUS"),
409 (Rw, b"RW", b"RWA"),
410 (Sa, b"SA", b"SAU"),
411 (Sb, b"SB", b"SLB"),
412 (Sc, b"SC", b"SYC"),
413 (Sd, b"SD", b"SDN"),
414 (Se, b"SE", b"SWE"),
415 (Sg, b"SG", b"SGP"),
416 (Sh, b"SH", b"SHN"),
417 (Si, b"SI", b"SVN"),
418 (Sj, b"SJ", b"SJM"),
419 (Sk, b"SK", b"SVK"),
420 (Sl, b"SL", b"SLE"),
421 (Sm, b"SM", b"SMR"),
422 (Sn, b"SN", b"SEN"),
423 (So, b"SO", b"SOM"),
424 (Sr, b"SR", b"SUR"),
425 (Ss, b"SS", b"SSD"),
426 (St, b"ST", b"STP"),
427 (Sv, b"SV", b"SLV"),
428 (Sx, b"SX", b"SXM"),
429 (Sy, b"SY", b"SYR"),
430 (Sz, b"SZ", b"SWZ"),
431 (Tc, b"TC", b"TCA"),
432 (Td, b"TD", b"TCD"),
433 (Tf, b"TF", b"ATF"),
434 (Tg, b"TG", b"TGO"),
435 (Th, b"TH", b"THA"),
436 (Tj, b"TJ", b"TJK"),
437 (Tk, b"TK", b"TKL"),
438 (Tl, b"TL", b"TLS"),
439 (Tm, b"TM", b"TKM"),
440 (Tn, b"TN", b"TUN"),
441 (To, b"TO", b"TON"),
442 (Tr, b"TR", b"TUR"),
443 (Tt, b"TT", b"TTO"),
444 (Tv, b"TV", b"TUV"),
445 (Tw, b"TW", b"TWN"),
446 (Tz, b"TZ", b"TZA"),
447 (Ua, b"UA", b"UKR"),
448 (Ug, b"UG", b"UGA"),
449 (Um, b"UM", b"UMI"),
450 (Us, b"US", b"USA"),
451 (Uy, b"UY", b"URY"),
452 (Uz, b"UZ", b"UZB"),
453 (Va, b"VA", b"VAT"),
454 (Vc, b"VC", b"VCT"),
455 (Ve, b"VE", b"VEN"),
456 (Vg, b"VG", b"VGB"),
457 (Vi, b"VI", b"VIR"),
458 (Vn, b"VN", b"VNM"),
459 (Vu, b"VU", b"VUT"),
460 (Wf, b"WF", b"WLF"),
461 (Ws, b"WS", b"WSM"),
462 (Ye, b"YE", b"YEM"),
463 (Yt, b"YT", b"MYT"),
464 (Za, b"ZA", b"ZAF"),
465 (Zm, b"ZM", b"ZMB"),
466 (Zw, b"ZW", b"ZWE")
467];
468
469#[cfg(test)]
470mod test {
471 #![allow(
472 clippy::unwrap_in_result,
473 reason = "unwraps are allowed anywhere in tests"
474 )]
475
476 use assert_matches::assert_matches;
477
478 use crate::{
479 json::{self, FromJson},
480 Verdict,
481 };
482
483 use super::{Code, CodeSet, WarningKind};
484
485 #[test]
486 fn alpha2_country_code_matches() {
487 let code = Code::from_alpha_2(*b"NL").unwrap();
488
489 assert_eq!(Code::Nl, code);
490 assert_eq!("NL", code.into_str());
491 }
492
493 #[test]
494 fn alpha3_country_code_matches() {
495 let code = Code::from_alpha_3(*b"NLD").unwrap();
496
497 assert_eq!(Code::Nl, code);
498 }
499
500 #[test]
501 fn should_create_country3_without_issue() {
502 const JSON: &str = r#"{ "country": "NLD" }"#;
503
504 let (code_set, warnings) = code_set_from_json(JSON).unwrap().into_parts();
505
506 let code = assert_matches!(code_set, CodeSet::Alpha3(code) => code);
507 assert_eq!(code, Code::Nl);
508 assert_matches!(*warnings, []);
509 }
510
511 #[test]
512 fn should_create_country2_without_issue() {
513 const JSON: &str = r#"{ "country": "NL" }"#;
514
515 let (code_set, warnings) = code_set_from_json(JSON).unwrap().into_parts();
516
517 let code = assert_matches!(code_set, CodeSet::Alpha2(code) => code);
518 assert_eq!(code, Code::Nl);
519 assert_matches!(*warnings, []);
520 }
521
522 #[test]
523 fn should_raise_country_content_issue() {
524 {
525 const JSON: &str = r#"{ "country": "VV" }"#;
526
527 let warnings = code_set_from_json(JSON).unwrap_err().into_kind_vec();
528
529 assert_matches!(*warnings, [WarningKind::InvalidCode]);
530 }
531
532 {
533 const JSON: &str = r#"{ "country": "VVV" }"#;
534
535 let warnings = code_set_from_json(JSON).unwrap_err().into_kind_vec();
536
537 assert_matches!(*warnings, [WarningKind::InvalidCode]);
538 }
539 }
540
541 #[test]
542 fn should_parse_invalid_case() {
543 {
544 const JSON: &str = r#"{ "country": "nl" }"#;
545
546 let (code_set, warnings) = code_set_from_json(JSON).unwrap().into_parts();
547 let warnings = warnings.into_kind_vec();
548
549 let code = assert_matches!(code_set, CodeSet::Alpha2(code) => code);
550 assert_eq!(code, Code::Nl);
551 assert_matches!(*warnings, [WarningKind::InvalidCase]);
552 }
553
554 {
555 const JSON: &str = r#"{ "country": "nld" }"#;
556
557 let (code_set, warnings) = code_set_from_json(JSON).unwrap().into_parts();
558 let warnings = warnings.into_kind_vec();
559
560 let code = assert_matches!(code_set, CodeSet::Alpha3(code) => code);
561 assert_eq!(code, Code::Nl);
562 assert_matches!(*warnings, [WarningKind::InvalidCase]);
563 }
564 }
565
566 #[test]
567 fn should_raise_country_length_issue() {
568 const JSON: &str = r#"{ "country": "IRELAND" }"#;
569
570 let warnings = code_set_from_json(JSON).unwrap_err().into_kind_vec();
571
572 assert_matches!(*warnings, [WarningKind::InvalidLength]);
573 }
574
575 #[track_caller]
576 fn code_set_from_json(json: &str) -> Verdict<CodeSet, WarningKind> {
577 let json = json::parse(json).unwrap();
578 let country_elem = json.find_field("country").unwrap();
579 CodeSet::from_json(country_elem.element())
580 }
581}