use crate::Priority;
use std::{
error::Error,
marker::PhantomData,
time::{Duration, SystemTime},
};
use temporalio_common::{
ActivityDefinition, RetryPolicy, UntypedActivity, WorkerDeploymentVersion,
data_converters::{
DataConverter, NoopDecodeHint, PayloadConversionError, SerializationContextData,
TemporalDeserializable,
},
error::IncomingError,
payload_visitor::decode_payloads,
protos::{
proto_ts_to_system_time,
temporal::api::{
activity::v1::{
ActivityExecutionInfo as RawInfo, ActivityExecutionListInfo as RawListInfo,
activity_execution_outcome::Value as ActivityExecutionOutcomeValue,
},
common::v1::{Payload, Payloads},
enums::v1::{
ActivityExecutionStatus as ProtoActivityExecutionStatus,
PendingActivityState as ProtoPendingActivityState,
},
failure::v1::Failure,
workflowservice::v1::DescribeActivityExecutionResponse,
},
utilities::TryIntoOrNone,
},
search_attributes::SearchAttributes,
};
pub trait ActivityExecutionInfoLike {
fn activity_id(&self) -> &str;
fn activity_run_id(&self) -> &str;
fn activity_type(&self) -> &str;
fn schedule_time(&self) -> Option<SystemTime>;
fn close_time(&self) -> Option<SystemTime>;
fn status(&self) -> ActivityExecutionStatus;
fn task_queue(&self) -> &str;
fn execution_duration(&self) -> Option<Duration>;
}
pub struct ActivityExecutionInfo {
raw: RawListInfo,
}
impl From<RawListInfo> for ActivityExecutionInfo {
fn from(raw: RawListInfo) -> Self {
Self { raw }
}
}
impl ActivityExecutionInfoLike for ActivityExecutionInfo {
fn activity_id(&self) -> &str {
&self.raw.activity_id
}
fn activity_run_id(&self) -> &str {
&self.raw.run_id
}
fn activity_type(&self) -> &str {
self.raw
.activity_type
.as_ref()
.map(|t| t.name.as_str())
.unwrap_or("")
}
fn schedule_time(&self) -> Option<SystemTime> {
self.raw
.schedule_time
.as_ref()
.and_then(proto_ts_to_system_time)
}
fn close_time(&self) -> Option<SystemTime> {
self.raw
.close_time
.as_ref()
.and_then(proto_ts_to_system_time)
}
fn status(&self) -> ActivityExecutionStatus {
ProtoActivityExecutionStatus::try_from(self.raw.status)
.map(Into::into)
.unwrap_or(ActivityExecutionStatus::Unknown)
}
fn task_queue(&self) -> &str {
&self.raw.task_queue
}
fn execution_duration(&self) -> Option<Duration> {
self.raw.execution_duration.try_into_or_none()
}
}
impl ActivityExecutionInfo {
pub fn raw_info(&self) -> &RawListInfo {
&self.raw
}
}
pub struct ActivityExecutionDescription<ActivityT = UntypedActivity>
where
ActivityT: ActivityDefinition,
{
raw_info: RawInfo,
raw_input: Option<Payloads>,
raw_outcome: Option<ActivityExecutionOutcomeValue>,
data_converter: DataConverter,
serialization_context: SerializationContextData,
_phantom: PhantomData<ActivityT>,
}
impl<ActivityT> ActivityExecutionInfoLike for ActivityExecutionDescription<ActivityT>
where
ActivityT: ActivityDefinition,
{
fn activity_id(&self) -> &str {
&self.raw_info.activity_id
}
fn activity_run_id(&self) -> &str {
&self.raw_info.run_id
}
fn activity_type(&self) -> &str {
self.raw_info
.activity_type
.as_ref()
.map(|t| t.name.as_str())
.unwrap_or("")
}
fn schedule_time(&self) -> Option<SystemTime> {
self.raw_info
.schedule_time
.as_ref()
.and_then(proto_ts_to_system_time)
}
fn close_time(&self) -> Option<SystemTime> {
self.raw_info
.close_time
.as_ref()
.and_then(proto_ts_to_system_time)
}
fn status(&self) -> ActivityExecutionStatus {
ProtoActivityExecutionStatus::try_from(self.raw_info.status)
.map(Into::into)
.unwrap_or(ActivityExecutionStatus::Unknown)
}
fn task_queue(&self) -> &str {
&self.raw_info.task_queue
}
fn execution_duration(&self) -> Option<Duration> {
self.raw_info.execution_duration.try_into_or_none()
}
}
impl<ActivityT> ActivityExecutionDescription<ActivityT>
where
ActivityT: ActivityDefinition,
{
pub(crate) async fn new(
data_converter: DataConverter,
serialization_context: SerializationContextData,
response: DescribeActivityExecutionResponse,
) -> Result<Self, Box<dyn Error + Send + Sync + 'static>> {
let Some(mut raw_info) = response.info else {
return Err("info missing in describe response".into());
};
if let Some(failure) = raw_info.last_failure.as_mut() {
decode_payloads(failure, data_converter.codec(), &serialization_context).await?;
}
let mut raw_outcome = response.outcome.and_then(|o| o.value);
if let Some(ActivityExecutionOutcomeValue::Failure(failure)) = raw_outcome.as_mut() {
decode_payloads(failure, data_converter.codec(), &serialization_context).await?;
}
Ok(Self {
raw_info,
raw_input: response.input,
raw_outcome,
data_converter,
serialization_context,
_phantom: PhantomData,
})
}
pub fn untyped(self) -> ActivityExecutionDescription {
ActivityExecutionDescription {
raw_info: self.raw_info,
raw_input: self.raw_input,
raw_outcome: self.raw_outcome,
data_converter: self.data_converter,
serialization_context: self.serialization_context,
_phantom: PhantomData,
}
}
pub fn raw_info(&self) -> &RawInfo {
&self.raw_info
}
pub fn has_input(&self) -> bool {
self.raw_input.is_some()
}
pub fn raw_input(&self) -> Option<&Payloads> {
self.raw_input.as_ref()
}
pub async fn input(&self) -> Result<Option<ActivityT::Input>, PayloadConversionError> {
let Some(input) = &self.raw_input else {
return Ok(None);
};
Ok(Some(self.convert_payloads(input).await?))
}
pub fn has_outcome(&self) -> bool {
self.raw_outcome.is_some()
}
pub fn raw_outcome(&self) -> Option<&ActivityExecutionOutcomeValue> {
self.raw_outcome.as_ref()
}
pub async fn outcome(
&self,
) -> Result<Option<Result<ActivityT::Output, IncomingError>>, PayloadConversionError> {
match &self.raw_outcome {
None => Ok(None),
Some(ActivityExecutionOutcomeValue::Result(payloads)) => {
Ok(Some(Ok(self.convert_payloads(payloads).await?)))
}
Some(ActivityExecutionOutcomeValue::Failure(failure)) => {
Ok(Some(Err(self.convert_failure(failure)?)))
}
}
}
pub fn run_state(&self) -> PendingActivityState {
ProtoPendingActivityState::try_from(self.raw_info.run_state)
.map(Into::into)
.unwrap_or(PendingActivityState::Unknown)
}
pub fn schedule_to_close_timeout(&self) -> Option<Duration> {
self.raw_info.schedule_to_close_timeout.try_into_or_none()
}
pub fn schedule_to_start_timeout(&self) -> Option<Duration> {
self.raw_info.schedule_to_start_timeout.try_into_or_none()
}
pub fn start_to_close_timeout(&self) -> Option<Duration> {
self.raw_info.start_to_close_timeout.try_into_or_none()
}
pub fn heartbeat_timeout(&self) -> Option<Duration> {
self.raw_info.heartbeat_timeout.try_into_or_none()
}
pub fn retry_policy(&self) -> Option<RetryPolicy> {
self.raw_info.retry_policy.clone().map(Into::into)
}
pub fn has_heartbeat_details(&self) -> bool {
self.raw_info.heartbeat_details.is_some()
}
pub async fn heartbeat_details<T: TemporalDeserializable + 'static>(
&self,
) -> Result<Option<T>, PayloadConversionError> {
let Some(details) = &self.raw_info.heartbeat_details else {
return Ok(None);
};
Ok(Some(self.convert_payloads(details).await?))
}
pub fn last_heartbeat_time(&self) -> Option<SystemTime> {
self.raw_info
.last_heartbeat_time
.as_ref()
.and_then(proto_ts_to_system_time)
}
pub fn last_started_time(&self) -> Option<SystemTime> {
self.raw_info
.last_started_time
.as_ref()
.and_then(proto_ts_to_system_time)
}
pub fn attempt(&self) -> u32 {
self.raw_info.attempt.try_into().unwrap_or_default()
}
pub fn execution_duration(&self) -> Option<Duration> {
self.raw_info.execution_duration.try_into_or_none()
}
pub fn expiration_time(&self) -> Option<SystemTime> {
self.raw_info
.expiration_time
.as_ref()
.and_then(proto_ts_to_system_time)
}
pub fn has_last_failure(&self) -> bool {
self.raw_info.last_failure.is_some()
}
pub fn last_failure(&self) -> Result<Option<IncomingError>, PayloadConversionError> {
let Some(failure) = &self.raw_info.last_failure else {
return Ok(None);
};
Ok(Some(self.convert_failure(failure)?))
}
pub fn last_worker_identity(&self) -> Option<&str> {
self.raw_info
.last_worker_identity
.is_empty()
.then_some(self.raw_info.last_worker_identity.as_str())
}
pub fn current_retry_interval(&self) -> Option<Duration> {
self.raw_info.current_retry_interval.try_into_or_none()
}
pub fn last_attempt_complete_time(&self) -> Option<SystemTime> {
self.raw_info
.last_attempt_complete_time
.as_ref()
.and_then(proto_ts_to_system_time)
}
pub fn next_attempt_schedule_time(&self) -> Option<SystemTime> {
self.raw_info
.next_attempt_schedule_time
.as_ref()
.and_then(proto_ts_to_system_time)
}
pub fn last_deployment_version(&self) -> Option<WorkerDeploymentVersion> {
self.raw_info
.last_deployment_version
.clone()
.map(Into::into)
}
pub fn priority(&self) -> Priority {
self.raw_info.priority.clone().unwrap_or_default().into()
}
pub fn search_attributes(&self) -> Option<SearchAttributes> {
self.raw_info
.search_attributes
.as_ref()
.map(SearchAttributes::from_proto)
}
pub async fn static_summary(&self) -> Result<Option<String>, PayloadConversionError> {
let Some(summary) = self
.raw_info
.user_metadata
.as_ref()
.and_then(|m| m.summary.clone())
else {
return Ok(None);
};
Ok(Some(self.convert_payload(summary).await?))
}
pub async fn static_details(&self) -> Result<Option<String>, PayloadConversionError> {
let Some(details) = self
.raw_info
.user_metadata
.as_ref()
.and_then(|m| m.details.clone())
else {
return Ok(None);
};
Ok(Some(self.convert_payload(details).await?))
}
pub fn canceled_reason(&self) -> Option<&str> {
let reason = self.raw_info.canceled_reason.as_str();
(!reason.is_empty()).then_some(reason)
}
pub fn start_delay(&self) -> Option<Duration> {
self.raw_info.start_delay.try_into_or_none()
}
async fn convert_payload<T: TemporalDeserializable + 'static>(
&self,
payload: Payload,
) -> Result<T, PayloadConversionError> {
self.data_converter
.from_payload(&self.serialization_context, payload)
.await
}
async fn convert_payloads<T: TemporalDeserializable + 'static>(
&self,
payloads: &Payloads,
) -> Result<T, PayloadConversionError> {
self.data_converter
.from_payloads(&self.serialization_context, payloads.payloads.clone())
.await
}
fn convert_failure(&self, failure: &Failure) -> Result<IncomingError, PayloadConversionError> {
self.data_converter
.to_error(&self.serialization_context, failure.clone(), NoopDecodeHint)
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ActivityExecutionStatus {
#[default]
Unspecified,
Running,
Completed,
Failed,
Canceled,
Terminated,
TimedOut,
Paused,
Unknown,
}
impl From<ProtoActivityExecutionStatus> for ActivityExecutionStatus {
fn from(value: ProtoActivityExecutionStatus) -> Self {
match value {
ProtoActivityExecutionStatus::Unspecified => Self::Unspecified,
ProtoActivityExecutionStatus::Running => Self::Running,
ProtoActivityExecutionStatus::Completed => Self::Completed,
ProtoActivityExecutionStatus::Failed => Self::Failed,
ProtoActivityExecutionStatus::Canceled => Self::Canceled,
ProtoActivityExecutionStatus::Terminated => Self::Terminated,
ProtoActivityExecutionStatus::TimedOut => Self::TimedOut,
ProtoActivityExecutionStatus::Paused => Self::Paused,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum PendingActivityState {
#[default]
Unspecified,
Scheduled,
Started,
CancelRequested,
Paused,
PauseRequested,
Unknown,
}
impl From<ProtoPendingActivityState> for PendingActivityState {
fn from(value: ProtoPendingActivityState) -> Self {
match value {
ProtoPendingActivityState::Unspecified => Self::Unspecified,
ProtoPendingActivityState::Scheduled => Self::Scheduled,
ProtoPendingActivityState::Started => Self::Started,
ProtoPendingActivityState::CancelRequested => Self::CancelRequested,
ProtoPendingActivityState::Paused => Self::Paused,
ProtoPendingActivityState::PauseRequested => Self::PauseRequested,
}
}
}