world_region/
lib.rs

1// ---------------- [ File: src/lib.rs ]
2#![forbid(unsafe_code)]
3#![allow(unused_variables)]
4#![deny(clippy::all)]
5
6#[macro_use] mod imports; use imports::*;
7
8x!{abbreviation}
9x!{from_str}
10x!{country}
11x!{error}
12x!{impl_serde}
13x!{impl_from}
14x!{world_region}
15
16#[cfg(test)]
17mod world_region_tests {
18    use super::*;
19    use std::str::FromStr;
20
21    #[test]
22    fn test_conversions() {
23        // Try from a country known in Africa
24        let c = Country::Nigeria;
25        let wr = WorldRegion::try_from(c).expect("should convert");
26        match wr {
27            WorldRegion::Africa(r) => assert_eq!(r, AfricaRegion::Nigeria),
28            _ => panic!("Expected Africa(Nigeria)"),
29        }
30
31        // Try to convert back to Country
32        let back: Country = wr.try_into().expect("should convert back");
33        assert_eq!(back, Country::Nigeria);
34    }
35
36    #[test]
37    fn test_abbreviation() {
38        let wr = WorldRegion::Asia(AsiaRegion::China(asia::ChinaRegion::Beijing));
39        assert_eq!(wr.abbreviation(), "CN-BJ");
40    }
41
42    #[test]
43    fn test_abbreviations() {
44        // Test a few abbreviations from different continents:
45        let wr_africa = WorldRegion::Africa(AfricaRegion::Nigeria);
46        assert_eq!(wr_africa.abbreviation(), "NG");
47
48        let wr_asia = WorldRegion::Asia(AsiaRegion::Japan(asia::JapanRegion::Hokkaido));
49        assert_eq!(wr_asia.abbreviation(), "JP-HKD");
50
51        let wr_europe = WorldRegion::Europe(EuropeRegion::France(europe::FranceRegion::IleDeFrance));
52        assert_eq!(wr_europe.abbreviation(), "FR-J");
53
54        let wr_naa = WorldRegion::NorthAmerica(NorthAmericaRegion::Canada(north_america::CanadaRegion::Ontario));
55        assert_eq!(wr_naa.abbreviation(), "ON");
56
57        let wr_saa = WorldRegion::SouthAmerica(SouthAmericaRegion::Brazil(south_america::BrazilRegion::Sudeste));
58        assert_eq!(wr_saa.abbreviation(), "BR-SE");
59
60        let wr_caa = WorldRegion::CentralAmerica(CentralAmericaRegion::CostaRica);
61        assert_eq!(wr_caa.abbreviation(), "CR");
62
63        let wr_aoa = WorldRegion::AustraliaOceaniaAntarctica(australia_oceania_antarctica::AustraliaOceaniaAntarcticaRegion::Fiji);
64        assert_eq!(wr_aoa.abbreviation(), "FJ");
65    }
66
67    #[test]
68    fn test_iso_conversions() {
69        // Pick a region that maps cleanly to a known country:
70        let wr = WorldRegion::Africa(AfricaRegion::Egypt);
71        let alpha2: Iso3166Alpha2 = wr.clone().try_into().expect("Alpha2 conversion");
72        let alpha3: Iso3166Alpha3 = wr.clone().try_into().expect("Alpha3 conversion");
73        let code: CountryCode = wr.try_into().expect("CountryCode conversion");
74
75        assert_eq!(alpha2, Iso3166Alpha2::EG);
76        assert_eq!(alpha3, Iso3166Alpha3::EGY);
77        match code {
78            CountryCode::Alpha2(a2) => assert_eq!(a2, Iso3166Alpha2::EG),
79            _ => panic!("Expected Alpha2 code"),
80        }
81
82        // Test a region that doesn't map to a single Country:
83        // For instance, if there's a special combined region in Asia or Africa
84        // that doesn't directly map to a single Country, we should see an error:
85        let wr_unsupported = WorldRegion::Asia(AsiaRegion::GccStates);
86        let res: Result<Iso3166Alpha2, _> = wr_unsupported.try_into();
87        assert!(res.is_err(), "GCC States should fail ISO conversion");
88    }
89
90    #[test]
91    fn test_country_conversions() {
92        // Convert from a known country to a WorldRegion
93        let c = Country::Nigeria;
94        let wr = WorldRegion::try_from(c).expect("should convert from Nigeria to WorldRegion(Africa)");
95        match wr {
96            WorldRegion::Africa(r) => assert_eq!(r, AfricaRegion::Nigeria),
97            _ => panic!("Expected Africa(Nigeria)"),
98        }
99
100        // Convert back from WorldRegion to Country
101        let back: Country = wr.try_into().expect("should convert back to Country");
102        assert_eq!(back, Country::Nigeria);
103
104        // Test a country not represented in any of these regions
105        // For example: if Country::Greenland or Country::VaticanCity isn't in any region
106        let c_not_rep = Country::VaticanCity; // Suppose VaticanCity is European but let's pretend it's not implemented.
107        let wr_fail = WorldRegion::try_from(c_not_rep.clone());
108        assert!(wr_fail.is_err(), "VaticanCity not represented should fail");
109
110        //TODO:
111        //this depends on a PartialEq implementation for the inner types, which requires all world crates to be republished. it can be done later.
112        //if let Err(e) = wr_fail { assert!(e == WorldRegionConversionError::NotRepresented { country: Country::VaticanCity }); }
113    }
114
115    #[test]
116    fn test_serialize_deserialize() {
117        // Check round-trip serialization for a non-subdivided region:
118        let wr_africa = WorldRegion::Africa(AfricaRegion::Kenya);
119        let serialized = serde_json::to_string(&wr_africa).expect("serialize africa");
120        let deserialized: WorldRegion = serde_json::from_str(&serialized).expect("deserialize africa");
121        assert_eq!(wr_africa, deserialized);
122
123        // Check round-trip serialization for a subdivided region:
124        let wr_asia = WorldRegion::Asia(AsiaRegion::China(asia::ChinaRegion::Beijing));
125        let serialized_asia = serde_json::to_string(&wr_asia).expect("serialize asia");
126        let deserialized_asia: WorldRegion = serde_json::from_str(&serialized_asia).expect("deserialize asia");
127        assert_eq!(wr_asia, deserialized_asia);
128
129        // Check that "continent" field is included:
130        let v: serde_json::Value = serde_json::from_str(&serialized_asia).expect("parse json");
131        assert_eq!(v.get("continent").and_then(|x| x.as_str()), Some("Asia"));
132        assert_eq!(v.get("country").and_then(|x| x.as_str()), Some("China"));
133        assert_eq!(v.get("region").and_then(|x| x.as_str()), Some("Beijing"));
134    }
135
136    #[test]
137    fn test_error_handling() {
138        // Test a WorldRegion that cannot map to a single Country
139        // For instance, a combined region in Africa:
140        let wr_unsupported = WorldRegion::Africa(AfricaRegion::SaintHelenaAscensionTristanDaCunha);
141        let country_res: Result<Country, _> = wr_unsupported.try_into();
142        assert!(country_res.is_err(), "Should fail for combined region");
143
144        // Check the error message
145        //if let Err(e) = country_res { assert!(e.to_string().contains("does not map cleanly")); }
146    }
147
148    #[test]
149    fn test_variant_names_consistency() {
150        // Ensure that we can round-trip from known countries to world regions for each continent.
151        let africa_test = Country::Ethiopia;
152        let wr_africa = WorldRegion::try_from(africa_test.clone()).expect("Ethiopia -> AfricaRegion");
153        let back_africa: Country = wr_africa.try_into().expect("AfricaRegion -> Ethiopia");
154        assert_eq!(back_africa, africa_test);
155
156        let asia_test = Country::Japan;
157        let wr_asia = WorldRegion::try_from(asia_test.clone()).expect("Japan -> AsiaRegion");
158        let back_asia: Country = wr_asia.try_into().expect("AsiaRegion -> Japan");
159        assert_eq!(back_asia, asia_test);
160
161        let europe_test = Country::France;
162        let wr_europe = WorldRegion::try_from(europe_test.clone()).expect("France -> EuropeRegion");
163        let back_europe: Country = wr_europe.try_into().expect("EuropeRegion -> France");
164        assert_eq!(back_europe, europe_test);
165
166        // ... and so on for NorthAmerica, SouthAmerica, CentralAmerica, AustraliaOceaniaAntarctica.
167        // This ensures coverage of each world region variant.
168
169        let na_test = Country::Canada;
170        let wr_na = WorldRegion::try_from(na_test.clone()).expect("Canada -> NorthAmericaRegion");
171        let back_na: Country = wr_na.try_into().expect("NorthAmericaRegion -> Canada");
172        assert_eq!(back_na, na_test);
173
174        let sa_test = Country::Argentina;
175        let wr_sa = WorldRegion::try_from(sa_test.clone()).expect("Argentina -> SouthAmericaRegion");
176        let back_sa: Country = wr_sa.try_into().expect("SouthAmericaRegion -> Argentina");
177        assert_eq!(back_sa, sa_test);
178
179        let ca_test = Country::Panama;
180        let wr_ca = WorldRegion::try_from(ca_test.clone()).expect("Panama -> CentralAmericaRegion");
181        let back_ca: Country = wr_ca.try_into().expect("CentralAmericaRegion -> Panama");
182        assert_eq!(back_ca, ca_test);
183
184        let aoa_test = Country::Fiji;
185        let wr_aoa = WorldRegion::try_from(aoa_test.clone()).expect("Fiji -> AustraliaOceaniaAntarcticaRegion");
186        let back_aoa: Country = wr_aoa.try_into().expect("AustraliaOceaniaAntarcticaRegion -> Fiji");
187        assert_eq!(back_aoa, aoa_test);
188    }
189
190    #[test]
191    fn test_from_str_and_variant_matching() {
192        // If the underlying enums support `FromStr` via strum, we can test that route:
193        // For example, testing AsiaRegion parsing:
194        let asia_region = AsiaRegion::from_str("Beijing").ok();
195        assert!(asia_region.is_some());
196        let wr = WorldRegion::Asia(asia_region.unwrap());
197        let serialized = serde_json::to_string(&wr).expect("serialize");
198        let deserialized: WorldRegion = serde_json::from_str(&serialized).expect("deserialize");
199        assert_eq!(wr, deserialized);
200    }
201
202    #[test]
203    fn test_unified_iso_conversion_failure() {
204        // Pick a region known to fail ISO conversion:
205        // For instance, Canary Islands in Africa if not directly mapped to a Country
206        let wr_canary = WorldRegion::Africa(AfricaRegion::CanaryIslands);
207        let res: Result<Iso3166Alpha2, _> = wr_canary.try_into();
208        assert!(res.is_err(), "Canary Islands should fail ISO conversion");
209    }
210}
211
212#[cfg(test)]
213mod exhaustive_world_region_tests {
214    use super::*;
215    use std::str::FromStr;
216
217    #[test]
218    fn test_all_continents_country_to_world_region() {
219        // For each continent, pick a few representative countries:
220        let test_cases = vec![
221            (Country::Nigeria   , "Africa")                       , 
222            (Country::Egypt     , "Africa")                       , 
223            (Country::China     , "Asia")                         , 
224            (Country::Japan     , "Asia")                         , 
225            (Country::France    , "Europe")                       , 
226            (Country::Germany   , "Europe")                       , 
227            (Country::Canada    , "North America")                , 
228            (Country::USA       , "North America")                , 
229            (Country::Argentina , "South America")                , 
230            (Country::Brazil    , "South America")                , 
231            (Country::CostaRica , "Central America")              , 
232            (Country::Panama    , "Central America")              , 
233            (Country::Fiji      , "Australia/Oceania/Antarctica") , 
234            (Country::Australia , "Australia/Oceania/Antarctica") , 
235        ];
236
237        for (country, expected_continent) in test_cases {
238            let wr = WorldRegion::try_from(country.clone())
239                .unwrap_or_else(|_| panic!("Could not map {:?} to WorldRegion", country));
240            // Check that the continent field matches expected_continent via serialization:
241            let serialized = serde_json::to_string(&wr).expect("serialize");
242            let v: serde_json::Value = serde_json::from_str(&serialized).expect("json parse");
243            let cont = v.get("continent").and_then(|x| x.as_str()).unwrap();
244            assert_eq!(cont, expected_continent, "Continent mismatch for {:?}", country);
245
246            // Round-trip back to country:
247            let back_country: Country = wr.try_into().expect("Should convert back to Country");
248            assert_eq!(back_country, country, "Round-trip country mismatch");
249        }
250    }
251
252    #[test]
253    fn test_iso_code_success() {
254        // Test ISO conversions for a known country-region mapping:
255        let wr = WorldRegion::Europe(EuropeRegion::France(FranceRegion::IleDeFrance));
256        let alpha2: Iso3166Alpha2 = wr.try_into().expect("Alpha2 conversion failed");
257        let alpha3: Iso3166Alpha3 = wr.try_into().expect("Alpha3 conversion failed");
258        let code: CountryCode = wr.try_into().expect("CountryCode conversion failed");
259
260        assert_eq!(alpha2, Iso3166Alpha2::FR);
261        assert_eq!(alpha3, Iso3166Alpha3::FRA);
262        match code {
263            CountryCode::Alpha2(a2) => assert_eq!(a2, Iso3166Alpha2::FR),
264            _ => panic!("Expected Alpha2 code"),
265        }
266    }
267
268    #[test]
269    fn test_iso_code_failure() {
270        // Pick a region that doesn't map cleanly to a single country:
271        // For example, if GCC States is a combined region in Asia:
272        let wr_unsupported = WorldRegion::Asia(AsiaRegion::GccStates);
273        let res: Result<Iso3166Alpha2, _> = wr_unsupported.try_into();
274        assert!(res.is_err(), "GCC States should fail ISO conversion");
275    }
276
277    #[test]
278    fn test_not_represented_country() {
279        // Choose a country that doesn't fit into any known world region mapping:
280        let c = Country::VaticanCity; // Suppose not represented by current logic
281        let res = WorldRegion::try_from(c.clone());
282        match res {
283            Err(WorldRegionConversionError::NotRepresented { country }) => {
284                assert_eq!(country, c, "Expected NotRepresented error for {:?}", c);
285            }
286            _ => panic!("Expected NotRepresented error for {:?}", c),
287        }
288    }
289
290    #[test]
291    fn test_unsupported_region_to_country() {
292        // Suppose we have a combined region that can't map back to a single Country:
293        let wr_unsupported = WorldRegion::Africa(AfricaRegion::SaintHelenaAscensionTristanDaCunha);
294        let res: Result<Country, _> = wr_unsupported.try_into();
295        match res {
296            Err(WorldRegionConversionError::Africa(_)) => {
297                // This indicates it's an African region error. Specific subtype can be tested if needed.
298            }
299            _ => panic!("Expected Africa(...) error for unsupported region"),
300        }
301    }
302
303    #[test]
304    fn test_serialize_deserialize_non_subdivided() {
305        // Non-subdivided example:
306        let wr = WorldRegion::CentralAmerica(CentralAmericaRegion::CostaRica);
307        let serialized = serde_json::to_string(&wr).expect("serialize");
308        let deserialized: WorldRegion = serde_json::from_str(&serialized).expect("deserialize");
309        assert_eq!(wr, deserialized);
310    }
311
312    #[test]
313    fn test_serialize_deserialize_subdivided() {
314        // Subdivided example (e.g., Canada(Ontario))
315        let wr = WorldRegion::NorthAmerica(NorthAmericaRegion::Canada(north_america::CanadaRegion::Ontario));
316        let serialized = serde_json::to_string(&wr).expect("serialize");
317        let deserialized: WorldRegion = serde_json::from_str(&serialized).expect("deserialize");
318        assert_eq!(wr, deserialized);
319
320        // Check that the continent/country/region fields are present:
321        let v: serde_json::Value = serde_json::from_str(&serialized).expect("parse json");
322        assert_eq!(v.get("continent").and_then(|x| x.as_str()), Some("North America"));
323        assert_eq!(v.get("country").and_then(|x| x.as_str()), Some("Canada"));
324        assert_eq!(v.get("region").and_then(|x| x.as_str()), Some("Ontario"));
325    }
326
327    #[test]
328    fn test_abbreviation_across_continents() {
329        let pairs = vec![
330            (WorldRegion::Africa(AfricaRegion::Nigeria), "NG"),
331            (WorldRegion::Asia(AsiaRegion::Japan(asia::JapanRegion::Hokkaido)), "JP-HKD"),
332            (WorldRegion::Europe(EuropeRegion::Germany(GermanyRegion::Berlin)), "DE-BE"),
333            (WorldRegion::NorthAmerica(NorthAmericaRegion::UnitedStates(usa::USRegion::UnitedState(usa::UnitedState::California))), "CA"),
334            (WorldRegion::SouthAmerica(SouthAmericaRegion::Brazil(south_america::BrazilRegion::Sudeste)), "BR-SE"),
335            (WorldRegion::CentralAmerica(CentralAmericaRegion::Panama), "PA"),
336            (WorldRegion::AustraliaOceaniaAntarctica(australia_oceania_antarctica::AustraliaOceaniaAntarcticaRegion::Fiji), "FJ"),
337        ];
338
339        for (wr, expected_abbr) in pairs {
340            assert_eq!(wr.abbreviation(), expected_abbr, "Abbreviation mismatch for {:?}", wr);
341        }
342    }
343
344    #[test]
345    fn test_string_parsing_case_insensitivity() {
346        // If we have FromStr implemented for subregions, we can test case-insensitivity:
347        // This depends on upstream enums (e.g., if AsiaRegion, EuropeRegion, etc., implement FromStr)
348        let wr_str = r#"Ile-De-France"#;
349        let wr = WorldRegion::from_str(wr_str).expect("deserialize");
350        if let WorldRegion::Europe(EuropeRegion::France(fr)) = wr {
351            assert_eq!(fr, FranceRegion::IleDeFrance);
352        } else {
353            panic!("Expected Europe(France(IleDeFrance))");
354        }
355
356        // Try deserializing with different cases/spaces:
357        let wr_str_alt = r#"iLe-De-FrAnCe"#;
358        let wr_alt = WorldRegion::from_str(wr_str_alt).expect("case-insensitive deserialize");
359        assert_eq!(wr, wr_alt);
360    }
361
362    #[test]
363    fn test_round_trip_all_example_continents() {
364        // This test ensures we can round-trip multiple examples:
365        let examples = vec![
366            WorldRegion::Africa(AfricaRegion::Kenya),
367            WorldRegion::Asia(AsiaRegion::China(asia::ChinaRegion::Beijing)),
368            WorldRegion::Europe(EuropeRegion::Italy(europe::ItalyRegion::Centro)),
369            WorldRegion::NorthAmerica(NorthAmericaRegion::Canada(north_america::CanadaRegion::Ontario)),
370            WorldRegion::SouthAmerica(SouthAmericaRegion::Brazil(south_america::BrazilRegion::Sul)),
371            WorldRegion::CentralAmerica(CentralAmericaRegion::Guatemala),
372            WorldRegion::AustraliaOceaniaAntarctica(australia_oceania_antarctica::AustraliaOceaniaAntarcticaRegion::NewZealand),
373        ];
374
375        for wr in examples {
376            let serialized = serde_json::to_string(&wr).expect("serialize");
377            let deserialized: WorldRegion = serde_json::from_str(&serialized).expect("deserialize");
378            assert_eq!(wr, deserialized, "Round-trip mismatch for {:?}", wr);
379        }
380    }
381
382    #[test]
383    fn test_fictional_error() {
384        let fictional_wr = WorldRegion::from_str("Atlantis");
385        assert!(fictional_wr.is_err());
386    }
387
388    #[test]
389    fn test_feature_flag_abbreviation() {
390        // If feature = "serde_abbreviation" is enabled, serialization differs.
391        // We can conditionally compile this test:
392        // Cargo.toml:
393        // [features]
394        // serde_abbreviation = []
395        //
396        // #[cfg(feature = "serde_abbreviation")]
397        // test serialization differs:
398        #[cfg(feature = "serde_abbreviation")]
399        {
400            let wr = WorldRegion::Europe(EuropeRegion::Spain(spain::SpainRegion::Madrid));
401            let s = serde_json::to_string(&wr).expect("serialize");
402            // Should just serialize abbreviation "ES":
403            assert_eq!(s, "\"ES\"");
404        }
405    }
406}