use std::borrow::Cow;
use std::future::Future;
use std::time::{Duration, Instant};
use bytes::Bytes;
use reqwest::Method;
use reqwest_middleware::RequestBuilder;
use serde::de::DeserializeOwned;
use super::join;
use super::model::{TaskInfo, TaskStatus};
use crate::client::{Client, Headers};
use crate::error::{Error, Result};
#[derive(Debug, Clone)]
pub struct TaskHandle {
client: Client,
route: Cow<'static, str>,
task_id: String,
headers: Headers,
}
impl TaskHandle {
pub(super) fn new(
client: Client,
route: Cow<'static, str>,
task_id: String,
headers: Headers,
) -> Self {
Self {
client,
route,
task_id,
headers,
}
}
pub fn task_id(&self) -> &str {
&self.task_id
}
#[cfg(feature = "tracing")]
fn request_id(&self) -> Option<&str> {
self.headers.request_id()
}
fn request(&self, op: &str, method: Method) -> Result<RequestBuilder> {
let url = self
.client
.endpoint_query(&join(&self.route, op), "task_id", &self.task_id)?;
let req = match method {
Method::GET => self.client.get_url(url),
Method::PUT => self.client.put_url(url),
_ => self.client.post_url(url),
};
self.headers.apply(req)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip(self), fields(route = %self.route, task_id = %self.task_id, request_id = self.request_id()), err)
)]
pub async fn status(&self) -> Result<TaskStatus> {
let req = self.request("status", Method::GET)?;
let info: TaskInfo = self.client.send(req).await?.json().await?;
Ok(info.status)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip(self, sleep), fields(route = %self.route, task_id = %self.task_id, request_id = self.request_id()), err)
)]
pub async fn wait<S, F>(
&self,
timeout: Duration,
interval: Duration,
mut sleep: S,
) -> Result<TaskStatus>
where
S: FnMut(Duration) -> F + Send,
F: Future<Output = ()> + Send,
{
let deadline = Instant::now() + timeout;
loop {
let status = self.status().await?;
if status.is_terminal() {
return Ok(status);
}
if Instant::now() >= deadline {
return Err(Error::Timeout { timeout });
}
sleep(interval).await;
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip(self), fields(route = %self.route, task_id = %self.task_id, request_id = self.request_id()), err)
)]
pub async fn json<R: DeserializeOwned>(&self) -> Result<R> {
Ok(self.result_response().await?.json().await?)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip(self), fields(route = %self.route, task_id = %self.task_id, request_id = self.request_id()), err)
)]
pub async fn bytes(&self) -> Result<Bytes> {
Ok(self.result_response().await?.bytes().await?)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip(self), fields(route = %self.route, task_id = %self.task_id, request_id = self.request_id()), err)
)]
pub async fn text(&self) -> Result<String> {
Ok(self.result_response().await?.text().await?)
}
async fn result_response(&self) -> Result<reqwest::Response> {
let status = self.status().await?;
if status != TaskStatus::Completed {
return Err(Error::TaskNotComplete {
task_id: self.task_id.clone(),
status,
});
}
let req = self.request("get", Method::GET)?;
self.client.send(req).await
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip(self), fields(route = %self.route, task_id = %self.task_id, request_id = self.request_id()), err)
)]
pub async fn retry(&self) -> Result<TaskHandle> {
let req = self.request("retry", Method::POST)?;
let info: TaskInfo = self.client.send(req).await?.json().await?;
Ok(TaskHandle {
client: self.client.clone(),
route: self.route.clone(),
task_id: info.task_id,
headers: self.headers.clone(),
})
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(skip(self), fields(route = %self.route, task_id = %self.task_id, request_id = self.request_id()), err)
)]
pub async fn cancel(&self) -> Result<()> {
let req = self.request("cancel", Method::PUT)?;
self.client.send(req).await?;
Ok(())
}
}