world-region 0.6.0

A Rust crate providing enums and conversions for World regions and their subregions, building on the africa, europe, north-america, etc crates.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
// ---------------- [ File: src/lib.rs ]
#![forbid(unsafe_code)]
#![allow(unused_variables)]
#![deny(clippy::all)]

#[macro_use] mod imports; use imports::*;

x!{abbreviation}
x!{from_str}
x!{country}
x!{error}
x!{impl_serde}
x!{impl_from}
x!{world_region}

#[cfg(test)]
mod world_region_tests {
    use super::*;
    use std::str::FromStr;

    #[test]
    fn test_conversions() {
        // Try from a country known in Africa
        let c = Country::Nigeria;
        let wr = WorldRegion::try_from(c).expect("should convert");
        match wr {
            WorldRegion::Africa(r) => assert_eq!(r, AfricaRegion::Nigeria),
            _ => panic!("Expected Africa(Nigeria)"),
        }

        // Try to convert back to Country
        let back: Country = wr.try_into().expect("should convert back");
        assert_eq!(back, Country::Nigeria);
    }

    #[test]
    fn test_abbreviation() {
        let wr = WorldRegion::Asia(AsiaRegion::China(asia::ChinaRegion::Beijing));
        assert_eq!(wr.abbreviation(), "CN-BJ");
    }

    #[test]
    fn test_abbreviations() {
        // Test a few abbreviations from different continents:
        let wr_africa = WorldRegion::Africa(AfricaRegion::Nigeria);
        assert_eq!(wr_africa.abbreviation(), "NG");

        let wr_asia = WorldRegion::Asia(AsiaRegion::Japan(asia::JapanRegion::Hokkaido));
        assert_eq!(wr_asia.abbreviation(), "JP-HKD");

        let wr_europe = WorldRegion::Europe(EuropeRegion::France(europe::FranceRegion::IleDeFrance));
        assert_eq!(wr_europe.abbreviation(), "FR-J");

        let wr_naa = WorldRegion::NorthAmerica(NorthAmericaRegion::Canada(north_america::CanadaRegion::Ontario));
        assert_eq!(wr_naa.abbreviation(), "ON");

        let wr_saa = WorldRegion::SouthAmerica(SouthAmericaRegion::Brazil(south_america::BrazilRegion::Sudeste));
        assert_eq!(wr_saa.abbreviation(), "BR-SE");

        let wr_caa = WorldRegion::CentralAmerica(CentralAmericaRegion::CostaRica);
        assert_eq!(wr_caa.abbreviation(), "CR");

        let wr_aoa = WorldRegion::AustraliaOceaniaAntarctica(australia_oceania_antarctica::AustraliaOceaniaAntarcticaRegion::Fiji);
        assert_eq!(wr_aoa.abbreviation(), "FJ");
    }

    #[test]
    fn test_iso_conversions() {
        // Pick a region that maps cleanly to a known country:
        let wr = WorldRegion::Africa(AfricaRegion::Egypt);
        let alpha2: Iso3166Alpha2 = wr.clone().try_into().expect("Alpha2 conversion");
        let alpha3: Iso3166Alpha3 = wr.clone().try_into().expect("Alpha3 conversion");
        let code: CountryCode = wr.try_into().expect("CountryCode conversion");

        assert_eq!(alpha2, Iso3166Alpha2::EG);
        assert_eq!(alpha3, Iso3166Alpha3::EGY);
        match code {
            CountryCode::Alpha2(a2) => assert_eq!(a2, Iso3166Alpha2::EG),
            _ => panic!("Expected Alpha2 code"),
        }

        // Test a region that doesn't map to a single Country:
        // For instance, if there's a special combined region in Asia or Africa
        // that doesn't directly map to a single Country, we should see an error:
        let wr_unsupported = WorldRegion::Asia(AsiaRegion::GccStates);
        let res: Result<Iso3166Alpha2, _> = wr_unsupported.try_into();
        assert!(res.is_err(), "GCC States should fail ISO conversion");
    }

