Skip to main content

sayiir_runtime/
error.rs

1//! Typed error for the sayiir runtime layer.
2
3use sayiir_core::error::{BoxError, BuildError, BuildErrors, CodecError, WorkflowError};
4use sayiir_persistence::BackendError;
5
6/// Typed error for the sayiir runtime layer.
7///
8/// Replaces `BoxError` in internal runtime APIs, keeping `BoxError` only at
9/// true user boundaries (codec traits, user task callbacks).
10#[derive(Debug, thiserror::Error)]
11pub enum RuntimeError {
12    /// Workflow logic error (cancellation, definition mismatch, task not found, etc.)
13    #[error(transparent)]
14    Workflow(#[from] WorkflowError),
15
16    /// Build/hydration errors (duplicate IDs, missing tasks, empty branches).
17    #[error(transparent)]
18    Build(#[from] BuildErrors),
19
20    /// Persistent backend error (storage failures).
21    #[error(transparent)]
22    Backend(#[from] BackendError),
23
24    /// Codec encode/decode error (schema mismatch, serialization failure).
25    #[error(transparent)]
26    Codec(#[from] CodecError),
27
28    /// User task execution error (opaque — from user-provided code).
29    #[error(transparent)]
30    Task(BoxError),
31
32    /// Tokio task join error (branch spawn failures).
33    #[error(transparent)]
34    Join(#[from] tokio::task::JoinError),
35
36    /// A workflow instance with this ID already exists (conflict policy = Fail).
37    #[error("Workflow instance already exists: {0}")]
38    InstanceAlreadyExists(String),
39}
40
41impl From<BoxError> for RuntimeError {
42    fn from(err: BoxError) -> Self {
43        match err.downcast::<CodecError>() {
44            Ok(codec_err) => Self::Codec(*codec_err),
45            Err(other) => Self::Task(other),
46        }
47    }
48}
49
50impl From<BuildError> for RuntimeError {
51    fn from(error: BuildError) -> Self {
52        Self::Build(BuildErrors::from(error))
53    }
54}
55
56impl RuntimeError {
57    /// Returns `true` if this error is a `TaskTimedOut` workflow error.
58    #[must_use]
59    pub fn is_timeout(&self) -> bool {
60        matches!(self, Self::Workflow(WorkflowError::TaskTimedOut { .. }))
61    }
62
63    /// Returns `true` if this error is a codec decode failure (schema mismatch).
64    #[must_use]
65    pub fn is_decode_error(&self) -> bool {
66        matches!(self, Self::Codec(CodecError::DecodeFailed { .. }))
67    }
68}