restate_sdk_shared_core/vm/
errors.rs

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