use serde::{Deserialize, Serialize};
use crate::Payload;
#[derive(Serialize, Deserialize, ts_rs::TS, Clone, Debug, PartialEq, Eq)]
pub enum ActivityErrorKind {
Retryable,
Terminal,
}
#[derive(Serialize, Deserialize, ts_rs::TS, Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[error("{message}")]
pub struct ActivityError {
pub kind: ActivityErrorKind,
pub message: String,
pub details: Option<Payload>,
}
impl ActivityError {
#[must_use]
pub fn is_retryable(&self) -> bool {
matches!(self.kind, ActivityErrorKind::Retryable)
}
}
#[derive(Serialize, Deserialize, ts_rs::TS, Clone, Debug, PartialEq, Eq, thiserror::Error)]
#[error("{message}")]
pub struct WorkflowError {
pub message: String,
pub details: Option<Payload>,
}
impl From<ActivityError> for WorkflowError {
fn from(error: ActivityError) -> Self {
Self {
message: error.message,
details: error.details,
}
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::{ActivityError, ActivityErrorKind, WorkflowError};
use crate::Payload;
#[test]
fn activity_error_reports_retryable_classification() {
let error = ActivityError {
kind: ActivityErrorKind::Retryable,
message: String::from("temporary outage"),
details: None,
};
assert!(error.is_retryable());
}
#[test]
fn activity_error_reports_terminal_classification() {
let error = ActivityError {
kind: ActivityErrorKind::Terminal,
message: String::from("invalid request"),
details: None,
};
assert!(!error.is_retryable());
}
#[test]
fn errors_round_trip_through_json() -> Result<(), Box<dyn std::error::Error>> {
let activity_error = ActivityError {
kind: ActivityErrorKind::Retryable,
message: String::from("connection reset"),
details: Some(Payload::from_json(&json!({"retry_after_ms": 500}))?),
};
let json = serde_json::to_string(&activity_error)?;
let decoded: ActivityError = serde_json::from_str(&json)?;
assert_eq!(activity_error, decoded);
let workflow_error = WorkflowError {
message: String::from("workflow failed"),
details: None,
};
let json = serde_json::to_string(&workflow_error)?;
let decoded: WorkflowError = serde_json::from_str(&json)?;
assert_eq!(workflow_error, decoded);
Ok(())
}
#[test]
fn workflow_error_from_activity_error_preserves_message_and_details()
-> Result<(), Box<dyn std::error::Error>> {
let details = Payload::from_json(&json!({"code": "rate_limited", "after_ms": 1000}))?;
let activity_error = ActivityError {
kind: ActivityErrorKind::Terminal,
message: String::from("activity failed permanently"),
details: Some(details.clone()),
};
let workflow_error = WorkflowError::from(activity_error);
assert_eq!(workflow_error.message, "activity failed permanently");
assert_eq!(workflow_error.details, Some(details));
Ok(())
}
}