Skip to main content

finance_query/providers/
config.rs

1use crate::adapters::yahoo::client::ClientConfig;
2use crate::error::Result;
3use crate::providers::{Fetch, Provider, ProviderSet, Routes, build_providers};
4use std::sync::Arc;
5use std::time::Duration;
6
7/// Central provider configuration shared across query handles.
8///
9/// Build once with [`Providers::builder`], then create lightweight
10/// [`Ticker`](crate::Ticker) handles that share the same underlying
11/// provider connections and authentication.
12///
13/// # Example
14///
15/// ```no_run
16/// use finance_query::{Providers, Provider, Fetch, Capability};
17///
18/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
19/// let providers = Providers::builder()
20///     .route(Capability::QUOTE, [Provider::Yahoo])
21///     .fetch(Fetch::Sequential)
22///     .build().await?;
23///
24/// // All Ticker handles share the same Arc<ProviderSet>
25/// let aapl = providers.ticker("AAPL").build().await?;
26/// let nvda = providers.ticker("NVDA").logo().build().await?;
27/// # Ok(())
28/// # }
29/// ```
30pub struct Providers {
31    pub(crate) set: Arc<ProviderSet>,
32    lang: String,
33}
34
35impl std::fmt::Debug for Providers {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        f.debug_struct("Providers")
38            .field("set", &self.set)
39            .field("lang", &self.lang)
40            .finish()
41    }
42}
43
44impl Providers {
45    /// Create a builder for configuring providers.
46    pub fn builder() -> ProvidersBuilder {
47        ProvidersBuilder::default()
48    }
49
50    /// Create a [`TickerBuilder`](crate::TickerBuilder) pre-wired to this provider set.
51    ///
52    /// The returned builder accepts the same optional configuration as
53    /// [`Ticker::builder`](crate::Ticker::builder) (`.cache()`, `.logo()`,
54    /// `.format()`) before calling `.build()`.
55    ///
56    /// The language configured via [`ProvidersBuilder::lang`] or
57    /// [`ProvidersBuilder::region`] is inherited (override with `.lang()` on
58    /// the returned builder). With the `translation` feature, a non-English
59    /// language translates text fields automatically.
60    pub fn ticker(&self, symbol: impl Into<String>) -> crate::TickerBuilder {
61        crate::Ticker::builder(symbol)
62            .lang(self.lang.clone())
63            .with_provider_set(Arc::clone(&self.set))
64    }
65
66    /// Create a [`TickersBuilder`](crate::TickersBuilder) pre-wired to this provider set.
67    ///
68    /// The returned builder accepts the same optional configuration as
69    /// [`Tickers::builder`](crate::Tickers::builder) (`.cache()`,
70    /// `.max_concurrency()`, `.logo()`, `.format()`) before calling `.build()`.
71    ///
72    /// The language configured via [`ProvidersBuilder::lang`] or
73    /// [`ProvidersBuilder::region`] is inherited (override with `.lang()` on
74    /// the returned builder). With the `translation` feature, a non-English
75    /// language translates text fields automatically.
76    pub fn tickers<S, I>(&self, symbols: I) -> crate::TickersBuilder
77    where
78        S: Into<String>,
79        I: IntoIterator<Item = S>,
80    {
81        crate::Tickers::builder(symbols)
82            .lang(self.lang.clone())
83            .with_provider_set(Arc::clone(&self.set))
84    }
85
86    /// Create a [`CryptoCoin`](crate::CryptoCoin) handle backed by this provider set.
87    #[cfg(any(
88        feature = "alphavantage",
89        feature = "crypto",
90        feature = "fmp",
91        feature = "polygon"
92    ))]
93    pub fn crypto(&self, id: impl Into<String>) -> crate::domains::CryptoCoin {
94        crate::domains::CryptoCoin::with_providers(id.into().into(), Arc::clone(&self.set))
95    }
96
97    /// Create a [`ForexPair`](crate::ForexPair) handle backed by this provider set.
98    #[cfg(any(feature = "alphavantage", feature = "fmp", feature = "polygon"))]
99    pub fn forex(
100        &self,
101        from: impl Into<String>,
102        to: impl Into<String>,
103    ) -> crate::domains::ForexPair {
104        crate::domains::ForexPair::with_providers(
105            from.into().into(),
106            to.into().into(),
107            Arc::clone(&self.set),
108        )
109    }
110
111    /// Create an [`EconomicIndicator`](crate::EconomicIndicator) handle backed by this provider set.
112    #[cfg(any(feature = "alphavantage", feature = "polygon", feature = "fred"))]
113    pub fn economic(&self, series_id: impl Into<String>) -> crate::domains::EconomicIndicator {
114        crate::domains::EconomicIndicator::with_providers(
115            series_id.into().into(),
116            Arc::clone(&self.set),
117        )
118    }
119
120    /// Create an [`Index`](crate::Index) handle backed by this provider set.
121    #[cfg(any(feature = "polygon", feature = "fmp"))]
122    pub fn index(&self, symbol: impl Into<String>) -> crate::domains::Index {
123        crate::domains::Index::with_providers(symbol.into().into(), Arc::clone(&self.set))
124    }
125
126    /// Create a [`FuturesContract`](crate::FuturesContract) handle backed by this provider set.
127    #[cfg(feature = "polygon")]
128    pub fn futures(&self, symbol: impl Into<String>) -> crate::domains::FuturesContract {
129        crate::domains::FuturesContract::with_providers(symbol.into().into(), Arc::clone(&self.set))
130    }
131
132    /// Create a [`Commodity`](crate::Commodity) handle backed by this provider set.
133    #[cfg(any(feature = "fmp", feature = "alphavantage"))]
134    pub fn commodity(&self, symbol: impl Into<String>) -> crate::domains::Commodity {
135        crate::domains::Commodity::with_providers(symbol.into().into(), Arc::clone(&self.set))
136    }
137
138    /// Create a [`Filings`](crate::Filings) handle backed by this provider set.
139    ///
140    /// Always available — EDGAR is auto-injected when no other FILINGS provider
141    /// is configured.
142    pub fn filings(&self, symbol: impl Into<String>) -> crate::domains::Filings {
143        crate::domains::Filings::with_providers(symbol.into().into(), Arc::clone(&self.set))
144    }
145}
146
147/// Builder for [`Providers`].
148#[derive(Debug)]
149pub struct ProvidersBuilder {
150    provider_ids: Vec<Provider>,
151    config: ClientConfig,
152    routes: Routes,
153}
154
155impl Default for ProvidersBuilder {
156    fn default() -> Self {
157        Self {
158            provider_ids: vec![Provider::Yahoo],
159            config: ClientConfig::default(),
160            routes: Routes::new(Fetch::Sequential),
161        }
162    }
163}
164
165impl ProvidersBuilder {
166    /// Configure how providers are queried. Default: `Sequential`.
167    ///
168    /// Use [`Fetch::Sequential`] or [`Fetch::Parallel`].
169    pub fn fetch(mut self, mode: Fetch) -> Self {
170        self.routes.fetch = mode;
171        self
172    }
173
174    /// Route a capability to a specific provider priority list.
175    ///
176    /// Providers referenced in the route are automatically added to the
177    /// initialisation list if not already present. If omitted for a capability,
178    /// Yahoo is used as default.
179    pub fn route(
180        mut self,
181        cap: crate::providers::Capability,
182        providers: impl IntoIterator<Item = Provider>,
183    ) -> Self {
184        let providers: Vec<Provider> = providers.into_iter().collect();
185        for provider in &providers {
186            if !self.provider_ids.contains(provider) {
187                self.provider_ids.push(*provider);
188            }
189        }
190        self.routes.map.insert(cap, providers);
191        self
192    }
193
194    /// Set the region (automatically sets lang and region code).
195    pub fn region(mut self, region: crate::constants::Region) -> Self {
196        self.config.lang = region.lang().to_string();
197        self.config.region = region.region().to_string();
198        self
199    }
200
201    /// Set the language code (e.g., "en-US", "ja-JP").
202    ///
203    /// Inherited by every `Ticker`/`Tickers` handle created from the built
204    /// [`Providers`]. With the `translation` feature, a non-English language
205    /// translates text fields on those handles automatically.
206    pub fn lang(mut self, lang: impl Into<String>) -> Self {
207        self.config.lang = lang.into();
208        self
209    }
210
211    /// Set the region code (e.g., "US", "JP", "DE").
212    ///
213    /// For standard countries, prefer `.region()` instead to ensure correct
214    /// lang/region pairing.
215    pub fn region_code(mut self, region: impl Into<String>) -> Self {
216        self.config.region = region.into();
217        self
218    }
219
220    /// Set the HTTP request timeout.
221    pub fn timeout(mut self, t: Duration) -> Self {
222        self.config.timeout = t;
223        self
224    }
225
226    /// Set the proxy URL.
227    pub fn proxy(mut self, p: impl Into<String>) -> Self {
228        self.config.proxy = Some(p.into());
229        self
230    }
231
232    /// Build the [`Providers`] instance, initialising all configured providers.
233    pub async fn build(self) -> Result<Providers> {
234        #[cfg(feature = "translation")]
235        crate::translation::Lang::parse(&self.config.lang)?;
236        let lang = self.config.lang.clone();
237        let set = build_providers(&self.provider_ids, &self.config, self.routes).await?;
238        Ok(Providers {
239            set: Arc::new(set),
240            lang,
241        })
242    }
243}