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}