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