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 Providers {
36 /// Create a builder for configuring providers.
37 pub fn builder() -> ProvidersBuilder {
38 ProvidersBuilder::default()
39 }
40
41 /// Create a [`TickerBuilder`](crate::TickerBuilder) pre-wired to this provider set.
42 ///
43 /// The returned builder accepts the same optional configuration as
44 /// [`Ticker::builder`](crate::Ticker::builder) (`.cache()`, `.logo()`,
45 /// `.format()`) before calling `.build()`.
46 ///
47 /// The language configured via [`ProvidersBuilder::lang`] or
48 /// [`ProvidersBuilder::region`] is inherited (override with `.lang()` on
49 /// the returned builder). With the `translation` feature, a non-English
50 /// language translates text fields automatically.
51 pub fn ticker(&self, symbol: impl Into<String>) -> crate::TickerBuilder {
52 crate::Ticker::builder(symbol)
53 .lang(self.lang.clone())
54 .with_provider_set(Arc::clone(&self.set))
55 }
56
57 /// Create a [`TickersBuilder`](crate::TickersBuilder) pre-wired to this provider set.
58 ///
59 /// The returned builder accepts the same optional configuration as
60 /// [`Tickers::builder`](crate::Tickers::builder) (`.cache()`,
61 /// `.max_concurrency()`, `.logo()`, `.format()`) before calling `.build()`.
62 ///
63 /// The language configured via [`ProvidersBuilder::lang`] or
64 /// [`ProvidersBuilder::region`] is inherited (override with `.lang()` on
65 /// the returned builder). With the `translation` feature, a non-English
66 /// language translates text fields automatically.
67 pub fn tickers<S, I>(&self, symbols: I) -> crate::TickersBuilder
68 where
69 S: Into<String>,
70 I: IntoIterator<Item = S>,
71 {
72 crate::Tickers::builder(symbols)
73 .lang(self.lang.clone())
74 .with_provider_set(Arc::clone(&self.set))
75 }
76
77 /// Create a [`CryptoCoin`](crate::CryptoCoin) handle backed by this provider set.
78 #[cfg(any(
79 feature = "alphavantage",
80 feature = "crypto",
81 feature = "fmp",
82 feature = "polygon"
83 ))]
84 pub fn crypto(&self, id: impl Into<String>) -> crate::domains::CryptoCoin {
85 crate::domains::CryptoCoin::with_providers(id.into().into(), Arc::clone(&self.set))
86 }
87
88 /// Create a [`ForexPair`](crate::ForexPair) handle backed by this provider set.
89 #[cfg(any(feature = "alphavantage", feature = "fmp", feature = "polygon"))]
90 pub fn forex(
91 &self,
92 from: impl Into<String>,
93 to: impl Into<String>,
94 ) -> crate::domains::ForexPair {
95 crate::domains::ForexPair::with_providers(
96 from.into().into(),
97 to.into().into(),
98 Arc::clone(&self.set),
99 )
100 }
101
102 /// Create an [`EconomicIndicator`](crate::EconomicIndicator) handle backed by this provider set.
103 #[cfg(any(feature = "alphavantage", feature = "polygon", feature = "fred"))]
104 pub fn economic(&self, series_id: impl Into<String>) -> crate::domains::EconomicIndicator {
105 crate::domains::EconomicIndicator::with_providers(
106 series_id.into().into(),
107 Arc::clone(&self.set),
108 )
109 }
110
111 /// Create an [`Index`](crate::Index) handle backed by this provider set.
112 #[cfg(any(feature = "polygon", feature = "fmp"))]
113 pub fn index(&self, symbol: impl Into<String>) -> crate::domains::Index {
114 crate::domains::Index::with_providers(symbol.into().into(), Arc::clone(&self.set))
115 }
116
117 /// Create a [`FuturesContract`](crate::FuturesContract) handle backed by this provider set.
118 #[cfg(feature = "polygon")]
119 pub fn futures(&self, symbol: impl Into<String>) -> crate::domains::FuturesContract {
120 crate::domains::FuturesContract::with_providers(symbol.into().into(), Arc::clone(&self.set))
121 }
122
123 /// Create a [`Commodity`](crate::Commodity) handle backed by this provider set.
124 #[cfg(any(feature = "fmp", feature = "alphavantage"))]
125 pub fn commodity(&self, symbol: impl Into<String>) -> crate::domains::Commodity {
126 crate::domains::Commodity::with_providers(symbol.into().into(), Arc::clone(&self.set))
127 }
128
129 /// Create a [`Filings`](crate::Filings) handle backed by this provider set.
130 ///
131 /// Always available — EDGAR is auto-injected when no other FILINGS provider
132 /// is configured.
133 pub fn filings(&self, symbol: impl Into<String>) -> crate::domains::Filings {
134 crate::domains::Filings::with_providers(symbol.into().into(), Arc::clone(&self.set))
135 }
136}
137
138/// Builder for [`Providers`].
139pub struct ProvidersBuilder {
140 provider_ids: Vec<Provider>,
141 config: ClientConfig,
142 routes: Routes,
143}
144
145impl Default for ProvidersBuilder {
146 fn default() -> Self {
147 Self {
148 provider_ids: vec![Provider::Yahoo],
149 config: ClientConfig::default(),
150 routes: Routes::new(Fetch::Sequential),
151 }
152 }
153}
154
155impl ProvidersBuilder {
156 /// Configure how providers are queried. Default: `Sequential`.
157 ///
158 /// Use [`Fetch::Sequential`] or [`Fetch::Parallel`].
159 pub fn fetch(mut self, mode: Fetch) -> Self {
160 self.routes.fetch = mode;
161 self
162 }
163
164 /// Route a capability to a specific provider priority list.
165 ///
166 /// Providers referenced in the route are automatically added to the
167 /// initialisation list if not already present. If omitted for a capability,
168 /// Yahoo is used as default.
169 pub fn route(mut self, cap: crate::providers::Capability, providers: &[Provider]) -> Self {
170 self.routes.map.insert(cap, providers.to_vec());
171 for provider in providers {
172 if !self.provider_ids.contains(provider) {
173 self.provider_ids.push(*provider);
174 }
175 }
176 self
177 }
178
179 /// Set the region (automatically sets lang and region code).
180 pub fn region(mut self, region: crate::constants::Region) -> Self {
181 self.config.lang = region.lang().to_string();
182 self.config.region = region.region().to_string();
183 self
184 }
185
186 /// Set the language code (e.g., "en-US", "ja-JP").
187 ///
188 /// Inherited by every `Ticker`/`Tickers` handle created from the built
189 /// [`Providers`]. With the `translation` feature, a non-English language
190 /// translates text fields on those handles automatically.
191 pub fn lang(mut self, lang: impl Into<String>) -> Self {
192 self.config.lang = lang.into();
193 self
194 }
195
196 /// Set the HTTP request timeout.
197 pub fn timeout(mut self, t: Duration) -> Self {
198 self.config.timeout = t;
199 self
200 }
201
202 /// Set the proxy URL.
203 pub fn proxy(mut self, p: impl Into<String>) -> Self {
204 self.config.proxy = Some(p.into());
205 self
206 }
207
208 /// Build the [`Providers`] instance, initialising all configured providers.
209 pub async fn build(self) -> Result<Providers> {
210 #[cfg(feature = "translation")]
211 crate::translation::Lang::parse(&self.config.lang)?;
212 let lang = self.config.lang.clone();
213 let set = build_providers(&self.provider_ids, &self.config, self.routes).await?;
214 Ok(Providers {
215 set: Arc::new(set),
216 lang,
217 })
218 }
219}