1use crate::{PluginApplyError, WorkflowExecutionStatus, workflow_handle::WorkflowResultDetails};
4use http::uri::InvalidUri;
5use temporalio_common::{
6 data_converters::{DecodablePayloads, PayloadConversionError},
7 error::{IncomingError, TimeoutType},
8 protos::{
9 temporal::api::{
10 errordetails::v1::ActivityExecutionAlreadyStartedFailure, failure::v1::Failure,
11 },
12 utilities::decode_status_detail,
13 },
14};
15use tonic::Code;
16
17#[derive(thiserror::Error, Debug)]
19#[non_exhaustive]
20pub enum ClientConnectError {
21 #[error(transparent)]
23 Plugin(#[from] PluginApplyError),
24 #[error("Invalid URI: {0:?}")]
26 InvalidUri(#[from] InvalidUri),
27 #[error("Invalid headers: {0}")]
29 InvalidHeaders(#[from] InvalidHeaderError),
30 #[error("Server connection error: {0:?}")]
32 TonicTransportError(#[from] tonic::transport::Error),
33 #[error("`get_system_info` call error after connection: {0:?}")]
36 SystemInfoCallError(tonic::Status),
37 #[error("DNS resolution error for '{host}': {source}")]
39 DnsResolutionError {
40 host: String,
42 #[source]
44 source: std::io::Error,
45 },
46 #[error("Invalid client configuration: {0}")]
48 InvalidConfig(String),
49}
50
51impl From<ClientNewError> for ClientConnectError {
52 fn from(value: ClientNewError) -> Self {
53 match value {
54 ClientNewError::Plugin(err) => Self::Plugin(err),
55 }
56 }
57}
58
59#[derive(thiserror::Error, Debug)]
61#[non_exhaustive]
62pub enum InvalidHeaderError {
63 #[error("Invalid binary header key '{key}': {source}")]
65 InvalidBinaryHeaderKey {
66 key: String,
68 source: tonic::metadata::errors::InvalidMetadataKey,
70 },
71 #[error("Invalid ASCII header key '{key}': {source}")]
73 InvalidAsciiHeaderKey {
74 key: String,
76 source: tonic::metadata::errors::InvalidMetadataKey,
78 },
79 #[error("Invalid ASCII header value for key '{key}': {source}")]
81 InvalidAsciiHeaderValue {
82 key: String,
84 value: String,
86 source: tonic::metadata::errors::InvalidMetadataValue,
88 },
89}
90
91#[derive(thiserror::Error, Debug)]
93#[non_exhaustive]
94pub enum WorkflowStartError {
95 #[error("Workflow already started with run ID: {run_id:?}")]
97 AlreadyStarted {
98 run_id: Option<String>,
100 #[source]
102 source: tonic::Status,
103 },
104 #[error("Failed to serialize workflow input: {0}")]
106 PayloadConversion(#[from] PayloadConversionError),
107 #[error("Server error: {0}")]
109 Rpc(#[from] tonic::Status),
110}
111
112#[derive(Debug, thiserror::Error)]
114#[non_exhaustive]
115pub enum WorkflowQueryError {
116 #[error("Workflow not found")]
118 NotFound(#[source] tonic::Status),
119
120 #[error("Query rejected: workflow status {status:?}")]
122 Rejected {
123 status: Option<WorkflowExecutionStatus>,
125 },
126
127 #[error("Payload conversion error: {0}")]
129 PayloadConversion(#[from] PayloadConversionError),
130
131 #[error("Server error: {0}")]
133 Rpc(tonic::Status),
134
135 #[error(transparent)]
137 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
138}
139
140impl WorkflowQueryError {
141 pub(crate) fn from_status(status: tonic::Status) -> Self {
142 if status.code() == Code::NotFound {
143 Self::NotFound(status)
144 } else {
145 Self::Rpc(status)
146 }
147 }
148}
149
150#[derive(Debug, thiserror::Error)]
152#[non_exhaustive]
153pub enum WorkflowUpdateError {
154 #[error("Workflow not found")]
156 NotFound(#[source] tonic::Status),
157
158 #[error("Update failed: {0:?}")]
160 Failed(Box<Failure>),
161
162 #[error("Payload conversion error: {0}")]
164 PayloadConversion(#[from] PayloadConversionError),
165
166 #[error("Server error: {0}")]
168 Rpc(tonic::Status),
169
170 #[error(transparent)]
172 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
173}
174
175impl WorkflowUpdateError {
176 pub(crate) fn from_status(status: tonic::Status) -> Self {
177 if status.code() == Code::NotFound {
178 Self::NotFound(status)
179 } else {
180 Self::Rpc(status)
181 }
182 }
183}
184
185#[derive(Debug, thiserror::Error)]
187#[non_exhaustive]
188pub enum WorkflowGetResultError {
189 #[error("Workflow failed: {0}")]
191 Failed(#[source] Box<IncomingError>),
192
193 #[error("Workflow cancelled")]
195 Cancelled {
196 details: WorkflowResultDetails,
198 },
199
200 #[error("Workflow terminated")]
202 Terminated {
203 details: WorkflowResultDetails,
205 },
206
207 #[error("Workflow timed out")]
209 TimedOut,
210
211 #[error("Workflow continued as new")]
213 ContinuedAsNew,
214
215 #[error("Workflow not found")]
217 NotFound(#[source] tonic::Status),
218
219 #[error("Payload conversion error: {0}")]
221 PayloadConversion(#[from] PayloadConversionError),
222
223 #[error("Server error: {0}")]
225 Rpc(tonic::Status),
226
227 #[error(transparent)]
229 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
230}
231
232impl From<WorkflowInteractionError> for WorkflowGetResultError {
233 fn from(err: WorkflowInteractionError) -> Self {
234 match err {
235 WorkflowInteractionError::NotFound(s) => Self::NotFound(s),
236 WorkflowInteractionError::PayloadConversion(e) => Self::PayloadConversion(e),
237 WorkflowInteractionError::Rpc(s) => Self::Rpc(s),
238 WorkflowInteractionError::Other(e) => Self::Other(e),
239 }
240 }
241}
242
243impl WorkflowGetResultError {
244 pub fn is_workflow_outcome(&self) -> bool {
248 matches!(
249 self,
250 Self::Failed(_)
251 | Self::Cancelled { .. }
252 | Self::Terminated { .. }
253 | Self::TimedOut
254 | Self::ContinuedAsNew
255 )
256 }
257}
258
259#[derive(thiserror::Error, Debug)]
261#[non_exhaustive]
262pub enum ClientError {
263 #[error("Payload conversion error: {0}")]
265 PayloadConversion(#[from] PayloadConversionError),
266 #[error("Server error: {0}")]
268 Rpc(#[from] tonic::Status),
269}
270
271#[derive(Debug, thiserror::Error)]
274#[non_exhaustive]
275pub enum WorkflowInteractionError {
276 #[error("Workflow not found")]
278 NotFound(#[source] tonic::Status),
279
280 #[error("Payload conversion error: {0}")]
282 PayloadConversion(#[from] PayloadConversionError),
283
284 #[error("Server error: {0}")]
286 Rpc(tonic::Status),
287
288 #[error(transparent)]
290 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
291}
292
293impl WorkflowInteractionError {
294 pub(crate) fn from_status(status: tonic::Status) -> Self {
295 if status.code() == Code::NotFound {
296 Self::NotFound(status)
297 } else {
298 Self::Rpc(status)
299 }
300 }
301}
302
303#[derive(Debug, thiserror::Error)]
305#[non_exhaustive]
306pub enum AsyncActivityError {
307 #[error("Activity not found")]
309 NotFound(#[source] tonic::Status),
310 #[error("Payload conversion error: {0}")]
312 PayloadConversion(#[from] PayloadConversionError),
313 #[error("Server error: {0}")]
315 Rpc(#[from] tonic::Status),
316}
317
318impl AsyncActivityError {
319 pub(crate) fn from_status(status: tonic::Status) -> Self {
320 if status.code() == Code::NotFound {
321 Self::NotFound(status)
322 } else {
323 Self::Rpc(status)
324 }
325 }
326}
327
328#[derive(Debug, thiserror::Error)]
330#[non_exhaustive]
331pub enum ClientNewError {
332 #[error(transparent)]
334 Plugin(#[from] PluginApplyError),
335}
336
337#[derive(Debug, thiserror::Error)]
339#[non_exhaustive]
340pub enum ActivityInteractionError {
341 #[error("Activity not found")]
343 NotFound(#[source] tonic::Status),
344
345 #[error("Payload conversion error: {0}")]
347 PayloadConversion(#[from] PayloadConversionError),
348
349 #[error("Server error: {0}")]
351 Rpc(#[source] tonic::Status),
352
353 #[error(transparent)]
355 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
356}
357
358impl From<tonic::Status> for ActivityInteractionError {
359 fn from(status: tonic::Status) -> Self {
360 if status.code() == Code::NotFound {
361 Self::NotFound(status)
362 } else {
363 Self::Rpc(status)
364 }
365 }
366}
367
368#[allow(clippy::large_enum_variant)]
370#[derive(Debug, thiserror::Error)]
371#[non_exhaustive]
372pub enum StartActivityError {
373 #[error("Activity already started with run_id={run_id}")]
376 AlreadyStarted {
377 run_id: String,
379 #[source]
381 source: tonic::Status,
382 },
383
384 #[error("Payload conversion error: {0}")]
386 PayloadConversion(#[from] PayloadConversionError),
387
388 #[error("Server error: {0}")]
390 Rpc(#[source] tonic::Status),
391
392 #[error(transparent)]
394 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
395}
396
397impl From<tonic::Status> for StartActivityError {
398 fn from(status: tonic::Status) -> Self {
399 if status.code() == tonic::Code::AlreadyExists
400 && let Some(details) =
401 decode_status_detail::<ActivityExecutionAlreadyStartedFailure>(status.details())
402 {
403 StartActivityError::AlreadyStarted {
404 run_id: details.run_id,
405 source: status,
406 }
407 } else {
408 StartActivityError::Rpc(status)
409 }
410 }
411}
412
413#[allow(clippy::large_enum_variant)]
415#[derive(Debug, thiserror::Error)]
416#[non_exhaustive]
417pub enum ActivityResultError {
418 #[error("Activity failed: {0}")]
420 ActivityFailed(#[source] IncomingError),
421
422 #[error("Activity canceled")]
424 Cancelled {
425 details: DecodablePayloads,
427 },
428
429 #[error("Activity terminated")]
431 Terminated,
432
433 #[error("Activity timed out: {0:?}")]
435 TimedOut(TimeoutType),
436
437 #[error("Activity not found")]
439 NotFound(#[source] tonic::Status),
440
441 #[error("Payload conversion error: {0}")]
443 PayloadConversion(#[from] PayloadConversionError),
444
445 #[error("Server error: {0}")]
447 Rpc(#[source] tonic::Status),
448
449 #[error(transparent)]
451 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
452}
453
454impl From<tonic::Status> for ActivityResultError {
455 fn from(status: tonic::Status) -> Self {
456 if status.code() == Code::NotFound {
457 Self::NotFound(status)
458 } else {
459 Self::Rpc(status)
460 }
461 }
462}