use std::sync::Arc;
use aion_client::{Client, StartOptions};
use aion_core::{RunId, WorkflowId, WorkflowStatus};
use serde::Serialize;
use tokio::runtime::Runtime;
use super::conversation::WorkflowConversation;
use super::error::{WorkflowCallError, WorkflowConnectError};
use super::id::{WorkflowConversationId, WorkflowIdentity, WorkflowRunId};
use super::outcome::{ReopenedRun, WorkflowPhase};
#[derive(Clone, Debug)]
pub struct WorkflowAttachment {
endpoint: String,
namespace: Option<String>,
}
impl WorkflowAttachment {
#[must_use]
pub fn new(endpoint: impl Into<String>) -> Self {
Self {
endpoint: endpoint.into(),
namespace: None,
}
}
#[must_use]
pub fn with_namespace(mut self, namespace: impl Into<String>) -> Self {
self.namespace = Some(namespace.into());
self
}
}
pub struct WorkflowClient {
pub(super) runtime: Arc<Runtime>,
pub(super) client: Client,
}
impl WorkflowClient {
pub fn connect(attachment: &WorkflowAttachment) -> Result<Self, WorkflowConnectError> {
let runtime = build_runtime()?;
let mut builder = Client::builder(attachment.endpoint.clone());
if let Some(namespace) = &attachment.namespace {
builder = builder.with_namespace(namespace.clone());
}
let client =
runtime
.block_on(builder.build())
.map_err(|error| WorkflowConnectError::Endpoint {
message: error.to_string(),
})?;
Ok(Self {
runtime: Arc::new(runtime),
client,
})
}
pub fn from_substrate_client(
client: aion_client::Client,
) -> Result<Self, WorkflowConnectError> {
Ok(Self {
runtime: Arc::new(build_runtime()?),
client,
})
}
pub fn open<T>(
&self,
workflow_kind: &str,
input: &T,
idempotency_key: &str,
) -> Result<WorkflowConversation, WorkflowCallError>
where
T: Serialize + ?Sized,
{
let options = StartOptions {
idempotency_key: Some(idempotency_key.to_owned()),
..StartOptions::default()
};
let handle = self
.runtime
.block_on(self.client.start_typed(workflow_kind, input, options))
.map_err(|error| WorkflowCallError::from_client(&error))?;
let identity = WorkflowIdentity {
conversation: WorkflowConversationId::from_uuid(handle.workflow_id().as_uuid()),
run: WorkflowRunId::from_uuid(handle.run_id().as_uuid()),
};
Ok(self.conversation(identity))
}
#[must_use]
pub fn conversation(&self, identity: WorkflowIdentity) -> WorkflowConversation {
WorkflowConversation {
runtime: Arc::clone(&self.runtime),
client: self.client.clone(),
identity,
}
}
pub fn reopen(
&self,
conversation: WorkflowConversationId,
run: Option<WorkflowRunId>,
) -> Result<ReopenedRun, WorkflowCallError> {
let workflow_id = WorkflowId::new(conversation.as_uuid());
let run_id = run.map(|run| RunId::new(run.as_uuid()));
let outcome = self
.runtime
.block_on(self.client.reopen(&workflow_id, run_id.as_ref()))
.map_err(|error| WorkflowCallError::from_client(&error))?;
Ok(ReopenedRun {
run: WorkflowRunId::from_uuid(outcome.run_id.as_uuid()),
phase: phase_from_status(outcome.status),
})
}
}
fn build_runtime() -> Result<Runtime, WorkflowConnectError> {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.enable_all()
.build()
.map_err(|error| WorkflowConnectError::Runtime {
message: error.to_string(),
})
}
pub(super) fn phase_from_status(status: WorkflowStatus) -> WorkflowPhase {
match status {
WorkflowStatus::Running => WorkflowPhase::Running,
WorkflowStatus::Completed => WorkflowPhase::Completed,
WorkflowStatus::Failed => WorkflowPhase::Failed,
WorkflowStatus::Cancelled => WorkflowPhase::Cancelled,
WorkflowStatus::TimedOut => WorkflowPhase::TimedOut,
WorkflowStatus::ContinuedAsNew => WorkflowPhase::ContinuedAsNew,
WorkflowStatus::Paused => WorkflowPhase::Paused,
}
}