Skip to main content

finance_query/adapters/fmp/
mod.rs

1//! Financial Modeling Prep API client for financial data.
2//!
3//! Requires the **`fmp`** feature flag and an API key from
4//! <https://financialmodelingprep.com/>.
5//!
6//! Call [`init`] once at startup before using any query functions.
7//!
8//! # Quick Start
9//!
10//! ```no_run
11//! use finance_query::adapters::fmp;
12//! use finance_query::adapters::fmp::Period;
13//!
14//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
15//! fmp::init("YOUR_API_KEY")?;
16//!
17//! // Real-time quote
18//! let quotes = fmp::quote("AAPL").await?;
19//!
20//! // Income statement
21//! let income = fmp::income_statement("AAPL", Period::Quarter, Some(4)).await?;
22//!
23//! // Company profile
24//! let profile = fmp::company_profile("AAPL").await?;
25//! # Ok(())
26//! # }
27//! ```
28
29mod 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
85/// FMP default rate limit: 5 req/sec.
86const 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
96/// Initialize the global FMP client with an API key.
97///
98/// Must be called once before using any query functions. Subsequent calls return an error.
99///
100/// # Errors
101///
102/// Returns [`FinanceError::InvalidParameter`] if already initialized.
103pub fn init(api_key: impl Into<String>) -> Result<()> {
104    init_with_timeout(api_key, Duration::from_secs(30))
105}
106
107/// Initialize the FMP client with a custom timeout.
108pub 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
121/// Build a fresh client from the singleton state.
122pub(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/// Build a test client pointing at a mock server URL.
135#[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}