use std::fmt;
use std::time::Duration;
#[derive(Debug, thiserror::Error)]
pub enum EngineError {
#[error("storage error: {0}")]
Storage(Box<dyn std::error::Error + Send + Sync>),
#[error("serialization error for step '{key}': {source}")]
Serialization {
key: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("step '{key}' failed: {source}")]
StepFailed {
key: String,
source: Box<dyn std::error::Error + Send + Sync>,
retryable: bool,
},
#[error("engine has not been started")]
NotStarted,
#[error("workflow '{0}' is not registered")]
WorkflowNotFound(String),
#[error("step '{key}' timed out after {duration:?}")]
StepTimeout {
key: String,
duration: Duration,
},
#[error("invalid key component ({label}): '{value}' must not contain '/'")]
InvalidKey {
label: &'static str,
value: String,
},
#[error("type mismatch for step '{key}': expected `{expected}`, found `{found}`")]
TypeMismatch {
key: String,
expected: String,
found: String,
},
#[error("signal rejected for step '{key}': {reason}")]
SignalRejected {
key: String,
reason: String,
},
#[error("signal superseded for step '{key}': another caller already claimed it")]
SignalSuperseded {
key: String,
},
#[error(
"step '{key}' has a suspended entry but was called as a regular step \
— the workflow code likely changed while an instance was suspended"
)]
SuspendedStepConflict {
key: String,
},
#[error("step '{key}' failed after {attempts} attempts: {source}")]
RetriesExhausted {
key: String,
attempts: u32,
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("workflow suspended at step '{key}'")]
Suspended {
key: String,
},
}
impl EngineError {
pub fn step_failed(
key: impl Into<String>,
source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
retryable: bool,
) -> Self {
Self::StepFailed {
key: key.into(),
source: source.into(),
retryable,
}
}
pub fn retries_exhausted(
key: impl Into<String>,
attempts: u32,
source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::RetriesExhausted {
key: key.into(),
attempts,
source: source.into(),
}
}
}
impl From<redb::DatabaseError> for EngineError {
fn from(err: redb::DatabaseError) -> Self {
Self::Storage(Box::new(err))
}
}
impl From<redb::TransactionError> for EngineError {
fn from(err: redb::TransactionError) -> Self {
Self::Storage(Box::new(err))
}
}
impl From<redb::TableError> for EngineError {
fn from(err: redb::TableError) -> Self {
Self::Storage(Box::new(err))
}
}
impl From<redb::StorageError> for EngineError {
fn from(err: redb::StorageError) -> Self {
Self::Storage(Box::new(err))
}
}
impl From<redb::CommitError> for EngineError {
fn from(err: redb::CommitError) -> Self {
Self::Storage(Box::new(err))
}
}
#[derive(Debug, thiserror::Error)]
pub enum SubscribeError {
#[error("no instance found for workflow '{workflow_name}' with id '{instance_id}'")]
NotFound {
workflow_name: String,
instance_id: String,
},
#[error(
"instance '{instance_id}' of workflow '{workflow_name}' has stale Running metadata \
(likely crashed) — consider calling resume()"
)]
StaleRunning {
workflow_name: String,
instance_id: String,
},
#[error("storage error: {0}")]
Storage(Box<dyn std::error::Error + Send + Sync>),
}
#[derive(Debug, thiserror::Error)]
pub enum StateError {
#[error("no instance found for workflow '{workflow_name}' with id '{instance_id}'")]
NotFound {
workflow_name: String,
instance_id: String,
},
#[error("storage error: {0}")]
Storage(Box<dyn std::error::Error + Send + Sync>),
}
#[derive(Debug)]
pub enum StepError {
Retryable(Box<dyn std::error::Error + Send + Sync>),
Permanent(Box<dyn std::error::Error + Send + Sync>),
}
impl StepError {
pub fn retryable(source: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
Self::Retryable(source.into())
}
pub fn permanent(source: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
Self::Permanent(source.into())
}
}
impl fmt::Display for StepError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Retryable(e) => write!(f, "retryable: {e}"),
Self::Permanent(e) => write!(f, "permanent: {e}"),
}
}
}
impl std::error::Error for StepError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Retryable(e) | Self::Permanent(e) => Some(e.as_ref()),
}
}
}