Skip to main content

telltale_runtime/effects/
handler_context.rs

1use super::{ChoreoResult, ChoreographyError};
2
3impl ChoreographyError {
4    /// Wrap this error with protocol context.
5    #[must_use]
6    pub fn with_protocol_context(
7        self,
8        protocol: &'static str,
9        role: &'static str,
10        phase: &'static str,
11    ) -> Self {
12        ChoreographyError::ProtocolContext {
13            protocol,
14            role,
15            phase,
16            inner: Box::new(self),
17        }
18    }
19
20    /// Wrap this error with role context.
21    #[must_use]
22    pub fn with_role_context(self, role: &'static str, index: Option<u32>) -> Self {
23        ChoreographyError::RoleContext {
24            role,
25            index,
26            inner: Box::new(self),
27        }
28    }
29
30    /// Wrap this error with message exchange context.
31    #[must_use]
32    pub fn with_message_context(
33        self,
34        operation: &'static str,
35        message_type: &'static str,
36        direction: &'static str,
37        other_role: &'static str,
38    ) -> Self {
39        ChoreographyError::MessageContext {
40            operation,
41            message_type,
42            direction,
43            other_role,
44            inner: Box::new(self),
45        }
46    }
47
48    /// Wrap this error with a generic context string.
49    #[must_use]
50    pub fn with_context(self, context: impl Into<String>) -> Self {
51        ChoreographyError::WithContext {
52            context: context.into(),
53            inner: Box::new(self),
54        }
55    }
56
57    /// Get the root cause of the error (unwrapping all context layers).
58    #[must_use]
59    pub fn root_cause(&self) -> &ChoreographyError {
60        match self {
61            ChoreographyError::ProtocolContext { inner, .. }
62            | ChoreographyError::RoleContext { inner, .. }
63            | ChoreographyError::MessageContext { inner, .. }
64            | ChoreographyError::WithContext { inner, .. } => inner.root_cause(),
65            _ => self,
66        }
67    }
68
69    /// Check if this error is a timeout.
70    #[must_use]
71    pub fn is_timeout(&self) -> bool {
72        matches!(self.root_cause(), ChoreographyError::Timeout(_))
73    }
74
75    /// Check if this error is a transport error.
76    #[must_use]
77    pub fn is_transport(&self) -> bool {
78        matches!(
79            self.root_cause(),
80            ChoreographyError::Transport(_)
81                | ChoreographyError::ChannelSendFailed { .. }
82                | ChoreographyError::ChannelClosed { .. }
83                | ChoreographyError::NoPeerChannel { .. }
84        )
85    }
86
87    /// Check if this error is a protocol violation.
88    #[must_use]
89    pub fn is_protocol_violation(&self) -> bool {
90        matches!(self.root_cause(), ChoreographyError::ProtocolViolation(_))
91    }
92
93    /// Check if this error is a serialization error.
94    #[must_use]
95    pub fn is_serialization(&self) -> bool {
96        matches!(
97            self.root_cause(),
98            ChoreographyError::Serialization(_)
99                | ChoreographyError::LabelSerializationFailed { .. }
100                | ChoreographyError::MessageSerializationFailed { .. }
101        )
102    }
103}
104
105/// Extension trait for adding context to Results.
106///
107/// This trait provides ergonomic methods for wrapping errors with
108/// protocol/role/phase context.
109pub trait ContextExt<T> {
110    /// Add protocol context to an error.
111    fn with_protocol_context(
112        self,
113        protocol: &'static str,
114        role: &'static str,
115        phase: &'static str,
116    ) -> ChoreoResult<T>;
117
118    /// Add role context to an error.
119    fn with_role_context(self, role: &'static str, index: Option<u32>) -> ChoreoResult<T>;
120
121    /// Add message context to an error.
122    fn with_message_context(
123        self,
124        operation: &'static str,
125        message_type: &'static str,
126        direction: &'static str,
127        other_role: &'static str,
128    ) -> ChoreoResult<T>;
129
130    /// Add generic context to an error.
131    fn with_context(self, context: impl Into<String>) -> ChoreoResult<T>;
132}
133
134impl<T> ContextExt<T> for ChoreoResult<T> {
135    fn with_protocol_context(
136        self,
137        protocol: &'static str,
138        role: &'static str,
139        phase: &'static str,
140    ) -> ChoreoResult<T> {
141        self.map_err(|e| e.with_protocol_context(protocol, role, phase))
142    }
143
144    fn with_role_context(self, role: &'static str, index: Option<u32>) -> ChoreoResult<T> {
145        self.map_err(|e| e.with_role_context(role, index))
146    }
147
148    fn with_message_context(
149        self,
150        operation: &'static str,
151        message_type: &'static str,
152        direction: &'static str,
153        other_role: &'static str,
154    ) -> ChoreoResult<T> {
155        self.map_err(|e| e.with_message_context(operation, message_type, direction, other_role))
156    }
157
158    fn with_context(self, context: impl Into<String>) -> ChoreoResult<T> {
159        self.map_err(|e| e.with_context(context))
160    }
161}