    #[test]
    fn test_country_conversions() {
        // Convert from a known country to a WorldRegion
        let c = Country::Nigeria;
        let wr = WorldRegion::try_from(c).expect("should convert from Nigeria to WorldRegion(Africa)");
        match wr {
            WorldRegion::Africa(r) => assert_eq!(r, AfricaRegion::Nigeria),
            _ => panic!("Expected Africa(Nigeria)"),
        }

        // Convert back from WorldRegion to Country
        let back: Country = wr.try_into().expect("should convert back to Country");
        assert_eq!(back, Country::Nigeria);

        // Test a country not represented in any of these regions
        // For example: if Country::Greenland or Country::VaticanCity isn't in any region
        let c_not_rep = Country::VaticanCity; // Suppose VaticanCity is European but let's pretend it's not implemented.
        let wr_fail = WorldRegion::try_from(c_not_rep.clone());
        assert!(wr_fail.is_err(), "VaticanCity not represented should fail");

        //TODO:
        //this depends on a PartialEq implementation for the inner types, which requires all world crates to be republished. it can be done later.
        //if let Err(e) = wr_fail { assert!(e == WorldRegionConversionError::NotRepresented { country: Country::VaticanCity }); }
    }

    #[test]
    fn test_serialize_deserialize() {
        // Check round-trip serialization for a non-subdivided region:
        let wr_africa = WorldRegion::Africa(AfricaRegion::Kenya);
        let serialized = serde_json::to_string(&wr_africa).expect("serialize africa");
        let deserialized: WorldRegion = serde_json::from_str(&serialized).expect("deserialize africa");
        assert_eq!(wr_africa, deserialized);

        // Check round-trip serialization for a subdivided region:
        let wr_asia = WorldRegion::Asia(AsiaRegion::China(asia::ChinaRegion::Beijing));
        let serialized_asia = serde_json::to_string(&wr_asia).expect("serialize asia");
        let deserialized_asia: WorldRegion = serde_json::from_str(&serialized_asia).expect("deserialize asia");
        assert_eq!(wr_asia, deserialized_asia);

        // Check that "continent" field is included:
        let v: serde_json::Value = serde_json::from_str(&serialized_asia).expect("parse json");
        assert_eq!(v.get("continent").and_then(|x| x.as_str()), Some("Asia"));
        assert_eq!(v.get("country").and_then(|x| x.as_str()), Some("China"));
        assert_eq!(v.get("region").and_then(|x| x.as_str()), Some("Beijing"));
    }

    #[test]
    fn test_error_handling() {
        // Test a WorldRegion that cannot map to a single Country
        // For instance, a combined region in Africa:
        let wr_unsupported = WorldRegion::Africa(AfricaRegion::SaintHelenaAscensionTristanDaCunha);
        let country_res: Result<Country, _> = wr_unsupported.try_into();
        assert!(country_res.is_err(), "Should fail for combined region");

        // Check the error message
        //if let Err(e) = country_res { assert!(e.to_string().contains("does not map cleanly")); }
    }

