restate_sdk_shared_core/vm/
errors.rs1use 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#[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
75impl 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#[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
261trait 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);