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