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