1use crate::error::NotificationMetadata;
2use crate::fmt::{display_closed_error, DiffFormatter};
3use crate::service_protocol::messages::{CommandMessageHeaderDiff, ErrorBehavior, RestateMessage};
4use crate::service_protocol::{
5 CompletionId, ContentTypeError, DecodingError, MessageType, NotificationId,
6};
7use crate::{Error, Version};
8use std::borrow::Cow;
9use std::collections::{HashMap, HashSet};
10use std::fmt;
11#[derive(Copy, Clone, PartialEq, Eq)]
14pub struct InvocationErrorCode(u16);
15
16impl InvocationErrorCode {
17 pub const fn new(code: u16) -> Self {
18 InvocationErrorCode(code)
19 }
20
21 pub const fn code(self) -> u16 {
22 self.0
23 }
24}
25
26impl fmt::Debug for InvocationErrorCode {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 write!(f, "{}", self.0)
29 }
30}
31
32impl fmt::Display for InvocationErrorCode {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 fmt::Debug::fmt(self, f)
35 }
36}
37
38impl From<u16> for InvocationErrorCode {
39 fn from(value: u16) -> Self {
40 InvocationErrorCode(value)
41 }
42}
43
44impl From<u32> for InvocationErrorCode {
45 fn from(value: u32) -> Self {
46 value
47 .try_into()
48 .map(InvocationErrorCode)
49 .unwrap_or(codes::INTERNAL)
50 }
51}
52
53impl From<InvocationErrorCode> for u16 {
54 fn from(value: InvocationErrorCode) -> Self {
55 value.0
56 }
57}
58
59impl From<InvocationErrorCode> for u32 {
60 fn from(value: InvocationErrorCode) -> Self {
61 value.0 as u32
62 }
63}
64
65pub mod codes {
66 use super::InvocationErrorCode;
67
68 pub const BAD_REQUEST: InvocationErrorCode = InvocationErrorCode(400);
69 pub const INTERNAL: InvocationErrorCode = InvocationErrorCode(500);
70 pub const UNSUPPORTED_MEDIA_TYPE: InvocationErrorCode = InvocationErrorCode(415);
71 pub const JOURNAL_MISMATCH: InvocationErrorCode = InvocationErrorCode(570);
72 pub const PROTOCOL_VIOLATION: InvocationErrorCode = InvocationErrorCode(571);
73 pub const AWAITING_TWO_ASYNC_RESULTS: InvocationErrorCode = InvocationErrorCode(572);
74 pub const UNSUPPORTED_FEATURE: InvocationErrorCode = InvocationErrorCode(573);
75 pub const CLOSED: InvocationErrorCode = InvocationErrorCode(598);
76 pub const SUSPENDED: InvocationErrorCode = InvocationErrorCode(599);
77}
78
79impl Error {
82 const fn new_const(code: InvocationErrorCode, message: &'static str) -> Self {
83 Error {
84 code: code.0,
85 message: Cow::Borrowed(message),
86 stacktrace: String::new(),
87 related_command: None,
88 next_retry_delay: None,
89 behavior: ErrorBehavior::Retry,
90 }
91 }
92}
93
94pub const MISSING_CONTENT_TYPE: Error = Error::new_const(
95 codes::UNSUPPORTED_MEDIA_TYPE,
96 "Missing content type when invoking the service deployment",
97);
98
99pub const UNEXPECTED_INPUT_MESSAGE: Error = Error::new_const(
100 codes::PROTOCOL_VIOLATION,
101 "Expected incoming message to be an entry",
102);
103
104pub const KNOWN_ENTRIES_IS_ZERO: Error =
105 Error::new_const(codes::INTERNAL, "Known entries is zero, expected >= 1");
106
107pub const UNEXPECTED_ENTRY_MESSAGE: Error = Error::new_const(
108 codes::PROTOCOL_VIOLATION,
109 "Expected entry messages only when waiting replay entries",
110);
111
112pub const INPUT_CLOSED_WHILE_WAITING_ENTRIES: Error = Error::new_const(
113 codes::PROTOCOL_VIOLATION,
114 "The input was closed while still waiting to receive all journal to replay",
115);
116
117pub const EMPTY_IDEMPOTENCY_KEY: Error = Error::new_const(
118 codes::INTERNAL,
119 "Trying to execute an idempotent request with an empty idempotency key. The idempotency key must be non-empty.",
120);
121
122pub const EMPTY_LIMIT_KEY: Error = Error::new_const(
123 codes::INTERNAL,
124 "Trying to execute a request with an empty limit key. The limit key must be non-empty.",
125);
126
127pub const EMPTY_SCOPE: Error = Error::new_const(
128 codes::INTERNAL,
129 "Trying to execute a request with an empty scope. The scope must be non-empty.",
130);
131
132pub const SUSPENDED: Error = Error::new_const(codes::SUSPENDED, "Suspended invocation");
133
134#[derive(Debug, Clone, thiserror::Error)]
137#[error("The execution replay ended unexpectedly. Expecting to read '{expected}' from the recorded journal, but the buffered messages were already drained.")]
138pub struct UnavailableEntryError {
139 expected: MessageType,
140}
141
142impl UnavailableEntryError {
143 pub fn new(expected: MessageType) -> Self {
144 Self { expected }
145 }
146}
147
148#[derive(Debug, thiserror::Error)]
149#[error("Unexpected state '{state:?}' when invoking '{event:?}'")]
150pub struct UnexpectedStateError {
151 state: &'static str,
152 event: String,
153}
154
155impl UnexpectedStateError {
156 pub fn new(state: &'static str, event: String) -> Self {
157 Self { state, event }
158 }
159}
160
161#[derive(Debug)]
162pub struct ClosedError {
163 event: String,
164}
165
166impl ClosedError {
167 pub fn new(event: String) -> Self {
168 Self { event }
169 }
170}
171
172impl fmt::Display for ClosedError {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 display_closed_error(f, &self.event)
175 }
176}
177
178impl std::error::Error for ClosedError {}
179
180#[derive(Debug)]
181pub struct CommandTypeMismatchError {
182 actual: MessageType,
184 command_index: i64,
185 expected: MessageType,
187}
188
189impl CommandTypeMismatchError {
190 pub fn new(
191 command_index: i64,
192 actual: MessageType,
193 expected: MessageType,
194 ) -> CommandTypeMismatchError {
195 Self {
196 command_index,
197 actual,
198 expected,
199 }
200 }
201}
202
203impl fmt::Display for CommandTypeMismatchError {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 write!(f,
206 "Found a mismatch between the code paths taken during the previous execution and the paths taken during this execution.
207This typically happens when some parts of the code are non-deterministic.
208 - The previous execution ran and recorded the following: '{}' (index '{}')
209 - The current execution attempts to perform the following: '{}'",
210 self.actual,
211 self.command_index,
212 self.expected,
213 )
214 }
215}
216
217impl std::error::Error for CommandTypeMismatchError {}
218
219#[derive(Debug)]
220pub struct CommandMismatchError<M> {
221 command_index: i64,
222 actual: M,
223 expected: M,
224}
225
226impl<M> CommandMismatchError<M> {
227 pub fn new(command_index: i64, actual: M, expected: M) -> CommandMismatchError<M> {
228 Self {
229 command_index,
230 actual,
231 expected,
232 }
233 }
234}
235
236impl<M: RestateMessage + CommandMessageHeaderDiff> fmt::Display for CommandMismatchError<M> {
237 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
238 write!(f,
239"Found a mismatch between the code paths taken during the previous execution and the paths taken during this execution.
240This typically happens when some parts of the code are non-deterministic.
241- The mismatch happened while executing '{}' (index '{}')
242- Difference:",
243 M::ty(), self.command_index,
244 )?;
245 self.actual
246 .write_diff(&self.expected, DiffFormatter::new(f, " "))
247 }
248}
249
250impl<M: RestateMessage + CommandMessageHeaderDiff + std::fmt::Debug> std::error::Error
251 for CommandMismatchError<M>
252{
253}
254
255#[derive(Debug)]
256pub struct UncompletedDoProgressDuringReplay {
257 notification_ids: Vec<NotificationId>,
258 additional_known_metadata: HashMap<NotificationId, NotificationMetadata>,
259}
260
261impl UncompletedDoProgressDuringReplay {
262 pub(crate) fn new(
263 notification_ids: HashSet<NotificationId>,
264 additional_known_metadata: HashMap<NotificationId, NotificationMetadata>,
265 ) -> Self {
266 let mut ordered_notification_ids = Vec::from_iter(notification_ids);
268 ordered_notification_ids.sort_by(|a, b| match (a, b) {
269 (NotificationId::CompletionId(a_id), NotificationId::CompletionId(b_id)) => {
270 a_id.cmp(b_id)
271 }
272 (NotificationId::CompletionId(_), _) => std::cmp::Ordering::Less,
273 (_, NotificationId::CompletionId(_)) => std::cmp::Ordering::Greater,
274
275 (NotificationId::SignalName(a_name), NotificationId::SignalName(b_name)) => {
276 a_name.cmp(b_name)
277 }
278 (NotificationId::SignalName(_), NotificationId::SignalId(_)) => {
279 std::cmp::Ordering::Less
280 }
281 (NotificationId::SignalId(_), NotificationId::SignalName(_)) => {
282 std::cmp::Ordering::Greater
283 }
284
285 (NotificationId::SignalId(a_id), NotificationId::SignalId(b_id)) => {
286 let a_is_cancel = *a_id == crate::service_protocol::CANCEL_SIGNAL_ID;
287 let b_is_cancel = *b_id == crate::service_protocol::CANCEL_SIGNAL_ID;
288 match (a_is_cancel, b_is_cancel) {
289 (true, false) => std::cmp::Ordering::Greater,
290 (false, true) => std::cmp::Ordering::Less,
291 _ => a_id.cmp(b_id),
292 }
293 }
294 });
295 Self {
296 notification_ids: ordered_notification_ids,
297 additional_known_metadata,
298 }
299 }
300}
301
302impl fmt::Display for UncompletedDoProgressDuringReplay {
303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304 write!(f,
305"Found a mismatch between the code paths taken during the previous execution and the paths taken during this execution.
306'await' could not be replayed. This usually means the code was mutated adding an 'await' without registering a new service revision.
307Notifications awaited on this await point:",
308 )?;
309
310 for notification_id in &self.notification_ids {
311 write!(f, "\n - ")?;
312 if let Some(metadata) = self.additional_known_metadata.get(notification_id) {
313 write!(f, "{}", metadata)?;
314 } else {
315 match notification_id {
316 NotificationId::CompletionId(completion_id) => {
317 write!(f, "completion id {}", completion_id)?;
318 }
319 NotificationId::SignalId(signal_id) => {
320 write!(f, "signal [{}]", signal_id)?;
321 }
322 NotificationId::SignalName(signal_name) => {
323 write!(f, "Named signal: {}", signal_name)?;
324 }
325 }
326 }
327 }
328
329 Ok(())
330 }
331}
332
333impl std::error::Error for UncompletedDoProgressDuringReplay {}
334
335#[derive(Debug, Clone, thiserror::Error)]
336#[error("Cannot convert a eager state key into UTF-8 String: {0:?}")]
337pub struct BadEagerStateKeyError(#[from] pub(crate) std::string::FromUtf8Error);
338
339pub const EMPTY_GET_EAGER_STATE: Error = Error::new_const(
340 codes::PROTOCOL_VIOLATION,
341 "Unexpected empty value variant for get eager state.",
342);
343
344pub const EMPTY_GET_EAGER_STATE_KEYS: Error = Error::new_const(
345 codes::PROTOCOL_VIOLATION,
346 "Unexpected empty value variant for state keys.",
347);
348
349#[derive(Debug, thiserror::Error)]
350#[error("Feature '{feature}' is not supported by the negotiated protocol version '{current_version}', the minimum required version is '{minimum_required_version}'")]
351pub struct UnsupportedFeatureForNegotiatedVersion {
352 feature: &'static str,
353 current_version: Version,
354 minimum_required_version: Version,
355}
356
357impl UnsupportedFeatureForNegotiatedVersion {
358 pub fn new(
359 feature: &'static str,
360 current_version: Version,
361 minimum_required_version: Version,
362 ) -> Self {
363 Self {
364 feature,
365 current_version,
366 minimum_required_version,
367 }
368 }
369}
370
371#[derive(Debug, thiserror::Error)]
372#[error("Received a run completion ack for completion id {completion_id}, but the related run was not proposed during this attempt.")]
373pub struct BadProposeRunCompletionAck {
374 completion_id: CompletionId,
375}
376
377impl BadProposeRunCompletionAck {
378 pub fn new(completion_id: CompletionId) -> Self {
379 Self { completion_id }
380 }
381}
382
383pub const EMPTY_GET_STATE_EPHEMERAL_NOTIFICATION: Error = Error::new_const(
384 codes::PROTOCOL_VIOLATION,
385 "Unexpected empty result variant for get state ephemeral notification.",
386);
387
388#[derive(Debug, thiserror::Error)]
389#[error("Cannot get state '{key}' without recording it in the journal: the input stream is closed, thus Restate cannot answer the request. Getting state without recording it in the journal requires a bidirectional stream between Restate and the service (not available in request/response mode, e.g. AWS Lambda), unless the state value is available in the eager state sent by Restate.")]
390pub struct EphemeralStateGetWithClosedInput {
391 key: String,
392}
393
394impl EphemeralStateGetWithClosedInput {
395 pub fn new(key: String) -> Self {
396 Self { key }
397 }
398}
399
400pub const INPUT_CLOSED_WHILE_WAITING_EPHEMERAL_NOTIFICATIONS: Error = Error::new_const(
401 codes::INTERNAL,
402 "The input stream was closed while waiting for the result of a get state not recorded in the journal. Getting state without recording it in the journal requires a bidirectional stream between Restate and the service (not available in request/response mode, e.g. AWS Lambda), unless the state value is available in the eager state sent by Restate.",
403);
404
405pub const EPHEMERAL_COMMAND_DURING_REPLAY: Error = Error::new_const(
408 codes::JOURNAL_MISMATCH,
409 "Cannot execute ephemeral commands (e.g. get state without recording it in the journal) during replay, as their result might differ from the original execution. Ephemeral commands can be used only while processing new journal entries, e.g. within a ctx.run closure. This usually means the code uses them outside a ctx.run closure, or the code was changed.",
410);
411
412#[derive(Debug, Clone, thiserror::Error)]
413#[error("The provided duration for '{0}' is out of bounds: {1:?}")]
414pub struct OutOfBoundsDuration(
415 pub(crate) &'static str,
416 pub(crate) std::num::TryFromIntError,
417);
418
419trait WithInvocationErrorCode {
422 fn code(&self) -> InvocationErrorCode;
423}
424
425impl<T: WithInvocationErrorCode + fmt::Display> From<T> for Error {
426 fn from(value: T) -> Self {
427 Error::new(value.code().0, value.to_string())
428 }
429}
430
431macro_rules! impl_error_code {
432 ($error_type:ident, $code:ident) => {
433 impl WithInvocationErrorCode for $error_type {
434 fn code(&self) -> InvocationErrorCode {
435 codes::$code
436 }
437 }
438 };
439}
440
441impl_error_code!(ContentTypeError, UNSUPPORTED_MEDIA_TYPE);
442impl WithInvocationErrorCode for DecodingError {
443 fn code(&self) -> InvocationErrorCode {
444 match self {
445 DecodingError::UnexpectedMessageType { .. } => codes::JOURNAL_MISMATCH,
446 _ => codes::INTERNAL,
447 }
448 }
449}
450impl_error_code!(UnavailableEntryError, PROTOCOL_VIOLATION);
451impl_error_code!(UnexpectedStateError, PROTOCOL_VIOLATION);
452impl_error_code!(ClosedError, CLOSED);
453impl_error_code!(CommandTypeMismatchError, JOURNAL_MISMATCH);
454impl_error_code!(UncompletedDoProgressDuringReplay, JOURNAL_MISMATCH);
455impl<M: RestateMessage + CommandMessageHeaderDiff> WithInvocationErrorCode
456 for CommandMismatchError<M>
457{
458 fn code(&self) -> InvocationErrorCode {
459 codes::JOURNAL_MISMATCH
460 }
461}
462impl_error_code!(BadEagerStateKeyError, INTERNAL);
463impl_error_code!(UnsupportedFeatureForNegotiatedVersion, UNSUPPORTED_FEATURE);
464impl_error_code!(BadProposeRunCompletionAck, PROTOCOL_VIOLATION);
465impl_error_code!(EphemeralStateGetWithClosedInput, INTERNAL);
466impl_error_code!(OutOfBoundsDuration, INTERNAL);