muse2/
region.rs

1//! Regions represent different geographical areas in which agents, processes, etc. are active.
2use crate::id::{define_id_getter, define_id_type, IDCollection};
3use anyhow::{ensure, Result};
4use indexmap::{IndexMap, IndexSet};
5use serde::Deserialize;
6
7define_id_type! {RegionID}
8
9/// A map of [`Region`]s, keyed by region ID
10pub type RegionMap = IndexMap<RegionID, Region>;
11
12/// Represents a region with an ID and a longer description.
13#[derive(Debug, Deserialize, PartialEq)]
14pub struct Region {
15    /// A unique identifier for a region (e.g. "GBR").
16    pub id: RegionID,
17    /// A text description of the region (e.g. "United Kingdom").
18    pub description: String,
19}
20define_id_getter! {Region, RegionID}
21
22/// Parse a string of regions separated by semicolons into a vector of RegionID.
23///
24/// The string can be either "all" (case-insensitive), a single region, or a semicolon-separated
25/// list of regions (e.g. "GBR;FRA;USA" or "GBR; FRA; USA")
26pub fn parse_region_str(s: &str, region_ids: &IndexSet<RegionID>) -> Result<IndexSet<RegionID>> {
27    let s = s.trim();
28    ensure!(!s.is_empty(), "No regions provided");
29
30    if s.eq_ignore_ascii_case("all") {
31        return Ok(region_ids.clone());
32    }
33
34    s.split(";")
35        .map(|y| Ok(region_ids.get_id(y.trim())?.clone()))
36        .collect()
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    #[test]
44    fn test_parse_region_str() {
45        let region_ids: IndexSet<RegionID> = ["GBR".into(), "USA".into()].into_iter().collect();
46
47        // List of regions
48        let parsed = parse_region_str("GBR;USA", &region_ids).unwrap();
49        assert_eq!(parsed.len(), 2);
50        assert!(parsed.contains(&RegionID::from("GBR")));
51        assert!(parsed.contains(&RegionID::from("USA")));
52
53        // All regions
54        let parsed = parse_region_str("all", &region_ids).unwrap();
55        assert_eq!(parsed.len(), 2);
56        assert!(parsed.contains(&RegionID::from("GBR")));
57        assert!(parsed.contains(&RegionID::from("USA")));
58
59        // Single region
60        let parsed = parse_region_str("GBR", &region_ids).unwrap();
61        assert_eq!(parsed.len(), 1);
62        assert!(parsed.contains(&RegionID::from("GBR")));
63
64        // Empty string
65        let result = parse_region_str("", &region_ids);
66        assert!(result.is_err());
67
68        // Invalid region
69        let result = parse_region_str("GBR;INVALID", &region_ids);
70        assert!(result.is_err());
71    }
72}