    #[test]
    fn test_variant_names_consistency() {
        // Ensure that we can round-trip from known countries to world regions for each continent.
        let africa_test = Country::Ethiopia;
        let wr_africa = WorldRegion::try_from(africa_test.clone()).expect("Ethiopia -> AfricaRegion");
        let back_africa: Country = wr_africa.try_into().expect("AfricaRegion -> Ethiopia");
        assert_eq!(back_africa, africa_test);

        let asia_test = Country::Japan;
        let wr_asia = WorldRegion::try_from(asia_test.clone()).expect("Japan -> AsiaRegion");
        let back_asia: Country = wr_asia.try_into().expect("AsiaRegion -> Japan");
        assert_eq!(back_asia, asia_test);

        let europe_test = Country::France;
        let wr_europe = WorldRegion::try_from(europe_test.clone()).expect("France -> EuropeRegion");
        let back_europe: Country = wr_europe.try_into().expect("EuropeRegion -> France");
        assert_eq!(back_europe, europe_test);

        // ... and so on for NorthAmerica, SouthAmerica, CentralAmerica, AustraliaOceaniaAntarctica.
        // This ensures coverage of each world region variant.

        let na_test = Country::Canada;
        let wr_na = WorldRegion::try_from(na_test.clone()).expect("Canada -> NorthAmericaRegion");
        let back_na: Country = wr_na.try_into().expect("NorthAmericaRegion -> Canada");
        assert_eq!(back_na, na_test);

        let sa_test = Country::Argentina;
        let wr_sa = WorldRegion::try_from(sa_test.clone()).expect("Argentina -> SouthAmericaRegion");
        let back_sa: Country = wr_sa.try_into().expect("SouthAmericaRegion -> Argentina");
        assert_eq!(back_sa, sa_test);

        let ca_test = Country::Panama;
        let wr_ca = WorldRegion::try_from(ca_test.clone()).expect("Panama -> CentralAmericaRegion");
        let back_ca: Country = wr_ca.try_into().expect("CentralAmericaRegion -> Panama");
        assert_eq!(back_ca, ca_test);

        let aoa_test = Country::Fiji;
        let wr_aoa = WorldRegion::try_from(aoa_test.clone()).expect("Fiji -> AustraliaOceaniaAntarcticaRegion");
        let back_aoa: Country = wr_aoa.try_into().expect("AustraliaOceaniaAntarcticaRegion -> Fiji");
        assert_eq!(back_aoa, aoa_test);
    }

    #[test]
    fn test_from_str_and_variant_matching() {
        // If the underlying enums support `FromStr` via strum, we can test that route:
        // For example, testing AsiaRegion parsing:
        let asia_region = AsiaRegion::from_str("Beijing").ok();
        assert!(asia_region.is_some());
        let wr = WorldRegion::Asia(asia_region.unwrap());
        let serialized = serde_json::to_string(&wr).expect("serialize");
        let deserialized: WorldRegion = serde_json::from_str(&serialized).expect("deserialize");
        assert_eq!(wr, deserialized);
    }

    #[test]
    fn test_unified_iso_conversion_failure() {
        // Pick a region known to fail ISO conversion:
        // For instance, Canary Islands in Africa if not directly mapped to a Country
        let wr_canary = WorldRegion::Africa(AfricaRegion::CanaryIslands);
        let res: Result<Iso3166Alpha2, _> = wr_canary.try_into();
        assert!(res.is_err(), "Canary Islands should fail ISO conversion");
    }
}

#[cfg(test)]
mod exhaustive_world_region_tests {
    use super::*;
    use std::str::FromStr;

    #[test]
    fn test_all_continents_country_to_world_region() {
        // For each continent, pick a few representative countries:
        let test_cases = vec![
            (Country::Nigeria   , "Africa")                       , 
            (Country::Egypt     , "Africa")                       , 
            (Country::China     , "Asia")                         , 
            (Country::Japan     , "Asia")                         , 
            (Country::France    , "Europe")                       , 
            (Country::Germany   , "Europe")                       , 
            (Country::Canada    , "North America")                , 
            (Country::USA       , "North America")                , 
            (Country::Argentina , "South America")                , 
            (Country::Brazil    , "South America")                , 
            (Country::CostaRica , "Central America")              , 
            (Country::Panama    , "Central America")              , 
            (Country::Fiji      , "Australia/Oceania/Antarctica") , 
            (Country::Australia , "Australia/Oceania/Antarctica") , 
        ];

        for (country, expected_continent) in test_cases {
            let wr = WorldRegion::try_from(country.clone())
                .unwrap_or_else(|_| panic!("Could not map {:?} to WorldRegion", country));
            // Check that the continent field matches expected_continent via serialization:
            let serialized = serde_json::to_string(&wr).expect("serialize");
            let v: serde_json::Value = serde_json::from_str(&serialized).expect("json parse");
            let cont = v.get("continent").and_then(|x| x.as_str()).unwrap();
            assert_eq!(cont, expected_continent, "Continent mismatch for {:?}", country);

            // Round-trip back to country:
            let back_country: Country = wr.try_into().expect("Should convert back to Country");
            assert_eq!(back_country, country, "Round-trip country mismatch");
        }
    }

