ig_client/application/services/
market_service.rs

1use crate::application::services::MarketService;
2use crate::{
3    application::models::market::{
4        HistoricalPricesResponse, MarketDetails, MarketNavigationResponse, MarketSearchResult,
5    },
6    config::Config,
7    error::AppError,
8    session::interface::IgSession,
9    transport::http_client::IgHttpClient,
10};
11use async_trait::async_trait;
12use reqwest::Method;
13use std::sync::Arc;
14use tracing::{debug, info};
15
16/// Implementation of the market service
17pub struct MarketServiceImpl<T: IgHttpClient> {
18    config: Arc<Config>,
19    client: Arc<T>,
20}
21
22impl<T: IgHttpClient> MarketServiceImpl<T> {
23    /// Creates a new instance of the market service
24    pub fn new(config: Arc<Config>, client: Arc<T>) -> Self {
25        Self { config, client }
26    }
27
28    /// Gets the current configuration
29    ///
30    /// # Returns
31    /// * Reference to the current configuration
32    pub fn get_config(&self) -> &Config {
33        &self.config
34    }
35
36    /// Sets a new configuration
37    ///
38    /// # Arguments
39    /// * `config` - The new configuration to use
40    pub fn set_config(&mut self, config: Arc<Config>) {
41        self.config = config;
42    }
43}
44
45#[async_trait]
46impl<T: IgHttpClient + 'static> MarketService for MarketServiceImpl<T> {
47    async fn search_markets(
48        &self,
49        session: &IgSession,
50        search_term: &str,
51    ) -> Result<MarketSearchResult, AppError> {
52        let path = format!("markets?searchTerm={}", search_term);
53        info!("Searching markets with term: {}", search_term);
54
55        let result = self
56            .client
57            .request::<(), MarketSearchResult>(Method::GET, &path, session, None, "1")
58            .await?;
59
60        debug!("{} markets found", result.markets.len());
61        Ok(result)
62    }
63
64    async fn get_market_details(
65        &self,
66        session: &IgSession,
67        epic: &str,
68    ) -> Result<MarketDetails, AppError> {
69        let path = format!("markets/{}", epic);
70        info!("Getting market details: {}", epic);
71
72        let result = self
73            .client
74            .request::<(), MarketDetails>(Method::GET, &path, session, None, "3")
75            .await?;
76
77        debug!("Market details obtained for: {}", epic);
78        Ok(result)
79    }
80
81    async fn get_multiple_market_details(
82        &self,
83        session: &IgSession,
84        epics: &[String],
85    ) -> Result<Vec<MarketDetails>, AppError> {
86        if epics.is_empty() {
87            return Ok(Vec::new());
88        }
89
90        // Join the EPICs with commas to create a single request
91        let epics_str = epics.join(",");
92        let path = format!("markets?epics={}", epics_str);
93
94        info!(
95            "Getting market details for {} EPICs in a batch: {}",
96            epics.len(),
97            epics_str
98        );
99
100        // The API returns an object with un array de MarketDetails en la propiedad marketDetails
101        #[derive(serde::Deserialize)]
102        struct MarketDetailsResponse {
103            #[serde(rename = "marketDetails")]
104            market_details: Vec<MarketDetails>,
105        }
106
107        let response = self
108            .client
109            .request::<(), MarketDetailsResponse>(Method::GET, &path, session, None, "2")
110            .await?;
111
112        debug!(
113            "Market details obtained for {} EPICs",
114            response.market_details.len()
115        );
116        Ok(response.market_details)
117    }
118
119    async fn get_historical_prices(
120        &self,
121        session: &IgSession,
122        epic: &str,
123        resolution: &str,
124        from: &str,
125        to: &str,
126    ) -> Result<HistoricalPricesResponse, AppError> {
127        let path = format!(
128            "prices/{}?resolution={}&from={}&to={}",
129            epic, resolution, from, to
130        );
131        info!("Getting historical prices for: {}", epic);
132
133        let result = self
134            .client
135            .request::<(), HistoricalPricesResponse>(Method::GET, &path, session, None, "3")
136            .await?;
137
138        debug!("Historical prices obtained for: {}", epic);
139        Ok(result)
140    }
141
142    async fn get_market_navigation(
143        &self,
144        session: &IgSession,
145    ) -> Result<MarketNavigationResponse, AppError> {
146        let path = "marketnavigation";
147        info!("Getting top-level market navigation nodes");
148
149        let result = self
150            .client
151            .request::<(), MarketNavigationResponse>(Method::GET, path, session, None, "1")
152            .await?;
153
154        debug!("{} navigation nodes found", result.nodes.len());
155        debug!("{} markets found at root level", result.markets.len());
156        Ok(result)
157    }
158
159    async fn get_market_navigation_node(
160        &self,
161        session: &IgSession,
162        node_id: &str,
163    ) -> Result<MarketNavigationResponse, AppError> {
164        let path = format!("marketnavigation/{}", node_id);
165        info!("Getting market navigation node: {}", node_id);
166
167        let result = self
168            .client
169            .request::<(), MarketNavigationResponse>(Method::GET, &path, session, None, "1")
170            .await?;
171
172        debug!("{} child nodes found", result.nodes.len());
173        debug!("{} markets found in node {}", result.markets.len(), node_id);
174        Ok(result)
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181    use crate::config::Config;
182    use crate::transport::http_client::IgHttpClientImpl;
183    use std::sync::Arc;
184
185    #[test]
186    fn test_get_and_set_config() {
187        let config = Arc::new(Config::new());
188        let client = Arc::new(IgHttpClientImpl::new(config.clone()));
189        let mut service = MarketServiceImpl::new(config.clone(), client.clone());
190        assert!(std::ptr::eq(service.get_config(), &*config));
191        let new_cfg = Arc::new(Config::default());
192        service.set_config(new_cfg.clone());
193        assert!(std::ptr::eq(service.get_config(), &*new_cfg));
194    }
195}