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            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    /// Return an alpha-2 `&str` version of the [Code]
159    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
170/// Macro to specify a list of valid ISO 3166-1 alpha-2 and alpha-3 country codes strings
171macro_rules! country_codes {
172    [$(($name:ident, $alph2:literal, $alph3:literal)),*] => {
173        /// An ISO 3166-1 alpha-2 country code.
174        ///
175        /// The impl is designed to be converted from `json::RawValue`.
176        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash,  PartialOrd, Ord)]
177        pub enum Code {
178            $($name),*
179        }
180
181        impl Code {
182            /// Try creating a `Code` from two upper ASCII bytes.
183            const fn from_alpha_2(code: [u8; 2]) -> Option<Self> {
184                match &code {
185                    $($alph2 => Some(Self::$name),)*
186                    _ => None
187                }
188            }
189
190            /// Try creating a `Code` from three upper ASCII bytes.
191            const fn from_alpha_3(code: [u8; 3]) -> Option<Self> {
192                match &code {
193                    $($alph3 => Some(Self::$name),)*
194                    _ => None
195                }
196            }
197
198            /// Return enum as two byte uppercase &str.
199            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}