rhood_core/
resolver_cache.rs1use std::sync::Arc;
8use std::time::Duration;
9
10use moka::future::Cache;
11use tokio::sync::OnceCell;
12
13use crate::config::CacheConfig;
14use crate::models::futures::FuturesContract;
15use crate::models::stock::{IndexInstrument, Instrument};
16
17#[derive(Clone)]
37pub struct ResolverCache {
38 pub(crate) enabled: bool,
39 pub(crate) enrichment_batch_size: usize,
40 pub(crate) instruments_by_symbol: Cache<String, Arc<Instrument>>,
41 pub(crate) instruments_by_id: Cache<String, String>,
42 pub(crate) index_instruments: Cache<String, Arc<IndexInstrument>>,
43 pub(crate) futures_contracts: Cache<String, Arc<FuturesContract>>,
44 pub(crate) futures_account_id: Arc<OnceCell<String>>,
45}
46
47impl ResolverCache {
48 pub fn from_config(config: &CacheConfig) -> Self {
54 let instruments_by_symbol = Cache::builder()
55 .max_capacity(config.instrument_max_entries)
56 .time_to_live(Duration::from_secs(config.instrument_ttl_secs))
57 .build();
58 let instruments_by_id = Cache::builder()
59 .max_capacity(config.instrument_id_max_entries)
60 .time_to_live(Duration::from_secs(config.instrument_id_ttl_secs))
61 .build();
62 let index_instruments = Cache::builder()
63 .max_capacity(config.index_max_entries)
64 .time_to_live(Duration::from_secs(config.index_ttl_secs))
65 .build();
66 let futures_contracts = Cache::builder()
67 .max_capacity(config.futures_max_entries)
68 .time_to_live(Duration::from_secs(config.futures_ttl_secs))
69 .build();
70 Self {
71 enabled: config.enabled,
72 enrichment_batch_size: config.enrichment_batch_size,
73 instruments_by_symbol,
74 instruments_by_id,
75 index_instruments,
76 futures_contracts,
77 futures_account_id: Arc::new(OnceCell::new()),
78 }
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[tokio::test]
87 async fn from_config_builds_empty_caches() {
88 let cache = ResolverCache::from_config(&CacheConfig::default());
89 assert!(cache.enabled);
90 assert_eq!(cache.instruments_by_symbol.entry_count(), 0);
91 assert_eq!(cache.instruments_by_id.entry_count(), 0);
92 assert_eq!(cache.index_instruments.entry_count(), 0);
93 assert_eq!(cache.futures_contracts.entry_count(), 0);
94 assert!(cache.futures_account_id.get().is_none());
95 }
96
97 #[tokio::test]
98 async fn disabled_flag_propagates() {
99 let cfg = CacheConfig {
100 enabled: false,
101 ..CacheConfig::default()
102 };
103 let cache = ResolverCache::from_config(&cfg);
104 assert!(!cache.enabled);
105 }
106}