Skip to main content

finance_query/adapters/polygon/
mod.rs

1//! Polygon/Massive API client for financial data.
2//!
3//! Requires the **`polygon`** feature flag and an API key from
4//! <https://massive.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, Interval, TimeRange};
12//! use finance_query::format::Raw;
13//!
14//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
15//! // Route chart and quote data to Polygon with Yahoo as fallback
16//! let providers = Providers::builder()
17//!     .route(Capability::CHART, [Provider::Polygon, Provider::Yahoo])
18//!     .route(Capability::QUOTE, [Provider::Polygon, Provider::Yahoo])
19//!     .build().await?;
20//!
21//! let ticker = providers.ticker("AAPL").build().await?;
22//! let chart = ticker.chart(Interval::OneDay, TimeRange::OneMonth).await?;
23//! let quote = ticker.quote::<Raw>().await?;
24//! # Ok(())
25//! # }
26//! ```
27
28mod client;
29pub(crate) mod models;
30
31// Capability-mapped subdirectory modules
32mod chart; // CHART
33mod corporate; // CORPORATE
34mod discovery; // DISCOVERY
35mod economic; // ECONOMIC
36mod filings; // FILINGS
37mod fundamentals; // FUNDAMENTALS
38mod quote; // QUOTE
39
40// Asset-class subdirectory modules
41mod crypto; // CRYPTO
42mod forex; // FOREX
43mod futures; // FUTURES
44mod indices; // INDICES
45pub(crate) mod options; // OPTIONS
46
47pub(crate) mod websocket;
48
49use crate::adapters::singleton::{provider_build_client, provider_singleton_state};
50use crate::error::{FinanceError, Result};
51use std::time::Duration;
52
53// Capability modules
54pub use chart::*;
55pub use corporate::*;
56pub use discovery::*;
57pub use fundamentals::*;
58pub use options::snapshots::fetch_options_response;
59pub use quote::*;
60
61// Asset-class modules
62pub use crypto::aggregates::fetch_crypto_grouped_daily_response;
63pub use crypto::snapshots::*;
64pub use forex::aggregates::fetch_forex_grouped_daily_response;
65pub use forex::quotes::*;
66pub use futures::snapshots::*;
67pub use indices::snapshots::*;
68
69// Other capability modules
70pub use economic::*;
71pub use filings::*;
72
73const PG_RATE_PER_SEC: f64 = 5.0;
74
75provider_singleton_state!(
76    name = PolygonSingleton,
77    static_name = PG_SINGLETON,
78    rate_const = PG_RATE_PER_SEC,
79    provider_key = "polygon",
80    already_init_reason = "Polygon client already initialized",
81);
82
83provider_build_client!(
84    name = PolygonSingleton,
85    static_name = PG_SINGLETON,
86    rate_const = PG_RATE_PER_SEC,
87    provider_key = "polygon",
88    env_var = "POLYGON_API_KEY",
89    env_missing_reason =
90        "POLYGON_API_KEY not set. Call polygon::init(key) or set POLYGON_API_KEY env var.",
91    builder = client::PolygonClientBuilder,
92    client_ty = client::PolygonClient,
93);
94
95/// Initialize the global Polygon client with an API key.
96///
97/// Must be called once before using any query functions. Subsequent calls return an error.
98///
99/// # Errors
100///
101/// Returns [`FinanceError::InvalidParameter`] if already initialized.
102#[allow(dead_code)] // public init path for direct adapter use; ProviderSet initialises via env var
103pub fn init(api_key: impl Into<String>) -> Result<()> {
104    init_with_timeout(api_key, Duration::from_secs(30))
105}
106
107/// Initialize the Polygon client with a custom timeout.
108#[allow(dead_code)] // public init path for direct adapter use; ProviderSet initialises via env var
109pub fn init_with_timeout(api_key: impl Into<String>, timeout: Duration) -> Result<()> {
110    set_singleton(api_key, timeout)
111}
112
113/// Internal: read the configured API key. Used by the websocket module.
114pub(crate) fn api_key() -> Result<String> {
115    if PG_SINGLETON.get().is_none() {
116        let _ = build_client()?;
117    }
118    PG_SINGLETON
119        .get()
120        .map(|s| s.api_key.clone())
121        .ok_or_else(|| FinanceError::InvalidParameter {
122            param: "polygon".to_string(),
123            reason: "Polygon not initialized. Call polygon::init(api_key) first.".to_string(),
124        })
125}
126
127#[cfg(test)]
128mod live_tests;
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_init_errors_on_double_init() {
136        let _ = init("test-key-1");
137        let result = init("test-key-2");
138        assert!(matches!(result, Err(FinanceError::InvalidParameter { .. })));
139    }
140}