1use async_trait::async_trait;
2use borsa_core::connector::{
3 AnalystPriceTargetProvider, BalanceSheetProvider, BorsaConnector, CalendarProvider,
4 CashflowProvider, EarningsProvider, EsgProvider, HistoryProvider, IncomeStatementProvider,
5 NewsProvider, OptionChainProvider, OptionsExpirationsProvider, ProfileProvider, QuoteProvider,
6 RecommendationsProvider, RecommendationsSummaryProvider, SearchProvider,
7 UpgradesDowngradesProvider,
8};
9use borsa_core::{
10 AssetKind, BalanceSheetRow, BorsaError, Calendar, CashflowRow, Earnings, EsgScores,
11 HistoryRequest, HistoryResponse, IncomeStatementRow, Instrument, Interval, NewsRequest,
12 OptionChain, Profile, Quote, RecommendationRow, RecommendationSummary, SearchRequest,
13 SearchResponse, UpgradeDowngradeRow, types,
14};
15
16mod fixtures;
17
18pub struct MockConnector;
20
21impl Default for MockConnector {
22 fn default() -> Self {
23 Self::new()
24 }
25}
26
27impl MockConnector {
28 #[must_use]
29 pub const fn new() -> Self {
30 Self
31 }
32
33 fn not_found(what: &str) -> BorsaError {
34 BorsaError::not_found(what.to_string())
35 }
36
37 async fn maybe_fail_or_timeout(
38 symbol: &str,
39 capability: &'static str,
40 ) -> Result<(), BorsaError> {
41 match symbol {
42 "FAIL" => Err(BorsaError::connector(
43 "borsa-mock",
44 format!("forced failure: {capability}"),
45 )),
46 "TIMEOUT" => {
47 tokio::time::sleep(std::time::Duration::from_millis(200)).await;
50 Ok(())
51 }
52 _ => Ok(()),
53 }
54 }
55}
56
57#[async_trait]
58impl BorsaConnector for MockConnector {
59 fn name(&self) -> &'static str {
60 "borsa-mock"
61 }
62 fn vendor(&self) -> &'static str {
63 "Mock"
64 }
65
66 fn supports_kind(&self, _kind: AssetKind) -> bool {
67 true
68 }
69
70 fn as_quote_provider(&self) -> Option<&dyn QuoteProvider> {
71 Some(self as &dyn QuoteProvider)
72 }
73 fn as_history_provider(&self) -> Option<&dyn HistoryProvider> {
74 Some(self as &dyn HistoryProvider)
75 }
76 fn as_search_provider(&self) -> Option<&dyn SearchProvider> {
77 Some(self as &dyn SearchProvider)
78 }
79 fn as_profile_provider(&self) -> Option<&dyn ProfileProvider> {
80 Some(self as &dyn ProfileProvider)
81 }
82 fn as_earnings_provider(&self) -> Option<&dyn EarningsProvider> {
83 Some(self as &dyn EarningsProvider)
84 }
85 fn as_income_statement_provider(&self) -> Option<&dyn IncomeStatementProvider> {
86 Some(self as &dyn IncomeStatementProvider)
87 }
88 fn as_balance_sheet_provider(&self) -> Option<&dyn BalanceSheetProvider> {
89 Some(self as &dyn BalanceSheetProvider)
90 }
91 fn as_cashflow_provider(&self) -> Option<&dyn CashflowProvider> {
92 Some(self as &dyn CashflowProvider)
93 }
94 fn as_calendar_provider(&self) -> Option<&dyn CalendarProvider> {
95 Some(self as &dyn CalendarProvider)
96 }
97 fn as_options_expirations_provider(&self) -> Option<&dyn OptionsExpirationsProvider> {
98 Some(self as &dyn OptionsExpirationsProvider)
99 }
100 fn as_option_chain_provider(&self) -> Option<&dyn OptionChainProvider> {
101 Some(self as &dyn OptionChainProvider)
102 }
103 fn as_recommendations_provider(&self) -> Option<&dyn RecommendationsProvider> {
104 Some(self as &dyn RecommendationsProvider)
105 }
106 fn as_recommendations_summary_provider(&self) -> Option<&dyn RecommendationsSummaryProvider> {
107 Some(self as &dyn RecommendationsSummaryProvider)
108 }
109 fn as_upgrades_downgrades_provider(&self) -> Option<&dyn UpgradesDowngradesProvider> {
110 Some(self as &dyn UpgradesDowngradesProvider)
111 }
112 fn as_analyst_price_target_provider(&self) -> Option<&dyn AnalystPriceTargetProvider> {
113 Some(self as &dyn AnalystPriceTargetProvider)
114 }
115 fn as_esg_provider(&self) -> Option<&dyn EsgProvider> {
116 Some(self as &dyn EsgProvider)
117 }
118 fn as_news_provider(&self) -> Option<&dyn NewsProvider> {
119 Some(self as &dyn NewsProvider)
120 }
121 }
123
124#[async_trait]
125impl QuoteProvider for MockConnector {
126 async fn quote(&self, instrument: &Instrument) -> Result<Quote, BorsaError> {
127 let s = instrument.symbol_str();
128 Self::maybe_fail_or_timeout(s, "quote").await?;
129 fixtures::quotes::by_symbol(s).ok_or_else(|| Self::not_found(&format!("quote for {s}")))
130 }
131}
132
133#[async_trait]
134impl HistoryProvider for MockConnector {
135 async fn history(
136 &self,
137 instrument: &Instrument,
138 _req: HistoryRequest,
139 ) -> Result<HistoryResponse, BorsaError> {
140 let s = instrument.symbol_str();
141 Self::maybe_fail_or_timeout(s, "history").await?;
142 fixtures::history::by_symbol(s).ok_or_else(|| Self::not_found(&format!("history for {s}")))
143 }
144
145 fn supported_history_intervals(&self, _kind: AssetKind) -> &'static [Interval] {
146 const ONLY_D1: &[Interval] = &[Interval::D1];
147 ONLY_D1
148 }
149}
150
151#[async_trait]
152impl SearchProvider for MockConnector {
153 async fn search(&self, req: SearchRequest) -> Result<SearchResponse, BorsaError> {
154 Ok(fixtures::search::search(&req))
155 }
156}
157
158#[async_trait]
159impl ProfileProvider for MockConnector {
160 async fn profile(&self, instrument: &Instrument) -> Result<Profile, BorsaError> {
161 let s = instrument.symbol_str();
162 Ok(fixtures::profile::by_symbol(s))
163 }
164}
165
166#[async_trait]
167impl EarningsProvider for MockConnector {
168 async fn earnings(&self, instrument: &Instrument) -> Result<Earnings, BorsaError> {
169 let s = instrument.symbol_str();
170 Ok(fixtures::fundamentals::earnings_by_symbol(s))
171 }
172}
173
174#[async_trait]
175impl IncomeStatementProvider for MockConnector {
176 async fn income_statement(
177 &self,
178 instrument: &Instrument,
179 _q: bool,
180 ) -> Result<Vec<IncomeStatementRow>, BorsaError> {
181 let s = instrument.symbol_str();
182 Ok(fixtures::fundamentals::income_stmt_by_symbol(s))
183 }
184}
185
186#[async_trait]
187impl BalanceSheetProvider for MockConnector {
188 async fn balance_sheet(
189 &self,
190 instrument: &Instrument,
191 _q: bool,
192 ) -> Result<Vec<BalanceSheetRow>, BorsaError> {
193 let s = instrument.symbol_str();
194 Ok(fixtures::fundamentals::balance_sheet_by_symbol(s))
195 }
196}
197
198#[async_trait]
199impl CashflowProvider for MockConnector {
200 async fn cashflow(
201 &self,
202 instrument: &Instrument,
203 _q: bool,
204 ) -> Result<Vec<CashflowRow>, BorsaError> {
205 let s = instrument.symbol_str();
206 Ok(fixtures::fundamentals::cashflow_by_symbol(s))
207 }
208}
209
210#[async_trait]
211impl CalendarProvider for MockConnector {
212 async fn calendar(&self, instrument: &Instrument) -> Result<Calendar, BorsaError> {
213 let s = instrument.symbol_str();
214 Ok(fixtures::calendar::by_symbol(s))
215 }
216}
217
218#[async_trait]
219impl OptionsExpirationsProvider for MockConnector {
220 async fn options_expirations(&self, instrument: &Instrument) -> Result<Vec<i64>, BorsaError> {
221 let s = instrument.symbol_str();
222 Ok(fixtures::options::expirations_by_symbol(s))
223 }
224}
225
226#[async_trait]
227impl OptionChainProvider for MockConnector {
228 async fn option_chain(
229 &self,
230 instrument: &Instrument,
231 date: Option<i64>,
232 ) -> Result<OptionChain, BorsaError> {
233 let s = instrument.symbol_str();
234 Ok(fixtures::options::chain_by_symbol_and_date(s, date))
235 }
236}
237
238#[async_trait]
239impl RecommendationsProvider for MockConnector {
240 async fn recommendations(
241 &self,
242 instrument: &Instrument,
243 ) -> Result<Vec<RecommendationRow>, BorsaError> {
244 let s = instrument.symbol_str();
245 Ok(fixtures::analysis::recommendations_by_symbol(s))
246 }
247}
248
249#[async_trait]
250impl RecommendationsSummaryProvider for MockConnector {
251 async fn recommendations_summary(
252 &self,
253 instrument: &Instrument,
254 ) -> Result<RecommendationSummary, BorsaError> {
255 let s = instrument.symbol_str();
256 Ok(fixtures::analysis::recommendations_summary_by_symbol(s))
257 }
258}
259
260#[async_trait]
261impl AnalystPriceTargetProvider for MockConnector {
262 async fn analyst_price_target(
263 &self,
264 instrument: &Instrument,
265 ) -> Result<borsa_core::PriceTarget, BorsaError> {
266 let _s = instrument.symbol_str();
267 Ok(fixtures::analysis::price_target_by_symbol(_s))
268 }
269}
270
271#[async_trait]
272impl UpgradesDowngradesProvider for MockConnector {
273 async fn upgrades_downgrades(
274 &self,
275 instrument: &Instrument,
276 ) -> Result<Vec<UpgradeDowngradeRow>, BorsaError> {
277 let s = instrument.symbol_str();
278 Ok(fixtures::analysis::upgrades_downgrades_by_symbol(s))
279 }
280}
281
282#[async_trait]
283impl EsgProvider for MockConnector {
284 async fn sustainability(&self, instrument: &Instrument) -> Result<EsgScores, BorsaError> {
285 let s = instrument.symbol_str();
286 Ok(fixtures::esg::by_symbol(s))
287 }
288}
289
290#[async_trait]
291impl NewsProvider for MockConnector {
292 async fn news(
293 &self,
294 instrument: &Instrument,
295 req: NewsRequest,
296 ) -> Result<Vec<types::NewsArticle>, BorsaError> {
297 let s = instrument.symbol_str();
298 Ok(fixtures::news::by_symbol(s, req))
299 }
300}