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