use http::Method;
use tracing::instrument;
use crate::client::IgClient;
use crate::dealing::common::DealConfirmation;
use crate::dealing::positions::models::{
ClosePositionRequest, ClosePositionResponse, PositionV1, PositionV2, PositionsEnvelopeV1,
PositionsEnvelopeV2, UpdatePositionRequest, UpdatePositionResponse,
};
use crate::dealing::positions::open_position::{Missing, OpenPositionBuilder};
use crate::error::Result;
use crate::models::common::DealReference;
#[derive(Debug)]
pub struct PositionsApi<'a> {
pub(crate) client: &'a IgClient,
}
impl<'a> PositionsApi<'a> {
#[instrument(skip_all, name = "dealing.positions.list_v1")]
pub async fn list_v1(&self) -> Result<Vec<PositionV1>> {
let env: PositionsEnvelopeV1 = self
.client
.transport
.request(
Method::GET,
"positions",
Some(1),
None::<&()>,
&self.client.session,
)
.await?;
Ok(env.into_vec())
}
#[instrument(skip_all, name = "dealing.positions.list_v2")]
pub async fn list_v2(&self) -> Result<Vec<PositionV2>> {
let env: PositionsEnvelopeV2 = self
.client
.transport
.request(
Method::GET,
"positions",
Some(2),
None::<&()>,
&self.client.session,
)
.await?;
Ok(env.into_vec())
}
pub async fn list(&self) -> Result<Vec<PositionV2>> {
self.list_v2().await
}
#[instrument(skip_all, name = "dealing.positions.get", fields(deal_id = %deal_id))]
pub async fn get(&self, deal_id: impl AsRef<str> + std::fmt::Display) -> Result<PositionV2> {
let path = format!("positions/{}", deal_id.as_ref());
let env: PositionsEnvelopeV2 = self
.client
.transport
.request(
Method::GET,
&path,
Some(2),
None::<&()>,
&self.client.session,
)
.await?;
env.into_vec().into_iter().next().ok_or_else(|| {
crate::error::Error::InvalidInput(
"positions/{dealId} returned an empty positions array".into(),
)
})
}
pub fn open(
&'a self,
) -> OpenPositionBuilder<'a, Missing, Missing, Missing, Missing, Missing, Missing, Missing>
{
OpenPositionBuilder::new(self)
}
#[instrument(skip_all, name = "dealing.positions.update", fields(deal_id = %deal_id))]
pub async fn update(
&self,
deal_id: impl AsRef<str> + std::fmt::Display,
req: UpdatePositionRequest,
) -> Result<DealConfirmation> {
let path = format!("positions/otc/{}", deal_id.as_ref());
let resp: UpdatePositionResponse = self
.client
.transport
.request(
Method::PUT,
&path,
Some(2),
Some(&req),
&self.client.session,
)
.await?;
self.confirm(&resp.deal_reference).await
}
#[instrument(skip_all, name = "dealing.positions.close")]
pub async fn close(&self, req: ClosePositionRequest) -> Result<DealConfirmation> {
let resp: ClosePositionResponse = self
.client
.transport
.request(
Method::DELETE,
"positions/otc",
Some(1),
Some(&req),
&self.client.session,
)
.await?;
self.confirm(&resp.deal_reference).await
}
#[instrument(skip_all, name = "dealing.positions.confirm", fields(deal_reference = %deal_reference))]
pub async fn confirm(&self, deal_reference: &DealReference) -> Result<DealConfirmation> {
let path = format!("confirms/{}", deal_reference.as_str());
let mut last_err: Option<crate::error::Error> = None;
for attempt in 0..5u8 {
if attempt > 0 {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
match self
.client
.transport
.request::<(), DealConfirmation>(
Method::GET,
&path,
Some(1),
None::<&()>,
&self.client.session,
)
.await
{
Ok(c) => return Ok(c),
Err(e) => {
tracing::warn!(
attempt,
error = %e,
"deal confirmation not yet available, will retry"
);
last_err = Some(e);
}
}
}
Err(last_err.unwrap_or_else(|| {
crate::error::Error::InvalidInput(
"confirm: exhausted retries with no error recorded".into(),
)
}))
}
}