restate_sdk/
errors.rs

1//! # Error Handling
2//!
3//! Restate handles retries for failed invocations.
4//! By default, Restate does infinite retries with an exponential backoff strategy.
5//!
6//! For failures for which you do not want retries, but instead want the invocation to end and the error message
7//! to be propagated back to the caller, you can return a [`TerminalError`].
8//!
9//! You can return a [`TerminalError`] with an optional HTTP status code and a message anywhere in your handler, as follows:
10//!
11//! ```rust,no_run
12//! # use restate_sdk::prelude::*;
13//! # async fn handle() -> Result<(), HandlerError> {
14//! Err(TerminalError::new("This is a terminal error").into())
15//! # }
16//! ```
17//!
18//! You can catch terminal exceptions. For example, you can catch the terminal exception that comes out of a [call to another service][crate::context::ContextClient], and build your control flow around it.
19use restate_sdk_shared_core::TerminalFailure;
20use std::error::Error as StdError;
21use std::fmt;
22
23#[derive(Debug)]
24pub(crate) enum HandlerErrorInner {
25    Retryable(Box<dyn StdError + Send + Sync + 'static>),
26    Terminal(TerminalErrorInner),
27}
28
29impl fmt::Display for HandlerErrorInner {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            HandlerErrorInner::Retryable(e) => {
33                write!(f, "Retryable error: {}", e)
34            }
35            HandlerErrorInner::Terminal(e) => fmt::Display::fmt(e, f),
36        }
37    }
38}
39
40impl StdError for HandlerErrorInner {
41    fn source(&self) -> Option<&(dyn StdError + 'static)> {
42        match self {
43            HandlerErrorInner::Retryable(e) => Some(e.as_ref()),
44            HandlerErrorInner::Terminal(e) => Some(e),
45        }
46    }
47}
48
49/// This error can contain either a [`TerminalError`], or any other Rust's [`StdError`].
50/// For the latter, the error is considered "retryable", and the execution will be retried.
51#[derive(Debug)]
52pub struct HandlerError(pub(crate) HandlerErrorInner);
53
54impl<E: Into<Box<dyn StdError + Send + Sync + 'static>>> From<E> for HandlerError {
55    fn from(value: E) -> Self {
56        Self(HandlerErrorInner::Retryable(value.into()))
57    }
58}
59
60impl From<TerminalError> for HandlerError {
61    fn from(value: TerminalError) -> Self {
62        Self(HandlerErrorInner::Terminal(value.0))
63    }
64}
65
66// Took from anyhow
67impl AsRef<dyn StdError + Send + Sync> for HandlerError {
68    fn as_ref(&self) -> &(dyn StdError + Send + Sync + 'static) {
69        &self.0
70    }
71}
72
73impl AsRef<dyn StdError> for HandlerError {
74    fn as_ref(&self) -> &(dyn StdError + 'static) {
75        &self.0
76    }
77}
78
79#[derive(Debug, Clone)]
80pub(crate) struct TerminalErrorInner {
81    code: u16,
82    message: String,
83}
84
85impl fmt::Display for TerminalErrorInner {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        write!(f, "Terminal error [{}]: {}", self.code, self.message)
88    }
89}
90
91impl StdError for TerminalErrorInner {}
92
93/// Error representing the result of an operation recorded in the journal.
94///
95/// When returned inside a [`crate::context::ContextSideEffects::run`] closure, or in a handler, it completes the operation with a failure value.
96#[derive(Debug, Clone)]
97pub struct TerminalError(pub(crate) TerminalErrorInner);
98
99impl TerminalError {
100    /// Create a new [`TerminalError`].
101    pub fn new(message: impl Into<String>) -> Self {
102        Self::new_with_code(500, message)
103    }
104
105    /// Create a new [`TerminalError`] with a status code.
106    pub fn new_with_code(code: u16, message: impl Into<String>) -> Self {
107        Self(TerminalErrorInner {
108            code,
109            message: message.into(),
110        })
111    }
112
113    pub fn code(&self) -> u16 {
114        self.0.code
115    }
116
117    pub fn message(&self) -> &str {
118        &self.0.message
119    }
120
121    pub fn from_error<E: StdError>(e: E) -> Self {
122        Self::new(e.to_string())
123    }
124}
125
126impl fmt::Display for TerminalError {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        fmt::Display::fmt(&self.0, f)
129    }
130}
131
132impl AsRef<dyn StdError + Send + Sync> for TerminalError {
133    fn as_ref(&self) -> &(dyn StdError + Send + Sync + 'static) {
134        &self.0
135    }
136}
137
138impl AsRef<dyn StdError> for TerminalError {
139    fn as_ref(&self) -> &(dyn StdError + 'static) {
140        &self.0
141    }
142}
143
144impl From<TerminalFailure> for TerminalError {
145    fn from(value: TerminalFailure) -> Self {
146        Self(TerminalErrorInner {
147            code: value.code,
148            message: value.message,
149        })
150    }
151}
152
153impl From<TerminalError> for TerminalFailure {
154    fn from(value: TerminalError) -> Self {
155        Self {
156            code: value.0.code,
157            message: value.0.message,
158        }
159    }
160}
161
162/// Result type for a Restate handler.
163pub type HandlerResult<T> = Result<T, HandlerError>;