Skip to main content

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}