finance_query/adapters/fmp/
mod.rs1mod client;
30pub mod models;
31
32mod advanced;
33mod analysis;
34mod bulk;
35mod calendars;
36mod commodities;
37mod company;
38mod crypto;
39mod dividends_splits;
40mod estimates;
41mod etf_mutual_funds;
42mod forex;
43mod fund_holdings;
44mod fundamentals;
45mod indexes;
46mod insider_trading;
47mod institutional;
48mod market_performance;
49mod news;
50mod prices;
51mod screener;
52mod stock_list;
53mod technical_indicators;
54
55use crate::error::{FinanceError, Result};
56use crate::rate_limiter::RateLimiter;
57use client::FmpClientBuilder;
58use std::sync::{Arc, OnceLock};
59use std::time::Duration;
60
61pub use advanced::*;
62pub use analysis::*;
63pub use bulk::*;
64pub use calendars::*;
65pub use commodities::*;
66pub use company::*;
67pub use crypto::*;
68pub use dividends_splits::*;
69pub use estimates::*;
70pub use etf_mutual_funds::*;
71pub use forex::*;
72pub use fund_holdings::*;
73pub use fundamentals::*;
74pub use indexes::*;
75pub use insider_trading::*;
76pub use institutional::*;
77pub use market_performance::*;
78pub use models::*;
79pub use news::*;
80pub use prices::*;
81pub use screener::*;
82pub use stock_list::*;
83pub use technical_indicators::*;
84
85const FMP_RATE_PER_SEC: f64 = 5.0;
87
88struct FmpSingleton {
89 api_key: String,
90 timeout: Duration,
91 limiter: Arc<RateLimiter>,
92}
93
94static FMP_SINGLETON: OnceLock<FmpSingleton> = OnceLock::new();
95
96pub fn init(api_key: impl Into<String>) -> Result<()> {
104 init_with_timeout(api_key, Duration::from_secs(30))
105}
106
107pub fn init_with_timeout(api_key: impl Into<String>, timeout: Duration) -> Result<()> {
109 FMP_SINGLETON
110 .set(FmpSingleton {
111 api_key: api_key.into(),
112 timeout,
113 limiter: Arc::new(RateLimiter::new(FMP_RATE_PER_SEC)),
114 })
115 .map_err(|_| FinanceError::InvalidParameter {
116 param: "fmp".to_string(),
117 reason: "FMP client already initialized".to_string(),
118 })
119}
120
121pub(crate) fn build_client() -> Result<client::FmpClient> {
123 let s = FMP_SINGLETON
124 .get()
125 .ok_or_else(|| FinanceError::InvalidParameter {
126 param: "fmp".to_string(),
127 reason: "FMP not initialized. Call fmp::init(api_key) first.".to_string(),
128 })?;
129 FmpClientBuilder::new(&s.api_key)
130 .timeout(s.timeout)
131 .build_with_limiter(Arc::clone(&s.limiter))
132}
133
134#[cfg(test)]
136pub(crate) fn build_test_client(base_url: &str) -> Result<client::FmpClient> {
137 FmpClientBuilder::new("test-key")
138 .timeout(Duration::from_secs(5))
139 .base_url(base_url)
140 .build_with_limiter(Arc::new(RateLimiter::new(100.0)))
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_init_errors_on_double_init() {
149 let _ = init("test-key-1");
150 let result = init("test-key-2");
151 assert!(matches!(result, Err(FinanceError::InvalidParameter { .. })));
152 }
153}