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    expected: MessageType,
162}
163
164impl CommandTypeMismatchError {
165    pub fn new(actual: MessageType, expected: MessageType) -> CommandTypeMismatchError {
166        Self { actual, expected }
167    }
168}
169
170impl fmt::Display for CommandTypeMismatchError {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        write!(f,
173               "Found a mismatch between the code paths taken during the previous execution and the paths taken during this execution.
174This typically happens when some parts of the code are non-deterministic.
175 - The previous execution ran and recorded the following: '{}'
176 - The current execution attempts to perform the following: '{}'",
177               self.expected,
178               self.actual,
179        )
180    }
181}
182
183impl std::error::Error for CommandTypeMismatchError {}
184
185#[derive(Debug)]
186pub struct CommandMismatchError<M> {
187    command_index: i64,
188    actual: M,
189    expected: M,
190}
191
192impl<M> CommandMismatchError<M> {
193    pub fn new(command_index: i64, actual: M, expected: M) -> CommandMismatchError<M> {
194        Self {
195            command_index,
196            actual,
197            expected,
198        }
199    }
200}
201
202impl<M: RestateMessage + CommandMessageHeaderDiff> fmt::Display for CommandMismatchError<M> {
203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204        write!(f,
205"Found a mismatch between the code paths taken during the previous execution and the paths taken during this execution.
206This typically happens when some parts of the code are non-deterministic.
207- The mismatch happened at index '{}' while executing '{}'
208- Difference:",
209            self.command_index,
210            M::ty(),
211        )?;
212        self.actual
213            .write_diff(&self.expected, DiffFormatter::new(f, "   "))
214    }
215}
216
217impl<M: RestateMessage + CommandMessageHeaderDiff> std::error::Error for CommandMismatchError<M> {}
218
219#[derive(Debug, Clone, thiserror::Error)]
220#[error("Cannot convert a eager state key into UTF-8 String: {0:?}")]
221pub struct BadEagerStateKeyError(#[from] pub(crate) std::string::FromUtf8Error);
222
223#[derive(Debug, Clone, thiserror::Error)]
224#[error("Cannot decode state keys message: {0}")]
225pub struct DecodeStateKeysProst(#[from] pub(crate) prost::DecodeError);
226
227#[derive(Debug, Clone, thiserror::Error)]
228#[error("Cannot decode state keys message: {0}")]
229pub struct DecodeStateKeysUtf8(#[from] pub(crate) std::string::FromUtf8Error);
230
231#[derive(Debug, Clone, thiserror::Error)]
232#[error("Unexpected empty value variant for get eager state")]
233pub struct EmptyGetEagerState;
234
235#[derive(Debug, Clone, thiserror::Error)]
236#[error("Unexpected empty value variant for state keys")]
237pub struct EmptyGetEagerStateKeys;
238
239#[derive(Debug, thiserror::Error)]
240#[error("Feature '{feature}' is not supported by the negotiated protocol version '{current_version}', the minimum required version is '{minimum_required_version}'")]
241pub struct UnsupportedFeatureForNegotiatedVersion {
242    feature: &'static str,
243    current_version: Version,
244    minimum_required_version: Version,
245}
246
247impl UnsupportedFeatureForNegotiatedVersion {
248    pub fn new(
249        feature: &'static str,
250        current_version: Version,
251        minimum_required_version: Version,
252    ) -> Self {
253        Self {
254            feature,
255            current_version,
256            minimum_required_version,
257        }
258    }
259}
260
261// Conversions to VMError
262
263trait WithInvocationErrorCode {
264    fn code(&self) -> InvocationErrorCode;
265}
266
267impl<T: WithInvocationErrorCode + fmt::Display> From<T> for Error {
268    fn from(value: T) -> Self {
269        Error::new(value.code().0, value.to_string())
270    }
271}
272
273macro_rules! impl_error_code {
274    ($error_type:ident, $code:ident) => {
275        impl WithInvocationErrorCode for $error_type {
276            fn code(&self) -> InvocationErrorCode {
277                codes::$code
278            }
279        }
280    };
281}
282
283impl_error_code!(ContentTypeError, UNSUPPORTED_MEDIA_TYPE);
284impl WithInvocationErrorCode for DecodingError {
285    fn code(&self) -> InvocationErrorCode {
286        match self {
287            DecodingError::UnexpectedMessageType { .. } => codes::JOURNAL_MISMATCH,
288            _ => codes::INTERNAL,
289        }
290    }
291}
292impl_error_code!(UnavailableEntryError, PROTOCOL_VIOLATION);
293impl_error_code!(UnexpectedStateError, PROTOCOL_VIOLATION);
294impl_error_code!(ClosedError, CLOSED);
295impl_error_code!(CommandTypeMismatchError, JOURNAL_MISMATCH);
296impl<M: RestateMessage + CommandMessageHeaderDiff> WithInvocationErrorCode
297    for CommandMismatchError<M>
298{
299    fn code(&self) -> InvocationErrorCode {
300        codes::JOURNAL_MISMATCH
301    }
302}
303impl_error_code!(BadEagerStateKeyError, INTERNAL);
304impl_error_code!(DecodeStateKeysProst, PROTOCOL_VIOLATION);
305impl_error_code!(DecodeStateKeysUtf8, PROTOCOL_VIOLATION);
306impl_error_code!(EmptyGetEagerState, PROTOCOL_VIOLATION);
307impl_error_code!(EmptyGetEagerStateKeys, PROTOCOL_VIOLATION);
308impl_error_code!(UnsupportedFeatureForNegotiatedVersion, UNSUPPORTED_FEATURE);