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(&SerializationContext::new(context, payload_converter), self)
127 }
128}
129
130#[derive(derive_more::Debug)]
132pub struct FailurePayloads {
133 repr: FailurePayloadsRepr,
134}
135
136#[derive(derive_more::Debug)]
137enum FailurePayloadsRepr {
138 #[debug("Serializable(...)")]
139 Serializable(#[debug(skip)] Box<dyn SerializableFailurePayload>),
140 Decoded(DecodablePayloads),
141}
142
143impl FailurePayloads {
144 pub(crate) fn encode(
145 &self,
146 payload_converter: &PayloadConverter,
147 context: &SerializationContextData,
148 ) -> Result<Payloads, PayloadConversionError> {
149 let payloads = match &self.repr {
150 FailurePayloadsRepr::Serializable(value) => {
151 value.to_payloads(payload_converter, context)?
152 }
153 FailurePayloadsRepr::Decoded(value) => value.raw().to_vec(),
154 };
155 Ok(Payloads { payloads })
156 }
157
158 pub fn deserialize<T: TemporalDeserializable + 'static>(
160 &self,
161 ) -> Result<T, PayloadConversionError> {
162 match &self.repr {
163 FailurePayloadsRepr::Decoded(value) => value.deserialize(),
164 FailurePayloadsRepr::Serializable(_) => Err(PayloadConversionError::WrongEncoding),
165 }
166 }
167
168 pub fn raw(&self) -> Option<&[Payload]> {
170 match &self.repr {
171 FailurePayloadsRepr::Decoded(value) => Some(value.raw()),
172 FailurePayloadsRepr::Serializable(_) => None,
173 }
174 }
175
176 pub fn into_raw(self) -> Option<RawValue> {
178 match self.repr {
179 FailurePayloadsRepr::Decoded(value) => Some(value.into_raw()),
180 FailurePayloadsRepr::Serializable(_) => None,
181 }
182 }
183}
184
185impl From<DecodablePayloads> for FailurePayloads {
186 fn from(value: DecodablePayloads) -> Self {
187 Self {
188 repr: FailurePayloadsRepr::Decoded(value),
189 }
190 }
191}
192
193impl<T> From<T> for FailurePayloads
194where
195 T: TemporalSerializable + Send + Sync + 'static,
196{
197 fn from(value: T) -> Self {
198 Self {
199 repr: FailurePayloadsRepr::Serializable(Box::new(value)),
200 }
201 }
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
206#[non_exhaustive]
207pub enum ApplicationErrorCategory {
208 #[default]
210 Unspecified,
211 Benign,
214}
215
216impl From<ApplicationErrorCategory> for ProtoApplicationErrorCategory {
217 fn from(value: ApplicationErrorCategory) -> Self {
218 match value {
219 ApplicationErrorCategory::Unspecified => ProtoApplicationErrorCategory::Unspecified,
220 ApplicationErrorCategory::Benign => ProtoApplicationErrorCategory::Benign,
221 }
222 }
223}
224
225impl From<ProtoApplicationErrorCategory> for ApplicationErrorCategory {
226 fn from(value: ProtoApplicationErrorCategory) -> Self {
227 match value {
228 ProtoApplicationErrorCategory::Unspecified => ApplicationErrorCategory::Unspecified,
229 ProtoApplicationErrorCategory::Benign => ApplicationErrorCategory::Benign,
230 }
231 }
232}
233
234#[derive(Debug, bon::Builder)]
236#[builder(start_fn = builder, state_mod(vis = "pub"))]
237pub struct ApplicationFailure {
238 #[builder(start_fn, into)]
239 source: Box<dyn std::error::Error + Send + Sync>,
240 type_name: Option<String>,
241 #[builder(default)]
242 non_retryable: bool,
243 next_retry_delay: Option<Duration>,
244 #[builder(default = ApplicationErrorCategory::Unspecified)]
245 category: ApplicationErrorCategory,
246 #[builder(into)]
247 details: Option<FailurePayloads>,
248 failure: Option<Failure>,
249 cause: Option<Box<IncomingError>>,
250}
251
252impl ApplicationFailure {
253 pub fn new(source: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
255 Self {
256 source: source.into(),
257 type_name: None,
258 non_retryable: false,
259 next_retry_delay: None,
260 category: ApplicationErrorCategory::Unspecified,
261 details: None,
262 failure: None,
263 cause: None,
264 }
265 }
266
267 pub fn non_retryable(source: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
269 Self {
270 non_retryable: true,
271 ..Self::new(source)
272 }
273 }
274
275 pub fn source_error(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
277 &*self.source as &(dyn std::error::Error + Send + Sync + 'static)
278 }
279
280 pub fn type_name(&self) -> Option<&str> {
282 self.type_name.as_deref()
283 }
284
285 pub fn is_non_retryable(&self) -> bool {
287 self.non_retryable
288 }
289
290 pub fn next_retry_delay(&self) -> Option<Duration> {
292 self.next_retry_delay
293 }
294
295 pub fn category(&self) -> ApplicationErrorCategory {
297 self.category
298 }
299
300 pub fn details<T: TemporalDeserializable + 'static>(
302 &self,
303 ) -> Result<Option<T>, PayloadConversionError> {
304 self.details
305 .as_ref()
306 .map(FailurePayloads::deserialize)
307 .transpose()
308 }
309
310 pub fn raw_details(&self) -> Option<&[Payload]> {
312 self.details.as_ref().and_then(FailurePayloads::raw)
313 }
314
315 pub(crate) fn failure_payloads(&self) -> Option<&FailurePayloads> {
316 self.details.as_ref()
317 }
318
319 pub fn failure(&self) -> Option<&Failure> {
321 self.failure.as_ref()
322 }
323
324 pub fn into_failure(self) -> Option<Failure> {
326 self.failure
327 }
328
329 pub fn cause(&self) -> Option<&IncomingError> {
331 self.cause.as_deref()
332 }
333
334 pub fn as_timeout(&self) -> Option<&TimeoutError> {
337 self.cause().and_then(IncomingError::as_timeout)
338 }
339
340 pub fn as_cancelled(&self) -> Option<&CancelledError> {
343 self.cause().and_then(IncomingError::as_cancelled)
344 }
345
346 pub(crate) fn from_failure(
347 failure: Failure,
348 cause: Option<IncomingError>,
349 payload_converter: &PayloadConverter,
350 context: &SerializationContextData,
351 ) -> Self {
352 let app_info = failure
353 .maybe_application_failure()
354 .cloned()
355 .unwrap_or_default();
356 let type_name = (!app_info.r#type.is_empty()).then_some(app_info.r#type.clone());
357 Self {
358 source: failure.message.clone().into(),
359 type_name,
360 non_retryable: app_info.non_retryable,
361 next_retry_delay: app_info.next_retry_delay.and_then(|d| d.try_into().ok()),
362 category: app_info.category().into(),
363 details: app_info.details.map(|details| {
364 FailurePayloads::from(DecodablePayloads::new(
365 details.payloads,
366 payload_converter.clone(),
367 context.clone(),
368 ))
369 }),
370 failure: Some(failure),
371 cause: cause.map(Box::new),
372 }
373 }
374}
375
376impl std::fmt::Display for ApplicationFailure {
377 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378 write!(f, "{}", self.source)
379 }
380}
381
382impl std::error::Error for ApplicationFailure {
383 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
384 self.cause
385 .as_deref()
386 .map(|cause| cause as &(dyn std::error::Error + 'static))
387 .or_else(|| Some(self.source.as_ref()))
388 }
389}
390
391impl From<anyhow::Error> for ApplicationFailure {
392 fn from(value: anyhow::Error) -> Self {
393 Self::new(value)
394 }
395}
396
397impl From<PayloadConversionError> for ApplicationFailure {
398 fn from(value: PayloadConversionError) -> Self {
399 Self::new(value)
400 }
401}
402
403#[derive(Debug, thiserror::Error)]
405#[non_exhaustive]
406pub enum OutgoingError {
407 #[error(transparent)]
409 Activity(#[from] OutgoingActivityError),
410 #[error(transparent)]
412 Workflow(#[from] OutgoingWorkflowError),
413}
414
415#[derive(Debug, thiserror::Error)]
417#[non_exhaustive]
418pub enum OutgoingActivityError {
419 #[error(transparent)]
421 Application(#[from] Box<ApplicationFailure>),
422 #[error("Activity cancelled")]
424 Cancelled {
425 details: Option<FailurePayloads>,
427 },
428}
429
430#[derive(Debug, thiserror::Error)]
432#[non_exhaustive]
433pub enum OutgoingWorkflowError {
434 #[error(transparent)]
436 Application(#[from] Box<ApplicationFailure>),
437 #[error(transparent)]
439 PayloadConversion(#[from] PayloadConversionError),
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 OutgoingWorkflowError {
455 pub fn as_cancelled(&self) -> Option<&CancelledError> {
458 match self {
459 Self::Application(err) => err.as_cancelled(),
460 Self::PayloadConversion(_) => None,
461 Self::ActivityExecution(err) => err.as_cancelled(),
462 Self::ChildWorkflowExecution(err) => err.as_cancelled(),
463 Self::ChildWorkflowStart(err) => err.as_cancelled(),
464 Self::WorkflowSignal(err) => err.as_cancelled(),
465 }
466 }
467}
468
469impl From<anyhow::Error> for OutgoingWorkflowError {
470 fn from(value: anyhow::Error) -> Self {
471 Self::Application(Box::new(ApplicationFailure::new(value)))
472 }
473}
474
475impl From<ApplicationFailure> for OutgoingWorkflowError {
476 fn from(value: ApplicationFailure) -> Self {
477 Self::Application(Box::new(value))
478 }
479}
480
481impl From<ActivityExecutionError> for OutgoingWorkflowError {
482 fn from(value: ActivityExecutionError) -> Self {
483 match value {
484 ActivityExecutionError::Serialization(err) => Self::PayloadConversion(err),
485 other => Self::ActivityExecution(Box::new(other)),
486 }
487 }
488}
489
490impl From<ChildWorkflowExecutionError> for OutgoingWorkflowError {
491 fn from(value: ChildWorkflowExecutionError) -> Self {
492 match value {
493 ChildWorkflowExecutionError::Serialization(err) => Self::PayloadConversion(err),
494 other => Self::ChildWorkflowExecution(Box::new(other)),
495 }
496 }
497}
498
499impl From<ChildWorkflowStartError> for OutgoingWorkflowError {
500 fn from(value: ChildWorkflowStartError) -> Self {
501 match value {
502 ChildWorkflowStartError::Serialization(err) => Self::PayloadConversion(err),
503 other => Self::ChildWorkflowStart(Box::new(other)),
504 }
505 }
506}
507
508impl From<WorkflowSignalError> for OutgoingWorkflowError {
509 fn from(value: WorkflowSignalError) -> Self {
510 match value {
511 WorkflowSignalError::Serialization(err) => Self::PayloadConversion(err),
512 other => Self::WorkflowSignal(Box::new(other)),
513 }
514 }
515}
516
517#[derive(Debug)]
519#[non_exhaustive]
520pub enum IncomingError {
521 Application(ApplicationFailure),
523 Timeout(TimeoutError),
525 Cancelled(CancelledError),
527 Terminated(TerminatedError),
529 Server(ServerError),
531 ResetWorkflow(ResetWorkflowError),
533 Activity(ActivityFailureError),
535 ChildWorkflowExecution(ChildWorkflowFailureError),
537 NexusOperationExecution(IncomingNexusOperationExecutionError),
539 NexusHandler(IncomingNexusHandlerError),
541}
542
543impl IncomingError {
544 pub fn failure(&self) -> &Failure {
546 match self {
547 IncomingError::Application(err) => err
548 .failure()
549 .expect("decoded application failures retain their original proto"),
550 IncomingError::Timeout(err) => err.failure(),
551 IncomingError::Cancelled(err) => err.failure(),
552 IncomingError::Terminated(err) => err.failure(),
553 IncomingError::Server(err) => err.failure(),
554 IncomingError::ResetWorkflow(err) => err.failure(),
555 IncomingError::Activity(err) => err.failure(),
556 IncomingError::ChildWorkflowExecution(err) => err.failure(),
557 IncomingError::NexusOperationExecution(err) => err.failure(),
558 IncomingError::NexusHandler(err) => err.failure(),
559 }
560 }
561
562 pub fn cause(&self) -> Option<&IncomingError> {
564 match self {
565 IncomingError::Application(err) => err.cause(),
566 IncomingError::Timeout(err) => err.cause(),
567 IncomingError::Cancelled(err) => err.cause(),
568 IncomingError::Terminated(err) => err.cause(),
569 IncomingError::Server(err) => err.cause(),
570 IncomingError::ResetWorkflow(err) => err.cause(),
571 IncomingError::Activity(err) => err.cause(),
572 IncomingError::ChildWorkflowExecution(err) => err.cause(),
573 IncomingError::NexusOperationExecution(err) => err.cause(),
574 IncomingError::NexusHandler(err) => err.cause(),
575 }
576 }
577
578 pub fn into_failure(self) -> Failure {
580 match self {
581 IncomingError::Application(err) => err
582 .into_failure()
583 .expect("decoded application failures retain their original proto"),
584 IncomingError::Timeout(err) => err.into_failure(),
585 IncomingError::Cancelled(err) => err.into_failure(),
586 IncomingError::Terminated(err) => err.into_failure(),
587 IncomingError::Server(err) => err.into_failure(),
588 IncomingError::ResetWorkflow(err) => err.into_failure(),
589 IncomingError::Activity(err) => err.into_failure(),
590 IncomingError::ChildWorkflowExecution(err) => err.into_failure(),
591 IncomingError::NexusOperationExecution(err) => err.into_failure(),
592 IncomingError::NexusHandler(err) => err.into_failure(),
593 }
594 }
595
596 pub fn as_timeout(&self) -> Option<&TimeoutError> {
598 match self {
599 IncomingError::Timeout(err) => Some(err),
600 _ => None,
601 }
602 }
603
604 pub fn as_cancelled(&self) -> Option<&CancelledError> {
606 match self {
607 IncomingError::Cancelled(err) => Some(err),
608 _ => None,
609 }
610 }
611}
612
613impl std::fmt::Display for IncomingError {
614 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
615 match self {
616 IncomingError::Application(err) => err.fmt(f),
617 IncomingError::Timeout(err) => err.fmt(f),
618 IncomingError::Cancelled(err) => err.fmt(f),
619 IncomingError::Terminated(err) => err.fmt(f),
620 IncomingError::Server(err) => err.fmt(f),
621 IncomingError::ResetWorkflow(err) => err.fmt(f),
622 IncomingError::Activity(err) => err.fmt(f),
623 IncomingError::ChildWorkflowExecution(err) => err.fmt(f),
624 IncomingError::NexusOperationExecution(err) => err.fmt(f),
625 IncomingError::NexusHandler(err) => err.fmt(f),
626 }
627 }
628}
629
630impl std::error::Error for IncomingError {
631 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
632 match self {
633 IncomingError::Application(err) => Some(err),
634 IncomingError::Timeout(err) => Some(err),
635 IncomingError::Cancelled(err) => Some(err),
636 IncomingError::Terminated(err) => Some(err),
637 IncomingError::Server(err) => Some(err),
638 IncomingError::ResetWorkflow(err) => Some(err),
639 IncomingError::Activity(err) => Some(err),
640 IncomingError::ChildWorkflowExecution(err) => Some(err),
641 IncomingError::NexusOperationExecution(err) => Some(err),
642 IncomingError::NexusHandler(err) => Some(err),
643 }
644 }
645}
646
647macro_rules! impl_incoming_failure_wrapper {
648 ($name:ident) => {
649 impl $name {
650 pub fn failure(&self) -> &Failure {
652 &self.failure
653 }
654
655 pub fn cause(&self) -> Option<&IncomingError> {
657 self.cause.as_deref()
658 }
659
660 pub fn into_failure(self) -> Failure {
662 self.failure
663 }
664
665 pub fn into_parts(self) -> (Failure, Option<IncomingError>) {
667 (self.failure, self.cause.map(|cause| *cause))
668 }
669 }
670
671 impl std::fmt::Display for $name {
672 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
673 self.failure.fmt(f)
674 }
675 }
676
677 impl std::error::Error for $name {
678 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
679 self.cause
680 .as_deref()
681 .map(|cause| cause as &(dyn std::error::Error + 'static))
682 }
683 }
684 };
685}
686
687macro_rules! incoming_failure_wrapper {
688 ($name:ident, $doc:literal) => {
689 #[doc = $doc]
690 #[derive(Debug)]
691 pub struct $name {
692 failure: Failure,
693 cause: Option<Box<IncomingError>>,
694 }
695
696 impl $name {
697 pub(crate) fn new(failure: Failure, cause: Option<IncomingError>) -> Self {
699 Self {
700 failure,
701 cause: cause.map(Box::new),
702 }
703 }
704 }
705
706 impl_incoming_failure_wrapper!($name);
707 };
708}
709
710#[derive(Debug)]
712pub struct TimeoutError {
713 failure: Failure,
714 cause: Option<Box<IncomingError>>,
715 timeout_type: TimeoutType,
716 last_heartbeat_details: Option<DecodablePayloads>,
717}
718
719impl TimeoutError {
720 pub(crate) fn new(
722 failure: Failure,
723 failure_info: crate::protos::temporal::api::failure::v1::TimeoutFailureInfo,
724 cause: Option<IncomingError>,
725 payload_converter: &PayloadConverter,
726 context: &SerializationContextData,
727 ) -> Self {
728 Self {
729 failure,
730 cause: cause.map(Box::new),
731 timeout_type: TimeoutType::from_raw(failure_info.timeout_type),
732 last_heartbeat_details: failure_info.last_heartbeat_details.map(|details| {
733 DecodablePayloads::new(details.payloads, payload_converter.clone(), context.clone())
734 }),
735 }
736 }
737
738 pub fn timeout_type(&self) -> TimeoutType {
740 self.timeout_type
741 }
742
743 pub fn last_heartbeat_details<T: TemporalDeserializable + 'static>(
745 &self,
746 ) -> Result<Option<T>, PayloadConversionError> {
747 self.last_heartbeat_details
748 .as_ref()
749 .map(DecodablePayloads::deserialize)
750 .transpose()
751 }
752
753 pub fn raw_last_heartbeat_details(&self) -> Option<&[Payload]> {
755 self.last_heartbeat_details
756 .as_ref()
757 .map(DecodablePayloads::raw)
758 }
759}
760
761impl_incoming_failure_wrapper!(TimeoutError);
762
763#[derive(Debug)]
765pub struct CancelledError {
766 failure: Failure,
767 cause: Option<Box<IncomingError>>,
768 details: Option<DecodablePayloads>,
769}
770
771impl CancelledError {
772 pub(crate) fn new(
774 failure: Failure,
775 failure_info: crate::protos::temporal::api::failure::v1::CanceledFailureInfo,
776 cause: Option<IncomingError>,
777 payload_converter: &PayloadConverter,
778 context: &SerializationContextData,
779 ) -> Self {
780 Self {
781 failure,
782 cause: cause.map(Box::new),
783 details: failure_info.details.map(|details| {
784 DecodablePayloads::new(details.payloads, payload_converter.clone(), context.clone())
785 }),
786 }
787 }
788
789 pub fn details<T: TemporalDeserializable + 'static>(
792 &self,
793 ) -> Result<Option<T>, PayloadConversionError> {
794 self.details
795 .as_ref()
796 .map(DecodablePayloads::deserialize)
797 .transpose()
798 }
799
800 pub fn raw_details(&self) -> Option<&[Payload]> {
802 self.details.as_ref().map(DecodablePayloads::raw)
803 }
804}
805
806impl_incoming_failure_wrapper!(CancelledError);
807incoming_failure_wrapper!(TerminatedError, "A normalized terminated failure.");
808incoming_failure_wrapper!(ServerError, "A normalized server failure.");
809incoming_failure_wrapper!(ResetWorkflowError, "A normalized reset-workflow failure.");
810
811#[derive(Debug)]
813pub struct ActivityFailureError {
814 failure: Failure,
815 cause: Option<Box<IncomingError>>,
816 activity_id: String,
817 activity_type: Option<String>,
818 scheduled_event_id: i64,
819 started_event_id: i64,
820 identity: String,
821 retry_state: RetryState,
822}
823
824impl ActivityFailureError {
825 pub(crate) fn new(
827 failure: Failure,
828 failure_info: crate::protos::temporal::api::failure::v1::ActivityFailureInfo,
829 cause: Option<IncomingError>,
830 ) -> Self {
831 let retry_state = RetryState::from_raw(failure_info.retry_state);
832 Self {
833 failure,
834 cause: cause.map(Box::new),
835 activity_id: failure_info.activity_id,
836 activity_type: failure_info
837 .activity_type
838 .map(|activity_type| activity_type.name),
839 scheduled_event_id: failure_info.scheduled_event_id,
840 started_event_id: failure_info.started_event_id,
841 identity: failure_info.identity,
842 retry_state,
843 }
844 }
845
846 pub fn activity_id(&self) -> &str {
848 &self.activity_id
849 }
850
851 pub fn activity_type(&self) -> Option<&str> {
853 self.activity_type.as_deref()
854 }
855
856 pub fn scheduled_event_id(&self) -> i64 {
858 self.scheduled_event_id
859 }
860
861 pub fn started_event_id(&self) -> i64 {
863 self.started_event_id
864 }
865
866 pub fn identity(&self) -> &str {
868 &self.identity
869 }
870
871 pub fn retry_state(&self) -> RetryState {
873 self.retry_state
874 }
875
876 pub fn as_timeout(&self) -> Option<&TimeoutError> {
879 self.cause().and_then(IncomingError::as_timeout)
880 }
881
882 pub fn as_cancelled(&self) -> Option<&CancelledError> {
885 self.cause().and_then(IncomingError::as_cancelled)
886 }
887}
888
889impl_incoming_failure_wrapper!(ActivityFailureError);
890#[derive(Debug)]
892pub struct ChildWorkflowFailureError {
893 failure: Failure,
894 cause: Option<Box<IncomingError>>,
895 namespace: String,
896 workflow_execution: Option<WorkflowExecution>,
897 workflow_type: Option<String>,
898 initiated_event_id: i64,
899 started_event_id: i64,
900 retry_state: RetryState,
901}
902
903impl ChildWorkflowFailureError {
904 pub(crate) fn new(
906 failure: Failure,
907 failure_info: crate::protos::temporal::api::failure::v1::ChildWorkflowExecutionFailureInfo,
908 cause: Option<IncomingError>,
909 ) -> Self {
910 let retry_state = RetryState::from_raw(failure_info.retry_state);
911 Self {
912 failure,
913 cause: cause.map(Box::new),
914 namespace: failure_info.namespace,
915 workflow_execution: failure_info.workflow_execution.map(Into::into),
916 workflow_type: failure_info
917 .workflow_type
918 .map(|workflow_type| workflow_type.name),
919 initiated_event_id: failure_info.initiated_event_id,
920 started_event_id: failure_info.started_event_id,
921 retry_state,
922 }
923 }
924
925 pub fn namespace(&self) -> &str {
927 &self.namespace
928 }
929
930 pub fn workflow_execution(&self) -> Option<&WorkflowExecution> {
932 self.workflow_execution.as_ref()
933 }
934
935 pub fn workflow_type(&self) -> Option<&str> {
937 self.workflow_type.as_deref()
938 }
939
940 pub fn initiated_event_id(&self) -> i64 {
942 self.initiated_event_id
943 }
944
945 pub fn started_event_id(&self) -> i64 {
947 self.started_event_id
948 }
949
950 pub fn retry_state(&self) -> RetryState {
952 self.retry_state
953 }
954
955 pub fn as_timeout(&self) -> Option<&TimeoutError> {
958 self.cause().and_then(IncomingError::as_timeout)
959 }
960
961 pub fn as_cancelled(&self) -> Option<&CancelledError> {
964 self.cause().and_then(IncomingError::as_cancelled)
965 }
966}
967
968impl_incoming_failure_wrapper!(ChildWorkflowFailureError);
969incoming_failure_wrapper!(
970 IncomingNexusOperationExecutionError,
971 "A normalized nexus operation failure wrapper."
972);
973incoming_failure_wrapper!(
974 IncomingNexusHandlerError,
975 "A normalized nexus handler failure wrapper."
976);
977
978#[derive(Debug, thiserror::Error)]
980pub enum ActivityExecutionError {
981 #[error("Activity failed: {}", .0.failure().message)]
983 Failed(#[source] ActivityFailureError),
984 #[error("Activity cancelled: {}", .0.failure().message)]
986 Cancelled(#[source] CancelledError),
987 #[error("Payload conversion failed: {0}")]
989 Serialization(#[from] PayloadConversionError),
990}
991
992impl ActivityExecutionError {
993 pub fn failure(&self) -> Option<&Failure> {
995 match self {
996 ActivityExecutionError::Failed(err) => Some(err.failure()),
997 ActivityExecutionError::Cancelled(err) => Some(err.failure()),
998 ActivityExecutionError::Serialization(_) => None,
999 }
1000 }
1001
1002 pub fn cause(&self) -> Option<&IncomingError> {
1004 match self {
1005 ActivityExecutionError::Failed(err) => err.cause(),
1006 ActivityExecutionError::Cancelled(err) => err.cause(),
1007 ActivityExecutionError::Serialization(_) => None,
1008 }
1009 }
1010
1011 pub fn reason(&self) -> Option<&IncomingError> {
1013 match self {
1014 ActivityExecutionError::Failed(err) => err.cause(),
1015 ActivityExecutionError::Cancelled(_) | ActivityExecutionError::Serialization(_) => None,
1016 }
1017 }
1018
1019 pub fn as_timeout(&self) -> Option<&TimeoutError> {
1022 match self {
1023 ActivityExecutionError::Failed(err) => err.as_timeout(),
1024 ActivityExecutionError::Serialization(_) | ActivityExecutionError::Cancelled(_) => None,
1025 }
1026 }
1027
1028 pub fn as_cancelled(&self) -> Option<&CancelledError> {
1031 match self {
1032 ActivityExecutionError::Failed(err) => err.as_cancelled(),
1033 ActivityExecutionError::Cancelled(err) => Some(err),
1034 ActivityExecutionError::Serialization(_) => None,
1035 }
1036 }
1037}
1038
1039#[derive(Debug, thiserror::Error)]
1041pub enum ChildWorkflowStartError {
1042 #[error("Child workflow start cancelled: {}", .0.failure().message)]
1044 Cancelled(#[source] Box<CancelledError>),
1045 #[error(
1047 "Child workflow start failed: workflow_id={workflow_id}, workflow_type={workflow_type}, cause={cause:?}"
1048 )]
1049 StartFailed {
1050 workflow_id: String,
1052 workflow_type: String,
1054 cause: StartChildWorkflowExecutionFailedCause,
1056 },
1057 #[error("Payload conversion failed: {0}")]
1059 Serialization(#[from] PayloadConversionError),
1060}
1061
1062impl ChildWorkflowStartError {
1063 pub fn failure(&self) -> Option<&Failure> {
1065 match self {
1066 ChildWorkflowStartError::Cancelled(err) => Some(err.failure()),
1067 ChildWorkflowStartError::StartFailed { .. }
1068 | ChildWorkflowStartError::Serialization(_) => None,
1069 }
1070 }
1071
1072 pub fn cause(&self) -> Option<&IncomingError> {
1074 match self {
1075 ChildWorkflowStartError::Cancelled(err) => err.cause(),
1076 ChildWorkflowStartError::StartFailed { .. }
1077 | ChildWorkflowStartError::Serialization(_) => None,
1078 }
1079 }
1080
1081 pub fn as_cancelled(&self) -> Option<&CancelledError> {
1084 match self {
1085 ChildWorkflowStartError::Cancelled(err) => Some(err),
1086 ChildWorkflowStartError::StartFailed { .. }
1087 | ChildWorkflowStartError::Serialization(_) => None,
1088 }
1089 }
1090}
1091
1092#[derive(Debug, thiserror::Error)]
1094pub enum ChildWorkflowExecutionError {
1095 #[error("Child workflow failed: {}", .0.failure().message)]
1097 Failed(#[source] Box<ChildWorkflowFailureError>),
1098 #[error("Payload conversion failed: {0}")]
1100 Serialization(#[from] PayloadConversionError),
1101}
1102
1103impl ChildWorkflowExecutionError {
1104 pub fn failure(&self) -> Option<&Failure> {
1106 match self {
1107 ChildWorkflowExecutionError::Failed(err) => Some(err.failure()),
1108 ChildWorkflowExecutionError::Serialization(_) => None,
1109 }
1110 }
1111
1112 pub fn cause(&self) -> Option<&IncomingError> {
1114 match self {
1115 ChildWorkflowExecutionError::Failed(err) => err.cause(),
1116 ChildWorkflowExecutionError::Serialization(_) => None,
1117 }
1118 }
1119
1120 pub fn reason(&self) -> Option<&IncomingError> {
1122 match self {
1123 ChildWorkflowExecutionError::Failed(err) => err.cause(),
1124 ChildWorkflowExecutionError::Serialization(_) => None,
1125 }
1126 }
1127
1128 pub fn as_timeout(&self) -> Option<&TimeoutError> {
1131 match self {
1132 ChildWorkflowExecutionError::Failed(err) => err.as_timeout(),
1133 ChildWorkflowExecutionError::Serialization(_) => None,
1134 }
1135 }
1136
1137 pub fn as_cancelled(&self) -> Option<&CancelledError> {
1140 match self {
1141 ChildWorkflowExecutionError::Failed(err) => err.as_cancelled(),
1142 ChildWorkflowExecutionError::Serialization(_) => None,
1143 }
1144 }
1145}
1146
1147#[derive(Debug, thiserror::Error)]
1149pub enum WorkflowSignalError {
1150 #[error("Child workflow signal failed: {}", .0.failure().message)]
1152 Failed(#[source] Box<WorkflowSignalFailureError>),
1153 #[error("Signal payload conversion failed: {0}")]
1155 Serialization(#[from] PayloadConversionError),
1156}
1157
1158impl WorkflowSignalError {
1159 pub fn failure(&self) -> Option<&Failure> {
1161 match self {
1162 WorkflowSignalError::Failed(err) => Some(err.failure()),
1163 WorkflowSignalError::Serialization(_) => None,
1164 }
1165 }
1166
1167 pub fn cause(&self) -> Option<&IncomingError> {
1169 match self {
1170 WorkflowSignalError::Failed(err) => err.cause(),
1171 WorkflowSignalError::Serialization(_) => None,
1172 }
1173 }
1174
1175 pub fn reason(&self) -> Option<&IncomingError> {
1177 match self {
1178 WorkflowSignalError::Failed(err) => Some(err.error()),
1179 WorkflowSignalError::Serialization(_) => None,
1180 }
1181 }
1182
1183 pub fn as_cancelled(&self) -> Option<&CancelledError> {
1186 self.reason()?.as_cancelled()
1187 }
1188}
1189
1190#[derive(Debug)]
1192pub struct WorkflowSignalFailureError {
1193 failure: Failure,
1194 error: Box<IncomingError>,
1195}
1196
1197impl WorkflowSignalFailureError {
1198 pub(crate) fn new(failure: Failure, error: IncomingError) -> Self {
1200 Self {
1201 failure,
1202 error: Box::new(error),
1203 }
1204 }
1205
1206 pub fn failure(&self) -> &Failure {
1208 &self.failure
1209 }
1210
1211 pub fn cause(&self) -> Option<&IncomingError> {
1213 self.error.cause()
1214 }
1215
1216 pub fn error(&self) -> &IncomingError {
1218 &self.error
1219 }
1220}
1221
1222impl std::fmt::Display for WorkflowSignalFailureError {
1223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1224 self.failure.fmt(f)
1225 }
1226}
1227
1228impl std::error::Error for WorkflowSignalFailureError {
1229 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
1230 self.cause()
1231 .map(|cause| cause as &(dyn std::error::Error + 'static))
1232 }
1233}
1234
1235#[cfg(test)]
1236mod tests {
1237 use super::*;
1238 use crate::{
1239 data_converters::{
1240 DefaultFailureConverter, FailureConverter, GenericPayloadConverter, PayloadConverter,
1241 SerializationContext, SerializationContextData, WorkflowSerializationContext,
1242 },
1243 protos::temporal::api::{
1244 common::v1::Payload,
1245 failure::v1::{ActivityFailureInfo, TimeoutFailureInfo, failure::FailureInfo},
1246 },
1247 };
1248
1249 struct AlwaysFailsSerialize;
1250
1251 impl serde::Serialize for AlwaysFailsSerialize {
1252 fn serialize<S: serde::Serializer>(&self, _serializer: S) -> Result<S::Ok, S::Error> {
1253 Err(serde::ser::Error::custom("serialize boom"))
1254 }
1255 }
1256
1257 #[test]
1258 fn decoded_failures_hide_unknown_state_values() {
1259 let failure = Failure {
1260 cause: Some(Box::new(Failure {
1261 failure_info: Some(FailureInfo::TimeoutFailureInfo(TimeoutFailureInfo {
1262 timeout_type: 654_321,
1263 ..Default::default()
1264 })),
1265 ..Default::default()
1266 })),
1267 failure_info: Some(FailureInfo::ActivityFailureInfo(ActivityFailureInfo {
1268 retry_state: 123_456,
1269 ..Default::default()
1270 })),
1271 ..Default::default()
1272 };
1273
1274 let decoded = DefaultFailureConverter::default()
1275 .to_error(
1276 failure,
1277 &PayloadConverter::default(),
1278 &SerializationContextData::Workflow(WorkflowSerializationContext::new()),
1279 )
1280 .unwrap();
1281 let IncomingError::Activity(activity) = decoded else {
1282 panic!("expected activity failure");
1283 };
1284 assert_eq!(activity.retry_state(), RetryState::Unknown);
1285 let Some(FailureInfo::ActivityFailureInfo(raw_activity)) =
1286 activity.failure().failure_info.as_ref()
1287 else {
1288 panic!("expected raw activity failure info");
1289 };
1290 assert_eq!(raw_activity.retry_state, 123_456);
1291 let Some(IncomingError::Timeout(timeout)) = activity.cause() else {
1292 panic!("expected timeout cause");
1293 };
1294 assert_eq!(timeout.timeout_type(), TimeoutType::Unknown);
1295 let Some(FailureInfo::TimeoutFailureInfo(raw_timeout)) =
1296 timeout.failure().failure_info.as_ref()
1297 else {
1298 panic!("expected raw timeout failure info");
1299 };
1300 assert_eq!(raw_timeout.timeout_type, 654_321);
1301 }
1302
1303 #[test]
1304 fn constructors_set_retryability_defaults() {
1305 assert!(!ApplicationFailure::new(anyhow::anyhow!("retryable")).is_non_retryable());
1306 assert!(
1307 ApplicationFailure::non_retryable(anyhow::anyhow!("non-retryable")).is_non_retryable()
1308 );
1309 }
1310
1311 #[test]
1312 fn conversion_preserves_application_metadata() {
1313 let payloads = Payloads {
1314 payloads: vec![Payload {
1315 data: b"details".to_vec(),
1316 ..Default::default()
1317 }],
1318 };
1319 let failure = DefaultFailureConverter::default().to_failure(
1320 OutgoingError::Workflow(OutgoingWorkflowError::Application(Box::new(
1321 ApplicationFailure::builder(anyhow::anyhow!("oops"))
1322 .type_name("MyType".to_owned())
1323 .non_retryable(true)
1324 .next_retry_delay(Duration::from_secs(3))
1325 .category(ApplicationErrorCategory::Benign)
1326 .details(RawValue::new(payloads.payloads.clone()))
1327 .build(),
1328 ))),
1329 &PayloadConverter::default(),
1330 &SerializationContextData::None,
1331 );
1332 let Some(FailureInfo::ApplicationFailureInfo(info)) = failure.failure_info else {
1333 panic!("expected application failure info");
1334 };
1335 assert_eq!(failure.message, "oops");
1336 assert_eq!(info.r#type, "MyType");
1337 assert!(info.non_retryable);
1338 assert_eq!(info.details, Some(payloads));
1339 assert_eq!(info.category(), ProtoApplicationErrorCategory::Benign);
1340 assert_eq!(info.next_retry_delay.unwrap().seconds, 3);
1341 }
1342
1343 #[test]
1344 fn builder_accepts_raw_payload_details() {
1345 let payload = Payload {
1346 data: b"details".to_vec(),
1347 ..Default::default()
1348 };
1349 let failure = DefaultFailureConverter::default().to_failure(
1350 OutgoingError::Workflow(OutgoingWorkflowError::Application(Box::new(
1351 ApplicationFailure::builder(anyhow::anyhow!("oops"))
1352 .details(RawValue::new(vec![payload.clone()]))
1353 .build(),
1354 ))),
1355 &PayloadConverter::default(),
1356 &SerializationContextData::None,
1357 );
1358
1359 let Some(FailureInfo::ApplicationFailureInfo(info)) = failure.failure_info else {
1360 panic!("expected application failure info");
1361 };
1362 assert_eq!(info.details.unwrap().payloads, vec![payload]);
1363 }
1364
1365 #[test]
1366 fn builder_accepts_serializable_details() {
1367 let failure = DefaultFailureConverter::default().to_failure(
1368 OutgoingError::Workflow(OutgoingWorkflowError::Application(Box::new(
1369 ApplicationFailure::builder(anyhow::anyhow!("oops"))
1370 .details("details".to_string())
1371 .build(),
1372 ))),
1373 &PayloadConverter::default(),
1374 &SerializationContextData::None,
1375 );
1376
1377 let Some(FailureInfo::ApplicationFailureInfo(info)) = failure.failure_info else {
1378 panic!("expected application failure info");
1379 };
1380 let payloads = info.details.expect("expected details").payloads;
1381 let converter = PayloadConverter::default();
1382 let details: String = converter
1383 .from_payloads(
1384 &SerializationContext::new(&SerializationContextData::None, &converter),
1385 payloads,
1386 )
1387 .unwrap();
1388 assert_eq!(details, "details");
1389 }
1390
1391 #[test]
1392 fn application_failure_encoding_surfaces_detail_encoding_errors() {
1393 let failure = DefaultFailureConverter::default().to_failure(
1394 OutgoingError::Workflow(OutgoingWorkflowError::Application(Box::new(
1395 ApplicationFailure::builder(anyhow::anyhow!("oops"))
1396 .details(AlwaysFailsSerialize)
1397 .build(),
1398 ))),
1399 &PayloadConverter::default(),
1400 &SerializationContextData::None,
1401 );
1402
1403 assert_eq!(
1404 failure.message,
1405 "Failed converting error to failure: Encoding error: serialize boom, original error message: oops"
1406 );
1407 assert!(matches!(
1408 failure.failure_info,
1409 Some(FailureInfo::ApplicationFailureInfo(_))
1410 ));
1411 }
1412
1413 #[test]
1414 fn anyhow_workflow_errors_default_to_application_outgoing_errors() {
1415 let outgoing: OutgoingWorkflowError = anyhow::anyhow!("workflow boom").into();
1416
1417 let OutgoingWorkflowError::Application(app) = outgoing else {
1418 panic!("plain workflow errors should default to application failures");
1419 };
1420 assert_eq!(app.to_string(), "workflow boom");
1421 }
1422
1423 #[test]
1424 fn payload_conversion_errors_use_dedicated_outgoing_variant() {
1425 let outgoing: OutgoingWorkflowError =
1426 PayloadConversionError::EncodingError(anyhow::anyhow!("encode boom").into()).into();
1427
1428 let OutgoingWorkflowError::PayloadConversion(err) = outgoing else {
1429 panic!("expected a payload conversion failure");
1430 };
1431 assert_eq!(err.to_string(), "Encoding error: encode boom");
1432 }
1433}