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 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
262trait 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);