Skip to main content

ocpi_tariffs/
country.rs

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