Skip to main content

rust_dix/
key.rs

1//! Service keys for keyed registrations.
2//!
3//! A key is either a **typed value** (`Eq + Hash + Debug`, any type) or the
4//! special [`ServiceKey::any_key`] sentinel used for "fallback for any key"
5//! registrations (MEDI `KeyedService.AnyKey` parity).
6//!
7//! String keys used by `keyed_singleton("name", …)` are just typed keys whose
8//! value is a `String`, so existing string-keyed code keeps working while
9//! typed keys enable MEDI-style keyed services (enums, integers, …).
10
11use std::any::Any;
12use std::fmt;
13use std::hash::{DefaultHasher, Hash, Hasher};
14use std::sync::Arc;
15
16/// Erased key value. Implemented for every `T: Any + Send + Sync + Eq + Hash + Debug`.
17#[doc(hidden)]
18pub trait ErasedKeyValue: Any + Send + Sync + fmt::Debug {
19    fn as_any(&self) -> &dyn Any;
20    fn key_eq(&self, other: &dyn Any) -> bool;
21    fn key_hash(&self) -> u64;
22}
23
24impl<T> ErasedKeyValue for T
25where
26    T: Any + Send + Sync + Eq + Hash + fmt::Debug,
27{
28    fn as_any(&self) -> &dyn Any {
29        self
30    }
31
32    fn key_eq(&self, other: &dyn Any) -> bool {
33        other.downcast_ref::<T>() == Some(self)
34    }
35
36    fn key_hash(&self) -> u64 {
37        let mut hasher = DefaultHasher::new();
38        self.hash(&mut hasher);
39        hasher.finish()
40    }
41}
42
43#[derive(Clone)]
44enum KeyRepr {
45    Value(Arc<dyn ErasedKeyValue>),
46    Any,
47}
48
49/// Key of a keyed service registration / resolution.
50///
51/// Construct a value key with [`ServiceKey::new`] (or `From<&str>`/`From<String>`
52/// for string keys), and the fallback sentinel with [`ServiceKey::any_key`].
53///
54/// # String keys
55///
56/// String-keyed registration APIs (`keyed_singleton("name", …)`) store the key
57/// as a `String`. Use `ServiceKey::from("name")` / `"name".into()` to build an
58/// equal key. `ServiceKey::new("name")` is a **different** key type (`&str`),
59/// because key equality includes the value type — use it only for intentional
60/// `&'static str`-typed keys.
61///
62/// # Reading a requested key
63///
64/// `AnyKey` fallback factories receive `&ServiceKey`. The erased key exposes
65/// `Debug`/`Display` and equality/hash; there is intentionally **no** typed
66/// downcast accessor (operations must be expressible through the `ServiceKey`
67/// API itself, keeping the erased-key representation private).
68#[derive(Clone)]
69pub struct ServiceKey(KeyRepr);
70
71impl ServiceKey {
72    /// Create a typed key value.
73    ///
74    /// ```
75    /// use rust_dix::ServiceKey;
76    ///
77    /// #[derive(Debug, PartialEq, Eq, Hash)]
78    /// enum Db { Read, Write }
79    ///
80    /// let key = ServiceKey::new(Db::Read);
81    /// assert_ne!(key, ServiceKey::new(Db::Write));
82    /// ```
83    pub fn new<T>(value: T) -> Self
84    where
85        T: Any + Send + Sync + Eq + Hash + fmt::Debug + 'static,
86    {
87        Self(KeyRepr::Value(Arc::new(value)))
88    }
89
90    /// The `AnyKey` fallback sentinel (MEDI `KeyedService.AnyKey`).
91    ///
92    /// Registering a service under this key makes it a fallback for **every**
93    /// key that has no exact registration. Resolving a single service *with*
94    /// this sentinel is not allowed (use [`ServiceKey`]-keyed lookups or
95    /// enumeration instead).
96    pub fn any_key() -> Self {
97        Self(KeyRepr::Any)
98    }
99
100    /// Whether this key is the [`ServiceKey::any_key`] sentinel.
101    pub fn is_any(&self) -> bool {
102        matches!(self.0, KeyRepr::Any)
103    }
104
105    /// Stable hash used for per-requested-key cycle markers.
106    pub(crate) fn stable_hash(&self) -> u64 {
107        let mut hasher = DefaultHasher::new();
108        self.hash(&mut hasher);
109        hasher.finish()
110    }
111}
112
113impl From<&str> for ServiceKey {
114    fn from(value: &str) -> Self {
115        Self::new(value.to_owned())
116    }
117}
118
119impl From<String> for ServiceKey {
120    fn from(value: String) -> Self {
121        Self::new(value)
122    }
123}
124
125impl From<&String> for ServiceKey {
126    fn from(value: &String) -> Self {
127        Self::new(value.clone())
128    }
129}
130
131impl From<&ServiceKey> for ServiceKey {
132    fn from(value: &ServiceKey) -> Self {
133        value.clone()
134    }
135}
136
137impl fmt::Debug for ServiceKey {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        match &self.0 {
140            KeyRepr::Value(v) => write!(f, "{v:?}"),
141            KeyRepr::Any => f.write_str("AnyKey"),
142        }
143    }
144}
145
146impl fmt::Display for ServiceKey {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        match &self.0 {
149            KeyRepr::Value(v) => {
150                // String keys display without quotes (nicer error messages);
151                // other typed keys fall back to their `Debug` representation.
152                if let Some(s) = v.as_any().downcast_ref::<String>() {
153                    f.write_str(s)
154                } else {
155                    write!(f, "{v:?}")
156                }
157            }
158            KeyRepr::Any => f.write_str("AnyKey"),
159        }
160    }
161}
162
163impl PartialEq for ServiceKey {
164    fn eq(&self, other: &Self) -> bool {
165        match (&self.0, &other.0) {
166            (KeyRepr::Any, KeyRepr::Any) => true,
167            (KeyRepr::Value(a), KeyRepr::Value(b)) => {
168                a.as_any().type_id() == b.as_any().type_id() && a.key_eq(b.as_any())
169            }
170            _ => false,
171        }
172    }
173}
174
175impl Eq for ServiceKey {}
176
177impl Hash for ServiceKey {
178    fn hash<H: Hasher>(&self, state: &mut H) {
179        match &self.0 {
180            KeyRepr::Any => state.write_u8(0),
181            KeyRepr::Value(v) => {
182                state.write_u8(1);
183                v.as_any().type_id().hash(state);
184                v.key_hash().hash(state);
185            }
186        }
187    }
188}
189
190/// MEDI-inspired namespace for the `AnyKey` sentinel.
191///
192/// `KeyedService::any_key()` equals `ServiceKey::any_key()`.
193#[derive(Debug, Clone, Copy, Default)]
194pub struct KeyedService;
195
196impl KeyedService {
197    /// The `AnyKey` fallback sentinel (MEDI `KeyedService.AnyKey`).
198    pub fn any_key() -> ServiceKey {
199        ServiceKey::any_key()
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206    use std::collections::HashSet;
207
208    #[derive(Debug, PartialEq, Eq, Hash)]
209    enum Db {
210        Read,
211        Write,
212    }
213
214    #[test]
215    fn typed_keys_compare_and_hash() {
216        let mut set = HashSet::new();
217        set.insert(ServiceKey::new(Db::Read));
218        set.insert(ServiceKey::new(Db::Write));
219        set.insert(ServiceKey::new(Db::Read));
220        assert_eq!(set.len(), 2);
221        assert_eq!(ServiceKey::new(Db::Read), ServiceKey::new(Db::Read));
222        assert_ne!(ServiceKey::new(Db::Read), ServiceKey::new(Db::Write));
223    }
224
225    #[test]
226    fn string_keys_are_typed_keys() {
227        assert_eq!(ServiceKey::from("a"), ServiceKey::from("a".to_string()));
228        assert_ne!(ServiceKey::from("a"), ServiceKey::from("b"));
229        // Different key types with the same debug/display must not collide.
230        assert_ne!(ServiceKey::new(1_u32), ServiceKey::new(1_u64));
231    }
232
233    #[test]
234    fn any_key_identity() {
235        assert!(ServiceKey::any_key().is_any());
236        assert_eq!(ServiceKey::any_key(), KeyedService::any_key());
237        assert_ne!(ServiceKey::any_key(), ServiceKey::from("AnyKey"));
238        assert_ne!(ServiceKey::any_key(), ServiceKey::new("AnyKey"));
239    }
240
241    #[test]
242    fn debug_and_display_are_stable_enough_for_errors() {
243        assert_eq!(format!("{:?}", ServiceKey::any_key()), "AnyKey");
244        assert_eq!(format!("{}", ServiceKey::from("x")), "x");
245        assert_eq!(format!("{:?}", ServiceKey::from("x")), "\"x\"");
246    }
247
248    #[test]
249    fn string_literal_typed_key_is_a_distinct_type() {
250        // `ServiceKey::new("x")` is an `&'static str` key, not the `String` key
251        // produced by string-keyed registration APIs.
252        assert_ne!(ServiceKey::new("x"), ServiceKey::from("x"));
253    }
254}