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
53fn invalid_page(field: &str) -> FinanceError {
54    FinanceError::ResponseStructureError {
55        field: field.into(),
56        context: "invalid stock provider response".into(),
57    }
58}
59
60// Capability modules
61pub use chart::*;
62pub use corporate::*;
63pub use discovery::*;
64pub use fundamentals::*;
65pub use options::snapshots::fetch_options_response;
66pub use quote::*;
67
68// Asset-class modules
69pub use crypto::aggregates::fetch_crypto_grouped_daily_response;
70pub use crypto::snapshots::*;
71pub use forex::aggregates::fetch_forex_grouped_daily_response;
72pub use forex::quotes::*;
73pub use futures::snapshots::*;
74pub use indices::snapshots::*;
75
76// Other capability modules
77pub use economic::*;
78pub use filings::*;
79
80const PG_RATE_PER_SEC: f64 = 5.0;
81
82provider_singleton_state!(
83    name = PolygonSingleton,
84    static_name = PG_SINGLETON,
85    rate_const = PG_RATE_PER_SEC,
86    provider_key = "polygon",
87    already_init_reason = "Polygon client already initialized",
88);
89
90provider_build_client!(
91    name = PolygonSingleton,
92    static_name = PG_SINGLETON,
93    rate_const = PG_RATE_PER_SEC,
94    provider_key = "polygon",
95    env_var = "POLYGON_API_KEY",
96    env_missing_reason =
97        "POLYGON_API_KEY not set. Call polygon::init(key) or set POLYGON_API_KEY env var.",
98    builder = client::PolygonClientBuilder,
99    client_ty = client::PolygonClient,
100);
101
102/// Initialize the global Polygon client with an API key.
103///
104/// Must be called once before using any query functions. Subsequent calls return an error.
105///
106/// # Errors
107///
108/// Returns [`FinanceError::InvalidParameter`] if already initialized.
109#[allow(dead_code)] // public init path for direct adapter use; ProviderSet initialises via env var
110pub fn init(api_key: impl Into<String>) -> Result<()> {
111    init_with_timeout(api_key, Duration::from_secs(30))
112}
113
114/// Initialize the Polygon client with a custom timeout.
115#[allow(dead_code)] // public init path for direct adapter use; ProviderSet initialises via env var
116pub fn init_with_timeout(api_key: impl Into<String>, timeout: Duration) -> Result<()> {
117    set_singleton(api_key, timeout)
118}
119
120/// Internal: read the configured API key. Used by the websocket module.
121pub(crate) fn api_key() -> Result<String> {
122    if PG_SINGLETON.get().is_none() {
123        let _ = build_client()?;
124    }
125    PG_SINGLETON
126        .get()
127        .map(|s| s.api_key.clone())
128        .ok_or_else(|| FinanceError::InvalidParameter {
129            param: "polygon".to_string(),
130            reason: "Polygon not initialized. Call polygon::init(api_key) first.".to_string(),
131        })
132}
133
134#[cfg(test)]
135mod live_tests;
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_init_errors_on_double_init() {
143        let _ = init("test-key-1");
144        let result = init("test-key-2");
145        assert!(matches!(result, Err(FinanceError::InvalidParameter { .. })));
146    }
147}