Skip to main content

telltale_runtime/effects/
handler_context.rs

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