omni_dev/daemon/
registry.rs1use std::sync::Arc;
4
5use anyhow::{anyhow, Result};
6use serde_json::Value;
7
8use crate::github_rate_limit::{RateLimitCache, RateLimitSnapshot};
9
10use super::service::{DaemonService, ServiceStatus, ServiceStream};
11
12#[derive(Clone, Default)]
15pub struct ServiceRegistry {
16 services: Vec<Arc<dyn DaemonService>>,
17 github_rate_limit: Option<Arc<RateLimitCache>>,
23}
24
25impl ServiceRegistry {
26 pub fn new() -> Self {
28 Self::default()
29 }
30
31 pub fn register(&mut self, service: Arc<dyn DaemonService>) {
36 let name = service.name();
37 if self.services.iter().any(|s| s.name() == name) {
38 tracing::warn!("ignoring duplicate registration of daemon service `{name}`");
39 return;
40 }
41 self.services.push(service);
42 }
43
44 pub fn get(&self, name: &str) -> Option<&Arc<dyn DaemonService>> {
46 self.services.iter().find(|s| s.name() == name)
47 }
48
49 pub fn services(&self) -> &[Arc<dyn DaemonService>] {
51 &self.services
52 }
53
54 pub fn set_github_rate_limit(&mut self, cache: Arc<RateLimitCache>) {
58 self.github_rate_limit = Some(cache);
59 }
60
61 #[must_use]
64 pub fn github_rate_limit(&self) -> Option<RateLimitSnapshot> {
65 self.github_rate_limit.as_ref().and_then(|c| c.get())
66 }
67
68 pub async fn dispatch(&self, service: &str, op: &str, payload: Value) -> Result<Value> {
71 let svc = self
72 .get(service)
73 .ok_or_else(|| anyhow!("unknown service: {service}"))?;
74 svc.handle(op, payload).await
75 }
76
77 pub fn subscribe(
82 &self,
83 service: &str,
84 op: &str,
85 payload: &Value,
86 ) -> Option<Box<dyn ServiceStream>> {
87 self.get(service)?.subscribe(op, payload)
88 }
89
90 pub async fn statuses(&self) -> Vec<ServiceStatus> {
92 let mut out = Vec::with_capacity(self.services.len());
93 for svc in &self.services {
94 out.push(svc.status().await);
95 }
96 out
97 }
98
99 pub async fn shutdown_all(&self) {
101 for svc in &self.services {
102 svc.shutdown().await;
103 }
104 }
105}
106
107#[cfg(test)]
108#[allow(clippy::unwrap_used, clippy::expect_used)]
109mod tests {
110 use super::*;
111 use crate::daemon::services::echo::EchoService;
112 use serde_json::json;
113
114 #[tokio::test]
115 async fn routes_known_service_and_rejects_unknown() {
116 let mut registry = ServiceRegistry::new();
117 assert!(registry.services().is_empty());
118 registry.register(Arc::new(EchoService));
119
120 assert!(registry.get("echo").is_some());
121 assert!(registry.get("missing").is_none());
122
123 assert_eq!(
125 registry
126 .dispatch("echo", "echo", json!({ "x": 1 }))
127 .await
128 .unwrap(),
129 json!({ "x": 1 })
130 );
131 let err = registry
132 .dispatch("missing", "echo", Value::Null)
133 .await
134 .unwrap_err();
135 assert!(err.to_string().contains("unknown service"));
136
137 assert_eq!(registry.statuses().await.len(), 1);
139 registry.shutdown_all().await;
140 }
141
142 #[test]
143 fn duplicate_registration_is_ignored() {
144 let mut registry = ServiceRegistry::new();
145 registry.register(Arc::new(EchoService));
146 registry.register(Arc::new(EchoService));
147 assert_eq!(registry.services().len(), 1);
150 }
151}