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::{Providers, Provider, Capability, StatementType, Frequency};
12//! use finance_query::format::Raw;
13//!
14//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
15//! // Route fundamentals and quote data to FMP with Yahoo as fallback
16//! let providers = Providers::builder()
17//!     .route(Capability::FUNDAMENTALS, [Provider::Fmp, Provider::Yahoo])
18//!     .route(Capability::QUOTE, [Provider::Fmp, Provider::Yahoo])
19//!     .build().await?;
20//!
21//! let ticker = providers.ticker("AAPL").build().await?;
22//! let quote = ticker.quote::<Raw>().await?;
23//! let income = ticker.financials(StatementType::Income, Frequency::Quarterly).await?;
24//! # Ok(())
25//! # }
26//! ```
27
28mod client;
29pub(crate) mod models;
30
31// Capability-mapped subdirectory modules
32pub(crate) mod commodities; // COMMODITIES
33pub(crate) mod corporate; // CORPORATE
34pub(crate) mod crypto; // CRYPTO
35pub(crate) mod discovery; // DISCOVERY
36pub(crate) mod forex; // FOREX
37pub(crate) mod fundamentals; // FUNDAMENTALS
38pub(crate) mod indices; // INDICES
39pub(crate) mod market; // CALENDAR + MARKET
40pub(crate) mod quote; // QUOTE
41
42use crate::adapters::singleton::{provider_build_client, provider_singleton_state};
43use crate::error::{FinanceError, Result};
44use std::time::Duration;
45
46pub use models::*;
47
48/// Take the first row of a single-row FMP response.
49///
50/// FMP wraps per-symbol rollups in a single-element array; an empty one means
51/// the symbol is absent from FMP's universe rather than that the call failed.
52pub(crate) fn first_or_missing<T>(rows: Vec<T>, symbol: &str, field: &str) -> Result<T> {
53    rows.into_iter()
54        .next()
55        .ok_or_else(|| FinanceError::SymbolNotFound {
56            symbol: Some(symbol.to_string()),
57            context: format!("FMP returned no {field} for {symbol}"),
58        })
59}
60
61/// FMP default rate limit: 5 req/sec.
62const FMP_RATE_PER_SEC: f64 = 5.0;
63
64provider_singleton_state!(
65    name = FmpSingleton,
66    static_name = FMP_SINGLETON,
67    rate_const = FMP_RATE_PER_SEC,
68    provider_key = "fmp",
69    already_init_reason = "FMP client already initialized",
70);
71
72provider_build_client!(
73    name = FmpSingleton,
74    static_name = FMP_SINGLETON,
75    rate_const = FMP_RATE_PER_SEC,
76    provider_key = "fmp",
77    env_var = "FMP_API_KEY",
78    env_missing_reason = "FMP_API_KEY not set. Call fmp::init(key) or set FMP_API_KEY env var.",
79    builder = client::FmpClientBuilder,
80    client_ty = client::FmpClient,
81);
82
83/// Initialize the global FMP client with an API key.
84///
85/// Must be called once before using any query functions. Subsequent calls return an error.
86///
87/// # Errors
88///
89/// Returns [`FinanceError::InvalidParameter`] if already initialized.
90#[allow(dead_code)] // public init path for direct adapter use; ProviderSet initialises via env var
91pub fn init(api_key: impl Into<String>) -> Result<()> {
92    init_with_timeout(api_key, Duration::from_secs(30))
93}
94
95/// Initialize the FMP client with a custom timeout.
96#[allow(dead_code)] // public init path for direct adapter use; ProviderSet initialises via env var
97pub fn init_with_timeout(api_key: impl Into<String>, timeout: Duration) -> Result<()> {
98    set_singleton(api_key, timeout)
99}
100
101#[cfg(test)]
102mod live_tests;
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use crate::error::FinanceError;
108
109    #[test]
110    fn test_init_errors_on_double_init() {
111        let _ = init("test-key-1");
112        let result = init("test-key-2");
113        assert!(matches!(result, Err(FinanceError::InvalidParameter { .. })));
114    }
115}