finance_query/providers/capability.rs
1//! The [`Capability`] bitflags a provider declares.
2
3use super::Provider;
4
5/// Capability bits that a provider can declare.
6///
7/// Route a capability to specific providers using `.route(Capability::QUOTE, [Provider::Fmp])`.
8/// If no route is configured for a capability, only Yahoo is used, or EDGAR
9/// then Yahoo for [`Capability::FILINGS`].
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub struct Capability(u32);
12
13impl Capability {
14 /// Equity quote data — price, volume, market cap, fundamentals summary.
15 pub const QUOTE: Self = Self(1 << 0);
16 /// Historical OHLCV chart data across intervals and ranges.
17 pub const CHART: Self = Self(1 << 1);
18 /// Financial statements — income, balance sheet, cash flow.
19 pub const FUNDAMENTALS: Self = Self(1 << 2);
20 /// Corporate events — news, recommendations, SEC filings metadata.
21 pub const CORPORATE: Self = Self(1 << 3);
22 /// Options chains and contract data.
23 pub const OPTIONS: Self = Self(1 << 4);
24 /// Symbol discovery — search, screeners, exchange and ticker reference data.
25 pub const DISCOVERY: Self = Self(1 << 5);
26
27 /// Cryptocurrency quotes and market data.
28 pub const CRYPTO: Self = Self(1 << 6);
29 /// Macro-economic data series (FRED, GDP, CPI, etc.).
30 pub const ECONOMIC: Self = Self(1 << 7);
31 /// Market-wide calendars — earnings, IPOs, dividends, splits, economic events.
32 pub const CALENDAR: Self = Self(1 << 8);
33
34 /// Foreign exchange currency pair quotes.
35 pub const FOREX: Self = Self(1 << 9);
36 /// Stock market index quotes (S&P 500, NASDAQ, etc.).
37 pub const INDICES: Self = Self(1 << 10);
38 /// Futures contract quotes.
39 pub const FUTURES: Self = Self(1 << 11);
40 /// Commodity price quotes (gold, oil, etc.).
41 pub const COMMODITIES: Self = Self(1 << 12);
42 /// Market-wide statistics — sector/industry performance and movers.
43 pub const MARKET: Self = Self(1 << 13);
44
45 /// SEC EDGAR filing data.
46 pub const FILINGS: Self = Self(1 << 14);
47
48 /// The empty capability set — starting point for derived accumulation.
49 pub const NONE: Self = Self(0);
50
51 /// Const-context union, for capability-set consts ([`std::ops::BitOr`]
52 /// isn't const-callable).
53 pub const fn union(self, other: Self) -> Self {
54 Self(self.0 | other.0)
55 }
56
57 /// Every single-bit capability, in declaration order.
58 ///
59 /// Combined sets are not included: this yields exactly the constants above.
60 pub fn all() -> impl Iterator<Item = Self> {
61 Self::ALL.iter().map(|(capability, _)| *capability)
62 }
63
64 /// Returns `true` if this capability set includes all bits in `other`.
65 pub const fn contains(self, other: Self) -> bool {
66 (self.0 & other.0) == other.0
67 }
68
69 /// Every single-bit capability paired with its name — the one place names
70 /// and bits meet. `name()`, `Display`, and the bit-uniqueness test all
71 /// derive from this table.
72 const ALL: [(Self, &'static str); 15] = [
73 (Self::QUOTE, "quote"),
74 (Self::CHART, "chart"),
75 (Self::FUNDAMENTALS, "fundamentals"),
76 (Self::CORPORATE, "corporate"),
77 (Self::OPTIONS, "options"),
78 (Self::DISCOVERY, "discovery"),
79 (Self::CRYPTO, "crypto"),
80 (Self::ECONOMIC, "economic"),
81 (Self::CALENDAR, "calendar"),
82 (Self::MARKET, "market"),
83 (Self::FOREX, "forex"),
84 (Self::INDICES, "indices"),
85 (Self::FUTURES, "futures"),
86 (Self::COMMODITIES, "commodities"),
87 (Self::FILINGS, "filings"),
88 ];
89
90 /// Returns a short lowercase name for this capability (e.g., `"quote"`, `"chart"`).
91 ///
92 /// Returns `"unknown"` for combined capability flags or unrecognised bits;
93 /// [`Display`](std::fmt::Display) spells combined sets out instead (e.g.
94 /// `"quote|chart"`).
95 pub fn name(self) -> &'static str {
96 Self::ALL
97 .iter()
98 .find(|(cap, _)| cap.0 == self.0)
99 .map(|(_, name)| *name)
100 .unwrap_or("unknown")
101 }
102}
103
104impl std::fmt::Display for Capability {
105 /// Single capabilities print their [`name`](Capability::name); combined
106 /// sets are spelled out `|`-separated (e.g. `"quote|chart"`) rather than
107 /// collapsing to `"unknown"`.
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 let mut remaining = self.0;
110 let mut first = true;
111 for (cap, name) in Self::ALL {
112 if remaining & cap.0 != 0 {
113 if !first {
114 f.write_str("|")?;
115 }
116 f.write_str(name)?;
117 first = false;
118 remaining &= !cap.0;
119 }
120 }
121 if first || remaining != 0 {
122 if !first {
123 f.write_str("|")?;
124 }
125 f.write_str("unknown")?;
126 }
127 Ok(())
128 }
129}
130
131impl Capability {
132 /// Providers whose `capabilities()` declare this capability, regardless of
133 /// which providers are actually configured/feature-enabled in this build.
134 ///
135 /// Purely informational — used to make [`crate::FinanceError::NotSupported`]/
136 /// [`crate::FinanceError::NoProviderAvailable`] point at what would need to
137 /// be enabled (feature flag) and/or routed (`Providers::builder().route(...)`).
138 ///
139 /// Built-ins only. A registered custom provider never appears here, since
140 /// [`Provider::capabilities`] cannot see an adapter's accessors.
141 pub fn candidate_providers(self) -> Vec<Provider> {
142 Provider::all()
143 .into_iter()
144 .filter(|p| p.capabilities().contains(self))
145 .collect()
146 }
147}
148
149impl std::ops::BitOr for Capability {
150 type Output = Self;
151 fn bitor(self, rhs: Self) -> Self {
152 Self(self.0 | rhs.0)
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn capability_bits_are_distinct_single_bits() {
162 for (i, (a, name_a)) in Capability::ALL.iter().enumerate() {
163 assert_eq!(a.0.count_ones(), 1, "{name_a} is not a single bit");
164 for (b, name_b) in &Capability::ALL[i + 1..] {
165 assert_ne!(a.0, b.0, "{name_a} and {name_b} share a bit");
166 }
167 }
168 }
169
170 #[test]
171 fn display_spells_out_combined_capabilities() {
172 assert_eq!(Capability::QUOTE.to_string(), "quote");
173 assert_eq!(
174 (Capability::QUOTE | Capability::CHART).to_string(),
175 "quote|chart"
176 );
177 assert_eq!(
178 (Capability::FILINGS | Capability::CORPORATE).to_string(),
179 "corporate|filings"
180 );
181 assert_eq!(Capability::NONE.to_string(), "unknown");
182 // name() keeps its documented single-bit contract.
183 assert_eq!((Capability::QUOTE | Capability::CHART).name(), "unknown");
184 }
185}