hypercall/readiness/
mod.rs1use std::sync::atomic::{AtomicBool, Ordering};
29use std::sync::Arc;
30
31use serde::Serialize;
32
33use crate::snapshot::SyncStatus;
34use crate::vol_oracle::SharedVolOracle;
35
36#[derive(Debug, Clone, Serialize)]
38pub struct ReadinessReport {
39 pub name: String,
41 pub ready: bool,
43 pub detail: Option<String>,
45}
46
47pub trait Readiness: Send + Sync {
51 fn report(&self) -> ReadinessReport;
53}
54
55pub struct SyncStatusReadiness {
59 name: &'static str,
60 sync: Arc<SyncStatus>,
61}
62
63impl SyncStatusReadiness {
64 pub fn new(name: &'static str, sync: Arc<SyncStatus>) -> Self {
70 Self { name, sync }
71 }
72}
73
74impl Readiness for SyncStatusReadiness {
75 fn report(&self) -> ReadinessReport {
76 let state = self.sync.state();
77 ReadinessReport {
78 name: self.name.to_string(),
79 ready: self.sync.is_ready(),
80 detail: Some(state.to_string()),
81 }
82 }
83}
84
85pub struct VolOracleReadiness {
87 name: &'static str,
88 oracle: SharedVolOracle,
89 latched_ready: AtomicBool,
90}
91
92impl VolOracleReadiness {
93 pub fn new(name: &'static str, oracle: SharedVolOracle) -> Self {
94 Self {
95 name,
96 oracle,
97 latched_ready: AtomicBool::new(false),
98 }
99 }
100}
101
102impl Readiness for VolOracleReadiness {
103 fn report(&self) -> ReadinessReport {
104 let statuses = self.oracle.statuses();
105 let current_ready = !statuses.is_empty() && statuses.iter().all(|status| status.ready);
106 if current_ready {
107 self.latched_ready.store(true, Ordering::Relaxed);
108 }
109 let ready = self.latched_ready.load(Ordering::Relaxed);
110 let detail = if ready && current_ready {
111 "all configured underlyings ready".to_string()
112 } else if ready {
113 "startup readiness latched; runtime oracle health should be checked via monitoring"
114 .to_string()
115 } else {
116 let degraded = statuses
117 .iter()
118 .filter(|status| !status.ready)
119 .map(|status| {
120 format!(
121 "{}:{}:{}",
122 status.provider.as_str(),
123 status.underlying,
124 status.last_error.as_deref().unwrap_or("surface not ready")
125 )
126 })
127 .collect::<Vec<_>>();
128 if degraded.is_empty() {
129 "no configured vol surfaces".to_string()
130 } else {
131 degraded.join("; ")
132 }
133 };
134
135 ReadinessReport {
136 name: self.name.to_string(),
137 ready,
138 detail: Some(detail),
139 }
140 }
141}
142
143pub struct ReadinessRegistry {
148 checks: Vec<Arc<dyn Readiness>>,
149}
150
151impl ReadinessRegistry {
152 pub fn new(checks: Vec<Arc<dyn Readiness>>) -> Self {
154 Self { checks }
155 }
156
157 pub fn reports(&self) -> Vec<ReadinessReport> {
159 self.checks.iter().map(|c| c.report()).collect()
160 }
161
162 pub fn all_ready(&self) -> bool {
164 self.checks.iter().all(|c| c.report().ready)
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_sync_status_readiness_initializing() {
174 let sync = Arc::new(SyncStatus::new());
175 let check = SyncStatusReadiness::new("portfolio", sync.clone());
176
177 let report = check.report();
178 assert_eq!(report.name, "portfolio");
179 assert!(!report.ready);
180 assert_eq!(report.detail.as_deref(), Some("Initializing"));
181 }
182
183 #[test]
184 fn test_sync_status_readiness_catching_up() {
185 let sync = Arc::new(SyncStatus::new());
186 sync.set_catching_up();
187 let check = SyncStatusReadiness::new("portfolio", sync.clone());
188
189 let report = check.report();
190 assert_eq!(report.name, "portfolio");
191 assert!(!report.ready);
192 assert_eq!(report.detail.as_deref(), Some("CatchingUp"));
193 }
194
195 #[test]
196 fn test_sync_status_readiness_ready() {
197 let sync = Arc::new(SyncStatus::new());
198 sync.set_ready();
199 let check = SyncStatusReadiness::new("portfolio", sync.clone());
200
201 let report = check.report();
202 assert_eq!(report.name, "portfolio");
203 assert!(report.ready);
204 assert_eq!(report.detail.as_deref(), Some("Ready"));
205 }
206
207 #[test]
208 fn test_registry_all_ready_when_empty() {
209 let registry = ReadinessRegistry::new(vec![]);
210 assert!(registry.all_ready());
212 assert!(registry.reports().is_empty());
213 }
214
215 #[test]
216 fn test_registry_single_check_not_ready() {
217 let sync = Arc::new(SyncStatus::new());
218 let check: Arc<dyn Readiness> =
219 Arc::new(SyncStatusReadiness::new("portfolio", sync.clone()));
220 let registry = ReadinessRegistry::new(vec![check]);
221
222 assert!(!registry.all_ready());
223 let reports = registry.reports();
224 assert_eq!(reports.len(), 1);
225 assert_eq!(reports[0].name, "portfolio");
226 assert!(!reports[0].ready);
227 }
228
229 #[test]
230 fn test_registry_single_check_ready() {
231 let sync = Arc::new(SyncStatus::new());
232 sync.set_ready();
233 let check: Arc<dyn Readiness> =
234 Arc::new(SyncStatusReadiness::new("portfolio", sync.clone()));
235 let registry = ReadinessRegistry::new(vec![check]);
236
237 assert!(registry.all_ready());
238 let reports = registry.reports();
239 assert_eq!(reports.len(), 1);
240 assert!(reports[0].ready);
241 }
242
243 #[test]
244 fn test_registry_multiple_checks_partial_ready() {
245 let sync1 = Arc::new(SyncStatus::new());
246 sync1.set_ready();
247 let sync2 = Arc::new(SyncStatus::new()); let check1: Arc<dyn Readiness> =
250 Arc::new(SyncStatusReadiness::new("portfolio", sync1.clone()));
251 let check2: Arc<dyn Readiness> =
252 Arc::new(SyncStatusReadiness::new("orderbook", sync2.clone()));
253 let registry = ReadinessRegistry::new(vec![check1, check2]);
254
255 assert!(!registry.all_ready());
257
258 let reports = registry.reports();
259 assert_eq!(reports.len(), 2);
260
261 let portfolio = reports.iter().find(|r| r.name == "portfolio").unwrap();
262 assert!(portfolio.ready);
263
264 let orderbook = reports.iter().find(|r| r.name == "orderbook").unwrap();
265 assert!(!orderbook.ready);
266 }
267
268 #[test]
269 fn test_registry_multiple_checks_all_ready() {
270 let sync1 = Arc::new(SyncStatus::new());
271 sync1.set_ready();
272 let sync2 = Arc::new(SyncStatus::new());
273 sync2.set_ready();
274
275 let check1: Arc<dyn Readiness> =
276 Arc::new(SyncStatusReadiness::new("portfolio", sync1.clone()));
277 let check2: Arc<dyn Readiness> =
278 Arc::new(SyncStatusReadiness::new("orderbook", sync2.clone()));
279 let registry = ReadinessRegistry::new(vec![check1, check2]);
280
281 assert!(registry.all_ready());
282 }
283
284 #[test]
285 fn test_registry_state_transitions() {
286 let sync = Arc::new(SyncStatus::new());
287 let check: Arc<dyn Readiness> =
288 Arc::new(SyncStatusReadiness::new("portfolio", sync.clone()));
289 let registry = ReadinessRegistry::new(vec![check]);
290
291 assert!(!registry.all_ready());
293 assert_eq!(
294 registry.reports()[0].detail.as_deref(),
295 Some("Initializing")
296 );
297
298 sync.set_catching_up();
300 assert!(!registry.all_ready());
301 assert_eq!(registry.reports()[0].detail.as_deref(), Some("CatchingUp"));
302
303 sync.set_ready();
305 assert!(registry.all_ready());
306 assert_eq!(registry.reports()[0].detail.as_deref(), Some("Ready"));
307 }
308}