use crate::error::{Error, Result};
use crate::provider::{is_terminal, StateProvider, WorkflowStatus, STATUS_CANCELLED, STATUS_ERROR};
use serde::de::DeserializeOwned;
use serde_json::Value;
use std::future::{Future, IntoFuture};
use std::marker::PhantomData;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::task::JoinHandle;
pub struct WorkflowHandle<O> {
id: String,
provider: Arc<dyn StateProvider>,
join: Arc<Mutex<Option<JoinHandle<Result<Value>>>>>,
poll_interval: Duration,
_marker: PhantomData<fn() -> O>,
}
impl<O> Clone for WorkflowHandle<O> {
fn clone(&self) -> Self {
Self {
id: self.id.clone(),
provider: self.provider.clone(),
join: self.join.clone(),
poll_interval: self.poll_interval,
_marker: PhantomData,
}
}
}
impl<O> WorkflowHandle<O> {
pub(crate) fn local(
id: String,
provider: Arc<dyn StateProvider>,
join: JoinHandle<Result<Value>>,
) -> Self {
Self {
id,
provider,
join: Arc::new(Mutex::new(Some(join))),
poll_interval: Duration::from_millis(100),
_marker: PhantomData,
}
}
pub(crate) fn polling(id: String, provider: Arc<dyn StateProvider>) -> Self {
Self {
id,
provider,
join: Arc::new(Mutex::new(None)),
poll_interval: Duration::from_millis(100),
_marker: PhantomData,
}
}
pub fn id(&self) -> &str {
&self.id
}
pub async fn get_status(&self) -> Result<WorkflowStatus> {
self.provider
.get_workflow_status(&self.id)
.await?
.ok_or_else(|| Error::UnknownWorkflow(self.id.clone()))
}
}
impl<O: DeserializeOwned> WorkflowHandle<O> {
pub async fn result(&self) -> Result<O> {
let owned = self
.join
.lock()
.expect("workflow handle mutex poisoned")
.take();
if let Some(join) = owned {
return match join.await {
Ok(res) => {
let value = res?;
Ok(serde_json::from_value(value)?)
}
Err(e) => Err(Error::app(format!("workflow task failed: {e}"))),
};
}
loop {
match self.get_status().await {
Ok(status) if is_terminal(&status.status) => {
return self.terminal_to_result(status);
}
Ok(_) | Err(Error::UnknownWorkflow(_)) => {}
Err(e) => return Err(e),
}
tokio::time::sleep(self.poll_interval).await;
}
}
fn terminal_to_result(&self, status: WorkflowStatus) -> Result<O> {
match status.status.as_str() {
STATUS_CANCELLED => Err(Error::Cancelled(self.id.clone())),
STATUS_ERROR => Err(match status.error_info {
Some(info) => Error::Portable(Box::new(info)),
None => Error::app(
status
.error
.unwrap_or_else(|| "workflow failed".to_string()),
),
}),
_ => {
let output = status.output.unwrap_or(Value::Null);
Ok(serde_json::from_value(output)?)
}
}
}
}
impl<O: DeserializeOwned + 'static> IntoFuture for WorkflowHandle<O> {
type Output = Result<O>;
type IntoFuture = Pin<Box<dyn Future<Output = Result<O>> + Send>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move { self.result().await })
}
}