muse2/
id.rs

1//! Code for handing IDs
2use anyhow::{Context, Result};
3use indexmap::IndexSet;
4use std::collections::HashSet;
5
6/// A trait alias for ID types
7pub trait IDLike:
8    Eq + std::hash::Hash + std::borrow::Borrow<str> + Clone + std::fmt::Display
9{
10}
11impl<T> IDLike for T where
12    T: Eq + std::hash::Hash + std::borrow::Borrow<str> + Clone + std::fmt::Display
13{
14}
15
16macro_rules! define_id_type {
17    ($name:ident) => {
18        #[derive(
19            Clone, std::hash::Hash, PartialEq, Eq, serde::Deserialize, Debug, serde::Serialize,
20        )]
21        /// An ID type (e.g. `AgentID`, `CommodityID`, etc.)
22        pub struct $name(pub std::rc::Rc<str>);
23
24        impl std::borrow::Borrow<str> for $name {
25            fn borrow(&self) -> &str {
26                &self.0
27            }
28        }
29
30        impl std::fmt::Display for $name {
31            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32                write!(f, "{}", self.0)
33            }
34        }
35
36        impl From<&str> for $name {
37            fn from(s: &str) -> Self {
38                $name(std::rc::Rc::from(s))
39            }
40        }
41
42        impl From<String> for $name {
43            fn from(s: String) -> Self {
44                $name(std::rc::Rc::from(s))
45            }
46        }
47
48        impl $name {
49            /// Create a new ID from a string slice
50            pub fn new(id: &str) -> Self {
51                $name(std::rc::Rc::from(id))
52            }
53        }
54    };
55}
56pub(crate) use define_id_type;
57
58#[cfg(test)]
59define_id_type!(GenericID);
60
61/// Indicates that the struct has an ID field
62pub trait HasID<ID: IDLike> {
63    /// Get the struct's ID
64    fn get_id(&self) -> &ID;
65}
66
67/// Implement the `HasID` trait for the given type, assuming it has a field called `id`
68macro_rules! define_id_getter {
69    ($t:ty, $id_ty:ty) => {
70        impl crate::id::HasID<$id_ty> for $t {
71            fn get_id(&self) -> &$id_ty {
72                &self.id
73            }
74        }
75    };
76}
77pub(crate) use define_id_getter;
78
79/// A data structure containing a set of IDs
80pub trait IDCollection<ID: IDLike> {
81    /// Get the ID from the collection by its string representation.
82    ///
83    /// # Arguments
84    ///
85    /// * `id` - The string representation of the ID
86    ///
87    /// # Returns
88    ///
89    /// A copy of the ID in `self`, or an error if not found.
90    fn get_id_by_str(&self, id: &str) -> Result<ID>;
91
92    /// Check if the ID is in the collection, returning a copy of it if found.
93    ///
94    /// # Arguments
95    ///
96    /// * `id` - The ID to check
97    ///
98    /// # Returns
99    ///
100    /// A copy of the ID in `self`, or an error if not found.
101    fn get_id(&self, id: &ID) -> Result<ID>;
102}
103
104macro_rules! define_id_methods {
105    () => {
106        fn get_id_by_str(&self, id: &str) -> Result<ID> {
107            let found = self
108                .get(id)
109                .with_context(|| format!("Unknown ID {id} found"))?;
110            Ok(found.clone())
111        }
112
113        fn get_id(&self, id: &ID) -> Result<ID> {
114            let found = self
115                .get(id.borrow())
116                .with_context(|| format!("Unknown ID {id} found"))?;
117            Ok(found.clone())
118        }
119    };
120}
121
122impl<ID: IDLike> IDCollection<ID> for HashSet<ID> {
123    define_id_methods!();
124}
125
126impl<ID: IDLike> IDCollection<ID> for IndexSet<ID> {
127    define_id_methods!();
128}