Skip to main content

restate_sdk_shared_core/
error.rs

1use crate::service_protocol::messages::ErrorBehavior;
2use crate::service_protocol::MessageType;
3use crate::CommandType;
4use std::borrow::Cow;
5use std::fmt;
6use std::time::Duration;
7
8// Export some stuff we need from the internal package
9pub use crate::vm::errors::{codes, InvocationErrorCode};
10
11// -- Error type
12
13#[derive(Debug, Clone, Eq, PartialEq)]
14pub(crate) struct CommandMetadata {
15    pub(crate) index: u32,
16    pub(crate) ty: MessageType,
17    pub(crate) name: Option<Cow<'static, str>>,
18}
19
20impl fmt::Display for CommandMetadata {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        write!(f, "{} ", self.ty)?;
23        if let Some(name) = &self.name {
24            write!(f, "[{name}]")?;
25        } else {
26            write!(f, "[{}]", self.index)?;
27        }
28        Ok(())
29    }
30}
31
32impl CommandMetadata {
33    pub(crate) fn new_named(
34        name: impl Into<Cow<'static, str>>,
35        index: u32,
36        ty: MessageType,
37    ) -> Self {
38        Self {
39            name: Some(name.into()),
40            index,
41            ty,
42        }
43    }
44
45    #[allow(unused)]
46    pub(crate) fn new(index: u32, ty: MessageType) -> Self {
47        Self {
48            name: None,
49            index,
50            ty,
51        }
52    }
53}
54
55#[derive(Debug, Clone, Eq, PartialEq)]
56pub(crate) enum NotificationMetadata {
57    RelatedToCommand(CommandMetadata),
58    Awakeable(String),
59    Cancellation,
60}
61
62impl fmt::Display for NotificationMetadata {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        match self {
65            NotificationMetadata::RelatedToCommand(cmd) => write!(f, "{}", cmd),
66            NotificationMetadata::Awakeable(awk_id) => write!(f, "Awakeable {}", awk_id),
67            NotificationMetadata::Cancellation => write!(f, "Cancellation"),
68        }
69    }
70}
71
72#[derive(Debug, Clone, Eq, PartialEq)]
73pub struct Error {
74    pub(crate) code: u16,
75    pub(crate) message: Cow<'static, str>,
76    pub(crate) stacktrace: String,
77    pub(crate) related_command: Option<CommandMetadata>,
78    pub(crate) next_retry_delay: Option<Duration>,
79    pub(crate) behavior: ErrorBehavior,
80}
81
82impl fmt::Display for Error {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        write!(f, "({}) {}", self.code, self.message)?;
85        if !self.stacktrace.is_empty() {
86            write!(f, "\nStacktrace: {}", self.stacktrace)?;
87        }
88        if let Some(related_command) = &self.related_command {
89            write!(f, "\nRelated command: {related_command}")?;
90        }
91
92        Ok(())
93    }
94}
95
96impl std::error::Error for Error {}
97
98impl Error {
99    pub fn new(code: impl Into<u16>, message: impl Into<Cow<'static, str>>) -> Self {
100        Error {
101            code: code.into(),
102            message: message.into(),
103            stacktrace: Default::default(),
104            related_command: None,
105            next_retry_delay: None,
106            behavior: ErrorBehavior::Retry,
107        }
108    }
109
110    pub fn internal(message: impl Into<Cow<'static, str>>) -> Self {
111        Self::new(codes::INTERNAL, message)
112    }
113
114    pub fn code(&self) -> u16 {
115        self.code
116    }
117
118    pub fn message(&self) -> &str {
119        &self.message
120    }
121
122    pub fn description(&self) -> &str {
123        &self.stacktrace
124    }
125
126    pub fn with_stacktrace(mut self, stacktrace: impl ToString) -> Self {
127        self.stacktrace = stacktrace.to_string();
128        self
129    }
130
131    pub fn with_next_retry_delay_override(mut self, delay: Duration) -> Self {
132        self.next_retry_delay = Some(delay);
133        self
134    }
135
136    /// When set to `true`, the runtime will pause the invocation instead of retrying
137    /// after this error. Requires service protocol V7 or newer.
138    pub fn with_should_pause(mut self, should_pause: bool) -> Self {
139        self.behavior = if should_pause {
140            ErrorBehavior::Pause
141        } else {
142            ErrorBehavior::Retry
143        };
144        self
145    }
146
147    pub fn is_suspended_error(&self) -> bool {
148        self == &crate::vm::errors::SUSPENDED
149    }
150
151    pub(crate) fn with_related_command_metadata(
152        mut self,
153        related_command: CommandMetadata,
154    ) -> Self {
155        self.related_command = Some(related_command);
156        self
157    }
158}
159
160impl From<CommandType> for MessageType {
161    fn from(value: CommandType) -> Self {
162        match value {
163            CommandType::Input => MessageType::InputCommand,
164            CommandType::Output => MessageType::OutputCommand,
165            CommandType::GetState => MessageType::GetLazyStateCommand,
166            CommandType::GetStateKeys => MessageType::GetLazyStateKeysCommand,
167            CommandType::SetState => MessageType::SetStateCommand,
168            CommandType::ClearState => MessageType::ClearStateCommand,
169            CommandType::ClearAllState => MessageType::ClearAllStateCommand,
170            CommandType::GetPromise => MessageType::GetPromiseCommand,
171            CommandType::PeekPromise => MessageType::PeekPromiseCommand,
172            CommandType::CompletePromise => MessageType::CompletePromiseCommand,
173            CommandType::Sleep => MessageType::SleepCommand,
174            CommandType::Call => MessageType::CallCommand,
175            CommandType::OneWayCall => MessageType::OneWayCallCommand,
176            CommandType::SendSignal => MessageType::SendSignalCommand,
177            CommandType::Run => MessageType::RunCommand,
178            CommandType::AttachInvocation => MessageType::AttachInvocationCommand,
179            CommandType::GetInvocationOutput => MessageType::GetInvocationOutputCommand,
180            CommandType::CompleteAwakeable => MessageType::CompleteAwakeableCommand,
181            CommandType::CancelInvocation => MessageType::SendSignalCommand,
182        }
183    }
184}
185
186impl TryFrom<MessageType> for CommandType {
187    type Error = MessageType;
188
189    fn try_from(value: MessageType) -> Result<Self, Self::Error> {
190        match value {
191            MessageType::InputCommand => Ok(CommandType::Input),
192            MessageType::OutputCommand => Ok(CommandType::Output),
193            MessageType::GetLazyStateCommand | MessageType::GetEagerStateCommand => {
194                Ok(CommandType::GetState)
195            }
196            MessageType::GetLazyStateKeysCommand | MessageType::GetEagerStateKeysCommand => {
197                Ok(CommandType::GetStateKeys)
198            }
199            MessageType::SetStateCommand => Ok(CommandType::SetState),
200            MessageType::ClearStateCommand => Ok(CommandType::ClearState),
201            MessageType::ClearAllStateCommand => Ok(CommandType::ClearAllState),
202            MessageType::GetPromiseCommand => Ok(CommandType::GetPromise),
203            MessageType::PeekPromiseCommand => Ok(CommandType::PeekPromise),
204            MessageType::CompletePromiseCommand => Ok(CommandType::CompletePromise),
205            MessageType::SleepCommand => Ok(CommandType::Sleep),
206            MessageType::CallCommand => Ok(CommandType::Call),
207            MessageType::OneWayCallCommand => Ok(CommandType::OneWayCall),
208            MessageType::SendSignalCommand => Ok(CommandType::SendSignal),
209            MessageType::RunCommand => Ok(CommandType::Run),
210            MessageType::AttachInvocationCommand => Ok(CommandType::AttachInvocation),
211            MessageType::GetInvocationOutputCommand => Ok(CommandType::GetInvocationOutput),
212            MessageType::CompleteAwakeableCommand => Ok(CommandType::CompleteAwakeable),
213            _ => Err(value),
214        }
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn test_message_type_to_command_type_conversion() {
224        // Test successful conversions
225        assert_eq!(
226            CommandType::try_from(MessageType::InputCommand).unwrap(),
227            CommandType::Input
228        );
229        assert_eq!(
230            CommandType::try_from(MessageType::OutputCommand).unwrap(),
231            CommandType::Output
232        );
233        assert_eq!(
234            CommandType::try_from(MessageType::GetLazyStateCommand).unwrap(),
235            CommandType::GetState
236        );
237        assert_eq!(
238            CommandType::try_from(MessageType::GetLazyStateKeysCommand).unwrap(),
239            CommandType::GetStateKeys
240        );
241        assert_eq!(
242            CommandType::try_from(MessageType::SetStateCommand).unwrap(),
243            CommandType::SetState
244        );
245        assert_eq!(
246            CommandType::try_from(MessageType::ClearStateCommand).unwrap(),
247            CommandType::ClearState
248        );
249        assert_eq!(
250            CommandType::try_from(MessageType::ClearAllStateCommand).unwrap(),
251            CommandType::ClearAllState
252        );
253        assert_eq!(
254            CommandType::try_from(MessageType::GetPromiseCommand).unwrap(),
255            CommandType::GetPromise
256        );
257        assert_eq!(
258            CommandType::try_from(MessageType::PeekPromiseCommand).unwrap(),
259            CommandType::PeekPromise
260        );
261        assert_eq!(
262            CommandType::try_from(MessageType::CompletePromiseCommand).unwrap(),
263            CommandType::CompletePromise
264        );
265        assert_eq!(
266            CommandType::try_from(MessageType::SleepCommand).unwrap(),
267            CommandType::Sleep
268        );
269        assert_eq!(
270            CommandType::try_from(MessageType::CallCommand).unwrap(),
271            CommandType::Call
272        );
273        assert_eq!(
274            CommandType::try_from(MessageType::OneWayCallCommand).unwrap(),
275            CommandType::OneWayCall
276        );
277        assert_eq!(
278            CommandType::try_from(MessageType::SendSignalCommand).unwrap(),
279            CommandType::SendSignal
280        );
281        assert_eq!(
282            CommandType::try_from(MessageType::RunCommand).unwrap(),
283            CommandType::Run
284        );
285        assert_eq!(
286            CommandType::try_from(MessageType::AttachInvocationCommand).unwrap(),
287            CommandType::AttachInvocation
288        );
289        assert_eq!(
290            CommandType::try_from(MessageType::GetInvocationOutputCommand).unwrap(),
291            CommandType::GetInvocationOutput
292        );
293        assert_eq!(
294            CommandType::try_from(MessageType::CompleteAwakeableCommand).unwrap(),
295            CommandType::CompleteAwakeable
296        );
297
298        // Test failed conversions
299        assert_eq!(
300            CommandType::try_from(MessageType::Start).err().unwrap(),
301            MessageType::Start
302        );
303        assert_eq!(
304            CommandType::try_from(MessageType::End).err().unwrap(),
305            MessageType::End
306        );
307        assert_eq!(
308            CommandType::try_from(MessageType::GetLazyStateCompletionNotification)
309                .err()
310                .unwrap(),
311            MessageType::GetLazyStateCompletionNotification
312        );
313    }
314}