restate_sdk_shared_core/vm/
errors.rs

1use crate::fmt::DiffFormatter;
2use crate::service_protocol::messages::{CommandMessageHeaderDiff, RestateMessage};
3use crate::service_protocol::{ContentTypeError, DecodingError, MessageType};
4use crate::{Error, Version};
5use std::borrow::Cow;
6use std::fmt;
7// Error codes
8
9#[derive(Copy, Clone, PartialEq, Eq)]
10pub struct InvocationErrorCode(u16);
11
12impl InvocationErrorCode {
13    pub const fn new(code: u16) -> Self {
14        InvocationErrorCode(code)
15    }
16
17    pub const fn code(self) -> u16 {
18        self.0
19    }
20}
21
22impl fmt::Debug for InvocationErrorCode {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        write!(f, "{}", self.0)
25    }
26}
27
28impl fmt::Display for InvocationErrorCode {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        fmt::Debug::fmt(self, f)
31    }
32}
33
34impl From<u16> for InvocationErrorCode {
35    fn from(value: u16) -> Self {
36        InvocationErrorCode(value)
37    }
38}
39
40impl From<u32> for InvocationErrorCode {
41    fn from(value: u32) -> Self {
42        value
43            .try_into()
44            .map(InvocationErrorCode)
45            .unwrap_or(codes::INTERNAL)
46    }
47}
48
49impl From<InvocationErrorCode> for u16 {
50    fn from(value: InvocationErrorCode) -> Self {
51        value.0
52    }
53}
54
55impl From<InvocationErrorCode> for u32 {
56    fn from(value: InvocationErrorCode) -> Self {
57        value.0 as u32
58    }
59}
60
61pub mod codes {
62    use super::InvocationErrorCode;
63
64    pub const BAD_REQUEST: InvocationErrorCode = InvocationErrorCode(400);
65    pub const INTERNAL: InvocationErrorCode = InvocationErrorCode(500);
66    pub const UNSUPPORTED_MEDIA_TYPE: InvocationErrorCode = InvocationErrorCode(415);
67    pub const JOURNAL_MISMATCH: InvocationErrorCode = InvocationErrorCode(570);
68    pub const PROTOCOL_VIOLATION: InvocationErrorCode = InvocationErrorCode(571);
69    pub const AWAITING_TWO_ASYNC_RESULTS: InvocationErrorCode = InvocationErrorCode(572);
70    pub const UNSUPPORTED_FEATURE: InvocationErrorCode = InvocationErrorCode(573);
71    pub const CLOSED: InvocationErrorCode = InvocationErrorCode(598);
72    pub const SUSPENDED: InvocationErrorCode = InvocationErrorCode(599);
73}
74
75// Const errors
76
77impl Error {
78    const fn new_const(code: InvocationErrorCode, message: &'static str) -> Self {
79        Error {
80            code: code.0,
81            message: Cow::Borrowed(message),
82            stacktrace: Cow::Borrowed(""),
83            related_command: None,
84            next_retry_delay: None,
85        }
86    }
87}
88
89pub const MISSING_CONTENT_TYPE: Error = Error::new_const(
90    codes::UNSUPPORTED_MEDIA_TYPE,
91    "Missing content type when invoking the service deployment",
92);
93
94pub const UNEXPECTED_INPUT_MESSAGE: Error = Error::new_const(
95    codes::PROTOCOL_VIOLATION,
96    "Expected incoming message to be an entry",
97);
98
99pub const KNOWN_ENTRIES_IS_ZERO: Error =
100    Error::new_const(codes::INTERNAL, "Known entries is zero, expected >= 1");
101
102pub const UNEXPECTED_ENTRY_MESSAGE: Error = Error::new_const(
103    codes::PROTOCOL_VIOLATION,
104    "Expected entry messages only when waiting replay entries",
105);
106
107pub const INPUT_CLOSED_WHILE_WAITING_ENTRIES: Error = Error::new_const(
108    codes::PROTOCOL_VIOLATION,
109    "The input was closed while still waiting to receive all journal to replay",
110);
111
112pub const EMPTY_IDEMPOTENCY_KEY: Error = Error::new_const(
113    codes::INTERNAL,
114    "Trying to execute an idempotent request with an empty idempotency key. The idempotency key must be non-empty.",
115);
116
117pub const SUSPENDED: Error = Error::new_const(codes::SUSPENDED, "Suspended invocation");
118
119// Other errors
120
121#[derive(Debug, Clone, thiserror::Error)]
122#[error("The execution replay ended unexpectedly. Expecting to read '{expected}' from the recorded journal, but the buffered messages were already drained.")]
123pub struct UnavailableEntryError {
124    expected: MessageType,
125}
126
127impl UnavailableEntryError {
128    pub fn new(expected: MessageType) -> Self {
129        Self { expected }
130    }
131}
132
133#[derive(Debug, thiserror::Error)]
134#[error("Unexpected state '{state:?}' when invoking '{event:?}'")]
135pub struct UnexpectedStateError {
136    state: &'static str,
137    event: &'static str,
138}
139
140impl UnexpectedStateError {
141    pub fn new(state: &'static str, event: &'static str) -> Self {
142        Self { state, event }
143    }
144}
145
146#[derive(Debug, thiserror::Error)]
147#[error("State machine was closed when invoking '{event:?}'")]
148pub struct ClosedError {
149    event: &'static str,
150}
151
152impl ClosedError {
153    pub fn new(event: &'static str) -> Self {
154        Self { event }
155    }
156}
157
158#[derive(Debug)]
159pub struct CommandTypeMismatchError {
160    actual: MessageType,
161    command_index: i64,
162    expected: MessageType,
163}
164
165impl CommandTypeMismatchError {
166    pub fn new(
167        command_index: i64,
168        actual: MessageType,
169        expected: MessageType,
170    ) -> CommandTypeMismatchError {
171        Self {
172            command_index,
173            actual,
174            expected,
175        }
176    }
177}
178
179impl fmt::Display for CommandTypeMismatchError {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        write!(f,
182               "Found a mismatch between the code paths taken during the previous execution and the paths taken during this execution.
183This typically happens when some parts of the code are non-deterministic.
184 - The previous execution ran and recorded the following: '{}' (index '{}')
185 - The current execution attempts to perform the following: '{}'",
186               self.expected,
187            self.command_index,
188               self.actual,
189        )
190    }
191}
192
193impl std::error::Error for CommandTypeMismatchError {}
194
195#[derive(Debug)]
196pub struct CommandMismatchError<M> {
197    command_index: i64,
198    actual: M,
199    expected: M,
200}
201
202impl<M> CommandMismatchError<M> {
203    pub fn new(command_index: i64, actual: M, expected: M) -> CommandMismatchError<M> {
204        Self {
205            command_index,
206            actual,
207            expected,
208        }
209    }
210}
211
212impl<M: RestateMessage + CommandMessageHeaderDiff> fmt::Display for CommandMismatchError<M> {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        write!(f,
215"Found a mismatch between the code paths taken during the previous execution and the paths taken during this execution.
216This typically happens when some parts of the code are non-deterministic.
217- The mismatch happened while executing '{}' (index '{}')
218- Difference:",
219            M::ty(), self.command_index,
220        )?;
221        self.actual
222            .write_diff(&self.expected, DiffFormatter::new(f, "   "))
223    }
224}
225
226impl<M: RestateMessage + CommandMessageHeaderDiff> std::error::Error for CommandMismatchError<M> {}
227
228#[derive(Debug, Clone, thiserror::Error)]
229#[error("Cannot convert a eager state key into UTF-8 String: {0:?}")]
230pub struct BadEagerStateKeyError(#[from] pub(crate) std::string::FromUtf8Error);
231
232#[derive(Debug, Clone, thiserror::Error)]
233#[error("Unexpected empty value variant for get eager state")]
234pub struct EmptyGetEagerState;
235
236#[derive(Debug, Clone, thiserror::Error)]
237#[error("Unexpected empty value variant for state keys")]
238pub struct EmptyGetEagerStateKeys;
239
240#[derive(Debug, thiserror::Error)]
241#[error("Feature '{feature}' is not supported by the negotiated protocol version '{current_version}', the minimum required version is '{minimum_required_version}'")]
242pub struct UnsupportedFeatureForNegotiatedVersion {
243    feature: &'static str,
244    current_version: Version,
245    minimum_required_version: Version,
246}
247
248impl UnsupportedFeatureForNegotiatedVersion {
249    pub fn new(
250        feature: &'static str,
251        current_version: Version,
252        minimum_required_version: Version,
253    ) -> Self {
254        Self {
255            feature,
256            current_version,
257            minimum_required_version,
258        }
259    }
260}
261
262// Conversions to VMError
263
264trait WithInvocationErrorCode {
265    fn code(&self) -> InvocationErrorCode;
266}
267
268impl<T: WithInvocationErrorCode + fmt::Display> From<T> for Error {
269    fn from(value: T) -> Self {
270        Error::new(value.code().0, value.to_string())
271    }
272}
273
274macro_rules! impl_error_code {
275    ($error_type:ident, $code:ident) => {
276        impl WithInvocationErrorCode for $error_type {
277            fn code(&self) -> InvocationErrorCode {
278                codes::$code
279            }
280        }
281    };
282}
283
284impl_error_code!(ContentTypeError, UNSUPPORTED_MEDIA_TYPE);
285impl WithInvocationErrorCode for DecodingError {
286    fn code(&self) -> InvocationErrorCode {
287        match self {
288            DecodingError::UnexpectedMessageType { .. } => codes::JOURNAL_MISMATCH,
289            _ => codes::INTERNAL,
290        }
291    }
292}
293impl_error_code!(UnavailableEntryError, PROTOCOL_VIOLATION);
294impl_error_code!(UnexpectedStateError, PROTOCOL_VIOLATION);
295impl_error_code!(ClosedError, CLOSED);
296impl_error_code!(CommandTypeMismatchError, JOURNAL_MISMATCH);
297impl<M: RestateMessage + CommandMessageHeaderDiff> WithInvocationErrorCode
298    for CommandMismatchError<M>
299{
300    fn code(&self) -> InvocationErrorCode {
301        codes::JOURNAL_MISMATCH
302    }
303}
304impl_error_code!(BadEagerStateKeyError, INTERNAL);
305impl_error_code!(EmptyGetEagerState, PROTOCOL_VIOLATION);
306impl_error_code!(EmptyGetEagerStateKeys, PROTOCOL_VIOLATION);
307impl_error_code!(UnsupportedFeatureForNegotiatedVersion, UNSUPPORTED_FEATURE);