    #[test]
    fn test_iso_code_success() {
        // Test ISO conversions for a known country-region mapping:
        let wr = WorldRegion::Europe(EuropeRegion::France(FranceRegion::IleDeFrance));
        let alpha2: Iso3166Alpha2 = wr.try_into().expect("Alpha2 conversion failed");
        let alpha3: Iso3166Alpha3 = wr.try_into().expect("Alpha3 conversion failed");
        let code: CountryCode = wr.try_into().expect("CountryCode conversion failed");

        assert_eq!(alpha2, Iso3166Alpha2::FR);
        assert_eq!(alpha3, Iso3166Alpha3::FRA);
        match code {
            CountryCode::Alpha2(a2) => assert_eq!(a2, Iso3166Alpha2::FR),
            _ => panic!("Expected Alpha2 code"),
        }
    }

    #[test]
    fn test_iso_code_failure() {
        // Pick a region that doesn't map cleanly to a single country:
        // For example, if GCC States is a combined region in Asia:
        let wr_unsupported = WorldRegion::Asia(AsiaRegion::GccStates);
        let res: Result<Iso3166Alpha2, _> = wr_unsupported.try_into();
        assert!(res.is_err(), "GCC States should fail ISO conversion");
    }

    #[test]
    fn test_not_represented_country() {
        // Choose a country that doesn't fit into any known world region mapping:
        let c = Country::VaticanCity; // Suppose not represented by current logic
        let res = WorldRegion::try_from(c.clone());
        match res {
            Err(WorldRegionConversionError::NotRepresented { country }) => {
                assert_eq!(country, c, "Expected NotRepresented error for {:?}", c);
            }
            _ => panic!("Expected NotRepresented error for {:?}", c),
        }
    }

    #[test]
    fn test_unsupported_region_to_country() {
        // Suppose we have a combined region that can't map back to a single Country:
        let wr_unsupported = WorldRegion::Africa(AfricaRegion::SaintHelenaAscensionTristanDaCunha);
        let res: Result<Country, _> = wr_unsupported.try_into();
        match res {
            Err(WorldRegionConversionError::Africa(_)) => {
                // This indicates it's an African region error. Specific subtype can be tested if needed.
            }
            _ => panic!("Expected Africa(...) error for unsupported region"),
        }
    }

    #[test]
    fn test_serialize_deserialize_non_subdivided() {
        // Non-subdivided example:
        let wr = WorldRegion::CentralAmerica(CentralAmericaRegion::CostaRica);
        let serialized = serde_json::to_string(&wr).expect("serialize");
        let deserialized: WorldRegion = serde_json::from_str(&serialized).expect("deserialize");
        assert_eq!(wr, deserialized);
    }

    #[test]
    fn test_serialize_deserialize_subdivided() {
        // Subdivided example (e.g., Canada(Ontario))
        let wr = WorldRegion::NorthAmerica(NorthAmericaRegion::Canada(north_america::CanadaRegion::Ontario));
        let serialized = serde_json::to_string(&wr).expect("serialize");
        let deserialized: WorldRegion = serde_json::from_str(&serialized).expect("deserialize");
        assert_eq!(wr, deserialized);

        // Check that the continent/country/region fields are present:
        let v: serde_json::Value = serde_json::from_str(&serialized).expect("parse json");
        assert_eq!(v.get("continent").and_then(|x| x.as_str()), Some("North America"));
        assert_eq!(v.get("country").and_then(|x| x.as_str()), Some("Canada"));
        assert_eq!(v.get("region").and_then(|x| x.as_str()), Some("Ontario"));
    }

