Skip to main content

trading_ig/repeat_dealing/
api.rs

1//! Repeat dealing API endpoint.
2
3use http::Method;
4use serde::Deserialize;
5use tracing::{instrument, warn};
6
7use crate::Result;
8use crate::client::IgClient;
9use crate::models::Epic;
10
11use super::models::RepeatDealingWindow;
12
13/// Typed accessor for the `/repeat-dealing-window` endpoint.
14///
15/// Obtain via [`IgClient::repeat_dealing`].
16#[derive(Debug)]
17pub struct RepeatDealingApi<'a> {
18    pub(crate) client: &'a IgClient,
19}
20
21#[derive(Deserialize)]
22#[serde(rename_all = "camelCase")]
23struct WindowEnvelope {
24    repeat_dealing_windows: Vec<RepeatDealingWindow>,
25}
26
27/// Maximum number of attempts for the repeat-dealing retry loop.
28const MAX_ATTEMPTS: u8 = 5;
29/// Delay between retry attempts (mirrors the Python library's 1-second back-off).
30const RETRY_DELAY: std::time::Duration = std::time::Duration::from_secs(1);
31
32impl RepeatDealingApi<'_> {
33    /// Fetch all active repeat-dealing windows.
34    ///
35    /// Retries up to 5 times with a 1-second delay on transient errors,
36    /// mirroring the Python library's behaviour.
37    #[instrument(skip(self))]
38    pub async fn window(&self) -> Result<Vec<RepeatDealingWindow>> {
39        self.fetch_with_retry("repeat-dealing-window").await
40    }
41
42    /// Fetch the repeat-dealing window for a specific epic.
43    ///
44    /// Retries up to 5 times with a 1-second delay on transient errors,
45    /// mirroring the Python library's behaviour.
46    #[instrument(skip(self), fields(epic = %epic))]
47    pub async fn window_for(&self, epic: &Epic) -> Result<Vec<RepeatDealingWindow>> {
48        let path = format!("repeat-dealing-window?epic={epic}");
49        self.fetch_with_retry(&path).await
50    }
51
52    /// Perform the request with up to [`MAX_ATTEMPTS`] retries on failure.
53    async fn fetch_with_retry(&self, path: &str) -> Result<Vec<RepeatDealingWindow>> {
54        let mut last_err = None;
55        for attempt in 1..=MAX_ATTEMPTS {
56            match self
57                .client
58                .transport
59                .request::<(), WindowEnvelope>(
60                    Method::GET,
61                    path,
62                    Some(1),
63                    None::<&()>,
64                    &self.client.session,
65                )
66                .await
67            {
68                Ok(envelope) => return Ok(envelope.repeat_dealing_windows),
69                Err(e) => {
70                    warn!(attempt, "repeat-dealing-window request failed, will retry");
71                    last_err = Some(e);
72                    if attempt < MAX_ATTEMPTS {
73                        tokio::time::sleep(RETRY_DELAY).await;
74                    }
75                }
76            }
77        }
78        Err(last_err.expect("loop always runs at least once"))
79    }
80}