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