finance_query/providers/routes.rs
1//! Per-capability routing: which providers serve a capability, and how they
2//! are queried.
3
4use std::collections::HashMap;
5
6use super::{Capability, Provider};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9/// How providers are queried.
10#[non_exhaustive]
11pub enum Fetch {
12 /// Try providers in priority order; first success wins.
13 Sequential,
14 /// Fire all providers concurrently; first success wins.
15 Parallel,
16}
17
18#[derive(Debug)]
19pub(crate) struct Route {
20 pub(crate) providers: Vec<Provider>,
21 pub(crate) fetch: Option<Fetch>,
22}
23
24/// Per-capability provider routing table.
25///
26/// Maps each [`Capability`] to an ordered list of [`Provider`]s to try. A
27/// capability with no entry falls back to Yahoo, or to EDGAR then Yahoo for
28/// [`Capability::FILINGS`].
29///
30/// Each route may carry its own [`Fetch`] mode, so a quota-limited capability
31/// can stay sequential while another races its providers. Routes without one
32/// use the table default.
33#[derive(Debug)]
34#[non_exhaustive]
35pub struct Routes {
36 pub(crate) map: HashMap<Capability, Route>,
37 pub(crate) fetch: Fetch,
38}
39
40impl Routes {
41 /// An empty route table (every capability falls back to its default
42 /// candidate providers) using the given concurrency [`Fetch`] mode.
43 pub fn new(fetch: Fetch) -> Self {
44 Self {
45 map: HashMap::new(),
46 fetch,
47 }
48 }
49
50 /// Route one capability to an ordered list of providers.
51 ///
52 /// Without this a hand-built table is empty and every capability falls
53 /// back to its default, so a registered adapter would never be reached.
54 #[must_use]
55 pub fn route(self, cap: Capability, providers: impl IntoIterator<Item = Provider>) -> Self {
56 self.insert(cap, providers, None)
57 }
58
59 /// Route one capability, overriding the table's [`Fetch`] mode for it.
60 #[must_use]
61 pub fn route_with(
62 self,
63 cap: Capability,
64 providers: impl IntoIterator<Item = Provider>,
65 fetch: Fetch,
66 ) -> Self {
67 self.insert(cap, providers, Some(fetch))
68 }
69
70 fn insert(
71 mut self,
72 cap: Capability,
73 providers: impl IntoIterator<Item = Provider>,
74 fetch: Option<Fetch>,
75 ) -> Self {
76 self.map.insert(
77 cap,
78 Route {
79 providers: providers.into_iter().collect(),
80 fetch,
81 },
82 );
83 self
84 }
85
86 /// The default concurrency mode for capabilities that do not override it.
87 pub fn fetch_mode(&self) -> Fetch {
88 self.fetch
89 }
90
91 /// The concurrency mode `cap` resolves to.
92 pub fn fetch_mode_for(&self, cap: Capability) -> Fetch {
93 self.map
94 .get(&cap)
95 .and_then(|r| r.fetch)
96 .unwrap_or(self.fetch)
97 }
98
99 /// The providers routed to `cap`, or `None` when it falls back to the default.
100 pub fn providers_for(&self, cap: Capability) -> Option<&[Provider]> {
101 self.map.get(&cap).map(|r| r.providers.as_slice())
102 }
103}