temporalio_client/activity/
activity_execution_info.rs1use crate::Priority;
2use std::{
3 error::Error,
4 marker::PhantomData,
5 time::{Duration, SystemTime},
6};
7use temporalio_common::{
8 ActivityDefinition, RetryPolicy, UntypedActivity, WorkerDeploymentVersion,
9 data_converters::{
10 DataConverter, NoopDecodeHint, PayloadConversionError, SerializationContextData,
11 TemporalDeserializable,
12 },
13 error::IncomingError,
14 payload_visitor::decode_payloads,
15 protos::{
16 proto_ts_to_system_time,
17 temporal::api::{
18 activity::v1::{
19 ActivityExecutionInfo as RawInfo, ActivityExecutionListInfo as RawListInfo,
20 activity_execution_outcome::Value as ActivityExecutionOutcomeValue,
21 },
22 common::v1::{Payload, Payloads},
23 enums::v1::{
24 ActivityExecutionStatus as ProtoActivityExecutionStatus,
25 PendingActivityState as ProtoPendingActivityState,
26 },
27 failure::v1::Failure,
28 workflowservice::v1::DescribeActivityExecutionResponse,
29 },
30 utilities::TryIntoOrNone,
31 },
32 search_attributes::SearchAttributes,
33};
34
35pub trait ActivityExecutionInfoLike {
37 fn activity_id(&self) -> &str;
39 fn activity_run_id(&self) -> &str;
41 fn activity_type(&self) -> &str;
43 fn schedule_time(&self) -> Option<SystemTime>;
45 fn close_time(&self) -> Option<SystemTime>;
47 fn status(&self) -> ActivityExecutionStatus;
50 fn task_queue(&self) -> &str;
52 fn execution_duration(&self) -> Option<Duration>;
55}
56
57pub struct ActivityExecutionInfo {
60 raw: RawListInfo,
61}
62
63impl From<RawListInfo> for ActivityExecutionInfo {
64 fn from(raw: RawListInfo) -> Self {
65 Self { raw }
66 }
67}
68
69impl ActivityExecutionInfoLike for ActivityExecutionInfo {
70 fn activity_id(&self) -> &str {
71 &self.raw.activity_id
72 }
73
74 fn activity_run_id(&self) -> &str {
75 &self.raw.run_id
76 }
77
78 fn activity_type(&self) -> &str {
79 self.raw
80 .activity_type
81 .as_ref()
82 .map(|t| t.name.as_str())
83 .unwrap_or("")
84 }
85
86 fn schedule_time(&self) -> Option<SystemTime> {
87 self.raw
88 .schedule_time
89 .as_ref()
90 .and_then(proto_ts_to_system_time)
91 }
92
93 fn close_time(&self) -> Option<SystemTime> {
94 self.raw
95 .close_time
96 .as_ref()
97 .and_then(proto_ts_to_system_time)
98 }
99
100 fn status(&self) -> ActivityExecutionStatus {
101 ProtoActivityExecutionStatus::try_from(self.raw.status)
102 .map(Into::into)
103 .unwrap_or(ActivityExecutionStatus::Unknown)
104 }
105
106 fn task_queue(&self) -> &str {
107 &self.raw.task_queue
108 }
109
110 fn execution_duration(&self) -> Option<Duration> {
111 self.raw.execution_duration.try_into_or_none()
112 }
113}
114
115impl ActivityExecutionInfo {
116 pub fn raw_info(&self) -> &RawListInfo {
118 &self.raw
119 }
120}
121
122pub struct ActivityExecutionDescription<ActivityT = UntypedActivity>
127where
128 ActivityT: ActivityDefinition,
129{
130 raw_info: RawInfo,
131 raw_input: Option<Payloads>,
132 raw_outcome: Option<ActivityExecutionOutcomeValue>,
133 data_converter: DataConverter,
134 serialization_context: SerializationContextData,
135 _phantom: PhantomData<ActivityT>,
136}
137
138impl<ActivityT> ActivityExecutionInfoLike for ActivityExecutionDescription<ActivityT>
139where
140 ActivityT: ActivityDefinition,
141{
142 fn activity_id(&self) -> &str {
143 &self.raw_info.activity_id
144 }
145
146 fn activity_run_id(&self) -> &str {
147 &self.raw_info.run_id
148 }
149
150 fn activity_type(&self) -> &str {
151 self.raw_info
152 .activity_type
153 .as_ref()
154 .map(|t| t.name.as_str())
155 .unwrap_or("")
156 }
157
158 fn schedule_time(&self) -> Option<SystemTime> {
159 self.raw_info
160 .schedule_time
161 .as_ref()
162 .and_then(proto_ts_to_system_time)
163 }
164
165 fn close_time(&self) -> Option<SystemTime> {
166 self.raw_info
167 .close_time
168 .as_ref()
169 .and_then(proto_ts_to_system_time)
170 }
171
172 fn status(&self) -> ActivityExecutionStatus {
173 ProtoActivityExecutionStatus::try_from(self.raw_info.status)
174 .map(Into::into)
175 .unwrap_or(ActivityExecutionStatus::Unknown)
176 }
177
178 fn task_queue(&self) -> &str {
179 &self.raw_info.task_queue
180 }
181
182 fn execution_duration(&self) -> Option<Duration> {
183 self.raw_info.execution_duration.try_into_or_none()
184 }
185}
186
187impl<ActivityT> ActivityExecutionDescription<ActivityT>
188where
189 ActivityT: ActivityDefinition,
190{
191 pub(crate) async fn new(
192 data_converter: DataConverter,
193 serialization_context: SerializationContextData,
194 response: DescribeActivityExecutionResponse,
195 ) -> Result<Self, Box<dyn Error + Send + Sync + 'static>> {
196 let Some(mut raw_info) = response.info else {
197 return Err("info missing in describe response".into());
198 };
199 if let Some(failure) = raw_info.last_failure.as_mut() {
200 decode_payloads(failure, data_converter.codec(), &serialization_context).await?;
201 }
202 let mut raw_outcome = response.outcome.and_then(|o| o.value);
203 if let Some(ActivityExecutionOutcomeValue::Failure(failure)) = raw_outcome.as_mut() {
204 decode_payloads(failure, data_converter.codec(), &serialization_context).await?;
205 }
206 Ok(Self {
207 raw_info,
208 raw_input: response.input,
209 raw_outcome,
210 data_converter,
211 serialization_context,
212 _phantom: PhantomData,
213 })
214 }
215
216 pub fn untyped(self) -> ActivityExecutionDescription {
218 ActivityExecutionDescription {
219 raw_info: self.raw_info,
220 raw_input: self.raw_input,
221 raw_outcome: self.raw_outcome,
222 data_converter: self.data_converter,
223 serialization_context: self.serialization_context,
224 _phantom: PhantomData,
225 }
226 }
227
228 pub fn raw_info(&self) -> &RawInfo {
230 &self.raw_info
231 }
232
233 pub fn has_input(&self) -> bool {
237 self.raw_input.is_some()
238 }
239
240 pub fn raw_input(&self) -> Option<&Payloads> {
242 self.raw_input.as_ref()
243 }
244
245 pub async fn input(&self) -> Result<Option<ActivityT::Input>, PayloadConversionError> {
248 let Some(input) = &self.raw_input else {
249 return Ok(None);
250 };
251 Ok(Some(self.convert_payloads(input).await?))
252 }
253
254 pub fn has_outcome(&self) -> bool {
258 self.raw_outcome.is_some()
259 }
260
261 pub fn raw_outcome(&self) -> Option<&ActivityExecutionOutcomeValue> {
263 self.raw_outcome.as_ref()
264 }
265
266 pub async fn outcome(
269 &self,
270 ) -> Result<Option<Result<ActivityT::Output, IncomingError>>, PayloadConversionError> {
271 match &self.raw_outcome {
272 None => Ok(None),
273 Some(ActivityExecutionOutcomeValue::Result(payloads)) => {
274 Ok(Some(Ok(self.convert_payloads(payloads).await?)))
275 }
276 Some(ActivityExecutionOutcomeValue::Failure(failure)) => {
277 Ok(Some(Err(self.convert_failure(failure)?)))
278 }
279 }
280 }
281
282 pub fn run_state(&self) -> PendingActivityState {
284 ProtoPendingActivityState::try_from(self.raw_info.run_state)
285 .map(Into::into)
286 .unwrap_or(PendingActivityState::Unknown)
287 }
288
289 pub fn schedule_to_close_timeout(&self) -> Option<Duration> {
292 self.raw_info.schedule_to_close_timeout.try_into_or_none()
293 }
294
295 pub fn schedule_to_start_timeout(&self) -> Option<Duration> {
298 self.raw_info.schedule_to_start_timeout.try_into_or_none()
299 }
300
301 pub fn start_to_close_timeout(&self) -> Option<Duration> {
304 self.raw_info.start_to_close_timeout.try_into_or_none()
305 }
306
307 pub fn heartbeat_timeout(&self) -> Option<Duration> {
309 self.raw_info.heartbeat_timeout.try_into_or_none()
310 }
311
312 pub fn retry_policy(&self) -> Option<RetryPolicy> {
314 self.raw_info.retry_policy.clone().map(Into::into)
315 }
316
317 pub fn has_heartbeat_details(&self) -> bool {
323 self.raw_info.heartbeat_details.is_some()
324 }
325
326 pub async fn heartbeat_details<T: TemporalDeserializable + 'static>(
329 &self,
330 ) -> Result<Option<T>, PayloadConversionError> {
331 let Some(details) = &self.raw_info.heartbeat_details else {
332 return Ok(None);
333 };
334 Ok(Some(self.convert_payloads(details).await?))
335 }
336
337 pub fn last_heartbeat_time(&self) -> Option<SystemTime> {
339 self.raw_info
340 .last_heartbeat_time
341 .as_ref()
342 .and_then(proto_ts_to_system_time)
343 }
344
345 pub fn last_started_time(&self) -> Option<SystemTime> {
347 self.raw_info
348 .last_started_time
349 .as_ref()
350 .and_then(proto_ts_to_system_time)
351 }
352
353 pub fn attempt(&self) -> u32 {
355 self.raw_info.attempt.try_into().unwrap_or_default()
356 }
357
358 pub fn execution_duration(&self) -> Option<Duration> {
361 self.raw_info.execution_duration.try_into_or_none()
362 }
363
364 pub fn expiration_time(&self) -> Option<SystemTime> {
366 self.raw_info
367 .expiration_time
368 .as_ref()
369 .and_then(proto_ts_to_system_time)
370 }
371
372 pub fn has_last_failure(&self) -> bool {
378 self.raw_info.last_failure.is_some()
379 }
380
381 pub fn last_failure(&self) -> Result<Option<IncomingError>, PayloadConversionError> {
384 let Some(failure) = &self.raw_info.last_failure else {
385 return Ok(None);
386 };
387 Ok(Some(self.convert_failure(failure)?))
388 }
389
390 pub fn last_worker_identity(&self) -> Option<&str> {
392 self.raw_info
393 .last_worker_identity
394 .is_empty()
395 .then_some(self.raw_info.last_worker_identity.as_str())
396 }
397
398 pub fn current_retry_interval(&self) -> Option<Duration> {
400 self.raw_info.current_retry_interval.try_into_or_none()
401 }
402
403 pub fn last_attempt_complete_time(&self) -> Option<SystemTime> {
405 self.raw_info
406 .last_attempt_complete_time
407 .as_ref()
408 .and_then(proto_ts_to_system_time)
409 }
410
411 pub fn next_attempt_schedule_time(&self) -> Option<SystemTime> {
413 self.raw_info
414 .next_attempt_schedule_time
415 .as_ref()
416 .and_then(proto_ts_to_system_time)
417 }
418
419 pub fn last_deployment_version(&self) -> Option<WorkerDeploymentVersion> {
421 self.raw_info
422 .last_deployment_version
423 .clone()
424 .map(Into::into)
425 }
426
427 pub fn priority(&self) -> Priority {
429 self.raw_info.priority.clone().unwrap_or_default().into()
430 }
431
432 pub fn search_attributes(&self) -> Option<SearchAttributes> {
434 self.raw_info
435 .search_attributes
436 .as_ref()
437 .map(SearchAttributes::from_proto)
438 }
439
440 pub async fn static_summary(&self) -> Result<Option<String>, PayloadConversionError> {
443 let Some(summary) = self
444 .raw_info
445 .user_metadata
446 .as_ref()
447 .and_then(|m| m.summary.clone())
448 else {
449 return Ok(None);
450 };
451 Ok(Some(self.convert_payload(summary).await?))
452 }
453
454 pub async fn static_details(&self) -> Result<Option<String>, PayloadConversionError> {
457 let Some(details) = self
458 .raw_info
459 .user_metadata
460 .as_ref()
461 .and_then(|m| m.details.clone())
462 else {
463 return Ok(None);
464 };
465 Ok(Some(self.convert_payload(details).await?))
466 }
467
468 pub fn canceled_reason(&self) -> Option<&str> {
470 let reason = self.raw_info.canceled_reason.as_str();
471 (!reason.is_empty()).then_some(reason)
472 }
473
474 pub fn start_delay(&self) -> Option<Duration> {
477 self.raw_info.start_delay.try_into_or_none()
478 }
479
480 async fn convert_payload<T: TemporalDeserializable + 'static>(
481 &self,
482 payload: Payload,
483 ) -> Result<T, PayloadConversionError> {
484 self.data_converter
485 .from_payload(&self.serialization_context, payload)
486 .await
487 }
488
489 async fn convert_payloads<T: TemporalDeserializable + 'static>(
490 &self,
491 payloads: &Payloads,
492 ) -> Result<T, PayloadConversionError> {
493 self.data_converter
494 .from_payloads(&self.serialization_context, payloads.payloads.clone())
495 .await
496 }
497
498 fn convert_failure(&self, failure: &Failure) -> Result<IncomingError, PayloadConversionError> {
499 self.data_converter
500 .to_error(&self.serialization_context, failure.clone(), NoopDecodeHint)
501 }
502}
503
504#[non_exhaustive]
506#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
507pub enum ActivityExecutionStatus {
508 #[default]
509 Unspecified,
511 Running,
514 Completed,
516 Failed,
518 Canceled,
521 Terminated,
523 TimedOut,
525 Paused,
527 Unknown,
529}
530
531impl From<ProtoActivityExecutionStatus> for ActivityExecutionStatus {
532 fn from(value: ProtoActivityExecutionStatus) -> Self {
533 match value {
534 ProtoActivityExecutionStatus::Unspecified => Self::Unspecified,
535 ProtoActivityExecutionStatus::Running => Self::Running,
536 ProtoActivityExecutionStatus::Completed => Self::Completed,
537 ProtoActivityExecutionStatus::Failed => Self::Failed,
538 ProtoActivityExecutionStatus::Canceled => Self::Canceled,
539 ProtoActivityExecutionStatus::Terminated => Self::Terminated,
540 ProtoActivityExecutionStatus::TimedOut => Self::TimedOut,
541 ProtoActivityExecutionStatus::Paused => Self::Paused,
542 }
543 }
544}
545
546#[non_exhaustive]
549#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
550pub enum PendingActivityState {
551 #[default]
552 Unspecified,
554 Scheduled,
556 Started,
558 CancelRequested,
560 Paused,
562 PauseRequested,
564 Unknown,
566}
567
568impl From<ProtoPendingActivityState> for PendingActivityState {
569 fn from(value: ProtoPendingActivityState) -> Self {
570 match value {
571 ProtoPendingActivityState::Unspecified => Self::Unspecified,
572 ProtoPendingActivityState::Scheduled => Self::Scheduled,
573 ProtoPendingActivityState::Started => Self::Started,
574 ProtoPendingActivityState::CancelRequested => Self::CancelRequested,
575 ProtoPendingActivityState::Paused => Self::Paused,
576 ProtoPendingActivityState::PauseRequested => Self::PauseRequested,
577 }
578 }
579}