luft_core/scheduler/
registry.rs1use super::error::SchedulerError;
5use crate::contract::backend::AgentBackend;
6use std::collections::HashMap;
7use std::sync::Arc;
8
9#[derive(Clone, Default)]
10pub struct BackendRegistry {
11 backends: HashMap<&'static str, Arc<dyn AgentBackend>>,
12}
13
14impl std::fmt::Debug for BackendRegistry {
15 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 f.debug_struct("BackendRegistry")
17 .field("backend_ids", &self.backends.keys().collect::<Vec<_>>())
18 .finish()
19 }
20}
21
22impl BackendRegistry {
23 pub fn new() -> Self {
24 Self::default()
25 }
26
27 pub fn register(&mut self, backend: Arc<dyn AgentBackend>) {
28 let id = backend.id();
29 tracing::debug!(id, "registering backend");
30 self.backends.insert(id, backend);
31 }
32
33 pub fn with(mut self, backend: Arc<dyn AgentBackend>) -> Self {
35 self.register(backend);
36 self
37 }
38
39 pub fn get(&self, id: &str) -> Result<Arc<dyn AgentBackend>, SchedulerError> {
40 self.backends.get(id).cloned().ok_or_else(|| {
41 tracing::error!(id, "backend not found in registry");
42 SchedulerError::UnknownBackend(id.to_owned())
43 })
44 }
45
46 pub fn default_backend(&self) -> Result<Arc<dyn AgentBackend>, SchedulerError> {
48 self.backends.values().next().cloned().ok_or_else(|| {
49 tracing::error!("no backend registered");
50 SchedulerError::NoBackendRegistered
51 })
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use crate::contract::*;
59 use async_trait::async_trait;
60
61 struct TestBackend {
63 id: &'static str,
64 }
65
66 #[async_trait]
67 impl AgentBackend for TestBackend {
68 fn id(&self) -> &'static str {
69 self.id
70 }
71 fn capabilities(&self) -> AgentCapabilities {
72 AgentCapabilities::default()
73 }
74 fn as_any(&self) -> &dyn std::any::Any {
75 self
76 }
77 async fn run(
78 &self,
79 _task: AgentTask,
80 _ctx: RunContext,
81 ) -> Result<AgentResult, BackendError> {
82 unimplemented!("registry tests never invoke run()")
83 }
84 }
85
86 fn make_backend(id: &'static str) -> Arc<dyn AgentBackend> {
87 Arc::new(TestBackend { id })
88 }
89
90 #[test]
93 fn test_new_is_empty() {
94 let reg = BackendRegistry::new();
95 assert!(matches!(
96 reg.default_backend(),
97 Err(SchedulerError::NoBackendRegistered)
98 ));
99 }
100
101 #[test]
102 fn test_default_is_empty() {
103 let reg = BackendRegistry::default();
104 assert!(matches!(
105 reg.default_backend(),
106 Err(SchedulerError::NoBackendRegistered)
107 ));
108 }
109
110 #[test]
113 fn test_register_adds_backend() {
114 let mut reg = BackendRegistry::new();
115 reg.register(make_backend("alpha"));
116 assert_eq!(reg.get("alpha").unwrap().id(), "alpha");
117 }
118
119 #[test]
120 fn test_with_builder_pattern() {
121 let reg = BackendRegistry::new()
122 .with(make_backend("a"))
123 .with(make_backend("b"))
124 .with(make_backend("c"));
125 assert_eq!(reg.get("a").unwrap().id(), "a");
126 assert_eq!(reg.get("b").unwrap().id(), "b");
127 assert_eq!(reg.get("c").unwrap().id(), "c");
128 }
129
130 #[test]
131 fn test_register_overwrites_existing_id() {
132 let mut reg = BackendRegistry::new();
133 reg.register(make_backend("dup"));
134 reg.register(make_backend("dup"));
135 assert_eq!(reg.get("dup").unwrap().id(), "dup");
136 assert!(reg.get("other").is_err());
138 }
139
140 #[test]
143 fn test_get_unknown_backend() {
144 let reg = BackendRegistry::new();
145 assert!(matches!(
146 reg.get("nonexistent"),
147 Err(SchedulerError::UnknownBackend(_))
148 ));
149 }
150
151 #[test]
152 fn test_get_after_register() {
153 let mut reg = BackendRegistry::new();
154 reg.register(make_backend("x"));
155 assert_eq!(reg.get("x").unwrap().id(), "x");
156 }
157
158 #[test]
159 fn test_get_multiple_backends() {
160 let mut reg = BackendRegistry::new();
161 reg.register(make_backend("first"));
162 reg.register(make_backend("second"));
163 reg.register(make_backend("third"));
164 assert_eq!(reg.get("first").unwrap().id(), "first");
165 assert_eq!(reg.get("second").unwrap().id(), "second");
166 assert_eq!(reg.get("third").unwrap().id(), "third");
167 assert!(reg.get("fourth").is_err());
168 }
169
170 #[test]
173 fn test_default_backend_empty() {
174 let reg = BackendRegistry::new();
175 assert!(matches!(
176 reg.default_backend(),
177 Err(SchedulerError::NoBackendRegistered)
178 ));
179 }
180
181 #[test]
182 fn test_default_backend_returns_one_of_registered() {
183 let mut reg = BackendRegistry::new();
184 reg.register(make_backend("alpha"));
185 reg.register(make_backend("beta"));
186 let id = reg.default_backend().unwrap().id();
187 assert!(id == "alpha" || id == "beta", "unexpected id: {id}");
189 }
190
191 #[test]
192 fn test_default_backend_single_entry() {
193 let mut reg = BackendRegistry::new();
194 reg.register(make_backend("sole"));
195 assert_eq!(reg.default_backend().unwrap().id(), "sole");
196 }
197
198 #[test]
201 fn test_clone_empty() {
202 let reg = BackendRegistry::new();
203 let cloned = reg.clone();
204 assert!(matches!(
205 cloned.default_backend(),
206 Err(SchedulerError::NoBackendRegistered)
207 ));
208 }
209
210 #[test]
211 fn test_clone_is_independent() {
212 let mut reg = BackendRegistry::new();
213 reg.register(make_backend("original"));
214 let mut cloned = reg.clone();
215 cloned.register(make_backend("new-only"));
216 assert!(reg.get("new-only").is_err());
218 assert_eq!(cloned.get("new-only").unwrap().id(), "new-only");
219 assert_eq!(reg.get("original").unwrap().id(), "original");
221 assert_eq!(cloned.get("original").unwrap().id(), "original");
222 }
223
224 #[test]
227 fn test_debug_empty() {
228 let reg = BackendRegistry::new();
229 let s = format!("{:?}", reg);
230 assert!(s.starts_with("BackendRegistry"));
231 }
232
233 #[test]
234 fn test_debug_with_backends() {
235 let mut reg = BackendRegistry::new();
236 reg.register(make_backend("dbg-a"));
237 reg.register(make_backend("dbg-b"));
238 let s = format!("{:?}", reg);
239 assert!(s.starts_with("BackendRegistry"));
240 assert!(s.contains("dbg-a") || s.contains("dbg-b"));
241 }
242}