ocpi_tariffs/
country.rs

1//! An ISO 3166-1 country code.
2//!
3//! Use `CodeSet` to parse a `Code` from JSON.
4use 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    /// Neither the timezone or country field require char escape codes.
19    ContainsEscapeCodes,
20
21    /// The field at the path could not be decoded.
22    Decode(json::decode::WarningKind),
23
24    /// The `country` is not a valid ISO 3166-1 country code because it's not uppercase.
25    PreferUpperCase,
26
27    /// The `country` is not a valid ISO 3166-1 country code.
28    InvalidCode,
29
30    /// The JSON value given is not a string.
31    InvalidType,
32
33    /// The `country` is not a valid ISO 3166-1 country code because it's not 2 or 3 chars in length.
34    InvalidLength,
35
36    /// The `country` is not a valid ISO 3166-1 country code because it's all codes beginning with 'X' are reserved.
37    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/// An alpha-2 or alpha-3 `Code`.
69///
70/// The caller can decide if they want to warn or fail if the wrong variant is parsed.
71#[derive(Debug)]
72pub(crate) enum CodeSet {
73    /// An alpha-2 country code was parsed.
74    Alpha2(Code),
75
76    /// An alpha-3 country code was parsed.
77    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    /// Return an alpha-2 `&str` version of the [Code]
155    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
166/// Macro to specify a list of valid ISO 3166-1 alpha-2 and alpha-3 country codes strings
167macro_rules! country_codes {
168    [$(($name:ident, $alph2:literal, $alph3:literal)),*] => {
169        /// An ISO 3166-1 alpha-2 country code.
170        ///
171        /// The impl is designed to be converted from `json::RawValue`.
172        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash,  PartialOrd, Ord)]
173        pub enum Code {
174            $($name),*
175        }
176
177        impl Code {
178            /// Try creating a `Code` from two upper ASCII bytes.
179            const fn from_alpha_2(code: [u8; 2]) -> Option<Self> {
180                match &code {
181                    $($alph2 => Some(Self::$name),)*
182                    _ => None
183                }
184            }
185
186            /// Try creating a `Code` from three upper ASCII bytes.
187            const fn from_alpha_3(code: [u8; 3]) -> Option<Self> {
188                match &code {
189                    $($alph3 => Some(Self::$name),)*
190                    _ => None
191                }
192            }
193
194            /// Return enum as two byte uppercase &str.
195            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}