Skip to main content

runifold_effect/
error.rs

1use runifold_core::{JournalError, RunError};
2use thiserror::Error;
3
4/// Normalized effect-coordination failure category.
5#[derive(Clone, Debug, Eq, PartialEq)]
6#[non_exhaustive]
7pub enum EffectExecutorErrorKind {
8    /// The owning Run lacks the required capability.
9    CapabilityDenied,
10    /// An idempotency key was reused for different work.
11    IdempotencyConflict,
12    /// Recovery cannot prove that retry is safe.
13    Ambiguous,
14    /// The effect store rejected an operation.
15    Store,
16    /// Structured event recording failed.
17    Observability,
18    /// The handler failed.
19    Handler,
20    /// Execution was cancelled.
21    Cancelled,
22    /// The effective deadline elapsed.
23    DeadlineExceeded,
24    /// Stored state violated the protocol.
25    Protocol,
26    /// A remote reconciliation query failed without resolving the effect.
27    Reconciliation,
28}
29
30/// Structured failure from effect coordination.
31#[derive(Clone, Debug, Error, PartialEq)]
32#[error("{kind:?}: {message}")]
33pub struct EffectExecutorError {
34    /// Normalized category.
35    pub kind: EffectExecutorErrorKind,
36    /// Safe failure explanation.
37    pub message: String,
38    /// Original handler error, when applicable.
39    #[source]
40    pub source_error: Option<RunError>,
41}
42
43impl EffectExecutorError {
44    /// Creates an error without a handler source.
45    pub fn new(kind: EffectExecutorErrorKind, message: impl Into<String>) -> Self {
46        Self {
47            kind,
48            message: message.into(),
49            source_error: None,
50        }
51    }
52
53    pub(crate) fn handler(error: RunError) -> Self {
54        Self {
55            kind: EffectExecutorErrorKind::Handler,
56            message: error.to_string(),
57            source_error: Some(error),
58        }
59    }
60
61    pub(crate) fn reconciliation(error: RunError) -> Self {
62        Self {
63            kind: EffectExecutorErrorKind::Reconciliation,
64            message: error.to_string(),
65            source_error: Some(error),
66        }
67    }
68
69    pub(crate) fn ambiguous_handler(error: RunError) -> Self {
70        Self {
71            kind: EffectExecutorErrorKind::Ambiguous,
72            message: "effect handler failed after the remote outcome became uncertain".into(),
73            source_error: Some(error),
74        }
75    }
76}
77
78impl From<JournalError> for EffectExecutorError {
79    fn from(error: JournalError) -> Self {
80        Self::new(EffectExecutorErrorKind::Observability, error.to_string())
81    }
82}