    #[test]
    fn test_abbreviation_across_continents() {
        let pairs = vec![
            (WorldRegion::Africa(AfricaRegion::Nigeria), "NG"),
            (WorldRegion::Asia(AsiaRegion::Japan(asia::JapanRegion::Hokkaido)), "JP-HKD"),
            (WorldRegion::Europe(EuropeRegion::Germany(GermanyRegion::Berlin)), "DE-BE"),
            (WorldRegion::NorthAmerica(NorthAmericaRegion::UnitedStates(usa::USRegion::UnitedState(usa::UnitedState::California))), "CA"),
            (WorldRegion::SouthAmerica(SouthAmericaRegion::Brazil(south_america::BrazilRegion::Sudeste)), "BR-SE"),
            (WorldRegion::CentralAmerica(CentralAmericaRegion::Panama), "PA"),
            (WorldRegion::AustraliaOceaniaAntarctica(australia_oceania_antarctica::AustraliaOceaniaAntarcticaRegion::Fiji), "FJ"),
        ];

        for (wr, expected_abbr) in pairs {
            assert_eq!(wr.abbreviation(), expected_abbr, "Abbreviation mismatch for {:?}", wr);
        }
    }

    #[test]
    fn test_string_parsing_case_insensitivity() {
        // If we have FromStr implemented for subregions, we can test case-insensitivity:
        // This depends on upstream enums (e.g., if AsiaRegion, EuropeRegion, etc., implement FromStr)
        let wr_str = r#"Ile-De-France"#;
        let wr = WorldRegion::from_str(wr_str).expect("deserialize");
        if let WorldRegion::Europe(EuropeRegion::France(fr)) = wr {
            assert_eq!(fr, FranceRegion::IleDeFrance);
        } else {
            panic!("Expected Europe(France(IleDeFrance))");
        }

        // Try deserializing with different cases/spaces:
        let wr_str_alt = r#"iLe-De-FrAnCe"#;
        let wr_alt = WorldRegion::from_str(wr_str_alt).expect("case-insensitive deserialize");
        assert_eq!(wr, wr_alt);
    }

    #[test]
    fn test_round_trip_all_example_continents() {
        // This test ensures we can round-trip multiple examples:
        let examples = vec![
            WorldRegion::Africa(AfricaRegion::Kenya),
            WorldRegion::Asia(AsiaRegion::China(asia::ChinaRegion::Beijing)),
            WorldRegion::Europe(EuropeRegion::Italy(europe::ItalyRegion::Centro)),
            WorldRegion::NorthAmerica(NorthAmericaRegion::Canada(north_america::CanadaRegion::Ontario)),
            WorldRegion::SouthAmerica(SouthAmericaRegion::Brazil(south_america::BrazilRegion::Sul)),
            WorldRegion::CentralAmerica(CentralAmericaRegion::Guatemala),
            WorldRegion::AustraliaOceaniaAntarctica(australia_oceania_antarctica::AustraliaOceaniaAntarcticaRegion::NewZealand),
        ];

        for wr in examples {
            let serialized = serde_json::to_string(&wr).expect("serialize");
            let deserialized: WorldRegion = serde_json::from_str(&serialized).expect("deserialize");
            assert_eq!(wr, deserialized, "Round-trip mismatch for {:?}", wr);
        }
    }

    #[test]
    fn test_fictional_error() {
        let fictional_wr = WorldRegion::from_str("Atlantis");
        assert!(fictional_wr.is_err());
    }

    #[test]
    fn test_feature_flag_abbreviation() {
        // If feature = "serde_abbreviation" is enabled, serialization differs.
        // We can conditionally compile this test:
        // Cargo.toml:
        // [features]
        // serde_abbreviation = []
        //
        // #[cfg(feature = "serde_abbreviation")]
        // test serialization differs:
        #[cfg(feature = "serde_abbreviation")]
        {
            let wr = WorldRegion::Europe(EuropeRegion::Spain(spain::SpainRegion::Madrid));
            let s = serde_json::to_string(&wr).expect("serialize");
            // Should just serialize abbreviation "ES":
            assert_eq!(s, "\"ES\"");
        }
    }
}