clawft_types/registry.rs
1//! Unified registry trait for key-value registries across WeftOS.
2//!
3//! Many subsystems maintain registries that map a string key to some
4//! value (services, tools, pipes, workspaces, etc.). This module
5//! provides a common read-side trait so GUI introspection, health
6//! checks, and cross-crate tooling can enumerate registry contents
7//! without depending on each concrete type.
8//!
9//! # Design notes
10//!
11//! The write side (`register`, `unregister`) is intentionally excluded
12//! from the trait because the concrete registries diverge significantly:
13//!
14//! - Some derive the key from the value (`service.name()`).
15//! - Some auto-generate the key (`ProcessTable::allocate_pid`).
16//! - Some require `&mut self`, others use interior mutability (`DashMap`).
17//!
18//! The read side, however, is consistent: look up by key, list keys,
19//! check membership, count entries.
20
21/// Read-side interface shared by all key-value registries.
22///
23/// Implementors expose a uniform way to inspect registry contents
24/// without coupling callers to concrete storage (DashMap, HashMap,
25/// Vec, etc.).
26///
27/// The `Value` associated type is the *returned* value, which may
28/// differ from the stored value (e.g., `Arc<dyn Trait>` vs `&T`).
29///
30/// # Examples
31///
32/// ```ignore
33/// fn dump_registry(reg: &dyn Registry<Value = String>) {
34/// for key in reg.list_keys() {
35/// if let Some(val) = reg.get(&key) {
36/// println!("{key}: {val}");
37/// }
38/// }
39/// }
40/// ```
41pub trait Registry {
42 /// The value returned by [`get`](Registry::get).
43 ///
44 /// Use an owned type (`Arc<T>`, `T: Clone`) when the registry
45 /// uses interior mutability and cannot hand out references.
46 type Value;
47
48 /// Look up a value by its string key.
49 fn get(&self, key: &str) -> Option<Self::Value>;
50
51 /// Return all keys currently in the registry.
52 ///
53 /// The order is unspecified unless a concrete implementation
54 /// documents otherwise.
55 fn list_keys(&self) -> Vec<String>;
56
57 /// Check whether `key` is present.
58 ///
59 /// Default implementation delegates to [`get`](Registry::get),
60 /// but concrete types should override when a cheaper check exists.
61 fn contains(&self, key: &str) -> bool {
62 self.get(key).is_some()
63 }
64
65 /// Number of entries in the registry.
66 fn count(&self) -> usize {
67 self.list_keys().len()
68 }
69
70 /// Whether the registry is empty.
71 fn is_empty(&self) -> bool {
72 self.count() == 0
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use std::collections::BTreeMap;
80
81 /// Minimal concrete registry for testing the trait.
82 struct SimpleRegistry {
83 map: BTreeMap<String, String>,
84 }
85
86 impl Registry for SimpleRegistry {
87 type Value = String;
88
89 fn get(&self, key: &str) -> Option<String> {
90 self.map.get(key).cloned()
91 }
92
93 fn list_keys(&self) -> Vec<String> {
94 self.map.keys().cloned().collect()
95 }
96
97 fn count(&self) -> usize {
98 self.map.len()
99 }
100 }
101
102 #[test]
103 fn empty_registry() {
104 let reg = SimpleRegistry {
105 map: BTreeMap::new(),
106 };
107 assert!(reg.is_empty());
108 assert_eq!(reg.count(), 0);
109 assert!(reg.list_keys().is_empty());
110 assert!(!reg.contains("anything"));
111 assert!(reg.get("anything").is_none());
112 }
113
114 #[test]
115 fn basic_operations() {
116 let mut map = BTreeMap::new();
117 map.insert("alpha".into(), "one".into());
118 map.insert("beta".into(), "two".into());
119
120 let reg = SimpleRegistry { map };
121
122 assert!(!reg.is_empty());
123 assert_eq!(reg.count(), 2);
124 assert!(reg.contains("alpha"));
125 assert!(!reg.contains("gamma"));
126 assert_eq!(reg.get("beta"), Some("two".into()));
127 assert_eq!(reg.list_keys(), vec!["alpha", "beta"]);
128 }
129
130 #[test]
131 fn trait_object_works() {
132 let mut map = BTreeMap::new();
133 map.insert("key".into(), "val".into());
134 let reg = SimpleRegistry { map };
135
136 // Ensure it can be used as a trait object.
137 let dyn_reg: &dyn Registry<Value = String> = ®
138 assert_eq!(dyn_reg.count(), 1);
139 assert_eq!(dyn_reg.get("key"), Some("val".into()));
140 }
141}