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