1use crate::{
4 WorkflowExecution,
5 data_converters::{
6 DecodablePayloads, GenericPayloadConverter, PayloadConversionError, PayloadConverter,
7 RawValue, SerializationContext, SerializationContextData, TemporalDeserializable,
8 TemporalSerializable,
9 },
10 protos::{
11 coresdk::child_workflow::StartChildWorkflowExecutionFailedCause,
12 temporal::api::{
13 common::v1::{Payload, Payloads},
14 enums::v1::{
15 ApplicationErrorCategory as ProtoApplicationErrorCategory,
16 RetryState as ProtoRetryState, TimeoutType as ProtoTimeoutType,
17 },
18 failure::v1::Failure,
19 },
20 },
21};
22use std::time::Duration;
23
24#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
26#[non_exhaustive]
27pub enum RetryState {
28 Unspecified,
30 InProgress,
32 NonRetryableFailure,
34 Timeout,
36 MaximumAttemptsReached,
38 RetryPolicyNotSet,
40 InternalServerError,
42 CancelRequested,
44 Unknown,
46}
47
48impl RetryState {
49 fn from_raw(value: i32) -> Self {
50 match ProtoRetryState::try_from(value) {
51 Ok(ProtoRetryState::Unspecified) => Self::Unspecified,
52 Ok(ProtoRetryState::InProgress) => Self::InProgress,
53 Ok(ProtoRetryState::NonRetryableFailure) => Self::NonRetryableFailure,
54 Ok(ProtoRetryState::Timeout) => Self::Timeout,
55 Ok(ProtoRetryState::MaximumAttemptsReached) => Self::MaximumAttemptsReached,
56 Ok(ProtoRetryState::RetryPolicyNotSet) => Self::RetryPolicyNotSet,
57 Ok(ProtoRetryState::InternalServerError) => Self::InternalServerError,
58 Ok(ProtoRetryState::CancelRequested) => Self::CancelRequested,
59 Err(_) => Self::Unknown,
60 }
61 }
62}
63
64impl From<ProtoRetryState> for RetryState {
65 fn from(value: ProtoRetryState) -> Self {
66 Self::from_raw(value as i32)
67 }
68}
69
70#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
72#[non_exhaustive]
73pub enum TimeoutType {
74 Unspecified,
76 StartToClose,
78 ScheduleToStart,
80 ScheduleToClose,
82 Heartbeat,
84 Unknown,
86}
87
88impl TimeoutType {
89 fn from_raw(value: i32) -> Self {
90 match ProtoTimeoutType::try_from(value) {
91 Ok(ProtoTimeoutType::Unspecified) => Self::Unspecified,
92 Ok(ProtoTimeoutType::StartToClose) => Self::StartToClose,
93 Ok(ProtoTimeoutType::ScheduleToStart) => Self::ScheduleToStart,
94 Ok(ProtoTimeoutType::ScheduleToClose) => Self::ScheduleToClose,
95 Ok(ProtoTimeoutType::Heartbeat) => Self::Heartbeat,
96 Err(_) => Self::Unknown,
97 }
98 }
99}
100
101impl From<ProtoTimeoutType> for TimeoutType {
102 fn from(value: ProtoTimeoutType) -> Self {
103 Self::from_raw(value as i32)
104 }
105}
106
107trait SerializableFailurePayload: Send + Sync {
110 fn to_payloads(
111 &self,
112 payload_converter: &PayloadConverter,
113 context: &SerializationContextData,
114 ) -> Result<Vec<Payload>, PayloadConversionError>;
115}
116
117impl<T> SerializableFailurePayload for T
118where
119 T: TemporalSerializable + Send + Sync + 'static,
120{
121 fn to_payloads(
122 &self,
123 payload_converter: &PayloadConverter,
124 context: &SerializationContextData,
125 ) -> Result<Vec<Payload>, PayloadConversionError> {
126 payload_converter.to_payloads(
127 &SerializationContext {
128 data: context,
129 converter: payload_converter,
130 },
131 self,
132 )
133 }
134}
135
136#[derive(derive_more::Debug)]
138pub struct FailurePayloads {
139 repr: FailurePayloadsRepr,
140}
141
142#[derive(derive_more::Debug)]
143enum FailurePayloadsRepr {
144 #[debug("Serializable(...)")]
145 Serializable(#[debug(skip)] Box<dyn SerializableFailurePayload>),
146 Decoded(DecodablePayloads),
147}
148
149impl FailurePayloads {
150 pub(crate) fn encode(
151 &self,
152 payload_converter: &PayloadConverter,
153 context: &SerializationContextData,
154 ) -> Result<Payloads, PayloadConversionError> {
155 let payloads = match &self.repr {
156 FailurePayloadsRepr::Serializable(value) => {
157 value.to_payloads(payload_converter, context)?
158 }
159 FailurePayloadsRepr::Decoded(value) => value.raw().to_vec(),
160 };
161 Ok(Payloads { payloads })
162 }
163
164 pub fn deserialize<T: TemporalDeserializable + 'static>(
166 &self,
167 ) -> Result<T, PayloadConversionError> {
168 match &self.repr {
169 FailurePayloadsRepr::Decoded(value) => value.deserialize(),
170 FailurePayloadsRepr::Serializable(_) => Err(PayloadConversionError::WrongEncoding),
171 }
172 }
173
174 pub fn raw(&self) -> Option<&[Payload]> {
176 match &self.repr {
177 FailurePayloadsRepr::Decoded(value) => Some(value.raw()),
178 FailurePayloadsRepr::Serializable(_) => None,
179 }
180 }
181
182 pub fn into_raw(self) -> Option<RawValue> {
184 match self.repr {
185 FailurePayloadsRepr::Decoded(value) => Some(value.into_raw()),
186 FailurePayloadsRepr::Serializable(_) => None,
187 }
188 }
189}
190
191impl From<DecodablePayloads> for FailurePayloads {
192 fn from(value: DecodablePayloads) -> Self {
193 Self {
194 repr: FailurePayloadsRepr::Decoded(value),
195 }
196 }
197}
198
199impl<T> From<T> for FailurePayloads
200where
201 T: TemporalSerializable + Send + Sync + 'static,
202{
203 fn from(value: T) -> Self {
204 Self {
205 repr: FailurePayloadsRepr::Serializable(Box::new(value)),
206 }
207 }
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
212#[non_exhaustive]
213pub enum ApplicationErrorCategory {
214 #[default]
216 Unspecified,
217 Benign,
220}
221
222impl From<ApplicationErrorCategory> for ProtoApplicationErrorCategory {
223 fn from(value: ApplicationErrorCategory) -> Self {
224 match value {
225 ApplicationErrorCategory::Unspecified => ProtoApplicationErrorCategory::Unspecified,
226 ApplicationErrorCategory::Benign => ProtoApplicationErrorCategory::Benign,
227 }
228 }
229}
230
231impl From<ProtoApplicationErrorCategory> for ApplicationErrorCategory {
232 fn from(value: ProtoApplicationErrorCategory) -> Self {
233 match value {
234 ProtoApplicationErrorCategory::Unspecified => ApplicationErrorCategory::Unspecified,
235 ProtoApplicationErrorCategory::Benign => ApplicationErrorCategory::Benign,
236 }
237 }
238}
239
240#[derive(Debug, bon::Builder)]
242#[builder(start_fn = builder, state_mod(vis = "pub"))]
243pub struct ApplicationFailure {
244 #[builder(start_fn, into)]
245 source: Box<dyn std::error::Error + Send + Sync>,
246 type_name: Option<String>,
247 #[builder(default)]
248 non_retryable: bool,
249 next_retry_delay: Option<Duration>,
250 #[builder(default = ApplicationErrorCategory::Unspecified)]
251 category: ApplicationErrorCategory,
252 #[builder(into)]
253 details: Option<FailurePayloads>,
254 failure: Option<Failure>,
255 cause: Option<Box<IncomingError>>,
256}
257
258impl ApplicationFailure {
259 pub fn new(source: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
261 Self {
262 source: source.into(),
263 type_name: None,
264 non_retryable: false,
265 next_retry_delay: None,
266 category: ApplicationErrorCategory::Unspecified,
267 details: None,
268 failure: None,
269 cause: None,
270 }
271 }
272
273 pub fn non_retryable(source: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
275 Self {
276 non_retryable: true,
277 ..Self::new(source)
278 }
279 }
280
281 pub fn source_error(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
283 &*self.source as &(dyn std::error::Error + Send + Sync + 'static)
284 }
285
286 pub fn type_name(&self) -> Option<&str> {
288 self.type_name.as_deref()
289 }
290
291 pub fn is_non_retryable(&self) -> bool {
293 self.non_retryable
294 }
295
296 pub fn next_retry_delay(&self) -> Option<Duration> {
298 self.next_retry_delay
299 }
300
301 pub fn category(&self) -> ApplicationErrorCategory {
303 self.category
304 }
305
306 pub fn details<T: TemporalDeserializable + 'static>(
308 &self,
309 ) -> Result<Option<T>, PayloadConversionError> {
310 self.details
311 .as_ref()
312 .map(FailurePayloads::deserialize)
313 .transpose()
314 }
315
316 pub fn raw_details(&self) -> Option<&[Payload]> {
318 self.details.as_ref().and_then(FailurePayloads::raw)
319 }
320
321 pub(crate) fn failure_payloads(&self) -> Option<&FailurePayloads> {
322 self.details.as_ref()
323 }
324
325 pub fn failure(&self) -> Option<&Failure> {
327 self.failure.as_ref()
328 }
329
330 pub fn into_failure(self) -> Option<Failure> {
332 self.failure
333 }
334
335 pub fn cause(&self) -> Option<&IncomingError> {
337 self.cause.as_deref()
338 }
339
340 pub fn as_timeout(&self) -> Option<&TimeoutError> {
343 self.cause().and_then(IncomingError::as_timeout)
344 }
345
346 pub fn as_cancelled(&self) -> Option<&CancelledError> {
349 self.cause().and_then(IncomingError::as_cancelled)
350 }
351
352 pub(crate) fn from_failure(
353 failure: Failure,
354 cause: Option<IncomingError>,
355 payload_converter: &PayloadConverter,
356 context: &SerializationContextData,
357 ) -> Self {
358 let app_info = failure
359 .maybe_application_failure()
360 .cloned()
361 .unwrap_or_default();
362 let type_name = (!app_info.r#type.is_empty()).then_some(app_info.r#type.clone());
363 Self {
364 source: failure.message.clone().into(),
365 type_name,
366 non_retryable: app_info.non_retryable,
367 next_retry_delay: app_info.next_retry_delay.and_then(|d| d.try_into().ok()),
368 category: app_info.category().into(),
369 details: app_info.details.map(|details| {
370 FailurePayloads::from(DecodablePayloads::new(
371 details.payloads,
372 payload_converter.clone(),
373 *context,
374 ))
375 }),
376 failure: Some(failure),
377 cause: cause.map(Box::new),
378 }
379 }
380}
381
382impl std::fmt::Display for ApplicationFailure {
383 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
384 write!(f, "{}", self.source)
385 }
386}
387
388impl std::error::Error for ApplicationFailure {
389 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
390 self.cause
391 .as_deref()
392 .map(|cause| cause as &(dyn std::error::Error + 'static))
393 .or_else(|| Some(self.source.as_ref()))
394 }
395}
396
397impl From<anyhow::Error> for ApplicationFailure {
398 fn from(value: anyhow::Error) -> Self {
399 Self::new(value)
400 }
401}
402
403impl From<PayloadConversionError> for ApplicationFailure {
404 fn from(value: PayloadConversionError) -> Self {
405 Self::new(value)
406 }
407}
408
409#[derive(Debug, thiserror::Error)]
411#[non_exhaustive]
412pub enum OutgoingError {
413 #[error(transparent)]
415 Activity(#[from] OutgoingActivityError),
416 #[error(transparent)]
418 Workflow(#[from] OutgoingWorkflowError),
419}
420
421#[derive(Debug, thiserror::Error)]
423#[non_exhaustive]
424pub enum OutgoingActivityError {
425 #[error(transparent)]
427 Application(#[from] Box<ApplicationFailure>),
428 #[error("Activity cancelled")]
430 Cancelled {
431 details: Option<FailurePayloads>,
433 },
434}
435
436#[derive(Debug, thiserror::Error)]
438#[non_exhaustive]
439pub enum OutgoingWorkflowError {
440 #[error(transparent)]
442 Application(#[from] Box<ApplicationFailure>),
443 #[error(transparent)]
445 PayloadConversion(#[from] PayloadConversionError),
446 #[error(transparent)]
448 ActivityExecution(#[from] Box<ActivityExecutionError>),
449 #[error(transparent)]
451 ChildWorkflowExecution(#[from] Box<ChildWorkflowExecutionError>),
452 #[error(transparent)]
454 ChildWorkflowStart(#[from] Box<ChildWorkflowStartError>),
455 #[error(transparent)]
457 WorkflowSignal(#[from] Box<WorkflowSignalError>),
458}
459
460impl OutgoingWorkflowError {
461 pub fn as_cancelled(&self) -> Option<&CancelledError> {
464 match self {
465 Self::Application(err) => err.as_cancelled(),
466 Self::PayloadConversion(_) => None,
467 Self::ActivityExecution(err) => err.as_cancelled(),
468 Self::ChildWorkflowExecution(err) => err.as_cancelled(),
469 Self::ChildWorkflowStart(err) => err.as_cancelled(),
470 Self::WorkflowSignal(err) => err.as_cancelled(),
471 }
472 }
473}
474
475impl From<anyhow::Error> for OutgoingWorkflowError {
476 fn from(value: anyhow::Error) -> Self {
477 Self::Application(Box::new(ApplicationFailure::new(value)))
478 }
479}
480
481impl From<ApplicationFailure> for OutgoingWorkflowError {
482 fn from(value: ApplicationFailure) -> Self {
483 Self::Application(Box::new(value))
484 }
485}
486
487impl From<ActivityExecutionError> for OutgoingWorkflowError {
488 fn from(value: ActivityExecutionError) -> Self {
489 match value {
490 ActivityExecutionError::Serialization(err) => Self::PayloadConversion(err),
491 other => Self::ActivityExecution(Box::new(other)),
492 }
493 }
494}
495
496impl From<ChildWorkflowExecutionError> for OutgoingWorkflowError {
497 fn from(value: ChildWorkflowExecutionError) -> Self {
498 match value {
499 ChildWorkflowExecutionError::Serialization(err) => Self::PayloadConversion(err),
500 other => Self::ChildWorkflowExecution(Box::new(other)),
501 }
502 }
503}
504
505impl From<ChildWorkflowStartError> for OutgoingWorkflowError {
506 fn from(value: ChildWorkflowStartError) -> Self {
507 match value {
508 ChildWorkflowStartError::Serialization(err) => Self::PayloadConversion(err),
509 other => Self::ChildWorkflowStart(Box::new(other)),
510 }
511 }
512}
513
514impl From<WorkflowSignalError> for OutgoingWorkflowError {
515 fn from(value: WorkflowSignalError) -> Self {
516 match value {
517 WorkflowSignalError::Serialization(err) => Self::PayloadConversion(err),
518 other => Self::WorkflowSignal(Box::new(other)),
519 }
520 }
521}
522
523#[derive(Debug)]
525pub enum IncomingError {
526 Application(ApplicationFailure),
528 Timeout(TimeoutError),
530 Cancelled(CancelledError),
532 Terminated(TerminatedError),
534 Server(ServerError),
536 ResetWorkflow(ResetWorkflowError),
538 Activity(ActivityFailureError),
540 ChildWorkflowExecution(ChildWorkflowFailureError),
542 NexusOperationExecution(IncomingNexusOperationExecutionError),
544 NexusHandler(IncomingNexusHandlerError),
546}
547
548impl IncomingError {
549 pub fn failure(&self) -> &Failure {
551 match self {
552 IncomingError::Application(err) => err
553 .failure()
554 .expect("decoded application failures retain their original proto"),
555 IncomingError::Timeout(err) => err.failure(),
556 IncomingError::Cancelled(err) => err.failure(),
557 IncomingError::Terminated(err) => err.failure(),
558 IncomingError::Server(err) => err.failure(),
559 IncomingError::ResetWorkflow(err) => err.failure(),
560 IncomingError::Activity(err) => err.failure(),
561 IncomingError::ChildWorkflowExecution(err) => err.failure(),
562 IncomingError::NexusOperationExecution(err) => err.failure(),
563 IncomingError::NexusHandler(err) => err.failure(),
564 }
565 }
566
567 pub fn cause(&self) -> Option<&IncomingError> {
569 match self {
570 IncomingError::Application(err) => err.cause(),
571 IncomingError::Timeout(err) => err.cause(),
572 IncomingError::Cancelled(err) => err.cause(),
573 IncomingError::Terminated(err) => err.cause(),
574 IncomingError::Server(err) => err.cause(),
575 IncomingError::ResetWorkflow(err) => err.cause(),
576 IncomingError::Activity(err) => err.cause(),
577 IncomingError::ChildWorkflowExecution(err) => err.cause(),
578 IncomingError::NexusOperationExecution(err) => err.cause(),
579 IncomingError::NexusHandler(err) => err.cause(),
580 }
581 }
582
583 pub fn into_failure(self) -> Failure {
585 match self {
586 IncomingError::Application(err) => err
587 .into_failure()
588 .expect("decoded application failures retain their original proto"),
589 IncomingError::Timeout(err) => err.into_failure(),
590 IncomingError::Cancelled(err) => err.into_failure(),
591 IncomingError::Terminated(err) => err.into_failure(),
592 IncomingError::Server(err) => err.into_failure(),
593 IncomingError::ResetWorkflow(err) => err.into_failure(),
594 IncomingError::Activity(err) => err.into_failure(),
595 IncomingError::ChildWorkflowExecution(err) => err.into_failure(),
596 IncomingError::NexusOperationExecution(err) => err.into_failure(),
597 IncomingError::NexusHandler(err) => err.into_failure(),
598 }
599 }
600
601 pub fn as_timeout(&self) -> Option<&TimeoutError> {
603 match self {
604 IncomingError::Timeout(err) => Some(err),
605 _ => None,
606 }
607 }
608
609 pub fn as_cancelled(&self) -> Option<&CancelledError> {
611 match self {
612 IncomingError::Cancelled(err) => Some(err),
613 _ => None,
614 }
615 }
616}
617
618impl std::fmt::Display for IncomingError {
619 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
620 match self {
621 IncomingError::Application(err) => err.fmt(f),
622 IncomingError::Timeout(err) => err.fmt(f),
623 IncomingError::Cancelled(err) => err.fmt(f),
624 IncomingError::Terminated(err) => err.fmt(f),
625 IncomingError::Server(err) => err.fmt(f),
626 IncomingError::ResetWorkflow(err) => err.fmt(f),
627 IncomingError::Activity(err) => err.fmt(f),
628 IncomingError::ChildWorkflowExecution(err) => err.fmt(f),
629 IncomingError::NexusOperationExecution(err) => err.fmt(f),
630 IncomingError::NexusHandler(err) => err.fmt(f),
631 }
632 }
633}
634
635impl std::error::Error for IncomingError {
636 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
637 match self {
638 IncomingError::Application(err) => Some(err),
639 IncomingError::Timeout(err) => Some(err),
640 IncomingError::Cancelled(err) => Some(err),
641 IncomingError::Terminated(err) => Some(err),
642 IncomingError::Server(err) => Some(err),
643 IncomingError::ResetWorkflow(err) => Some(err),
644 IncomingError::Activity(err) => Some(err),
645 IncomingError::ChildWorkflowExecution(err) => Some(err),
646 IncomingError::NexusOperationExecution(err) => Some(err),
647 IncomingError::NexusHandler(err) => Some(err),
648 }
649 }
650}
651
652macro_rules! impl_incoming_failure_wrapper {
653 ($name:ident) => {
654 impl $name {
655 pub fn failure(&self) -> &Failure {
657 &self.failure
658 }
659
660 pub fn cause(&self) -> Option<&IncomingError> {
662 self.cause.as_deref()
663 }
664
665 pub fn into_failure(self) -> Failure {
667 self.failure
668 }
669
670 pub fn into_parts(self) -> (Failure, Option<IncomingError>) {
672 (self.failure, self.cause.map(|cause| *cause))
673 }
674 }
675
676 impl std::fmt::Display for $name {
677 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
678 self.failure.fmt(f)
679 }
680 }
681
682 impl std::error::Error for $name {
683 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
684 self.cause
685 .as_deref()
686 .map(|cause| cause as &(dyn std::error::Error + 'static))
687 }
688 }
689 };
690}
691
692macro_rules! incoming_failure_wrapper {
693 ($name:ident, $doc:literal) => {
694 #[doc = $doc]
695 #[derive(Debug)]
696 pub struct $name {
697 failure: Failure,
698 cause: Option<Box<IncomingError>>,
699 }
700
701 impl $name {
702 pub(crate) fn new(failure: Failure, cause: Option<IncomingError>) -> Self {
704 Self {
705 failure,
706 cause: cause.map(Box::new),
707 }
708 }
709 }
710
711 impl_incoming_failure_wrapper!($name);
712 };
713}
714
715#[derive(Debug)]
717pub struct TimeoutError {
718 failure: Failure,
719 cause: Option<Box<IncomingError>>,
720 timeout_type: TimeoutType,
721 last_heartbeat_details: Option<DecodablePayloads>,
722}
723
724impl TimeoutError {
725 pub(crate) fn new(
727 failure: Failure,
728 failure_info: crate::protos::temporal::api::failure::v1::TimeoutFailureInfo,
729 cause: Option<IncomingError>,
730 payload_converter: &PayloadConverter,
731 context: &SerializationContextData,
732 ) -> Self {
733 Self {
734 failure,
735 cause: cause.map(Box::new),
736 timeout_type: TimeoutType::from_raw(failure_info.timeout_type),
737 last_heartbeat_details: failure_info.last_heartbeat_details.map(|details| {
738 DecodablePayloads::new(details.payloads, payload_converter.clone(), *context)
739 }),
740 }
741 }
742
743 pub fn timeout_type(&self) -> TimeoutType {
745 self.timeout_type
746 }
747
748 pub fn last_heartbeat_details<T: TemporalDeserializable + 'static>(
750 &self,
751 ) -> Result<Option<T>, PayloadConversionError> {
752 self.last_heartbeat_details
753 .as_ref()
754 .map(DecodablePayloads::deserialize)
755 .transpose()
756 }
757
758 pub fn raw_last_heartbeat_details(&self) -> Option<&[Payload]> {
760 self.last_heartbeat_details
761 .as_ref()
762 .map(DecodablePayloads::raw)
763 }
764}
765
766impl_incoming_failure_wrapper!(TimeoutError);
767
768#[derive(Debug)]
770pub struct CancelledError {
771 failure: Failure,
772 cause: Option<Box<IncomingError>>,
773 details: Option<DecodablePayloads>,
774}
775
776impl CancelledError {
777 pub(crate) fn new(
779 failure: Failure,
780 failure_info: crate::protos::temporal::api::failure::v1::CanceledFailureInfo,
781 cause: Option<IncomingError>,
782 payload_converter: &PayloadConverter,
783 context: &SerializationContextData,
784 ) -> Self {
785 Self {
786 failure,
787 cause: cause.map(Box::new),
788 details: failure_info.details.map(|details| {
789 DecodablePayloads::new(details.payloads, payload_converter.clone(), *context)
790 }),
791 }
792 }
793
794 pub fn details<T: TemporalDeserializable + 'static>(
797 &self,
798 ) -> Result<Option<T>, PayloadConversionError> {
799 self.details
800 .as_ref()
801 .map(DecodablePayloads::deserialize)
802 .transpose()
803 }
804
805 pub fn raw_details(&self) -> Option<&[Payload]> {
807 self.details.as_ref().map(DecodablePayloads::raw)
808 }
809}
810
811impl_incoming_failure_wrapper!(CancelledError);
812incoming_failure_wrapper!(TerminatedError, "A normalized terminated failure.");
813incoming_failure_wrapper!(ServerError, "A normalized server failure.");
814incoming_failure_wrapper!(ResetWorkflowError, "A normalized reset-workflow failure.");
815
816#[derive(Debug)]
818pub struct ActivityFailureError {
819 failure: Failure,
820 cause: Option<Box<IncomingError>>,
821 activity_id: String,
822 activity_type: Option<String>,
823 scheduled_event_id: i64,
824 started_event_id: i64,
825 identity: String,
826 retry_state: RetryState,
827}
828
829impl ActivityFailureError {
830 pub(crate) fn new(
832 failure: Failure,
833 failure_info: crate::protos::temporal::api::failure::v1::ActivityFailureInfo,
834 cause: Option<IncomingError>,
835 ) -> Self {
836 let retry_state = RetryState::from_raw(failure_info.retry_state);
837 Self {
838 failure,
839 cause: cause.map(Box::new),
840 activity_id: failure_info.activity_id,
841 activity_type: failure_info
842 .activity_type
843 .map(|activity_type| activity_type.name),
844 scheduled_event_id: failure_info.scheduled_event_id,
845 started_event_id: failure_info.started_event_id,
846 identity: failure_info.identity,
847 retry_state,
848 }
849 }
850
851 pub fn activity_id(&self) -> &str {
853 &self.activity_id
854 }
855
856 pub fn activity_type(&self) -> Option<&str> {
858 self.activity_type.as_deref()
859 }
860
861 pub fn scheduled_event_id(&self) -> i64 {
863 self.scheduled_event_id
864 }
865
866 pub fn started_event_id(&self) -> i64 {
868 self.started_event_id
869 }
870
871 pub fn identity(&self) -> &str {
873 &self.identity
874 }
875
876 pub fn retry_state(&self) -> RetryState {
878 self.retry_state
879 }
880
881 pub fn as_timeout(&self) -> Option<&TimeoutError> {
884 self.cause().and_then(IncomingError::as_timeout)
885 }
886
887 pub fn as_cancelled(&self) -> Option<&CancelledError> {
890 self.cause().and_then(IncomingError::as_cancelled)
891 }
892}
893
894impl_incoming_failure_wrapper!(ActivityFailureError);
895#[derive(Debug)]
897pub struct ChildWorkflowFailureError {
898 failure: Failure,
899 cause: Option<Box<IncomingError>>,
900 namespace: String,
901 workflow_execution: Option<WorkflowExecution>,
902 workflow_type: Option<String>,
903 initiated_event_id: i64,
904 started_event_id: i64,
905 retry_state: RetryState,
906}
907
908impl ChildWorkflowFailureError {
909 pub(crate) fn new(
911 failure: Failure,
912 failure_info: crate::protos::temporal::api::failure::v1::ChildWorkflowExecutionFailureInfo,
913 cause: Option<IncomingError>,
914 ) -> Self {
915 let retry_state = RetryState::from_raw(failure_info.retry_state);
916 Self {
917 failure,
918 cause: cause.map(Box::new),
919 namespace: failure_info.namespace,
920 workflow_execution: failure_info.workflow_execution.map(Into::into),
921 workflow_type: failure_info
922 .workflow_type
923 .map(|workflow_type| workflow_type.name),
924 initiated_event_id: failure_info.initiated_event_id,
925 started_event_id: failure_info.started_event_id,
926 retry_state,
927 }
928 }
929
930 pub fn namespace(&self) -> &str {
932 &self.namespace
933 }
934
935 pub fn workflow_execution(&self) -> Option<&WorkflowExecution> {
937 self.workflow_execution.as_ref()
938 }
939
940 pub fn workflow_type(&self) -> Option<&str> {
942 self.workflow_type.as_deref()
943 }
944
945 pub fn initiated_event_id(&self) -> i64 {
947 self.initiated_event_id
948 }
949
950 pub fn started_event_id(&self) -> i64 {
952 self.started_event_id
953 }
954
955 pub fn retry_state(&self) -> RetryState {
957 self.retry_state
958 }
959
960 pub fn as_timeout(&self) -> Option<&TimeoutError> {
963 self.cause().and_then(IncomingError::as_timeout)
964 }
965
966 pub fn as_cancelled(&self) -> Option<&CancelledError> {
969 self.cause().and_then(IncomingError::as_cancelled)
970 }
971}
972
973impl_incoming_failure_wrapper!(ChildWorkflowFailureError);
974incoming_failure_wrapper!(
975 IncomingNexusOperationExecutionError,
976 "A normalized nexus operation failure wrapper."
977);
978incoming_failure_wrapper!(
979 IncomingNexusHandlerError,
980 "A normalized nexus handler failure wrapper."
981);
982
983#[derive(Debug, thiserror::Error)]
985pub enum ActivityExecutionError {
986 #[error("Activity failed: {}", .0.failure().message)]
988 Failed(#[source] ActivityFailureError),
989 #[error("Activity cancelled: {}", .0.failure().message)]
991 Cancelled(#[source] CancelledError),
992 #[error("Payload conversion failed: {0}")]
994 Serialization(#[from] PayloadConversionError),
995}
996
997impl ActivityExecutionError {
998 pub fn failure(&self) -> Option<&Failure> {
1000 match self {
1001 ActivityExecutionError::Failed(err) => Some(err.failure()),
1002 ActivityExecutionError::Cancelled(err) => Some(err.failure()),
1003 ActivityExecutionError::Serialization(_) => None,
1004 }
1005 }
1006
1007 pub fn cause(&self) -> Option<&IncomingError> {
1009 match self {
1010 ActivityExecutionError::Failed(err) => err.cause(),
1011 ActivityExecutionError::Cancelled(err) => err.cause(),
1012 ActivityExecutionError::Serialization(_) => None,
1013 }
1014 }
1015
1016 pub fn reason(&self) -> Option<&IncomingError> {
1018 match self {
1019 ActivityExecutionError::Failed(err) => err.cause(),
1020 ActivityExecutionError::Cancelled(_) | ActivityExecutionError::Serialization(_) => None,
1021 }
1022 }
1023
1024 pub fn as_timeout(&self) -> Option<&TimeoutError> {
1027 match self {
1028 ActivityExecutionError::Failed(err) => err.as_timeout(),
1029 ActivityExecutionError::Serialization(_) | ActivityExecutionError::Cancelled(_) => None,
1030 }
1031 }
1032
1033 pub fn as_cancelled(&self) -> Option<&CancelledError> {
1036 match self {
1037 ActivityExecutionError::Failed(err) => err.as_cancelled(),
1038 ActivityExecutionError::Cancelled(err) => Some(err),
1039 ActivityExecutionError::Serialization(_) => None,
1040 }
1041 }
1042}
1043
1044#[derive(Debug, thiserror::Error)]
1046pub enum ChildWorkflowStartError {
1047 #[error("Child workflow start cancelled: {}", .0.failure().message)]
1049 Cancelled(#[source] Box<CancelledError>),
1050 #[error(
1052 "Child workflow start failed: workflow_id={workflow_id}, workflow_type={workflow_type}, cause={cause:?}"
1053 )]
1054 StartFailed {
1055 workflow_id: String,
1057 workflow_type: String,
1059 cause: StartChildWorkflowExecutionFailedCause,
1061 },
1062 #[error("Payload conversion failed: {0}")]
1064 Serialization(#[from] PayloadConversionError),
1065}
1066
1067impl ChildWorkflowStartError {
1068 pub fn failure(&self) -> Option<&Failure> {
1070 match self {
1071 ChildWorkflowStartError::Cancelled(err) => Some(err.failure()),
1072 ChildWorkflowStartError::StartFailed { .. }
1073 | ChildWorkflowStartError::Serialization(_) => None,
1074 }
1075 }
1076
1077 pub fn cause(&self) -> Option<&IncomingError> {
1079 match self {
1080 ChildWorkflowStartError::Cancelled(err) => err.cause(),
1081 ChildWorkflowStartError::StartFailed { .. }
1082 | ChildWorkflowStartError::Serialization(_) => None,
1083 }
1084 }
1085
1086 pub fn as_cancelled(&self) -> Option<&CancelledError> {
1089 match self {
1090 ChildWorkflowStartError::Cancelled(err) => Some(err),
1091 ChildWorkflowStartError::StartFailed { .. }
1092 | ChildWorkflowStartError::Serialization(_) => None,
1093 }
1094 }
1095}
1096
1097#[derive(Debug, thiserror::Error)]
1099pub enum ChildWorkflowExecutionError {
1100 #[error("Child workflow failed: {}", .0.failure().message)]
1102 Failed(#[source] Box<ChildWorkflowFailureError>),
1103 #[error("Payload conversion failed: {0}")]
1105 Serialization(#[from] PayloadConversionError),
1106}
1107
1108impl ChildWorkflowExecutionError {
1109 pub fn failure(&self) -> Option<&Failure> {
1111 match self {
1112 ChildWorkflowExecutionError::Failed(err) => Some(err.failure()),
1113 ChildWorkflowExecutionError::Serialization(_) => None,
1114 }
1115 }
1116
1117 pub fn cause(&self) -> Option<&IncomingError> {
1119 match self {
1120 ChildWorkflowExecutionError::Failed(err) => err.cause(),
1121 ChildWorkflowExecutionError::Serialization(_) => None,
1122 }
1123 }
1124
1125 pub fn reason(&self) -> Option<&IncomingError> {
1127 match self {
1128 ChildWorkflowExecutionError::Failed(err) => err.cause(),
1129 ChildWorkflowExecutionError::Serialization(_) => None,
1130 }
1131 }
1132
1133 pub fn as_timeout(&self) -> Option<&TimeoutError> {
1136 match self {
1137 ChildWorkflowExecutionError::Failed(err) => err.as_timeout(),
1138 ChildWorkflowExecutionError::Serialization(_) => None,
1139 }
1140 }
1141
1142 pub fn as_cancelled(&self) -> Option<&CancelledError> {
1145 match self {
1146 ChildWorkflowExecutionError::Failed(err) => err.as_cancelled(),
1147 ChildWorkflowExecutionError::Serialization(_) => None,
1148 }
1149 }
1150}
1151
1152#[derive(Debug, thiserror::Error)]
1154pub enum WorkflowSignalError {
1155 #[error("Child workflow signal failed: {}", .0.failure().message)]
1157 Failed(#[source] Box<WorkflowSignalFailureError>),
1158 #[error("Signal payload conversion failed: {0}")]
1160 Serialization(#[from] PayloadConversionError),
1161}
1162
1163impl WorkflowSignalError {
1164 pub fn failure(&self) -> Option<&Failure> {
1166 match self {
1167 WorkflowSignalError::Failed(err) => Some(err.failure()),
1168 WorkflowSignalError::Serialization(_) => None,
1169 }
1170 }
1171
1172 pub fn cause(&self) -> Option<&IncomingError> {
1174 match self {
1175 WorkflowSignalError::Failed(err) => err.cause(),
1176 WorkflowSignalError::Serialization(_) => None,
1177 }
1178 }
1179
1180 pub fn reason(&self) -> Option<&IncomingError> {
1182 match self {
1183 WorkflowSignalError::Failed(err) => Some(err.error()),
1184 WorkflowSignalError::Serialization(_) => None,
1185 }
1186 }
1187
1188 pub fn as_cancelled(&self) -> Option<&CancelledError> {
1191 self.reason()?.as_cancelled()
1192 }
1193}
1194
1195#[derive(Debug)]
1197pub struct WorkflowSignalFailureError {
1198 failure: Failure,
1199 error: Box<IncomingError>,
1200}
1201
1202impl WorkflowSignalFailureError {
1203 pub(crate) fn new(failure: Failure, error: IncomingError) -> Self {
1205 Self {
1206 failure,
1207 error: Box::new(error),
1208 }
1209 }
1210
1211 pub fn failure(&self) -> &Failure {
1213 &self.failure
1214 }
1215
1216 pub fn cause(&self) -> Option<&IncomingError> {
1218 self.error.cause()
1219 }
1220
1221 pub fn error(&self) -> &IncomingError {
1223 &self.error
1224 }
1225}
1226
1227impl std::fmt::Display for WorkflowSignalFailureError {
1228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1229 self.failure.fmt(f)
1230 }
1231}
1232
1233impl std::error::Error for WorkflowSignalFailureError {
1234 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1235 self.cause()
1236 .map(|cause| cause as &(dyn std::error::Error + 'static))
1237 }
1238}
1239
1240#[cfg(test)]
1241mod tests {
1242 use super::*;
1243 use crate::{
1244 data_converters::{
1245 DefaultFailureConverter, FailureConverter, GenericPayloadConverter, PayloadConverter,
1246 SerializationContext, SerializationContextData,
1247 },
1248 protos::temporal::api::{
1249 common::v1::Payload,
1250 failure::v1::{ActivityFailureInfo, TimeoutFailureInfo, failure::FailureInfo},
1251 },
1252 };
1253
1254 struct AlwaysFailsSerialize;
1255
1256 impl serde::Serialize for AlwaysFailsSerialize {
1257 fn serialize<S: serde::Serializer>(&self, _serializer: S) -> Result<S::Ok, S::Error> {
1258 Err(serde::ser::Error::custom("serialize boom"))
1259 }
1260 }
1261
1262 #[test]
1263 fn decoded_failures_hide_unknown_state_values() {
1264 let failure = Failure {
1265 cause: Some(Box::new(Failure {
1266 failure_info: Some(FailureInfo::TimeoutFailureInfo(TimeoutFailureInfo {
1267 timeout_type: 654_321,
1268 ..Default::default()
1269 })),
1270 ..Default::default()
1271 })),
1272 failure_info: Some(FailureInfo::ActivityFailureInfo(ActivityFailureInfo {
1273 retry_state: 123_456,
1274 ..Default::default()
1275 })),
1276 ..Default::default()
1277 };
1278
1279 let decoded = DefaultFailureConverter
1280 .to_error(
1281 failure,
1282 &PayloadConverter::default(),
1283 &SerializationContextData::Workflow,
1284 )
1285 .unwrap();
1286 let IncomingError::Activity(activity) = decoded else {
1287 panic!("expected activity failure");
1288 };
1289 assert_eq!(activity.retry_state(), RetryState::Unknown);
1290 let Some(FailureInfo::ActivityFailureInfo(raw_activity)) =
1291 activity.failure().failure_info.as_ref()
1292 else {
1293 panic!("expected raw activity failure info");
1294 };
1295 assert_eq!(raw_activity.retry_state, 123_456);
1296 let Some(IncomingError::Timeout(timeout)) = activity.cause() else {
1297 panic!("expected timeout cause");
1298 };
1299 assert_eq!(timeout.timeout_type(), TimeoutType::Unknown);
1300 let Some(FailureInfo::TimeoutFailureInfo(raw_timeout)) =
1301 timeout.failure().failure_info.as_ref()
1302 else {
1303 panic!("expected raw timeout failure info");
1304 };
1305 assert_eq!(raw_timeout.timeout_type, 654_321);
1306 }
1307
1308 #[test]
1309 fn constructors_set_retryability_defaults() {
1310 assert!(!ApplicationFailure::new(anyhow::anyhow!("retryable")).is_non_retryable());
1311 assert!(
1312 ApplicationFailure::non_retryable(anyhow::anyhow!("non-retryable")).is_non_retryable()
1313 );
1314 }
1315
1316 #[test]
1317 fn conversion_preserves_application_metadata() {
1318 let payloads = Payloads {
1319 payloads: vec![Payload {
1320 data: b"details".to_vec(),
1321 ..Default::default()
1322 }],
1323 };
1324 let failure = DefaultFailureConverter.to_failure(
1325 OutgoingError::Workflow(OutgoingWorkflowError::Application(Box::new(
1326 ApplicationFailure::builder(anyhow::anyhow!("oops"))
1327 .type_name("MyType".to_owned())
1328 .non_retryable(true)
1329 .next_retry_delay(Duration::from_secs(3))
1330 .category(ApplicationErrorCategory::Benign)
1331 .details(RawValue::new(payloads.payloads.clone()))
1332 .build(),
1333 ))),
1334 &PayloadConverter::default(),
1335 &SerializationContextData::None,
1336 );
1337 let Some(FailureInfo::ApplicationFailureInfo(info)) = failure.failure_info else {
1338 panic!("expected application failure info");
1339 };
1340 assert_eq!(failure.message, "oops");
1341 assert_eq!(info.r#type, "MyType");
1342 assert!(info.non_retryable);
1343 assert_eq!(info.details, Some(payloads));
1344 assert_eq!(info.category(), ProtoApplicationErrorCategory::Benign);
1345 assert_eq!(info.next_retry_delay.unwrap().seconds, 3);
1346 }
1347
1348 #[test]
1349 fn builder_accepts_raw_payload_details() {
1350 let payload = Payload {
1351 data: b"details".to_vec(),
1352 ..Default::default()
1353 };
1354 let failure = DefaultFailureConverter.to_failure(
1355 OutgoingError::Workflow(OutgoingWorkflowError::Application(Box::new(
1356 ApplicationFailure::builder(anyhow::anyhow!("oops"))
1357 .details(RawValue::new(vec![payload.clone()]))
1358 .build(),
1359 ))),
1360 &PayloadConverter::default(),
1361 &SerializationContextData::None,
1362 );
1363
1364 let Some(FailureInfo::ApplicationFailureInfo(info)) = failure.failure_info else {
1365 panic!("expected application failure info");
1366 };
1367 assert_eq!(info.details.unwrap().payloads, vec![payload]);
1368 }
1369
1370 #[test]
1371 fn builder_accepts_serializable_details() {
1372 let failure = DefaultFailureConverter.to_failure(
1373 OutgoingError::Workflow(OutgoingWorkflowError::Application(Box::new(
1374 ApplicationFailure::builder(anyhow::anyhow!("oops"))
1375 .details("details".to_string())
1376 .build(),
1377 ))),
1378 &PayloadConverter::default(),
1379 &SerializationContextData::None,
1380 );
1381
1382 let Some(FailureInfo::ApplicationFailureInfo(info)) = failure.failure_info else {
1383 panic!("expected application failure info");
1384 };
1385 let payloads = info.details.expect("expected details").payloads;
1386 let converter = PayloadConverter::default();
1387 let details: String = converter
1388 .from_payloads(
1389 &SerializationContext {
1390 data: &SerializationContextData::None,
1391 converter: &converter,
1392 },
1393 payloads,
1394 )
1395 .unwrap();
1396 assert_eq!(details, "details");
1397 }
1398
1399 #[test]
1400 fn application_failure_encoding_surfaces_detail_encoding_errors() {
1401 let failure = DefaultFailureConverter.to_failure(
1402 OutgoingError::Workflow(OutgoingWorkflowError::Application(Box::new(
1403 ApplicationFailure::builder(anyhow::anyhow!("oops"))
1404 .details(AlwaysFailsSerialize)
1405 .build(),
1406 ))),
1407 &PayloadConverter::default(),
1408 &SerializationContextData::None,
1409 );
1410
1411 assert_eq!(
1412 failure.message,
1413 "Failed converting error to failure: Encoding error: serialize boom, original error message: oops"
1414 );
1415 assert!(matches!(
1416 failure.failure_info,
1417 Some(FailureInfo::ApplicationFailureInfo(_))
1418 ));
1419 }
1420
1421 #[test]
1422 fn anyhow_workflow_errors_default_to_application_outgoing_errors() {
1423 let outgoing: OutgoingWorkflowError = anyhow::anyhow!("workflow boom").into();
1424
1425 let OutgoingWorkflowError::Application(app) = outgoing else {
1426 panic!("plain workflow errors should default to application failures");
1427 };
1428 assert_eq!(app.to_string(), "workflow boom");
1429 }
1430
1431 #[test]
1432 fn payload_conversion_errors_use_dedicated_outgoing_variant() {
1433 let outgoing: OutgoingWorkflowError =
1434 PayloadConversionError::EncodingError(anyhow::anyhow!("encode boom").into()).into();
1435
1436 let OutgoingWorkflowError::PayloadConversion(err) = outgoing else {
1437 panic!("expected a payload conversion failure");
1438 };
1439 assert_eq!(err.to_string(), "Encoding error: encode boom");
1440 }
1441}