Skip to main content

temporalio_client/
errors.rs

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