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 default_id: Option<&'static str>,
19}
20
21impl std::fmt::Debug for BackendRegistry {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 f.debug_struct("BackendRegistry")
24 .field("backend_ids", &self.backends.keys().collect::<Vec<_>>())
25 .field("default_id", &self.default_id)
26 .finish()
27 }
28}
29
30impl BackendRegistry {
31 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub fn register(&mut self, backend: Arc<dyn AgentBackend>) {
36 let id = backend.id();
37 tracing::debug!(id, "registering backend");
38 if self.default_id.is_none() {
41 self.default_id = Some(id);
42 }
43 self.backends.insert(id, backend);
44 }
45
46 pub fn with(mut self, backend: Arc<dyn AgentBackend>) -> Self {
49 self.register(backend);
50 self
51 }
52
53 pub fn with_default(mut self, backend: Arc<dyn AgentBackend>) -> Self {
56 let id = backend.id();
57 self.backends.insert(id, backend);
58 self.default_id = Some(id);
59 self
60 }
61
62 pub fn set_default(&mut self, id: &str) -> Result<(), SchedulerError> {
64 match self.backends.get_key_value(id) {
65 Some((key, _)) => {
66 self.default_id = Some(*key);
67 Ok(())
68 }
69 None => Err(SchedulerError::UnknownBackend(id.to_owned())),
70 }
71 }
72
73 pub fn get(&self, id: &str) -> Result<Arc<dyn AgentBackend>, SchedulerError> {
74 self.backends.get(id).cloned().ok_or_else(|| {
75 tracing::error!(id, "backend not found in registry");
76 SchedulerError::UnknownBackend(id.to_owned())
77 })
78 }
79
80 pub fn default_backend(&self) -> Result<Arc<dyn AgentBackend>, SchedulerError> {
85 match self.default_id {
86 Some(id) => self.backends.get(id).cloned().ok_or_else(|| {
87 tracing::error!(default_id = id, "default backend not registered");
88 SchedulerError::NoBackendRegistered
89 }),
90 None => Err(SchedulerError::NoBackendRegistered),
91 }
92 }
93
94 pub fn default_id(&self) -> Option<&'static str> {
96 self.default_id
97 }
98
99 pub fn available_ids(&self) -> Vec<&'static str> {
101 let mut ids: Vec<&'static str> = self.backends.keys().copied().collect();
102 ids.sort_unstable();
103 ids
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use crate::contract::*;
111 use async_trait::async_trait;
112
113 struct TestBackend {
115 id: &'static str,
116 }
117
118 #[async_trait]
119 impl AgentBackend for TestBackend {
120 fn id(&self) -> &'static str {
121 self.id
122 }
123 fn capabilities(&self) -> AgentCapabilities {
124 AgentCapabilities::default()
125 }
126 fn as_any(&self) -> &dyn std::any::Any {
127 self
128 }
129 async fn run(
130 &self,
131 _task: AgentTask,
132 _ctx: RunContext,
133 ) -> Result<AgentResult, BackendError> {
134 unimplemented!("registry tests never invoke run()")
135 }
136 }
137
138 fn make_backend(id: &'static str) -> Arc<dyn AgentBackend> {
139 Arc::new(TestBackend { id })
140 }
141
142 #[test]
145 fn test_new_is_empty() {
146 let reg = BackendRegistry::new();
147 assert!(matches!(
148 reg.default_backend(),
149 Err(SchedulerError::NoBackendRegistered)
150 ));
151 }
152
153 #[test]
154 fn test_default_is_empty() {
155 let reg = BackendRegistry::default();
156 assert!(matches!(
157 reg.default_backend(),
158 Err(SchedulerError::NoBackendRegistered)
159 ));
160 }
161
162 #[test]
165 fn test_register_adds_backend() {
166 let mut reg = BackendRegistry::new();
167 reg.register(make_backend("alpha"));
168 assert_eq!(reg.get("alpha").unwrap().id(), "alpha");
169 }
170
171 #[test]
172 fn test_with_builder_pattern() {
173 let reg = BackendRegistry::new()
174 .with(make_backend("a"))
175 .with(make_backend("b"))
176 .with(make_backend("c"));
177 assert_eq!(reg.get("a").unwrap().id(), "a");
178 assert_eq!(reg.get("b").unwrap().id(), "b");
179 assert_eq!(reg.get("c").unwrap().id(), "c");
180 }
181
182 #[test]
183 fn test_register_overwrites_existing_id() {
184 let mut reg = BackendRegistry::new();
185 reg.register(make_backend("dup"));
186 reg.register(make_backend("dup"));
187 assert_eq!(reg.get("dup").unwrap().id(), "dup");
188 assert!(reg.get("other").is_err());
190 }
191
192 #[test]
195 fn test_get_unknown_backend() {
196 let reg = BackendRegistry::new();
197 assert!(matches!(
198 reg.get("nonexistent"),
199 Err(SchedulerError::UnknownBackend(_))
200 ));
201 }
202
203 #[test]
204 fn test_get_after_register() {
205 let mut reg = BackendRegistry::new();
206 reg.register(make_backend("x"));
207 assert_eq!(reg.get("x").unwrap().id(), "x");
208 }
209
210 #[test]
211 fn test_get_multiple_backends() {
212 let mut reg = BackendRegistry::new();
213 reg.register(make_backend("first"));
214 reg.register(make_backend("second"));
215 reg.register(make_backend("third"));
216 assert_eq!(reg.get("first").unwrap().id(), "first");
217 assert_eq!(reg.get("second").unwrap().id(), "second");
218 assert_eq!(reg.get("third").unwrap().id(), "third");
219 assert!(reg.get("fourth").is_err());
220 }
221
222 #[test]
225 fn test_default_backend_empty() {
226 let reg = BackendRegistry::new();
227 assert!(matches!(
228 reg.default_backend(),
229 Err(SchedulerError::NoBackendRegistered)
230 ));
231 }
232
233 #[test]
234 fn test_default_backend_returns_one_of_registered() {
235 let mut reg = BackendRegistry::new();
236 reg.register(make_backend("alpha"));
237 reg.register(make_backend("beta"));
238 assert_eq!(reg.default_backend().unwrap().id(), "alpha");
240 }
241
242 #[test]
243 fn test_default_backend_single_entry() {
244 let mut reg = BackendRegistry::new();
245 reg.register(make_backend("sole"));
246 assert_eq!(reg.default_backend().unwrap().id(), "sole");
247 }
248
249 #[test]
250 fn test_with_default_overrides_first_registered() {
251 let reg = BackendRegistry::new()
252 .with(make_backend("a"))
253 .with_default(make_backend("b"))
254 .with(make_backend("c"));
255 assert_eq!(reg.default_backend().unwrap().id(), "b");
256 assert_eq!(reg.default_id(), Some("b"));
257 }
258
259 #[test]
260 fn test_set_default_marks_existing_backend() {
261 let mut reg = BackendRegistry::new();
262 reg.register(make_backend("a"));
263 reg.register(make_backend("b"));
264 assert_eq!(reg.default_id(), Some("a"));
265 reg.set_default("b").unwrap();
266 assert_eq!(reg.default_backend().unwrap().id(), "b");
267 }
268
269 #[test]
270 fn test_set_default_rejects_unknown_id() {
271 let mut reg = BackendRegistry::new();
272 reg.register(make_backend("a"));
273 assert!(matches!(
274 reg.set_default("nope"),
275 Err(SchedulerError::UnknownBackend(_))
276 ));
277 }
278
279 #[test]
282 fn test_clone_empty() {
283 let reg = BackendRegistry::new();
284 let cloned = reg.clone();
285 assert!(matches!(
286 cloned.default_backend(),
287 Err(SchedulerError::NoBackendRegistered)
288 ));
289 }
290
291 #[test]
292 fn test_clone_is_independent() {
293 let mut reg = BackendRegistry::new();
294 reg.register(make_backend("original"));
295 let mut cloned = reg.clone();
296 cloned.register(make_backend("new-only"));
297 assert!(reg.get("new-only").is_err());
299 assert_eq!(cloned.get("new-only").unwrap().id(), "new-only");
300 assert_eq!(reg.get("original").unwrap().id(), "original");
302 assert_eq!(cloned.get("original").unwrap().id(), "original");
303 }
304
305 #[test]
308 fn test_debug_empty() {
309 let reg = BackendRegistry::new();
310 let s = format!("{:?}", reg);
311 assert!(s.starts_with("BackendRegistry"));
312 }
313
314 #[test]
315 fn test_debug_with_backends() {
316 let mut reg = BackendRegistry::new();
317 reg.register(make_backend("dbg-a"));
318 reg.register(make_backend("dbg-b"));
319 let s = format!("{:?}", reg);
320 assert!(s.starts_with("BackendRegistry"));
321 assert!(s.contains("dbg-a") || s.contains("dbg-b"));
322 }
323}