use std::time::Duration;
use reqwest::Method;
use serde_json::json;
use tokio::time::Instant;
use wyrd_spec::error::WyrdError;
use wyrd_spec::ids::ExecutionId;
use wyrd_spec::verification::VerificationExecutionStatus;
use wyrd_spec::verification::execution::{VerificationExecutionState, VerificationResult};
use crate::client::WyrdClient;
use crate::error::code_to_wyrd_error;
const FIRST_POLL: Duration = Duration::from_millis(100);
const MAX_POLL: Duration = Duration::from_secs(2);
#[derive(Debug, Clone)]
pub struct Execution {
id: ExecutionId,
client: WyrdClient,
}
impl Execution {
pub(crate) fn new(id: ExecutionId, client: WyrdClient) -> Self {
Self { id, client }
}
pub async fn connect(id: ExecutionId) -> Result<Self, WyrdError> {
Ok(Self::new(id, WyrdClient::connect_ambient().await?))
}
pub async fn connect_with(id: ExecutionId, client: &WyrdClient) -> Result<Self, WyrdError> {
Ok(Self::new(id, client.connected().await?))
}
#[must_use]
pub fn id(&self) -> &ExecutionId {
&self.id
}
pub async fn status(&self) -> Result<VerificationExecutionState, WyrdError> {
self.client
.http
.request_json(
Method::GET,
&format!("/v1/verification/executions/{}", self.id),
None::<&()>,
)
.await
}
pub async fn wait(&self, timeout: Duration) -> Result<VerificationResult, WyrdError> {
self.wait_until(Instant::now() + timeout).await
}
pub async fn wait_until(&self, deadline: Instant) -> Result<VerificationResult, WyrdError> {
let mut pause = FIRST_POLL;
let mut last = None;
loop {
let read = if Instant::now() < deadline {
tokio::time::timeout_at(deadline, self.status()).await.ok()
} else {
None
};
let Some(read) = read else {
return Err(Self::wait_timed_out(&self.id, last));
};
match read {
Ok(state) => match (state.status, state.result, state.error) {
(_, Some(result), _) => return Ok(result),
(
VerificationExecutionStatus::Errored
| VerificationExecutionStatus::Cancelled
| VerificationExecutionStatus::TimedOut,
None,
error,
) => {
let details = json!({ "execution_id": self.id, "status": state.status });
return Err(match error {
Some(error) => code_to_wyrd_error(&error.code, error.message, details),
None => WyrdError::Internal {
message: "execution settled without its error".to_owned(),
details,
},
});
}
(status, None, _) => last = Some(status),
},
Err(error) if error.status() == 404 || error.status() >= 500 => {}
Err(error) => return Err(error),
}
tokio::time::sleep_until(deadline.min(Instant::now() + pause)).await;
pause = (pause * 2).min(MAX_POLL);
}
}
#[must_use]
pub fn wait_timed_out(
id: &ExecutionId,
last: Option<VerificationExecutionStatus>,
) -> WyrdError {
WyrdError::SdkVerificationWaitTimedOut {
message: format!("execution {id} did not settle before the wait deadline"),
details: json!({ "execution_id": id, "last_status": last }),
}
}
}