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