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)]
411pub enum OutgoingError {
412 #[error(transparent)]
414 Activity(#[from] OutgoingActivityError),
415 #[error(transparent)]
417 Workflow(#[from] OutgoingWorkflowError),
418}
419
420#[derive(Debug, thiserror::Error)]
422pub enum OutgoingActivityError {
423 #[error(transparent)]
425 Application(#[from] Box<ApplicationFailure>),
426 #[error("Activity cancelled")]
428 Cancelled {
429 details: Option<FailurePayloads>,
431 },
432}
433
434#[derive(Debug, thiserror::Error)]
436pub enum OutgoingWorkflowError {
437 #[error(transparent)]
439 Application(#[from] Box<ApplicationFailure>),
440 #[error(transparent)]
442 ActivityExecution(#[from] Box<ActivityExecutionError>),
443 #[error(transparent)]
445 ChildWorkflowExecution(#[from] Box<ChildWorkflowExecutionError>),
446 #[error(transparent)]
448 ChildWorkflowStart(#[from] Box<ChildWorkflowStartError>),
449 #[error(transparent)]
451 WorkflowSignal(#[from] Box<WorkflowSignalError>),
452}
453
454impl From<anyhow::Error> for OutgoingWorkflowError {
455 fn from(value: anyhow::Error) -> Self {
456 Self::Application(Box::new(ApplicationFailure::new(value)))
457 }
458}
459
460impl From<PayloadConversionError> for OutgoingWorkflowError {
461 fn from(value: PayloadConversionError) -> Self {
462 Self::Application(Box::new(value.into()))
463 }
464}
465
466impl From<ApplicationFailure> for OutgoingWorkflowError {
467 fn from(value: ApplicationFailure) -> Self {
468 Self::Application(Box::new(value))
469 }
470}
471
472impl From<ActivityExecutionError> for OutgoingWorkflowError {
473 fn from(value: ActivityExecutionError) -> Self {
474 Self::ActivityExecution(Box::new(value))
475 }
476}
477
478impl From<ChildWorkflowExecutionError> for OutgoingWorkflowError {
479 fn from(value: ChildWorkflowExecutionError) -> Self {
480 Self::ChildWorkflowExecution(Box::new(value))
481 }
482}
483
484impl From<ChildWorkflowStartError> for OutgoingWorkflowError {
485 fn from(value: ChildWorkflowStartError) -> Self {
486 Self::ChildWorkflowStart(Box::new(value))
487 }
488}
489
490impl From<WorkflowSignalError> for OutgoingWorkflowError {
491 fn from(value: WorkflowSignalError) -> Self {
492 Self::WorkflowSignal(Box::new(value))
493 }
494}
495
496#[derive(Debug)]
498pub enum IncomingError {
499 Application(ApplicationFailure),
501 Timeout(TimeoutError),
503 Cancelled(CancelledError),
505 Terminated(TerminatedError),
507 Server(ServerError),
509 ResetWorkflow(ResetWorkflowError),
511 Activity(ActivityFailureError),
513 ChildWorkflowExecution(ChildWorkflowFailureError),
515 NexusOperationExecution(IncomingNexusOperationExecutionError),
517 NexusHandler(IncomingNexusHandlerError),
519}
520
521impl IncomingError {
522 pub fn failure(&self) -> &Failure {
524 match self {
525 IncomingError::Application(err) => err
526 .failure()
527 .expect("decoded application failures retain their original proto"),
528 IncomingError::Timeout(err) => err.failure(),
529 IncomingError::Cancelled(err) => err.failure(),
530 IncomingError::Terminated(err) => err.failure(),
531 IncomingError::Server(err) => err.failure(),
532 IncomingError::ResetWorkflow(err) => err.failure(),
533 IncomingError::Activity(err) => err.failure(),
534 IncomingError::ChildWorkflowExecution(err) => err.failure(),
535 IncomingError::NexusOperationExecution(err) => err.failure(),
536 IncomingError::NexusHandler(err) => err.failure(),
537 }
538 }
539
540 pub fn cause(&self) -> Option<&IncomingError> {
542 match self {
543 IncomingError::Application(err) => err.cause(),
544 IncomingError::Timeout(err) => err.cause(),
545 IncomingError::Cancelled(err) => err.cause(),
546 IncomingError::Terminated(err) => err.cause(),
547 IncomingError::Server(err) => err.cause(),
548 IncomingError::ResetWorkflow(err) => err.cause(),
549 IncomingError::Activity(err) => err.cause(),
550 IncomingError::ChildWorkflowExecution(err) => err.cause(),
551 IncomingError::NexusOperationExecution(err) => err.cause(),
552 IncomingError::NexusHandler(err) => err.cause(),
553 }
554 }
555
556 pub fn into_failure(self) -> Failure {
558 match self {
559 IncomingError::Application(err) => err
560 .into_failure()
561 .expect("decoded application failures retain their original proto"),
562 IncomingError::Timeout(err) => err.into_failure(),
563 IncomingError::Cancelled(err) => err.into_failure(),
564 IncomingError::Terminated(err) => err.into_failure(),
565 IncomingError::Server(err) => err.into_failure(),
566 IncomingError::ResetWorkflow(err) => err.into_failure(),
567 IncomingError::Activity(err) => err.into_failure(),
568 IncomingError::ChildWorkflowExecution(err) => err.into_failure(),
569 IncomingError::NexusOperationExecution(err) => err.into_failure(),
570 IncomingError::NexusHandler(err) => err.into_failure(),
571 }
572 }
573
574 pub fn as_timeout(&self) -> Option<&TimeoutError> {
576 match self {
577 IncomingError::Timeout(err) => Some(err),
578 _ => None,
579 }
580 }
581
582 pub fn as_cancelled(&self) -> Option<&CancelledError> {
584 match self {
585 IncomingError::Cancelled(err) => Some(err),
586 _ => None,
587 }
588 }
589}
590
591impl std::fmt::Display for IncomingError {
592 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
593 match self {
594 IncomingError::Application(err) => err.fmt(f),
595 IncomingError::Timeout(err) => err.fmt(f),
596 IncomingError::Cancelled(err) => err.fmt(f),
597 IncomingError::Terminated(err) => err.fmt(f),
598 IncomingError::Server(err) => err.fmt(f),
599 IncomingError::ResetWorkflow(err) => err.fmt(f),
600 IncomingError::Activity(err) => err.fmt(f),
601 IncomingError::ChildWorkflowExecution(err) => err.fmt(f),
602 IncomingError::NexusOperationExecution(err) => err.fmt(f),
603 IncomingError::NexusHandler(err) => err.fmt(f),
604 }
605 }
606}
607
608impl std::error::Error for IncomingError {
609 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
610 match self {
611 IncomingError::Application(err) => Some(err),
612 IncomingError::Timeout(err) => Some(err),
613 IncomingError::Cancelled(err) => Some(err),
614 IncomingError::Terminated(err) => Some(err),
615 IncomingError::Server(err) => Some(err),
616 IncomingError::ResetWorkflow(err) => Some(err),
617 IncomingError::Activity(err) => Some(err),
618 IncomingError::ChildWorkflowExecution(err) => Some(err),
619 IncomingError::NexusOperationExecution(err) => Some(err),
620 IncomingError::NexusHandler(err) => Some(err),
621 }
622 }
623}
624
625macro_rules! impl_incoming_failure_wrapper {
626 ($name:ident) => {
627 impl $name {
628 pub fn failure(&self) -> &Failure {
630 &self.failure
631 }
632
633 pub fn cause(&self) -> Option<&IncomingError> {
635 self.cause.as_deref()
636 }
637
638 pub fn into_failure(self) -> Failure {
640 self.failure
641 }
642
643 pub fn into_parts(self) -> (Failure, Option<IncomingError>) {
645 (self.failure, self.cause.map(|cause| *cause))
646 }
647 }
648
649 impl std::fmt::Display for $name {
650 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
651 self.failure.fmt(f)
652 }
653 }
654
655 impl std::error::Error for $name {
656 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
657 self.cause
658 .as_deref()
659 .map(|cause| cause as &(dyn std::error::Error + 'static))
660 }
661 }
662 };
663}
664
665macro_rules! incoming_failure_wrapper {
666 ($name:ident, $doc:literal) => {
667 #[doc = $doc]
668 #[derive(Debug)]
669 pub struct $name {
670 failure: Failure,
671 cause: Option<Box<IncomingError>>,
672 }
673
674 impl $name {
675 pub(crate) fn new(failure: Failure, cause: Option<IncomingError>) -> Self {
677 Self {
678 failure,
679 cause: cause.map(Box::new),
680 }
681 }
682 }
683
684 impl_incoming_failure_wrapper!($name);
685 };
686}
687
688#[derive(Debug)]
690pub struct TimeoutError {
691 failure: Failure,
692 cause: Option<Box<IncomingError>>,
693 timeout_type: TimeoutType,
694 last_heartbeat_details: Option<DecodablePayloads>,
695}
696
697impl TimeoutError {
698 pub(crate) fn new(
700 failure: Failure,
701 failure_info: crate::protos::temporal::api::failure::v1::TimeoutFailureInfo,
702 cause: Option<IncomingError>,
703 payload_converter: &PayloadConverter,
704 context: &SerializationContextData,
705 ) -> Self {
706 Self {
707 failure,
708 cause: cause.map(Box::new),
709 timeout_type: TimeoutType::from_raw(failure_info.timeout_type),
710 last_heartbeat_details: failure_info.last_heartbeat_details.map(|details| {
711 DecodablePayloads::new(details.payloads, payload_converter.clone(), *context)
712 }),
713 }
714 }
715
716 pub fn timeout_type(&self) -> TimeoutType {
718 self.timeout_type
719 }
720
721 pub fn last_heartbeat_details<T: TemporalDeserializable + 'static>(
723 &self,
724 ) -> Result<Option<T>, PayloadConversionError> {
725 self.last_heartbeat_details
726 .as_ref()
727 .map(DecodablePayloads::deserialize)
728 .transpose()
729 }
730
731 pub fn raw_last_heartbeat_details(&self) -> Option<&[Payload]> {
733 self.last_heartbeat_details
734 .as_ref()
735 .map(DecodablePayloads::raw)
736 }
737}
738
739impl_incoming_failure_wrapper!(TimeoutError);
740
741#[derive(Debug)]
743pub struct CancelledError {
744 failure: Failure,
745 cause: Option<Box<IncomingError>>,
746 details: Option<DecodablePayloads>,
747}
748
749impl CancelledError {
750 pub(crate) fn new(
752 failure: Failure,
753 failure_info: crate::protos::temporal::api::failure::v1::CanceledFailureInfo,
754 cause: Option<IncomingError>,
755 payload_converter: &PayloadConverter,
756 context: &SerializationContextData,
757 ) -> Self {
758 Self {
759 failure,
760 cause: cause.map(Box::new),
761 details: failure_info.details.map(|details| {
762 DecodablePayloads::new(details.payloads, payload_converter.clone(), *context)
763 }),
764 }
765 }
766
767 pub fn details<T: TemporalDeserializable + 'static>(
770 &self,
771 ) -> Result<Option<T>, PayloadConversionError> {
772 self.details
773 .as_ref()
774 .map(DecodablePayloads::deserialize)
775 .transpose()
776 }
777
778 pub fn raw_details(&self) -> Option<&[Payload]> {
780 self.details.as_ref().map(DecodablePayloads::raw)
781 }
782}
783
784impl_incoming_failure_wrapper!(CancelledError);
785incoming_failure_wrapper!(TerminatedError, "A normalized terminated failure.");
786incoming_failure_wrapper!(ServerError, "A normalized server failure.");
787incoming_failure_wrapper!(ResetWorkflowError, "A normalized reset-workflow failure.");
788
789#[derive(Debug)]
791pub struct ActivityFailureError {
792 failure: Failure,
793 cause: Option<Box<IncomingError>>,
794 activity_id: String,
795 activity_type: Option<String>,
796 scheduled_event_id: i64,
797 started_event_id: i64,
798 identity: String,
799 retry_state: RetryState,
800}
801
802impl ActivityFailureError {
803 pub(crate) fn new(
805 failure: Failure,
806 failure_info: crate::protos::temporal::api::failure::v1::ActivityFailureInfo,
807 cause: Option<IncomingError>,
808 ) -> Self {
809 let retry_state = RetryState::from_raw(failure_info.retry_state);
810 Self {
811 failure,
812 cause: cause.map(Box::new),
813 activity_id: failure_info.activity_id,
814 activity_type: failure_info
815 .activity_type
816 .map(|activity_type| activity_type.name),
817 scheduled_event_id: failure_info.scheduled_event_id,
818 started_event_id: failure_info.started_event_id,
819 identity: failure_info.identity,
820 retry_state,
821 }
822 }
823
824 pub fn activity_id(&self) -> &str {
826 &self.activity_id
827 }
828
829 pub fn activity_type(&self) -> Option<&str> {
831 self.activity_type.as_deref()
832 }
833
834 pub fn scheduled_event_id(&self) -> i64 {
836 self.scheduled_event_id
837 }
838
839 pub fn started_event_id(&self) -> i64 {
841 self.started_event_id
842 }
843
844 pub fn identity(&self) -> &str {
846 &self.identity
847 }
848
849 pub fn retry_state(&self) -> RetryState {
851 self.retry_state
852 }
853
854 pub fn as_timeout(&self) -> Option<&TimeoutError> {
857 self.cause().and_then(IncomingError::as_timeout)
858 }
859
860 pub fn as_cancelled(&self) -> Option<&CancelledError> {
863 self.cause().and_then(IncomingError::as_cancelled)
864 }
865}
866
867impl_incoming_failure_wrapper!(ActivityFailureError);
868#[derive(Debug)]
870pub struct ChildWorkflowFailureError {
871 failure: Failure,
872 cause: Option<Box<IncomingError>>,
873 namespace: String,
874 workflow_execution: Option<WorkflowExecution>,
875 workflow_type: Option<String>,
876 initiated_event_id: i64,
877 started_event_id: i64,
878 retry_state: RetryState,
879}
880
881impl ChildWorkflowFailureError {
882 pub(crate) fn new(
884 failure: Failure,
885 failure_info: crate::protos::temporal::api::failure::v1::ChildWorkflowExecutionFailureInfo,
886 cause: Option<IncomingError>,
887 ) -> Self {
888 let retry_state = RetryState::from_raw(failure_info.retry_state);
889 Self {
890 failure,
891 cause: cause.map(Box::new),
892 namespace: failure_info.namespace,
893 workflow_execution: failure_info.workflow_execution.map(Into::into),
894 workflow_type: failure_info
895 .workflow_type
896 .map(|workflow_type| workflow_type.name),
897 initiated_event_id: failure_info.initiated_event_id,
898 started_event_id: failure_info.started_event_id,
899 retry_state,
900 }
901 }
902
903 pub fn namespace(&self) -> &str {
905 &self.namespace
906 }
907
908 pub fn workflow_execution(&self) -> Option<&WorkflowExecution> {
910 self.workflow_execution.as_ref()
911 }
912
913 pub fn workflow_type(&self) -> Option<&str> {
915 self.workflow_type.as_deref()
916 }
917
918 pub fn initiated_event_id(&self) -> i64 {
920 self.initiated_event_id
921 }
922
923 pub fn started_event_id(&self) -> i64 {
925 self.started_event_id
926 }
927
928 pub fn retry_state(&self) -> RetryState {
930 self.retry_state
931 }
932
933 pub fn as_timeout(&self) -> Option<&TimeoutError> {
936 self.cause().and_then(IncomingError::as_timeout)
937 }
938
939 pub fn as_cancelled(&self) -> Option<&CancelledError> {
942 self.cause().and_then(IncomingError::as_cancelled)
943 }
944}
945
946impl_incoming_failure_wrapper!(ChildWorkflowFailureError);
947incoming_failure_wrapper!(
948 IncomingNexusOperationExecutionError,
949 "A normalized nexus operation failure wrapper."
950);
951incoming_failure_wrapper!(
952 IncomingNexusHandlerError,
953 "A normalized nexus handler failure wrapper."
954);
955
956#[derive(Debug, thiserror::Error)]
958pub enum ActivityExecutionError {
959 #[error("Activity failed: {}", .0.failure().message)]
961 Failed(#[source] ActivityFailureError),
962 #[error("Activity cancelled: {}", .0.failure().message)]
964 Cancelled(#[source] CancelledError),
965 #[error("Payload conversion failed: {0}")]
967 Serialization(#[from] PayloadConversionError),
968}
969
970impl ActivityExecutionError {
971 pub fn failure(&self) -> Option<&Failure> {
973 match self {
974 ActivityExecutionError::Failed(err) => Some(err.failure()),
975 ActivityExecutionError::Cancelled(err) => Some(err.failure()),
976 ActivityExecutionError::Serialization(_) => None,
977 }
978 }
979
980 pub fn cause(&self) -> Option<&IncomingError> {
982 match self {
983 ActivityExecutionError::Failed(err) => err.cause(),
984 ActivityExecutionError::Cancelled(err) => err.cause(),
985 ActivityExecutionError::Serialization(_) => None,
986 }
987 }
988
989 pub fn reason(&self) -> Option<&IncomingError> {
991 match self {
992 ActivityExecutionError::Failed(err) => err.cause(),
993 ActivityExecutionError::Cancelled(_) | ActivityExecutionError::Serialization(_) => None,
994 }
995 }
996
997 pub fn as_timeout(&self) -> Option<&TimeoutError> {
1000 match self {
1001 ActivityExecutionError::Failed(err) => err.as_timeout(),
1002 ActivityExecutionError::Serialization(_) | ActivityExecutionError::Cancelled(_) => None,
1003 }
1004 }
1005
1006 pub fn as_cancelled(&self) -> Option<&CancelledError> {
1009 match self {
1010 ActivityExecutionError::Failed(err) => err.as_cancelled(),
1011 ActivityExecutionError::Cancelled(err) => Some(err),
1012 ActivityExecutionError::Serialization(_) => None,
1013 }
1014 }
1015}
1016
1017#[derive(Debug, thiserror::Error)]
1019pub enum ChildWorkflowStartError {
1020 #[error("Child workflow start cancelled: {}", .0.failure().message)]
1022 Cancelled(#[source] Box<CancelledError>),
1023 #[error(
1025 "Child workflow start failed: workflow_id={workflow_id}, workflow_type={workflow_type}, cause={cause:?}"
1026 )]
1027 StartFailed {
1028 workflow_id: String,
1030 workflow_type: String,
1032 cause: StartChildWorkflowExecutionFailedCause,
1034 },
1035 #[error("Payload conversion failed: {0}")]
1037 Serialization(#[from] PayloadConversionError),
1038}
1039
1040impl ChildWorkflowStartError {
1041 pub fn failure(&self) -> Option<&Failure> {
1043 match self {
1044 ChildWorkflowStartError::Cancelled(err) => Some(err.failure()),
1045 ChildWorkflowStartError::StartFailed { .. }
1046 | ChildWorkflowStartError::Serialization(_) => None,
1047 }
1048 }
1049
1050 pub fn cause(&self) -> Option<&IncomingError> {
1052 match self {
1053 ChildWorkflowStartError::Cancelled(err) => err.cause(),
1054 ChildWorkflowStartError::StartFailed { .. }
1055 | ChildWorkflowStartError::Serialization(_) => None,
1056 }
1057 }
1058}
1059
1060#[derive(Debug, thiserror::Error)]
1062pub enum ChildWorkflowExecutionError {
1063 #[error("Child workflow failed: {}", .0.failure().message)]
1065 Failed(#[source] Box<ChildWorkflowFailureError>),
1066 #[error("Payload conversion failed: {0}")]
1068 Serialization(#[from] PayloadConversionError),
1069}
1070
1071impl ChildWorkflowExecutionError {
1072 pub fn failure(&self) -> Option<&Failure> {
1074 match self {
1075 ChildWorkflowExecutionError::Failed(err) => Some(err.failure()),
1076 ChildWorkflowExecutionError::Serialization(_) => None,
1077 }
1078 }
1079
1080 pub fn cause(&self) -> Option<&IncomingError> {
1082 match self {
1083 ChildWorkflowExecutionError::Failed(err) => err.cause(),
1084 ChildWorkflowExecutionError::Serialization(_) => None,
1085 }
1086 }
1087
1088 pub fn reason(&self) -> Option<&IncomingError> {
1090 match self {
1091 ChildWorkflowExecutionError::Failed(err) => err.cause(),
1092 ChildWorkflowExecutionError::Serialization(_) => None,
1093 }
1094 }
1095
1096 pub fn as_timeout(&self) -> Option<&TimeoutError> {
1099 match self {
1100 ChildWorkflowExecutionError::Failed(err) => err.as_timeout(),
1101 ChildWorkflowExecutionError::Serialization(_) => None,
1102 }
1103 }
1104
1105 pub fn as_cancelled(&self) -> Option<&CancelledError> {
1108 match self {
1109 ChildWorkflowExecutionError::Failed(err) => err.as_cancelled(),
1110 ChildWorkflowExecutionError::Serialization(_) => None,
1111 }
1112 }
1113}
1114
1115#[derive(Debug, thiserror::Error)]
1117pub enum WorkflowSignalError {
1118 #[error("Child workflow signal failed: {}", .0.failure().message)]
1120 Failed(#[source] Box<WorkflowSignalFailureError>),
1121 #[error("Signal payload conversion failed: {0}")]
1123 Serialization(#[from] PayloadConversionError),
1124}
1125
1126impl WorkflowSignalError {
1127 pub fn failure(&self) -> Option<&Failure> {
1129 match self {
1130 WorkflowSignalError::Failed(err) => Some(err.failure()),
1131 WorkflowSignalError::Serialization(_) => None,
1132 }
1133 }
1134
1135 pub fn cause(&self) -> Option<&IncomingError> {
1137 match self {
1138 WorkflowSignalError::Failed(err) => err.cause(),
1139 WorkflowSignalError::Serialization(_) => None,
1140 }
1141 }
1142
1143 pub fn reason(&self) -> Option<&IncomingError> {
1145 match self {
1146 WorkflowSignalError::Failed(err) => Some(err.error()),
1147 WorkflowSignalError::Serialization(_) => None,
1148 }
1149 }
1150}
1151
1152#[derive(Debug)]
1154pub struct WorkflowSignalFailureError {
1155 failure: Failure,
1156 error: Box<IncomingError>,
1157}
1158
1159impl WorkflowSignalFailureError {
1160 pub(crate) fn new(failure: Failure, error: IncomingError) -> Self {
1162 Self {
1163 failure,
1164 error: Box::new(error),
1165 }
1166 }
1167
1168 pub fn failure(&self) -> &Failure {
1170 &self.failure
1171 }
1172
1173 pub fn cause(&self) -> Option<&IncomingError> {
1175 self.error.cause()
1176 }
1177
1178 pub fn error(&self) -> &IncomingError {
1180 &self.error
1181 }
1182}
1183
1184impl std::fmt::Display for WorkflowSignalFailureError {
1185 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1186 self.failure.fmt(f)
1187 }
1188}
1189
1190impl std::error::Error for WorkflowSignalFailureError {
1191 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1192 self.cause()
1193 .map(|cause| cause as &(dyn std::error::Error + 'static))
1194 }
1195}
1196
1197#[cfg(test)]
1198mod tests {
1199 use super::*;
1200 use crate::{
1201 data_converters::{
1202 DefaultFailureConverter, FailureConverter, GenericPayloadConverter, PayloadConverter,
1203 SerializationContext, SerializationContextData,
1204 },
1205 protos::temporal::api::{
1206 common::v1::Payload,
1207 failure::v1::{ActivityFailureInfo, TimeoutFailureInfo, failure::FailureInfo},
1208 },
1209 };
1210
1211 struct AlwaysFailsSerialize;
1212
1213 impl serde::Serialize for AlwaysFailsSerialize {
1214 fn serialize<S: serde::Serializer>(&self, _serializer: S) -> Result<S::Ok, S::Error> {
1215 Err(serde::ser::Error::custom("serialize boom"))
1216 }
1217 }
1218
1219 #[test]
1220 fn decoded_failures_hide_unknown_state_values() {
1221 let failure = Failure {
1222 cause: Some(Box::new(Failure {
1223 failure_info: Some(FailureInfo::TimeoutFailureInfo(TimeoutFailureInfo {
1224 timeout_type: 654_321,
1225 ..Default::default()
1226 })),
1227 ..Default::default()
1228 })),
1229 failure_info: Some(FailureInfo::ActivityFailureInfo(ActivityFailureInfo {
1230 retry_state: 123_456,
1231 ..Default::default()
1232 })),
1233 ..Default::default()
1234 };
1235
1236 let decoded = DefaultFailureConverter
1237 .to_error(
1238 failure,
1239 &PayloadConverter::default(),
1240 &SerializationContextData::Workflow,
1241 )
1242 .unwrap();
1243 let IncomingError::Activity(activity) = decoded else {
1244 panic!("expected activity failure");
1245 };
1246 assert_eq!(activity.retry_state(), RetryState::Unknown);
1247 let Some(FailureInfo::ActivityFailureInfo(raw_activity)) =
1248 activity.failure().failure_info.as_ref()
1249 else {
1250 panic!("expected raw activity failure info");
1251 };
1252 assert_eq!(raw_activity.retry_state, 123_456);
1253 let Some(IncomingError::Timeout(timeout)) = activity.cause() else {
1254 panic!("expected timeout cause");
1255 };
1256 assert_eq!(timeout.timeout_type(), TimeoutType::Unknown);
1257 let Some(FailureInfo::TimeoutFailureInfo(raw_timeout)) =
1258 timeout.failure().failure_info.as_ref()
1259 else {
1260 panic!("expected raw timeout failure info");
1261 };
1262 assert_eq!(raw_timeout.timeout_type, 654_321);
1263 }
1264
1265 #[test]
1266 fn constructors_set_retryability_defaults() {
1267 assert!(!ApplicationFailure::new(anyhow::anyhow!("retryable")).is_non_retryable());
1268 assert!(
1269 ApplicationFailure::non_retryable(anyhow::anyhow!("non-retryable")).is_non_retryable()
1270 );
1271 }
1272
1273 #[test]
1274 fn conversion_preserves_application_metadata() {
1275 let payloads = Payloads {
1276 payloads: vec![Payload {
1277 data: b"details".to_vec(),
1278 ..Default::default()
1279 }],
1280 };
1281 let failure = DefaultFailureConverter.to_failure(
1282 OutgoingError::Workflow(OutgoingWorkflowError::Application(Box::new(
1283 ApplicationFailure::builder(anyhow::anyhow!("oops"))
1284 .type_name("MyType".to_owned())
1285 .non_retryable(true)
1286 .next_retry_delay(Duration::from_secs(3))
1287 .category(ApplicationErrorCategory::Benign)
1288 .details(RawValue::new(payloads.payloads.clone()))
1289 .build(),
1290 ))),
1291 &PayloadConverter::default(),
1292 &SerializationContextData::None,
1293 );
1294 let Some(FailureInfo::ApplicationFailureInfo(info)) = failure.failure_info else {
1295 panic!("expected application failure info");
1296 };
1297 assert_eq!(failure.message, "oops");
1298 assert_eq!(info.r#type, "MyType");
1299 assert!(info.non_retryable);
1300 assert_eq!(info.details, Some(payloads));
1301 assert_eq!(info.category(), ProtoApplicationErrorCategory::Benign);
1302 assert_eq!(info.next_retry_delay.unwrap().seconds, 3);
1303 }
1304
1305 #[test]
1306 fn builder_accepts_raw_payload_details() {
1307 let payload = Payload {
1308 data: b"details".to_vec(),
1309 ..Default::default()
1310 };
1311 let failure = DefaultFailureConverter.to_failure(
1312 OutgoingError::Workflow(OutgoingWorkflowError::Application(Box::new(
1313 ApplicationFailure::builder(anyhow::anyhow!("oops"))
1314 .details(RawValue::new(vec![payload.clone()]))
1315 .build(),
1316 ))),
1317 &PayloadConverter::default(),
1318 &SerializationContextData::None,
1319 );
1320
1321 let Some(FailureInfo::ApplicationFailureInfo(info)) = failure.failure_info else {
1322 panic!("expected application failure info");
1323 };
1324 assert_eq!(info.details.unwrap().payloads, vec![payload]);
1325 }
1326
1327 #[test]
1328 fn builder_accepts_serializable_details() {
1329 let failure = DefaultFailureConverter.to_failure(
1330 OutgoingError::Workflow(OutgoingWorkflowError::Application(Box::new(
1331 ApplicationFailure::builder(anyhow::anyhow!("oops"))
1332 .details("details".to_string())
1333 .build(),
1334 ))),
1335 &PayloadConverter::default(),
1336 &SerializationContextData::None,
1337 );
1338
1339 let Some(FailureInfo::ApplicationFailureInfo(info)) = failure.failure_info else {
1340 panic!("expected application failure info");
1341 };
1342 let payloads = info.details.expect("expected details").payloads;
1343 let converter = PayloadConverter::default();
1344 let details: String = converter
1345 .from_payloads(
1346 &SerializationContext {
1347 data: &SerializationContextData::None,
1348 converter: &converter,
1349 },
1350 payloads,
1351 )
1352 .unwrap();
1353 assert_eq!(details, "details");
1354 }
1355
1356 #[test]
1357 fn application_failure_encoding_surfaces_detail_encoding_errors() {
1358 let failure = DefaultFailureConverter.to_failure(
1359 OutgoingError::Workflow(OutgoingWorkflowError::Application(Box::new(
1360 ApplicationFailure::builder(anyhow::anyhow!("oops"))
1361 .details(AlwaysFailsSerialize)
1362 .build(),
1363 ))),
1364 &PayloadConverter::default(),
1365 &SerializationContextData::None,
1366 );
1367
1368 assert_eq!(
1369 failure.message,
1370 "Failed converting error to failure: Encoding error: serialize boom, original error message: oops"
1371 );
1372 assert!(matches!(
1373 failure.failure_info,
1374 Some(FailureInfo::ApplicationFailureInfo(_))
1375 ));
1376 }
1377
1378 #[test]
1379 fn anyhow_workflow_errors_default_to_application_outgoing_errors() {
1380 let outgoing: OutgoingWorkflowError = anyhow::anyhow!("workflow boom").into();
1381
1382 let OutgoingWorkflowError::Application(app) = outgoing else {
1383 panic!("plain workflow errors should default to application failures");
1384 };
1385 assert_eq!(app.to_string(), "workflow boom");
1386 }
1387
1388 #[test]
1389 fn payload_conversion_errors_default_to_application_outgoing_errors() {
1390 let outgoing: OutgoingWorkflowError =
1391 PayloadConversionError::EncodingError(anyhow::anyhow!("encode boom").into()).into();
1392
1393 let OutgoingWorkflowError::Application(app) = outgoing else {
1394 panic!("payload conversion errors should default to application failures");
1395 };
1396 assert_eq!(app.to_string(), "Encoding error: encode boom");
1397 }
1398}