Skip to main content

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}