1use crate::{
2 CancelWorkflowInput, DescribeWorkflowInput, DescribeWorkflowOutput,
3 FetchWorkflowHistoryPageInput, FetchWorkflowHistoryPageOutput, NamespacedClient, Next,
4 PollWorkflowUpdateInput, PollWorkflowUpdateOutput, QueryWorkflowInput, QueryWorkflowOutput,
5 RpcOptions, SignalWorkflowInput, StartWorkflowUpdateInput, StartWorkflowUpdateOutput,
6 TerminateWorkflowInput, WorkflowCancelOptions, WorkflowDescribeOptions,
7 WorkflowExecuteUpdateOptions, WorkflowExecutionStatus, WorkflowFetchHistoryOptions,
8 WorkflowGetResultOptions, WorkflowQueryOptions, WorkflowSignalOptions,
9 WorkflowStartUpdateOptions, WorkflowTerminateOptions,
10 errors::{
11 WorkflowGetResultError, WorkflowInteractionError, WorkflowQueryError, WorkflowUpdateError,
12 },
13 grpc::WorkflowService,
14 interceptors,
15};
16use futures_util::future::BoxFuture;
17use std::{fmt::Debug, marker::PhantomData};
18pub use temporalio_common::UntypedWorkflow;
19use temporalio_common::{
20 HasWorkflowDefinition, QueryDefinition, SignalDefinition, UpdateDefinition, WorkflowDefinition,
21 data_converters::{
22 DataConverter, DecodablePayloads, GenericPayloadConverter, PayloadConversionError,
23 PayloadConverter, RawValue, SerializationContext, SerializationContextData,
24 },
25 error::IncomingError,
26 payload_visitor::decode_payloads,
27 protos::{
28 coresdk::FromPayloadsExt,
29 proto_ts_to_system_time,
30 temporal::api::{
31 common::v1::{Payload, Payloads, WorkflowExecution as ProtoWorkflowExecution},
32 enums::v1::{HistoryEventFilterType, UpdateWorkflowExecutionLifecycleStage},
33 history::{
34 self,
35 v1::{History, HistoryEvent, history_event::Attributes},
36 },
37 query::v1::WorkflowQuery,
38 sdk::v1::UserMetadata,
39 update::{self, v1::WaitPolicy},
40 workflow::v1 as workflow,
41 workflowservice::v1::{
42 DescribeWorkflowExecutionRequest, DescribeWorkflowExecutionResponse,
43 GetWorkflowExecutionHistoryRequest, PollWorkflowExecutionUpdateRequest,
44 QueryWorkflowRequest, RequestCancelWorkflowExecutionRequest,
45 SignalWorkflowExecutionRequest, TerminateWorkflowExecutionRequest,
46 UpdateWorkflowExecutionRequest,
47 },
48 },
49 },
50 search_attributes::SearchAttributes,
51};
52use tonic::IntoRequest;
53use uuid::Uuid;
54
55#[derive(Debug, Clone, Default, PartialEq, Eq)]
56struct DecodedUserMetadata {
57 summary: Option<String>,
58 details: Option<String>,
59}
60
61fn decode_user_metadata(
62 context: &SerializationContextData,
63 user_metadata: Option<UserMetadata>,
64) -> Result<DecodedUserMetadata, PayloadConversionError> {
65 let payload_converter = PayloadConverter::default();
66 let context = SerializationContext {
67 data: context,
68 converter: &payload_converter,
69 };
70 let (summary, details) = user_metadata
71 .map(|metadata| (metadata.summary, metadata.details))
72 .unwrap_or_default();
73 Ok(DecodedUserMetadata {
74 summary: match summary {
75 Some(payload) => Some(payload_converter.from_payload(&context, payload)?),
76 None => None,
77 },
78 details: match details {
79 Some(payload) => Some(payload_converter.from_payload(&context, payload)?),
80 None => None,
81 },
82 })
83}
84
85#[derive(Clone, Debug)]
87#[non_exhaustive]
88pub struct WorkflowResultDetails {
89 payloads: DecodablePayloads,
90}
91
92impl WorkflowResultDetails {
93 async fn new(
94 payloads: Vec<Payload>,
95 data_converter: &DataConverter,
96 ) -> Result<Self, PayloadConversionError> {
97 let payloads = data_converter
98 .codec()
99 .decode(&SerializationContextData::Workflow, payloads)
100 .await?;
101 Ok(Self {
102 payloads: DecodablePayloads::new(
103 payloads,
104 data_converter.payload_converter().clone(),
105 SerializationContextData::Workflow,
106 ),
107 })
108 }
109
110 pub fn deserialize<T: temporalio_common::data_converters::TemporalDeserializable + 'static>(
112 &self,
113 ) -> Result<T, PayloadConversionError> {
114 self.payloads.deserialize()
115 }
116
117 pub fn raw(&self) -> &[Payload] {
119 self.payloads.raw()
120 }
121
122 pub fn into_raw(self) -> RawValue {
124 self.payloads.into_raw()
125 }
126}
127
128#[derive(Debug)]
130#[allow(clippy::large_enum_variant)]
131pub enum WorkflowExecutionResult<T> {
132 Succeeded(T),
134 Failed(IncomingError),
136 Cancelled {
138 details: WorkflowResultDetails,
140 },
141 Terminated {
143 details: WorkflowResultDetails,
145 },
146 TimedOut,
148 ContinuedAsNew,
150}
151
152#[derive(Debug, Clone)]
156pub struct WorkflowExecutionDescription {
157 pub raw_description: DescribeWorkflowExecutionResponse,
159 history_length: usize,
160 static_summary: Option<String>,
161 static_details: Option<String>,
162 data_converter: DataConverter,
163}
164
165impl WorkflowExecutionDescription {
166 async fn new(
167 mut raw_description: DescribeWorkflowExecutionResponse,
168 data_converter: &DataConverter,
169 ) -> Result<Self, PayloadConversionError> {
170 let raw_user_metadata = raw_description
171 .execution_config
172 .as_ref()
173 .and_then(|cfg| cfg.user_metadata.clone());
174 decode_payloads(
175 &mut raw_description,
176 data_converter.codec(),
177 &SerializationContextData::Workflow,
178 )
179 .await?;
180 let decoded_metadata =
181 decode_user_metadata(&SerializationContextData::Workflow, raw_user_metadata)?;
182 let history_length_raw = raw_description
183 .workflow_execution_info
184 .as_ref()
185 .map(|info| info.history_length)
186 .unwrap_or(0);
187 let history_length = history_length_raw.try_into().map_err(|_| {
188 PayloadConversionError::EncodingError(
189 format!("workflow history_length must be non-negative, got {history_length_raw}")
190 .into(),
191 )
192 })?;
193 Ok(Self {
194 raw_description,
195 history_length,
196 static_summary: decoded_metadata.summary,
197 static_details: decoded_metadata.details,
198 data_converter: data_converter.clone(),
199 })
200 }
201
202 pub fn id(&self) -> &str {
204 self.execution().workflow_id.as_str()
205 }
206
207 pub fn run_id(&self) -> &str {
209 self.execution().run_id.as_str()
210 }
211
212 pub fn workflow_type(&self) -> &str {
214 self.workflow_type_info().name.as_str()
215 }
216
217 pub fn status(&self) -> WorkflowExecutionStatus {
219 WorkflowExecutionStatus::from_raw(self.workflow_info().status)
220 }
221
222 pub fn start_time(&self) -> Option<std::time::SystemTime> {
224 self.workflow_info()
225 .start_time
226 .as_ref()
227 .and_then(proto_ts_to_system_time)
228 }
229
230 pub fn execution_time(&self) -> Option<std::time::SystemTime> {
232 self.workflow_info()
233 .execution_time
234 .as_ref()
235 .and_then(proto_ts_to_system_time)
236 }
237
238 pub fn close_time(&self) -> Option<std::time::SystemTime> {
240 self.workflow_info()
241 .close_time
242 .as_ref()
243 .and_then(proto_ts_to_system_time)
244 }
245
246 pub fn task_queue(&self) -> &str {
248 self.workflow_info().task_queue.as_str()
249 }
250
251 pub fn history_length(&self) -> usize {
253 self.history_length
254 }
255
256 pub fn memo(&self) -> crate::Memo {
258 crate::Memo::from_raw(
259 self.workflow_info().memo.clone(),
260 self.data_converter.payload_converter().clone(),
261 SerializationContextData::Workflow,
262 )
263 }
264
265 pub fn parent_id(&self) -> Option<&str> {
267 self.workflow_info()
268 .parent_execution
269 .as_ref()
270 .map(|e| e.workflow_id.as_str())
271 }
272
273 pub fn parent_run_id(&self) -> Option<&str> {
275 self.workflow_info()
276 .parent_execution
277 .as_ref()
278 .map(|e| e.run_id.as_str())
279 }
280
281 pub fn search_attributes(&self) -> SearchAttributes {
283 self.workflow_info()
284 .search_attributes
285 .as_ref()
286 .map(SearchAttributes::from_proto)
287 .unwrap_or_default()
288 }
289
290 pub fn static_summary(&self) -> Option<&str> {
292 self.static_summary.as_deref()
293 }
294
295 pub fn static_details(&self) -> Option<&str> {
297 self.static_details.as_deref()
298 }
299
300 pub fn raw(&self) -> &DescribeWorkflowExecutionResponse {
302 &self.raw_description
303 }
304
305 pub fn into_raw(self) -> DescribeWorkflowExecutionResponse {
307 self.raw_description
308 }
309
310 fn workflow_info(&self) -> &workflow::WorkflowExecutionInfo {
311 self.raw_description
312 .workflow_execution_info
313 .as_ref()
314 .expect("describe response missing workflow_execution_info")
315 }
316
317 fn execution(&self) -> &ProtoWorkflowExecution {
318 self.workflow_info()
319 .execution
320 .as_ref()
321 .expect("describe response missing workflow_execution_info.execution")
322 }
323
324 fn workflow_type_info(
325 &self,
326 ) -> &temporalio_common::protos::temporal::api::common::v1::WorkflowType {
327 self.workflow_info()
328 .r#type
329 .as_ref()
330 .expect("describe response missing workflow_execution_info.type")
331 }
332}
333
334#[derive(Debug, Clone)]
337pub struct WorkflowHistory {
338 events: Vec<HistoryEvent>,
339 workflow_id: Option<String>,
340}
341impl From<WorkflowHistory> for history::v1::History {
342 fn from(h: WorkflowHistory) -> Self {
343 Self { events: h.events }
344 }
345}
346
347#[derive(Debug, thiserror::Error)]
349#[error("failed to convert workflow history JSON: {0}")]
350pub struct WorkflowHistoryJsonError(#[from] serde_json::Error);
351
352impl WorkflowHistory {
353 fn new(events: Vec<HistoryEvent>, workflow_id: Option<String>) -> Self {
354 Self {
355 events,
356 workflow_id,
357 }
358 }
359
360 pub fn from_json(bytes: &[u8]) -> Result<Self, WorkflowHistoryJsonError> {
362 let history: History = serde_json::from_slice(bytes)?;
363 let workflow_id = history
364 .events
365 .first()
366 .and_then(|event| match event.attributes.as_ref() {
367 Some(Attributes::WorkflowExecutionStartedEventAttributes(attributes)) => {
368 Some(attributes)
369 }
370 _ => None,
371 })
372 .map(|attributes| attributes.workflow_id.clone())
373 .filter(|wfid| !wfid.is_empty());
374 Ok(Self::new(history.events, workflow_id))
375 }
376
377 pub fn to_json(&self) -> Result<Vec<u8>, WorkflowHistoryJsonError> {
379 Ok(serde_json::to_vec(&History {
380 events: self.events.clone(),
381 })?)
382 }
383
384 pub fn workflow_id(&self) -> Option<&str> {
386 self.workflow_id.as_deref()
387 }
388
389 pub fn events(&self) -> &[HistoryEvent] {
391 &self.events
392 }
393
394 pub fn into_events(self) -> Vec<HistoryEvent> {
396 self.events
397 }
398}
399
400#[derive(Clone)]
403pub struct WorkflowHandle<ClientT, W> {
404 client: ClientT,
405 info: WorkflowExecutionInfo,
406
407 _wf_type: PhantomData<W>,
408}
409
410impl<CT, W> WorkflowHandle<CT, W> {
411 pub fn run_id(&self) -> Option<&str> {
413 self.info.run_id.as_deref()
414 }
415}
416
417#[derive(Debug, Clone)]
419pub struct WorkflowExecutionInfo {
420 pub namespace: String,
422 pub workflow_id: String,
424 pub run_id: Option<String>,
426 pub first_execution_run_id: Option<String>,
430}
431
432impl WorkflowExecutionInfo {
433 pub fn bind_untyped<CT>(self, client: CT) -> UntypedWorkflowHandle<CT>
435 where
436 CT: WorkflowService + Clone,
437 {
438 UntypedWorkflowHandle::new(client, self)
439 }
440}
441
442pub type UntypedWorkflowHandle<CT> = WorkflowHandle<CT, UntypedWorkflow>;
445
446pub struct UntypedSignal<W> {
450 name: String,
451 _wf: PhantomData<W>,
452}
453
454impl<W> UntypedSignal<W> {
455 pub fn new(name: impl Into<String>) -> Self {
457 Self {
458 name: name.into(),
459 _wf: PhantomData,
460 }
461 }
462}
463
464impl<W: WorkflowDefinition> SignalDefinition for UntypedSignal<W> {
465 type Workflow = W;
466 type Input = RawValue;
467
468 fn name(&self) -> &str {
469 &self.name
470 }
471}
472
473pub struct UntypedQuery<W> {
477 name: String,
478 _wf: PhantomData<W>,
479}
480
481impl<W> UntypedQuery<W> {
482 pub fn new(name: impl Into<String>) -> Self {
484 Self {
485 name: name.into(),
486 _wf: PhantomData,
487 }
488 }
489}
490
491impl<W: WorkflowDefinition> QueryDefinition for UntypedQuery<W> {
492 type Workflow = W;
493 type Input = RawValue;
494 type Output = RawValue;
495
496 fn name(&self) -> &str {
497 &self.name
498 }
499}
500
501pub struct UntypedUpdate<W> {
505 name: String,
506 _wf: PhantomData<W>,
507}
508
509impl<W> UntypedUpdate<W> {
510 pub fn new(name: impl Into<String>) -> Self {
512 Self {
513 name: name.into(),
514 _wf: PhantomData,
515 }
516 }
517}
518
519impl<W: WorkflowDefinition> UpdateDefinition for UntypedUpdate<W> {
520 type Workflow = W;
521 type Input = RawValue;
522 type Output = RawValue;
523
524 fn name(&self) -> &str {
525 &self.name
526 }
527}
528
529impl<CT, W> WorkflowHandle<CT, W>
530where
531 CT: WorkflowService + Clone,
532 W: HasWorkflowDefinition,
533{
534 pub fn new(client: CT, info: WorkflowExecutionInfo) -> Self {
536 Self {
537 client,
538 info,
539 _wf_type: PhantomData::<W>,
540 }
541 }
542
543 pub fn info(&self) -> &WorkflowExecutionInfo {
545 &self.info
546 }
547
548 pub fn client(&self) -> &CT {
550 &self.client
551 }
552
553 pub async fn get_result(
555 &self,
556 opts: WorkflowGetResultOptions,
557 ) -> Result<W::Output, WorkflowGetResultError>
558 where
559 CT: WorkflowService + NamespacedClient + Clone,
560 {
561 let raw = self.get_result_raw(opts).await?;
562 match raw {
563 WorkflowExecutionResult::Succeeded(v) => Ok(v),
564 WorkflowExecutionResult::Failed(f) => Err(WorkflowGetResultError::Failed(Box::new(f))),
565 WorkflowExecutionResult::Cancelled { details } => {
566 Err(WorkflowGetResultError::Cancelled { details })
567 }
568 WorkflowExecutionResult::Terminated { details } => {
569 Err(WorkflowGetResultError::Terminated { details })
570 }
571 WorkflowExecutionResult::TimedOut => Err(WorkflowGetResultError::TimedOut),
572 WorkflowExecutionResult::ContinuedAsNew => Err(WorkflowGetResultError::ContinuedAsNew),
573 }
574 }
575
576 async fn get_result_raw(
580 &self,
581 opts: WorkflowGetResultOptions,
582 ) -> Result<WorkflowExecutionResult<W::Output>, WorkflowInteractionError>
583 where
584 CT: WorkflowService + NamespacedClient + Clone,
585 {
586 let mut run_id = self.info.run_id.clone().unwrap_or_default();
587 let fetch_opts = WorkflowFetchHistoryOptions::builder()
588 .skip_archival(true)
589 .wait_new_event(true)
590 .event_filter_type(HistoryEventFilterType::CloseEvent)
591 .rpc_options(opts.rpc_options.clone())
592 .build();
593
594 loop {
595 let history = self.fetch_history_for_run(&run_id, &fetch_opts).await?;
596 let mut events = history.into_events();
597
598 if events.is_empty() {
599 continue;
600 }
601
602 let event_attrs = events.pop().and_then(|ev| ev.attributes);
603
604 macro_rules! follow {
605 ($attrs:ident) => {
606 if opts.follow_runs && $attrs.new_execution_run_id != "" {
607 run_id = $attrs.new_execution_run_id;
608 continue;
609 }
610 };
611 }
612
613 let dc = self.client.data_converter();
614
615 break match event_attrs {
616 Some(Attributes::WorkflowExecutionCompletedEventAttributes(attrs)) => {
617 follow!(attrs);
618 let payload = attrs
619 .result
620 .and_then(|p| p.payloads.into_iter().next())
621 .unwrap_or_default();
622 let result: W::Output = dc
623 .from_payload(&SerializationContextData::Workflow, payload)
624 .await?;
625 Ok(WorkflowExecutionResult::Succeeded(result))
626 }
627 Some(Attributes::WorkflowExecutionFailedEventAttributes(attrs)) => {
628 follow!(attrs);
629 let mut failure = attrs.failure.unwrap_or_default();
630 decode_payloads(
631 &mut failure,
632 dc.codec(),
633 &SerializationContextData::Workflow,
634 )
635 .await?;
636 let error = dc.failure_converter().to_error(
637 failure,
638 dc.payload_converter(),
639 &SerializationContextData::Workflow,
640 )?;
641 Ok(WorkflowExecutionResult::Failed(error))
642 }
643 Some(Attributes::WorkflowExecutionCanceledEventAttributes(attrs)) => {
644 Ok(WorkflowExecutionResult::Cancelled {
645 details: WorkflowResultDetails::new(Vec::from_payloads(attrs.details), dc)
646 .await?,
647 })
648 }
649 Some(Attributes::WorkflowExecutionTimedOutEventAttributes(attrs)) => {
650 follow!(attrs);
651 Ok(WorkflowExecutionResult::TimedOut)
652 }
653 Some(Attributes::WorkflowExecutionTerminatedEventAttributes(attrs)) => {
654 Ok(WorkflowExecutionResult::Terminated {
655 details: WorkflowResultDetails::new(Vec::from_payloads(attrs.details), dc)
656 .await?,
657 })
658 }
659 Some(Attributes::WorkflowExecutionContinuedAsNewEventAttributes(attrs)) => {
660 if opts.follow_runs {
661 if !attrs.new_execution_run_id.is_empty() {
662 run_id = attrs.new_execution_run_id;
663 continue;
664 } else {
665 return Err(WorkflowInteractionError::Other(
666 "New execution run id was empty in continue as new event!".into(),
667 ));
668 }
669 } else {
670 Ok(WorkflowExecutionResult::ContinuedAsNew)
671 }
672 }
673 o => Err(WorkflowInteractionError::Other(
674 format!(
675 "Server returned an event that didn't match the CloseEvent filter. \
676 This is either a server bug or a new event the SDK does not understand. \
677 Event details: {o:?}"
678 )
679 .into(),
680 )),
681 };
682 }
683 }
684
685 pub async fn signal<S>(
687 &self,
688 signal: S,
689 input: S::Input,
690 opts: WorkflowSignalOptions,
691 ) -> Result<(), WorkflowInteractionError>
692 where
693 CT: WorkflowService + NamespacedClient + Clone,
694 S: SignalDefinition<Workflow = W::Run>,
695 S::Input: Send,
696 {
697 interceptors::call_signal_workflow(
698 self.client.client_interceptors(),
699 SignalWorkflowInput::new(
700 self.info.workflow_id.clone(),
701 self.info.run_id.clone().unwrap_or_default(),
702 signal.name().to_string(),
703 input,
704 opts,
705 ),
706 Next::new({
707 let mut client = self.client.clone();
708 move |input: SignalWorkflowInput| -> BoxFuture<
709 '_,
710 Result<(), WorkflowInteractionError>,
711 > {
712 Box::pin(async move {
713 let (workflow_id, run_id, signal_name, args, options) =
714 input.into_parts();
715 let data_converter = client.data_converter().clone();
716 let unencoded_payloads = {
717 let payload_converter = data_converter.payload_converter();
718 let context = SerializationContext {
719 data: &SerializationContextData::Workflow,
720 converter: payload_converter,
721 };
722 args.serialize_payloads(&context)
723 };
724 drop(args);
725 let payloads = data_converter
726 .codec()
727 .encode(&SerializationContextData::Workflow, unencoded_payloads?)
728 .await?;
729 let mut request = SignalWorkflowExecutionRequest {
730 namespace: client.namespace(),
731 workflow_execution: Some(ProtoWorkflowExecution {
732 workflow_id,
733 run_id,
734 }),
735 signal_name,
736 input: Some(Payloads { payloads }),
737 identity: client.identity(),
738 request_id: options
739 .request_id
740 .unwrap_or_else(|| Uuid::new_v4().to_string()),
741 header: options.header,
742 ..Default::default()
743 }
744 .into_request();
745 options.rpc_options.apply_to(&mut request);
746 WorkflowService::signal_workflow_execution(&mut client, request)
747 .await
748 .map_err(WorkflowInteractionError::from_status)?;
749 Ok(())
750 })
751 }
752 }),
753 )
754 .await
755 }
756
757 pub async fn query<Q>(
759 &self,
760 query: Q,
761 input: Q::Input,
762 opts: WorkflowQueryOptions,
763 ) -> Result<Q::Output, WorkflowQueryError>
764 where
765 CT: WorkflowService + NamespacedClient + Clone,
766 Q: QueryDefinition<Workflow = W::Run>,
767 Q::Input: Send,
768 {
769 let output = interceptors::call_query_workflow(
770 self.client.client_interceptors(),
771 QueryWorkflowInput::new(
772 self.info.workflow_id.clone(),
773 self.info.run_id.clone().unwrap_or_default(),
774 query.name().to_string(),
775 input,
776 opts,
777 ),
778 Next::new({
779 let mut client = self.client.clone();
780 move |input: QueryWorkflowInput| -> BoxFuture<
781 '_,
782 Result<QueryWorkflowOutput, WorkflowQueryError>,
783 > {
784 Box::pin(async move {
785 let (workflow_id, run_id, query_name, args, options) = input.into_parts();
786 let data_converter = client.data_converter().clone();
787 let unencoded_payloads = {
788 let payload_converter = data_converter.payload_converter();
789 let context = SerializationContext {
790 data: &SerializationContextData::Workflow,
791 converter: payload_converter,
792 };
793 args.serialize_payloads(&context)
794 };
795 drop(args);
796 let payloads = data_converter
797 .codec()
798 .encode(&SerializationContextData::Workflow, unencoded_payloads?)
799 .await?;
800 let mut request = QueryWorkflowRequest {
801 namespace: client.namespace(),
802 execution: Some(ProtoWorkflowExecution {
803 workflow_id,
804 run_id,
805 }),
806 query: Some(WorkflowQuery {
807 query_type: query_name,
808 query_args: Some(Payloads { payloads }),
809 header: options.header,
810 }),
811 query_reject_condition: options
812 .reject_condition
813 .map(|condition| condition as i32)
814 .unwrap_or(1),
815 }
816 .into_request();
817 options.rpc_options.apply_to(&mut request);
818 let response = client
819 .query_workflow(request)
820 .await
821 .map_err(WorkflowQueryError::from_status)?
822 .into_inner();
823 Ok(QueryWorkflowOutput::new(response))
824 })
825 }
826 }),
827 )
828 .await?;
829 let response = output.response;
830
831 if let Some(rejected) = response.query_rejected {
832 return Err(WorkflowQueryError::Rejected {
833 status: (rejected.status != 0)
834 .then(|| WorkflowExecutionStatus::from_raw(rejected.status)),
835 });
836 }
837
838 let result_payloads = response
839 .query_result
840 .map(|p| p.payloads)
841 .unwrap_or_default();
842
843 self.client
844 .data_converter()
845 .from_payloads(&SerializationContextData::Workflow, result_payloads)
846 .await
847 .map_err(WorkflowQueryError::from)
848 }
849
850 pub async fn execute_update<U>(
852 &self,
853 update: U,
854 input: U::Input,
855 options: WorkflowExecuteUpdateOptions,
856 ) -> Result<U::Output, WorkflowUpdateError>
857 where
858 CT: WorkflowService + NamespacedClient + Clone,
859 U: UpdateDefinition<Workflow = W::Run>,
860 U::Input: Send,
861 U::Output: 'static,
862 {
863 let rpc_options = options.rpc_options.clone();
864 let handle = self
865 .start_update(
866 update,
867 input,
868 WorkflowStartUpdateOptions::builder()
869 .maybe_update_id(options.update_id)
870 .maybe_header(options.header)
871 .rpc_options(rpc_options.clone())
872 .build(),
873 )
874 .await?;
875 handle.get_result(rpc_options).await
876 }
877
878 pub async fn start_update<U>(
881 &self,
882 update: U,
883 input: U::Input,
884 options: WorkflowStartUpdateOptions,
885 ) -> Result<WorkflowUpdateHandle<CT, U::Output>, WorkflowUpdateError>
886 where
887 CT: WorkflowService + NamespacedClient + Clone,
888 U: UpdateDefinition<Workflow = W::Run>,
889 U::Input: Send,
890 {
891 let output = interceptors::call_start_workflow_update(
892 self.client.client_interceptors(),
893 StartWorkflowUpdateInput::new(
894 self.info().workflow_id.clone(),
895 self.info().run_id.clone().unwrap_or_default(),
896 update.name().to_string(),
897 input,
898 options,
899 ),
900 Next::new({
901 let mut client = self.client.clone();
902 move |input: StartWorkflowUpdateInput| -> BoxFuture<
903 '_,
904 Result<StartWorkflowUpdateOutput, WorkflowUpdateError>,
905 > {
906 Box::pin(async move {
907 let (workflow_id, run_id, update_name, args, options) = input.into_parts();
908 let data_converter = client.data_converter().clone();
909 let unencoded_payloads = {
910 let payload_converter = data_converter.payload_converter();
911 let context = SerializationContext {
912 data: &SerializationContextData::Workflow,
913 converter: payload_converter,
914 };
915 args.serialize_payloads(&context)
916 };
917 drop(args);
918 let payloads = data_converter
919 .codec()
920 .encode(&SerializationContextData::Workflow, unencoded_payloads?)
921 .await?;
922 let update_id = options
923 .update_id
924 .unwrap_or_else(|| Uuid::new_v4().to_string());
925 let mut request = UpdateWorkflowExecutionRequest {
926 namespace: client.namespace(),
927 workflow_execution: Some(ProtoWorkflowExecution {
928 workflow_id: workflow_id.clone(),
929 run_id,
930 }),
931 wait_policy: Some(WaitPolicy {
932 lifecycle_stage:
933 UpdateWorkflowExecutionLifecycleStage::Accepted.into(),
934 }),
935 request: Some(update::v1::Request {
936 meta: Some(update::v1::Meta {
937 update_id: update_id.clone(),
938 identity: client.identity(),
939 }),
940 input: Some(update::v1::Input {
941 header: options.header,
942 name: update_name,
943 args: Some(Payloads { payloads }),
944 }),
945 ..Default::default()
946 }),
947 ..Default::default()
948 }
949 .into_request();
950 options.rpc_options.apply_to(&mut request);
951 let response = WorkflowService::update_workflow_execution(
952 &mut client,
953 request,
954 )
955 .await
956 .map_err(WorkflowUpdateError::from_status)?
957 .into_inner();
958 let run_id = response
959 .update_ref
960 .as_ref()
961 .and_then(|reference| reference.workflow_execution.as_ref())
962 .map(|execution| execution.run_id.clone())
963 .filter(|run_id| !run_id.is_empty());
964 Ok(StartWorkflowUpdateOutput::new(
965 update_id,
966 workflow_id,
967 run_id,
968 response.outcome,
969 ))
970 })
971 }
972 }),
973 )
974 .await?;
975
976 Ok(WorkflowUpdateHandle {
977 client: self.client.clone(),
978 update_id: output.update_id,
979 workflow_id: output.workflow_id,
980 run_id: output.run_id.or_else(|| self.info().run_id.clone()),
981 known_outcome: output.known_outcome,
982 _output: PhantomData,
983 })
984 }
985
986 pub async fn cancel(&self, opts: WorkflowCancelOptions) -> Result<(), WorkflowInteractionError>
988 where
989 CT: NamespacedClient,
990 {
991 interceptors::call_cancel_workflow(
992 self.client.client_interceptors(),
993 CancelWorkflowInput {
994 workflow_id: self.info.workflow_id.clone(),
995 run_id: self.info.run_id.clone().unwrap_or_default(),
996 first_execution_run_id: self
997 .info
998 .first_execution_run_id
999 .clone()
1000 .unwrap_or_default(),
1001 options: opts,
1002 },
1003 Next::new({
1004 let mut client = self.client.clone();
1005 move |input: CancelWorkflowInput| -> BoxFuture<
1006 '_,
1007 Result<(), WorkflowInteractionError>,
1008 > {
1009 Box::pin(async move {
1010 let mut request = RequestCancelWorkflowExecutionRequest {
1011 namespace: client.namespace(),
1012 workflow_execution: Some(ProtoWorkflowExecution {
1013 workflow_id: input.workflow_id,
1014 run_id: input.run_id,
1015 }),
1016 identity: client.identity(),
1017 request_id: input
1018 .options
1019 .request_id
1020 .clone()
1021 .unwrap_or_else(|| Uuid::new_v4().to_string()),
1022 first_execution_run_id: input.first_execution_run_id,
1023 reason: input.options.reason.clone(),
1024 links: vec![],
1025 }
1026 .into_request();
1027 input.options.rpc_options.apply_to(&mut request);
1028 WorkflowService::request_cancel_workflow_execution(&mut client, request)
1029 .await
1030 .map_err(WorkflowInteractionError::from_status)?;
1031 Ok(())
1032 })
1033 }
1034 }),
1035 )
1036 .await
1037 }
1038
1039 pub async fn terminate(
1041 &self,
1042 opts: WorkflowTerminateOptions,
1043 ) -> Result<(), WorkflowInteractionError>
1044 where
1045 CT: NamespacedClient,
1046 {
1047 interceptors::call_terminate_workflow(
1048 self.client.client_interceptors(),
1049 TerminateWorkflowInput {
1050 workflow_id: self.info.workflow_id.clone(),
1051 run_id: self.info.run_id.clone().unwrap_or_default(),
1052 first_execution_run_id: self
1053 .info
1054 .first_execution_run_id
1055 .clone()
1056 .unwrap_or_default(),
1057 options: opts,
1058 },
1059 Next::new({
1060 let mut client = self.client.clone();
1061 move |input: TerminateWorkflowInput| -> BoxFuture<
1062 '_,
1063 Result<(), WorkflowInteractionError>,
1064 > {
1065 Box::pin(async move {
1066 let mut request = TerminateWorkflowExecutionRequest {
1067 namespace: client.namespace(),
1068 workflow_execution: Some(ProtoWorkflowExecution {
1069 workflow_id: input.workflow_id,
1070 run_id: input.run_id,
1071 }),
1072 reason: input.options.reason.clone(),
1073 details: input.options.details.clone(),
1074 identity: client.identity(),
1075 first_execution_run_id: input.first_execution_run_id,
1076 links: vec![],
1077 }
1078 .into_request();
1079 input.options.rpc_options.apply_to(&mut request);
1080 WorkflowService::terminate_workflow_execution(&mut client, request)
1081 .await
1082 .map_err(WorkflowInteractionError::from_status)?;
1083 Ok(())
1084 })
1085 }
1086 }),
1087 )
1088 .await
1089 }
1090
1091 pub async fn describe(
1093 &self,
1094 opts: WorkflowDescribeOptions,
1095 ) -> Result<WorkflowExecutionDescription, WorkflowInteractionError>
1096 where
1097 CT: NamespacedClient,
1098 {
1099 let output = interceptors::call_describe_workflow(
1100 self.client.client_interceptors(),
1101 DescribeWorkflowInput {
1102 workflow_id: self.info.workflow_id.clone(),
1103 run_id: self.info.run_id.clone().unwrap_or_default(),
1104 options: opts,
1105 },
1106 Next::new({
1107 let mut client = self.client.clone();
1108 move |input: DescribeWorkflowInput| -> BoxFuture<
1109 '_,
1110 Result<DescribeWorkflowOutput, WorkflowInteractionError>,
1111 > {
1112 Box::pin(async move {
1113 let mut request = DescribeWorkflowExecutionRequest {
1114 namespace: client.namespace(),
1115 execution: Some(ProtoWorkflowExecution {
1116 workflow_id: input.workflow_id,
1117 run_id: input.run_id,
1118 }),
1119 }
1120 .into_request();
1121 input.options.rpc_options.apply_to(&mut request);
1122 let response =
1123 WorkflowService::describe_workflow_execution(&mut client, request)
1124 .await
1125 .map_err(WorkflowInteractionError::from_status)?
1126 .into_inner();
1127 Ok(DescribeWorkflowOutput::new(response))
1128 })
1129 }
1130 }),
1131 )
1132 .await?;
1133 WorkflowExecutionDescription::new(output.response, self.client.data_converter())
1134 .await
1135 .map_err(WorkflowInteractionError::from)
1136 }
1137 pub async fn fetch_history(
1139 &self,
1140 opts: WorkflowFetchHistoryOptions,
1141 ) -> Result<WorkflowHistory, WorkflowInteractionError>
1142 where
1143 CT: NamespacedClient,
1144 {
1145 let run_id = self.info.run_id.clone().unwrap_or_default();
1146 self.fetch_history_for_run(&run_id, &opts).await
1147 }
1148
1149 async fn fetch_history_for_run(
1151 &self,
1152 run_id: &str,
1153 opts: &WorkflowFetchHistoryOptions,
1154 ) -> Result<WorkflowHistory, WorkflowInteractionError>
1155 where
1156 CT: NamespacedClient,
1157 {
1158 let mut all_events = Vec::new();
1159 let mut next_page_token = vec![];
1160
1161 loop {
1162 let output = interceptors::call_fetch_workflow_history_page(
1163 self.client.client_interceptors(),
1164 FetchWorkflowHistoryPageInput {
1165 workflow_id: self.info.workflow_id.clone(),
1166 run_id: run_id.to_string(),
1167 next_page_token,
1168 options: opts.clone(),
1169 },
1170 Next::new({
1171 let mut client = self.client.clone();
1172 move |input: FetchWorkflowHistoryPageInput| -> BoxFuture<
1173 '_,
1174 Result<FetchWorkflowHistoryPageOutput, WorkflowInteractionError>,
1175 > {
1176 Box::pin(async move {
1177 let mut request = GetWorkflowExecutionHistoryRequest {
1178 namespace: client.namespace(),
1179 execution: Some(ProtoWorkflowExecution {
1180 workflow_id: input.workflow_id,
1181 run_id: input.run_id,
1182 }),
1183 next_page_token: input.next_page_token,
1184 skip_archival: input.options.skip_archival,
1185 wait_new_event: input.options.wait_new_event,
1186 history_event_filter_type: input.options.event_filter_type as i32,
1187 ..Default::default()
1188 }
1189 .into_request();
1190 input.options.rpc_options.apply_to(&mut request);
1191 let response = WorkflowService::get_workflow_execution_history(
1192 &mut client,
1193 request,
1194 )
1195 .await
1196 .map_err(WorkflowInteractionError::from_status)?
1197 .into_inner();
1198 Ok(FetchWorkflowHistoryPageOutput::new(
1199 response
1200 .history
1201 .map(|history| history.events)
1202 .unwrap_or_default(),
1203 response.next_page_token,
1204 ))
1205 })
1206 }
1207 }),
1208 )
1209 .await?;
1210
1211 all_events.extend(output.events);
1212 if output.next_page_token.is_empty() {
1213 break;
1214 }
1215 next_page_token = output.next_page_token;
1216 }
1217
1218 Ok(WorkflowHistory::new(
1219 all_events,
1220 Some(self.info.workflow_id.clone()),
1221 ))
1222 }
1223}
1224
1225pub struct WorkflowUpdateHandle<CT, T> {
1229 client: CT,
1230 update_id: String,
1231 workflow_id: String,
1232 run_id: Option<String>,
1233 known_outcome: Option<update::v1::Outcome>,
1235 _output: PhantomData<T>,
1236}
1237
1238impl<CT, T> WorkflowUpdateHandle<CT, T> {
1239 pub fn id(&self) -> &str {
1241 &self.update_id
1242 }
1243
1244 pub fn workflow_id(&self) -> &str {
1246 &self.workflow_id
1247 }
1248
1249 pub fn workflow_run_id(&self) -> Option<&str> {
1251 self.run_id.as_deref()
1252 }
1253}
1254
1255impl<CT, T: 'static> WorkflowUpdateHandle<CT, T>
1256where
1257 CT: WorkflowService + NamespacedClient + Clone,
1258{
1259 pub async fn get_result(&self, rpc_options: RpcOptions) -> Result<T, WorkflowUpdateError>
1261 where
1262 T: temporalio_common::data_converters::TemporalDeserializable,
1263 {
1264 let output = interceptors::call_poll_workflow_update(
1265 self.client.client_interceptors(),
1266 PollWorkflowUpdateInput {
1267 update_id: self.update_id.clone(),
1268 workflow_id: self.workflow_id.clone(),
1269 run_id: self.run_id.clone().unwrap_or_default(),
1270 rpc_options,
1271 },
1272 Next::new({
1273 let mut client = self.client.clone();
1274 let known_outcome = self.known_outcome.clone();
1275 move |input: PollWorkflowUpdateInput| -> BoxFuture<
1276 '_,
1277 Result<PollWorkflowUpdateOutput, WorkflowUpdateError>,
1278 > {
1279 Box::pin(async move {
1280 if let Some(outcome) = known_outcome {
1281 return Ok(PollWorkflowUpdateOutput::new(outcome));
1282 }
1283 loop {
1287 let mut request = PollWorkflowExecutionUpdateRequest {
1288 namespace: client.namespace(),
1289 update_ref: Some(update::v1::UpdateRef {
1290 workflow_execution: Some(ProtoWorkflowExecution {
1291 workflow_id: input.workflow_id.clone(),
1292 run_id: input.run_id.clone(),
1293 }),
1294 update_id: input.update_id.clone(),
1295 }),
1296 identity: client.identity(),
1297 wait_policy: Some(WaitPolicy {
1298 lifecycle_stage:
1299 UpdateWorkflowExecutionLifecycleStage::Completed.into(),
1300 }),
1301 }
1302 .into_request();
1303 input.rpc_options.apply_to(&mut request);
1304 let response = WorkflowService::poll_workflow_execution_update(
1305 &mut client,
1306 request,
1307 )
1308 .await
1309 .map_err(WorkflowUpdateError::from_status)?
1310 .into_inner();
1311 if let Some(outcome) = response.outcome {
1312 return Ok(PollWorkflowUpdateOutput::new(outcome));
1313 }
1314 }
1315 })
1316 }
1317 }),
1318 )
1319 .await?;
1320 let outcome = output.outcome;
1321
1322 match outcome.value {
1323 Some(update::v1::outcome::Value::Success(success)) => self
1324 .client
1325 .data_converter()
1326 .from_payloads(&SerializationContextData::Workflow, success.payloads)
1327 .await
1328 .map_err(WorkflowUpdateError::from),
1329 Some(update::v1::outcome::Value::Failure(failure)) => {
1330 Err(WorkflowUpdateError::Failed(Box::new(failure)))
1331 }
1332 None => Err(WorkflowUpdateError::Other(
1333 "Update returned no outcome value".into(),
1334 )),
1335 }
1336 }
1337}
1338
1339#[cfg(test)]
1340mod tests {
1341 use super::*;
1342 use crate::test_helpers::XorCodec;
1343 use std::collections::HashMap;
1344 use temporalio_common::{
1345 data_converters::DefaultFailureConverter,
1346 protos::temporal::api::{
1347 common::v1::{Memo, SearchAttributes},
1348 enums::v1::WorkflowExecutionStatus as ProtoWorkflowExecutionStatus,
1349 history::v1::WorkflowExecutionStartedEventAttributes,
1350 sdk::v1::UserMetadata,
1351 workflow::v1::WorkflowExecutionConfig,
1352 },
1353 };
1354
1355 #[test]
1356 fn workflow_history_workflow_id_roundtrips() {
1357 let event = HistoryEvent {
1358 event_id: 1,
1359 attributes: Some(Attributes::WorkflowExecutionStartedEventAttributes(
1360 WorkflowExecutionStartedEventAttributes {
1361 workflow_id: "workflow-id".to_owned(),
1362 original_execution_run_id: "run-id".to_owned(),
1363 ..Default::default()
1364 },
1365 )),
1366 ..Default::default()
1367 };
1368 let history = WorkflowHistory::new(vec![event], None);
1369
1370 let bytes = history.to_json().unwrap();
1371
1372 let decoded = WorkflowHistory::from_json(&bytes).unwrap();
1373 assert_eq!(decoded.workflow_id(), Some("workflow-id"));
1374 }
1375
1376 #[tokio::test]
1377 async fn workflow_result_details_support_typed_decoding() {
1378 let converter = DataConverter::new(
1379 PayloadConverter::default(),
1380 DefaultFailureConverter,
1381 XorCodec,
1382 );
1383 let payloads = converter
1384 .to_payloads(
1385 &SerializationContextData::Workflow,
1386 &"workflow-result-details".to_owned(),
1387 )
1388 .await
1389 .unwrap();
1390 let details = WorkflowResultDetails::new(payloads.clone(), &converter)
1391 .await
1392 .unwrap();
1393
1394 assert_ne!(details.raw(), payloads);
1395 let decoded_payloads = details.raw().to_vec();
1396 assert_eq!(
1397 details.deserialize::<String>().unwrap(),
1398 "workflow-result-details"
1399 );
1400 assert_eq!(details.into_raw().payloads, decoded_payloads);
1401 }
1402
1403 #[tokio::test]
1404 async fn workflow_result_detail_conversion_errors_are_reported() {
1405 let details =
1406 WorkflowResultDetails::new(vec![Payload::default()], &DataConverter::default())
1407 .await
1408 .unwrap();
1409
1410 assert_eq!(details.raw(), &[Payload::default()]);
1411 assert!(details.deserialize::<String>().is_err());
1412 }
1413
1414 #[tokio::test]
1415 async fn workflow_description_memo_uses_saved_converter() {
1416 let converter = DataConverter::new(
1417 PayloadConverter::default(),
1418 DefaultFailureConverter,
1419 XorCodec,
1420 );
1421 let encoded = converter
1422 .to_payload(
1423 &SerializationContextData::Workflow,
1424 &"memo-value".to_owned(),
1425 )
1426 .await
1427 .unwrap();
1428 let description = WorkflowExecutionDescription::new(
1429 DescribeWorkflowExecutionResponse {
1430 workflow_execution_info: Some(workflow::WorkflowExecutionInfo {
1431 memo: Some(Memo {
1432 fields: HashMap::from([("memo-key".to_owned(), encoded)]),
1433 }),
1434 ..Default::default()
1435 }),
1436 ..Default::default()
1437 },
1438 &converter,
1439 )
1440 .await
1441 .unwrap();
1442 let memo = description.memo();
1443
1444 assert_eq!(
1445 memo.get::<String>("memo-key").unwrap(),
1446 Some("memo-value".to_owned())
1447 );
1448 }
1449
1450 #[tokio::test]
1451 async fn workflow_description_accessors_expose_decoded_fields() {
1452 let converter = DataConverter::default();
1453 let memo_payload = converter
1454 .to_payload(&SerializationContextData::Workflow, &"memo-value")
1455 .await
1456 .unwrap();
1457 let search_attr_payload = converter
1458 .to_payload(&SerializationContextData::Workflow, &"search-value")
1459 .await
1460 .unwrap();
1461 let summary_payload = converter
1462 .to_payload(&SerializationContextData::Workflow, &"workflow summary")
1463 .await
1464 .unwrap();
1465 let details_payload = converter
1466 .to_payload(&SerializationContextData::Workflow, &"workflow details")
1467 .await
1468 .unwrap();
1469 let description = WorkflowExecutionDescription::new(
1470 DescribeWorkflowExecutionResponse {
1471 workflow_execution_info: Some(workflow::WorkflowExecutionInfo {
1472 execution: Some(ProtoWorkflowExecution {
1473 workflow_id: "wf-id".to_string(),
1474 run_id: "run-id".to_string(),
1475 }),
1476 r#type: Some(
1477 temporalio_common::protos::temporal::api::common::v1::WorkflowType {
1478 name: "wf-type".to_string(),
1479 },
1480 ),
1481 status: ProtoWorkflowExecutionStatus::Completed as i32,
1482 task_queue: "task-queue".to_string(),
1483 history_length: 42,
1484 memo: Some(Memo {
1485 fields: HashMap::from([("memo-key".to_string(), memo_payload.clone())]),
1486 }),
1487 parent_execution: Some(ProtoWorkflowExecution {
1488 workflow_id: "parent-id".to_string(),
1489 run_id: "parent-run-id".to_string(),
1490 }),
1491 search_attributes: Some(SearchAttributes {
1492 indexed_fields: HashMap::from([(
1493 "CustomKeywordField".to_string(),
1494 search_attr_payload.clone(),
1495 )]),
1496 }),
1497 ..Default::default()
1498 }),
1499 execution_config: Some(WorkflowExecutionConfig {
1500 user_metadata: Some(UserMetadata {
1501 summary: Some(summary_payload),
1502 details: Some(details_payload),
1503 }),
1504 ..Default::default()
1505 }),
1506 ..Default::default()
1507 },
1508 &converter,
1509 )
1510 .await
1511 .unwrap();
1512
1513 assert_eq!(description.id(), "wf-id");
1514 assert_eq!(description.run_id(), "run-id");
1515 assert_eq!(description.workflow_type(), "wf-type");
1516 assert_eq!(description.status(), WorkflowExecutionStatus::Completed);
1517 let mut unknown_status_description = description.clone();
1518 unknown_status_description
1519 .raw_description
1520 .workflow_execution_info
1521 .as_mut()
1522 .unwrap()
1523 .status = 123_456;
1524 assert_eq!(
1525 unknown_status_description.status(),
1526 WorkflowExecutionStatus::Unknown
1527 );
1528 assert_eq!(description.task_queue(), "task-queue");
1529 assert_eq!(description.history_length(), 42);
1530 assert_eq!(description.parent_id(), Some("parent-id"));
1531 assert_eq!(description.parent_run_id(), Some("parent-run-id"));
1532 let memo = description.memo();
1533 assert_eq!(memo.raw_value("memo-key"), Some(&memo_payload));
1534 assert_eq!(
1535 memo.get::<String>("memo-key").unwrap(),
1536 Some("memo-value".to_owned())
1537 );
1538 let search_attributes = description.search_attributes();
1539 assert_eq!(
1540 search_attributes.raw_payload("CustomKeywordField"),
1541 Some(&search_attr_payload)
1542 );
1543 assert_eq!(description.static_summary(), Some("workflow summary"));
1544 assert_eq!(description.static_details(), Some("workflow details"));
1545 }
1546
1547 #[tokio::test]
1548 async fn workflow_description_rejects_negative_history_length() {
1549 let err = WorkflowExecutionDescription::new(
1550 DescribeWorkflowExecutionResponse {
1551 workflow_execution_info: Some(workflow::WorkflowExecutionInfo {
1552 history_length: -1,
1553 ..Default::default()
1554 }),
1555 ..Default::default()
1556 },
1557 &DataConverter::default(),
1558 )
1559 .await
1560 .unwrap_err();
1561
1562 assert_eq!(
1563 err.to_string(),
1564 "Encoding error: workflow history_length must be non-negative, got -1"
1565 );
1566 }
1567}