1use std::collections::HashMap;
4use std::sync::{Arc, OnceLock, RwLock};
5
6use crate::error::Result;
7use crate::query::EventAggregateResult;
8use crate::storage::{
9 EventsAggregateFilter, EventsQueryFilter, MetricsQueryRange, NoOpEventBackend,
10 NoOpMetricsBackend, SharedEventBackend, SharedMetricsBackend,
11};
12
13static GLOBAL_ROUTER: OnceLock<Arc<SpectraRouter>> = OnceLock::new();
14
15pub struct SpectraRouter {
43 events: RwLock<HashMap<String, SharedEventBackend>>,
44 metrics: RwLock<HashMap<String, SharedMetricsBackend>>,
45 default_events: SharedEventBackend,
46 default_metrics: SharedMetricsBackend,
47}
48
49impl Default for SpectraRouter {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55impl SpectraRouter {
56 pub fn new() -> Self {
58 Self {
59 events: RwLock::new(HashMap::new()),
60 metrics: RwLock::new(HashMap::new()),
61 default_events: Arc::new(NoOpEventBackend),
62 default_metrics: Arc::new(NoOpMetricsBackend),
63 }
64 }
65
66 pub fn with_defaults(
68 default_metrics: SharedMetricsBackend,
69 default_events: SharedEventBackend,
70 ) -> Self {
71 Self {
72 events: RwLock::new(HashMap::new()),
73 metrics: RwLock::new(HashMap::new()),
74 default_events,
75 default_metrics,
76 }
77 }
78
79 pub fn register_event_backend(&self, table: impl Into<String>, backend: SharedEventBackend) {
81 if let Ok(mut guard) = self.events.write() {
82 guard.insert(table.into(), backend);
83 }
84 }
85
86 pub fn register_metrics_backend(&self, name: impl Into<String>, backend: SharedMetricsBackend) {
88 if let Ok(mut guard) = self.metrics.write() {
89 guard.insert(name.into(), backend);
90 }
91 }
92
93 pub fn resolve_event(&self, table: &str) -> SharedEventBackend {
95 self.events
96 .read()
97 .ok()
98 .and_then(|g| g.get(table).cloned())
99 .unwrap_or_else(|| Arc::clone(&self.default_events))
100 }
101
102 pub fn resolve_metrics(&self, name: &str) -> SharedMetricsBackend {
104 self.metrics
105 .read()
106 .ok()
107 .and_then(|g| g.get(name).cloned())
108 .unwrap_or_else(|| Arc::clone(&self.default_metrics))
109 }
110
111 pub async fn query_events(
113 &self,
114 filter: EventsQueryFilter,
115 ) -> Result<Vec<crate::storage::EventRow>> {
116 let backend = self.resolve_event(&filter.table);
117 backend.query_rows(filter).await
118 }
119
120 pub async fn query_metrics(
122 &self,
123 query: MetricsQueryRange,
124 ) -> Result<Vec<crate::storage::MetricPoint>> {
125 let backend = self.resolve_metrics(&query.metric_name);
126 backend.query_range(query).await
127 }
128
129 pub async fn query_event_aggregate(
131 &self,
132 filter: EventsAggregateFilter,
133 ) -> Result<EventAggregateResult> {
134 let table = filter.table.clone();
135 let backend = self.resolve_event(&table);
136 backend.query_aggregate(filter).await
137 }
138
139 pub fn set_global(router: Arc<Self>) {
141 let _ = GLOBAL_ROUTER.set(router);
142 }
143
144 pub fn global() -> Arc<SpectraRouter> {
146 GLOBAL_ROUTER
147 .get()
148 .cloned()
149 .expect("SpectraRouter::set_global was not called")
150 }
151
152 pub fn try_global() -> Option<Arc<SpectraRouter>> {
154 GLOBAL_ROUTER.get().cloned()
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::storage::{EventStorageBackend, EventsQueryFilter};
162 use async_trait::async_trait;
163 use chrono::Utc;
164 use serde_json::json;
165 use std::sync::atomic::{AtomicU32, Ordering};
166
167 struct CountingEventBackend {
168 appends: AtomicU32,
169 }
170
171 #[async_trait]
172 impl EventStorageBackend for CountingEventBackend {
173 fn engine_type(&self) -> crate::storage::StorageEngineType {
174 crate::storage::StorageEngineType::NoOp
175 }
176
177 async fn append_row(
178 &self,
179 _: &str,
180 _: &serde_json::Value,
181 _: chrono::DateTime<Utc>,
182 _: Option<&str>,
183 ) -> crate::error::Result<()> {
184 self.appends.fetch_add(1, Ordering::SeqCst);
185 Ok(())
186 }
187 }
188
189 #[test]
190 fn router_noop_default() {
191 let router = SpectraRouter::new();
192 let rt = tokio::runtime::Runtime::new().expect("runtime");
193 let rows = rt
194 .block_on(router.query_events(EventsQueryFilter {
195 table: "missing".into(),
196 ..Default::default()
197 }))
198 .expect("query");
199 assert!(rows.is_empty());
200 }
201
202 #[test]
203 fn router_resolve_event_backend() {
204 let router = SpectraRouter::new();
205 let counting = Arc::new(CountingEventBackend {
206 appends: AtomicU32::new(0),
207 });
208 let backend: SharedEventBackend = Arc::clone(&counting) as SharedEventBackend;
209 router.register_event_backend("t1", backend);
210 let resolved = router.resolve_event("t1");
211 let rt = tokio::runtime::Runtime::new().expect("runtime");
212 rt.block_on(async {
213 resolved
214 .append_row("t1", &json!({}), Utc::now(), None)
215 .await
216 .expect("append");
217 });
218 assert_eq!(counting.appends.load(Ordering::SeqCst), 1);
219 }
220}