Skip to main content

temporalio_client/
errors.rs

1//! Contains errors that can be returned by clients.
2
3use http::uri::InvalidUri;
4use temporalio_common::{
5    data_converters::PayloadConversionError,
6    protos::temporal::api::{common::v1::Payload, failure::v1::Failure, query::v1::QueryRejected},
7};
8use tonic::Code;
9
10/// Errors thrown while attempting to establish a connection to the server
11#[derive(thiserror::Error, Debug)]
12#[non_exhaustive]
13pub enum ClientConnectError {
14    /// Invalid URI. Configuration error, fatal.
15    #[error("Invalid URI: {0:?}")]
16    InvalidUri(#[from] InvalidUri),
17    /// Invalid gRPC metadata headers. Configuration error.
18    #[error("Invalid headers: {0}")]
19    InvalidHeaders(#[from] InvalidHeaderError),
20    /// Server connection error. Crashing and restarting the worker is likely best.
21    #[error("Server connection error: {0:?}")]
22    TonicTransportError(#[from] tonic::transport::Error),
23    /// We couldn't successfully make the `get_system_info` call at connection time to establish
24    /// server capabilities / verify server is responding.
25    #[error("`get_system_info` call error after connection: {0:?}")]
26    SystemInfoCallError(tonic::Status),
27    /// DNS resolution failed when attempting load-balanced connection.
28    #[error("DNS resolution error for '{host}': {source}")]
29    DnsResolutionError {
30        /// The host that failed to resolve.
31        host: String,
32        /// The underlying IO error.
33        #[source]
34        source: std::io::Error,
35    },
36    /// Invalid client configuration.
37    #[error("Invalid client configuration: {0}")]
38    InvalidConfig(String),
39}
40
41/// Errors thrown when a gRPC metadata header is invalid.
42#[derive(thiserror::Error, Debug)]
43#[non_exhaustive]
44pub enum InvalidHeaderError {
45    /// A binary header key was invalid
46    #[error("Invalid binary header key '{key}': {source}")]
47    InvalidBinaryHeaderKey {
48        /// The invalid key
49        key: String,
50        /// The source error from tonic
51        source: tonic::metadata::errors::InvalidMetadataKey,
52    },
53    /// An ASCII header key was invalid
54    #[error("Invalid ASCII header key '{key}': {source}")]
55    InvalidAsciiHeaderKey {
56        /// The invalid key
57        key: String,
58        /// The source error from tonic
59        source: tonic::metadata::errors::InvalidMetadataKey,
60    },
61    /// An ASCII header value was invalid
62    #[error("Invalid ASCII header value for key '{key}': {source}")]
63    InvalidAsciiHeaderValue {
64        /// The key
65        key: String,
66        /// The invalid value
67        value: String,
68        /// The source error from tonic
69        source: tonic::metadata::errors::InvalidMetadataValue,
70    },
71}
72
73/// Errors that can occur when starting a workflow.
74#[derive(thiserror::Error, Debug)]
75#[non_exhaustive]
76pub enum WorkflowStartError {
77    /// The workflow already exists.
78    #[error("Workflow already started with run ID: {run_id:?}")]
79    AlreadyStarted {
80        /// Run ID of the already-started workflow if this was raised by the client.
81        run_id: Option<String>,
82        /// The original gRPC status from the server.
83        #[source]
84        source: tonic::Status,
85    },
86    /// Error converting the input to a payload.
87    #[error("Failed to serialize workflow input: {0}")]
88    PayloadConversion(#[from] PayloadConversionError),
89    /// An uncategorized rpc error from the server.
90    #[error("Server error: {0}")]
91    Rpc(#[from] tonic::Status),
92}
93
94/// Errors returned by query operations on [crate::WorkflowHandle].
95#[derive(Debug, thiserror::Error)]
96#[non_exhaustive]
97pub enum WorkflowQueryError {
98    /// The workflow was not found.
99    #[error("Workflow not found")]
100    NotFound(#[source] tonic::Status),
101
102    /// The query was rejected based on the rejection condition.
103    #[error("Query rejected: workflow status {:?}", .0.status)]
104    Rejected(QueryRejected),
105
106    /// Error serializing input or deserializing output.
107    #[error("Payload conversion error: {0}")]
108    PayloadConversion(#[from] PayloadConversionError),
109
110    /// An uncategorized RPC error from the server.
111    #[error("Server error: {0}")]
112    Rpc(tonic::Status),
113
114    /// Other errors.
115    #[error(transparent)]
116    Other(#[from] Box<dyn std::error::Error + Send + Sync>),
117}
118
119impl WorkflowQueryError {
120    pub(crate) fn from_status(status: tonic::Status) -> Self {
121        if status.code() == Code::NotFound {
122            Self::NotFound(status)
123        } else {
124            Self::Rpc(status)
125        }
126    }
127}
128
129/// Errors returned by update operations on [crate::WorkflowHandle].
130#[derive(Debug, thiserror::Error)]
131#[non_exhaustive]
132pub enum WorkflowUpdateError {
133    /// The workflow was not found.
134    #[error("Workflow not found")]
135    NotFound(#[source] tonic::Status),
136
137    /// The update failed with an application-level failure.
138    #[error("Update failed: {0:?}")]
139    Failed(Box<Failure>),
140
141    /// Error serializing input or deserializing output.
142    #[error("Payload conversion error: {0}")]
143    PayloadConversion(#[from] PayloadConversionError),
144
145    /// An uncategorized RPC error from the server.
146    #[error("Server error: {0}")]
147    Rpc(tonic::Status),
148
149    /// Other errors.
150    #[error(transparent)]
151    Other(#[from] Box<dyn std::error::Error + Send + Sync>),
152}
153
154impl WorkflowUpdateError {
155    pub(crate) fn from_status(status: tonic::Status) -> Self {
156        if status.code() == Code::NotFound {
157            Self::NotFound(status)
158        } else {
159            Self::Rpc(status)
160        }
161    }
162}
163
164/// Errors returned by workflow get_result operations.
165#[derive(Debug, thiserror::Error)]
166#[non_exhaustive]
167pub enum WorkflowGetResultError {
168    /// The workflow finished in failure.
169    #[error("Workflow failed: {0:?}")]
170    Failed(Box<Failure>),
171
172    /// The workflow was cancelled.
173    #[error("Workflow cancelled")]
174    Cancelled {
175        /// Details provided at cancellation time.
176        details: Vec<Payload>,
177    },
178
179    /// The workflow was terminated.
180    #[error("Workflow terminated")]
181    Terminated {
182        /// Details provided at termination time.
183        details: Vec<Payload>,
184    },
185
186    /// The workflow timed out.
187    #[error("Workflow timed out")]
188    TimedOut,
189
190    /// The workflow continued as new.
191    #[error("Workflow continued as new")]
192    ContinuedAsNew,
193
194    /// The workflow was not found.
195    #[error("Workflow not found")]
196    NotFound(#[source] tonic::Status),
197
198    /// Error serializing input or deserializing output.
199    #[error("Payload conversion error: {0}")]
200    PayloadConversion(#[from] PayloadConversionError),
201
202    /// An uncategorized RPC error from the server.
203    #[error("Server error: {0}")]
204    Rpc(tonic::Status),
205
206    /// Other errors.
207    #[error(transparent)]
208    Other(#[from] Box<dyn std::error::Error + Send + Sync>),
209}
210
211impl From<WorkflowInteractionError> for WorkflowGetResultError {
212    fn from(err: WorkflowInteractionError) -> Self {
213        match err {
214            WorkflowInteractionError::NotFound(s) => Self::NotFound(s),
215            WorkflowInteractionError::PayloadConversion(e) => Self::PayloadConversion(e),
216            WorkflowInteractionError::Rpc(s) => Self::Rpc(s),
217            WorkflowInteractionError::Other(e) => Self::Other(e),
218        }
219    }
220}
221
222impl WorkflowGetResultError {
223    /// Returns `true` if this error represents a workflow-level non-success outcome
224    /// (Failed, Cancelled, Terminated, TimedOut, or ContinuedAsNew) rather than an
225    /// infrastructure/RPC error.
226    pub fn is_workflow_outcome(&self) -> bool {
227        matches!(
228            self,
229            Self::Failed(_)
230                | Self::Cancelled { .. }
231                | Self::Terminated { .. }
232                | Self::TimedOut
233                | Self::ContinuedAsNew
234        )
235    }
236}
237
238/// Errors returned by client methods that don't need more specific error types.
239#[derive(thiserror::Error, Debug)]
240#[non_exhaustive]
241pub enum ClientError {
242    /// An uncategorized rpc error from the server.
243    #[error("Server error: {0}")]
244    Rpc(#[from] tonic::Status),
245}
246
247/// Errors returned by methods on [crate::WorkflowHandle] for general operations
248/// like signal, cancel, terminate, describe, fetch_history, and get_result.
249#[derive(Debug, thiserror::Error)]
250#[non_exhaustive]
251pub enum WorkflowInteractionError {
252    /// The workflow was not found.
253    #[error("Workflow not found")]
254    NotFound(#[source] tonic::Status),
255
256    /// Error serializing input or deserializing output.
257    #[error("Payload conversion error: {0}")]
258    PayloadConversion(#[from] PayloadConversionError),
259
260    /// An uncategorized RPC error from the server.
261    #[error("Server error: {0}")]
262    Rpc(tonic::Status),
263
264    /// Other errors.
265    #[error(transparent)]
266    Other(#[from] Box<dyn std::error::Error + Send + Sync>),
267}
268
269impl WorkflowInteractionError {
270    pub(crate) fn from_status(status: tonic::Status) -> Self {
271        if status.code() == Code::NotFound {
272            Self::NotFound(status)
273        } else {
274            Self::Rpc(status)
275        }
276    }
277}
278
279/// Errors that can occur when completing an activity asynchronously.
280#[derive(Debug, thiserror::Error)]
281#[non_exhaustive]
282pub enum AsyncActivityError {
283    /// The activity was not found (e.g., already completed, cancelled, or never existed).
284    #[error("Activity not found")]
285    NotFound(#[source] tonic::Status),
286    /// An uncategorized rpc error from the server.
287    #[error("Server error: {0}")]
288    Rpc(#[from] tonic::Status),
289}
290
291impl AsyncActivityError {
292    pub(crate) fn from_status(status: tonic::Status) -> Self {
293        if status.code() == Code::NotFound {
294            Self::NotFound(status)
295        } else {
296            Self::Rpc(status)
297        }
298    }
299}
300
301/// Errors that can occur when constructing a [`crate::Client`].
302///
303/// Currently has no variants, but may be extended in the future.
304#[derive(Debug, thiserror::Error)]
305#[non_exhaustive]
306pub enum ClientNewError {}