Skip to main content

hydro_lang/location/
member_id.rs

1use std::fmt::{Debug, Display};
2use std::hash::Hash;
3use std::marker::PhantomData;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Deserialize, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
8#[non_exhaustive] // Variants change based on features.
9pub enum TaglessMemberId {
10    #[cfg(feature = "deploy_integration")]
11    #[cfg_attr(docsrs, doc(cfg(feature = "deploy_integration")))]
12    Legacy { raw_id: u32 },
13    #[cfg(feature = "docker_runtime")]
14    #[cfg_attr(docsrs, doc(cfg(feature = "docker_runtime")))]
15    Docker { container_name: String },
16    #[cfg(feature = "maelstrom_runtime")]
17    #[cfg_attr(docsrs, doc(cfg(feature = "maelstrom_runtime")))]
18    Maelstrom { node_id: String },
19}
20
21macro_rules! assert_feature {
22    (#[cfg(feature = $feat:expr)] $( $code:stmt )+) => {
23        #[cfg(not(feature = $feat))]
24        panic!("Feature {:?} is not enabled.", $feat);
25
26        #[cfg(feature = $feat)]
27        {
28            $( $code )+
29        }
30    };
31}
32
33impl TaglessMemberId {
34    pub fn from_raw_id(_raw_id: u32) -> Self {
35        assert_feature! {
36            #[cfg(feature = "deploy_integration")]
37            Self::Legacy { raw_id: _raw_id }
38        }
39    }
40
41    pub fn get_raw_id(&self) -> u32 {
42        assert_feature! {
43            #[cfg(feature = "deploy_integration")]
44            #[expect(clippy::allow_attributes, reason = "Depends on features.")]
45            #[allow(
46                irrefutable_let_patterns,
47                reason = "Depends on features."
48            )]
49            let TaglessMemberId::Legacy { raw_id } = self else {
50                panic!("Not `Legacy` variant.");
51            }
52            *raw_id
53        }
54    }
55
56    pub fn from_container_name(_container_name: impl Into<String>) -> Self {
57        assert_feature! {
58            #[cfg(feature = "docker_runtime")]
59            Self::Docker {
60                container_name: _container_name.into(),
61            }
62        }
63    }
64
65    pub fn get_container_name(&self) -> &str {
66        assert_feature! {
67            #[cfg(feature = "docker_runtime")]
68            #[expect(clippy::allow_attributes, reason = "Depends on features.")]
69            #[allow(
70                irrefutable_let_patterns,
71                reason = "Depends on features."
72            )]
73            let TaglessMemberId::Docker { container_name } = self else {
74                panic!("Not `Docker` variant.");
75            }
76            container_name
77        }
78    }
79
80    pub fn from_maelstrom_node_id(_node_id: impl Into<String>) -> Self {
81        assert_feature! {
82                #[cfg(feature = "maelstrom_runtime")]
83                Self::Maelstrom {
84                node_id: _node_id.into(),
85            }
86        }
87    }
88
89    pub fn get_maelstrom_node_id(&self) -> &str {
90        assert_feature! {
91            #[cfg(feature = "maelstrom_runtime")]
92            #[expect(clippy::allow_attributes, reason = "Depends on features.")]
93            #[allow(
94                irrefutable_let_patterns,
95                reason = "Depends on features."
96            )]
97            let TaglessMemberId::Maelstrom { node_id } = self else {
98                panic!("Not `Maelstrom` variant.");
99            }
100            node_id
101        }
102    }
103}
104
105impl Display for TaglessMemberId {
106    fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        match self {
108            #[cfg(feature = "deploy_integration")]
109            TaglessMemberId::Legacy { raw_id } => write!(_f, "{:?}", raw_id),
110            #[cfg(feature = "docker_runtime")]
111            TaglessMemberId::Docker { container_name } => write!(_f, "{:?}", container_name),
112            #[cfg(feature = "maelstrom_runtime")]
113            TaglessMemberId::Maelstrom { node_id } => write!(_f, "{:?}", node_id),
114            #[expect(
115                clippy::allow_attributes,
116                reason = "Only triggers when `TaglessMemberId` is empty."
117            )]
118            #[allow(
119                unreachable_patterns,
120                reason = "Needed when `TaglessMemberId` is empty."
121            )]
122            _ => panic!(),
123        }
124    }
125}
126
127#[repr(transparent)]
128pub struct MemberId<Tag> {
129    inner: TaglessMemberId,
130    _phantom: PhantomData<Tag>,
131}
132
133impl<Tag> MemberId<Tag> {
134    pub fn into_tagless(self) -> TaglessMemberId {
135        self.inner
136    }
137
138    pub fn from_tagless(inner: TaglessMemberId) -> Self {
139        Self {
140            inner,
141            _phantom: Default::default(),
142        }
143    }
144
145    pub fn from_raw_id(raw_id: u32) -> Self {
146        #[expect(clippy::allow_attributes, reason = "Depends on features.")]
147        #[allow(
148            unreachable_code,
149            reason = "`inner` may be uninhabited depending on features."
150        )]
151        Self {
152            inner: TaglessMemberId::from_raw_id(raw_id),
153            _phantom: Default::default(),
154        }
155    }
156
157    pub fn get_raw_id(&self) -> u32 {
158        self.inner.get_raw_id()
159    }
160}
161
162impl<Tag> Debug for MemberId<Tag> {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        Display::fmt(self, f)
165    }
166}
167
168impl<Tag> Display for MemberId<Tag> {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        write!(
171            f,
172            "MemberId::<{}>({})",
173            std::any::type_name::<Tag>(),
174            self.inner
175        )
176    }
177}
178
179impl<Tag> Clone for MemberId<Tag> {
180    fn clone(&self) -> Self {
181        #[expect(clippy::allow_attributes, reason = "Depends on features.")]
182        #[allow(
183            unreachable_code,
184            reason = "`inner` may be uninhabited depending on features."
185        )]
186        Self {
187            inner: self.inner.clone(),
188            _phantom: Default::default(),
189        }
190    }
191}
192
193impl<Tag> Serialize for MemberId<Tag> {
194    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
195    where
196        S: serde::Serializer,
197    {
198        self.inner.serialize(serializer)
199    }
200}
201
202impl<'a, Tag> Deserialize<'a> for MemberId<Tag> {
203    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
204    where
205        D: serde::Deserializer<'a>,
206    {
207        #[expect(clippy::allow_attributes, reason = "Depends on features.")]
208        #[allow(
209            unreachable_code,
210            reason = "`inner` may be uninhabited depending on features."
211        )]
212        Ok(Self::from_tagless(TaglessMemberId::deserialize(
213            deserializer,
214        )?))
215    }
216}
217
218impl<Tag> PartialOrd for MemberId<Tag> {
219    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
220        Some(self.cmp(other))
221    }
222}
223
224impl<Tag> Ord for MemberId<Tag> {
225    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
226        self.inner.cmp(&other.inner)
227    }
228}
229
230impl<Tag> PartialEq for MemberId<Tag> {
231    fn eq(&self, other: &Self) -> bool {
232        self.inner == other.inner
233    }
234}
235
236impl<Tag> Eq for MemberId<Tag> {}
237
238impl<Tag> Hash for MemberId<Tag> {
239    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
240        self.inner.hash(state);
241        // This seems like the a good thing to do. This will ensure that two member ids that come from different
242        // clusters but the same underlying host receive different hashes.
243        std::any::type_name::<Tag>().hash(state);
244    }
245}