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, WorkflowUpdateWaitStage,
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::{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}
340impl From<WorkflowHistory> for history::v1::History {
341 fn from(h: WorkflowHistory) -> Self {
342 Self { events: h.events }
343 }
344}
345
346impl WorkflowHistory {
347 fn new(events: Vec<HistoryEvent>) -> Self {
348 Self { events }
349 }
350
351 pub fn events(&self) -> &[HistoryEvent] {
353 &self.events
354 }
355
356 pub fn into_events(self) -> Vec<HistoryEvent> {
358 self.events
359 }
360}
361
362#[derive(Clone)]
365pub struct WorkflowHandle<ClientT, W> {
366 client: ClientT,
367 info: WorkflowExecutionInfo,
368
369 _wf_type: PhantomData<W>,
370}
371
372impl<CT, W> WorkflowHandle<CT, W> {
373 pub fn run_id(&self) -> Option<&str> {
375 self.info.run_id.as_deref()
376 }
377}
378
379#[derive(Debug, Clone)]
381pub struct WorkflowExecutionInfo {
382 pub namespace: String,
384 pub workflow_id: String,
386 pub run_id: Option<String>,
388 pub first_execution_run_id: Option<String>,
392}
393
394impl WorkflowExecutionInfo {
395 pub fn bind_untyped<CT>(self, client: CT) -> UntypedWorkflowHandle<CT>
397 where
398 CT: WorkflowService + Clone,
399 {
400 UntypedWorkflowHandle::new(client, self)
401 }
402}
403
404pub type UntypedWorkflowHandle<CT> = WorkflowHandle<CT, UntypedWorkflow>;
407
408pub struct UntypedSignal<W> {
412 name: String,
413 _wf: PhantomData<W>,
414}
415
416impl<W> UntypedSignal<W> {
417 pub fn new(name: impl Into<String>) -> Self {
419 Self {
420 name: name.into(),
421 _wf: PhantomData,
422 }
423 }
424}
425
426impl<W: WorkflowDefinition> SignalDefinition for UntypedSignal<W> {
427 type Workflow = W;
428 type Input = RawValue;
429
430 fn name(&self) -> &str {
431 &self.name
432 }
433}
434
435pub struct UntypedQuery<W> {
439 name: String,
440 _wf: PhantomData<W>,
441}
442
443impl<W> UntypedQuery<W> {
444 pub fn new(name: impl Into<String>) -> Self {
446 Self {
447 name: name.into(),
448 _wf: PhantomData,
449 }
450 }
451}
452
453impl<W: WorkflowDefinition> QueryDefinition for UntypedQuery<W> {
454 type Workflow = W;
455 type Input = RawValue;
456 type Output = RawValue;
457
458 fn name(&self) -> &str {
459 &self.name
460 }
461}
462
463pub struct UntypedUpdate<W> {
467 name: String,
468 _wf: PhantomData<W>,
469}
470
471impl<W> UntypedUpdate<W> {
472 pub fn new(name: impl Into<String>) -> Self {
474 Self {
475 name: name.into(),
476 _wf: PhantomData,
477 }
478 }
479}
480
481impl<W: WorkflowDefinition> UpdateDefinition for UntypedUpdate<W> {
482 type Workflow = W;
483 type Input = RawValue;
484 type Output = RawValue;
485
486 fn name(&self) -> &str {
487 &self.name
488 }
489}
490
491impl<CT, W> WorkflowHandle<CT, W>
492where
493 CT: WorkflowService + Clone,
494 W: HasWorkflowDefinition,
495{
496 pub fn new(client: CT, info: WorkflowExecutionInfo) -> Self {
498 Self {
499 client,
500 info,
501 _wf_type: PhantomData::<W>,
502 }
503 }
504
505 pub fn info(&self) -> &WorkflowExecutionInfo {
507 &self.info
508 }
509
510 pub fn client(&self) -> &CT {
512 &self.client
513 }
514
515 pub async fn get_result(
517 &self,
518 opts: WorkflowGetResultOptions,
519 ) -> Result<W::Output, WorkflowGetResultError>
520 where
521 CT: WorkflowService + NamespacedClient + Clone,
522 {
523 let raw = self.get_result_raw(opts).await?;
524 match raw {
525 WorkflowExecutionResult::Succeeded(v) => Ok(v),
526 WorkflowExecutionResult::Failed(f) => Err(WorkflowGetResultError::Failed(Box::new(f))),
527 WorkflowExecutionResult::Cancelled { details } => {
528 Err(WorkflowGetResultError::Cancelled { details })
529 }
530 WorkflowExecutionResult::Terminated { details } => {
531 Err(WorkflowGetResultError::Terminated { details })
532 }
533 WorkflowExecutionResult::TimedOut => Err(WorkflowGetResultError::TimedOut),
534 WorkflowExecutionResult::ContinuedAsNew => Err(WorkflowGetResultError::ContinuedAsNew),
535 }
536 }
537
538 async fn get_result_raw(
542 &self,
543 opts: WorkflowGetResultOptions,
544 ) -> Result<WorkflowExecutionResult<W::Output>, WorkflowInteractionError>
545 where
546 CT: WorkflowService + NamespacedClient + Clone,
547 {
548 let mut run_id = self.info.run_id.clone().unwrap_or_default();
549 let fetch_opts = WorkflowFetchHistoryOptions::builder()
550 .skip_archival(true)
551 .wait_new_event(true)
552 .event_filter_type(HistoryEventFilterType::CloseEvent)
553 .rpc_options(opts.rpc_options.clone())
554 .build();
555
556 loop {
557 let history = self.fetch_history_for_run(&run_id, &fetch_opts).await?;
558 let mut events = history.into_events();
559
560 if events.is_empty() {
561 continue;
562 }
563
564 let event_attrs = events.pop().and_then(|ev| ev.attributes);
565
566 macro_rules! follow {
567 ($attrs:ident) => {
568 if opts.follow_runs && $attrs.new_execution_run_id != "" {
569 run_id = $attrs.new_execution_run_id;
570 continue;
571 }
572 };
573 }
574
575 let dc = self.client.data_converter();
576
577 break match event_attrs {
578 Some(Attributes::WorkflowExecutionCompletedEventAttributes(attrs)) => {
579 follow!(attrs);
580 let payload = attrs
581 .result
582 .and_then(|p| p.payloads.into_iter().next())
583 .unwrap_or_default();
584 let result: W::Output = dc
585 .from_payload(&SerializationContextData::Workflow, payload)
586 .await?;
587 Ok(WorkflowExecutionResult::Succeeded(result))
588 }
589 Some(Attributes::WorkflowExecutionFailedEventAttributes(attrs)) => {
590 follow!(attrs);
591 let mut failure = attrs.failure.unwrap_or_default();
592 decode_payloads(
593 &mut failure,
594 dc.codec(),
595 &SerializationContextData::Workflow,
596 )
597 .await?;
598 let error = dc.failure_converter().to_error(
599 failure,
600 dc.payload_converter(),
601 &SerializationContextData::Workflow,
602 )?;
603 Ok(WorkflowExecutionResult::Failed(error))
604 }
605 Some(Attributes::WorkflowExecutionCanceledEventAttributes(attrs)) => {
606 Ok(WorkflowExecutionResult::Cancelled {
607 details: WorkflowResultDetails::new(Vec::from_payloads(attrs.details), dc)
608 .await?,
609 })
610 }
611 Some(Attributes::WorkflowExecutionTimedOutEventAttributes(attrs)) => {
612 follow!(attrs);
613 Ok(WorkflowExecutionResult::TimedOut)
614 }
615 Some(Attributes::WorkflowExecutionTerminatedEventAttributes(attrs)) => {
616 Ok(WorkflowExecutionResult::Terminated {
617 details: WorkflowResultDetails::new(Vec::from_payloads(attrs.details), dc)
618 .await?,
619 })
620 }
621 Some(Attributes::WorkflowExecutionContinuedAsNewEventAttributes(attrs)) => {
622 if opts.follow_runs {
623 if !attrs.new_execution_run_id.is_empty() {
624 run_id = attrs.new_execution_run_id;
625 continue;
626 } else {
627 return Err(WorkflowInteractionError::Other(
628 "New execution run id was empty in continue as new event!".into(),
629 ));
630 }
631 } else {
632 Ok(WorkflowExecutionResult::ContinuedAsNew)
633 }
634 }
635 o => Err(WorkflowInteractionError::Other(
636 format!(
637 "Server returned an event that didn't match the CloseEvent filter. \
638 This is either a server bug or a new event the SDK does not understand. \
639 Event details: {o:?}"
640 )
641 .into(),
642 )),
643 };
644 }
645 }
646
647 pub async fn signal<S>(
649 &self,
650 signal: S,
651 input: S::Input,
652 opts: WorkflowSignalOptions,
653 ) -> Result<(), WorkflowInteractionError>
654 where
655 CT: WorkflowService + NamespacedClient + Clone,
656 S: SignalDefinition<Workflow = W::Run>,
657 S::Input: Send,
658 {
659 interceptors::call_signal_workflow(
660 self.client.client_interceptors(),
661 SignalWorkflowInput::new(
662 self.info.workflow_id.clone(),
663 self.info.run_id.clone().unwrap_or_default(),
664 signal.name().to_string(),
665 input,
666 opts,
667 ),
668 Next::new({
669 let mut client = self.client.clone();
670 move |input: SignalWorkflowInput| -> BoxFuture<
671 '_,
672 Result<(), WorkflowInteractionError>,
673 > {
674 Box::pin(async move {
675 let (workflow_id, run_id, signal_name, args, options) =
676 input.into_parts();
677 let data_converter = client.data_converter().clone();
678 let unencoded_payloads = {
679 let payload_converter = data_converter.payload_converter();
680 let context = SerializationContext {
681 data: &SerializationContextData::Workflow,
682 converter: payload_converter,
683 };
684 args.serialize_payloads(&context)
685 };
686 drop(args);
687 let payloads = data_converter
688 .codec()
689 .encode(&SerializationContextData::Workflow, unencoded_payloads?)
690 .await?;
691 let mut request = SignalWorkflowExecutionRequest {
692 namespace: client.namespace(),
693 workflow_execution: Some(ProtoWorkflowExecution {
694 workflow_id,
695 run_id,
696 }),
697 signal_name,
698 input: Some(Payloads { payloads }),
699 identity: client.identity(),
700 request_id: options
701 .request_id
702 .unwrap_or_else(|| Uuid::new_v4().to_string()),
703 header: options.header,
704 ..Default::default()
705 }
706 .into_request();
707 options.rpc_options.apply_to(&mut request);
708 WorkflowService::signal_workflow_execution(&mut client, request)
709 .await
710 .map_err(WorkflowInteractionError::from_status)?;
711 Ok(())
712 })
713 }
714 }),
715 )
716 .await
717 }
718
719 pub async fn query<Q>(
721 &self,
722 query: Q,
723 input: Q::Input,
724 opts: WorkflowQueryOptions,
725 ) -> Result<Q::Output, WorkflowQueryError>
726 where
727 CT: WorkflowService + NamespacedClient + Clone,
728 Q: QueryDefinition<Workflow = W::Run>,
729 Q::Input: Send,
730 {
731 let output = interceptors::call_query_workflow(
732 self.client.client_interceptors(),
733 QueryWorkflowInput::new(
734 self.info.workflow_id.clone(),
735 self.info.run_id.clone().unwrap_or_default(),
736 query.name().to_string(),
737 input,
738 opts,
739 ),
740 Next::new({
741 let mut client = self.client.clone();
742 move |input: QueryWorkflowInput| -> BoxFuture<
743 '_,
744 Result<QueryWorkflowOutput, WorkflowQueryError>,
745 > {
746 Box::pin(async move {
747 let (workflow_id, run_id, query_name, args, options) = input.into_parts();
748 let data_converter = client.data_converter().clone();
749 let unencoded_payloads = {
750 let payload_converter = data_converter.payload_converter();
751 let context = SerializationContext {
752 data: &SerializationContextData::Workflow,
753 converter: payload_converter,
754 };
755 args.serialize_payloads(&context)
756 };
757 drop(args);
758 let payloads = data_converter
759 .codec()
760 .encode(&SerializationContextData::Workflow, unencoded_payloads?)
761 .await?;
762 let mut request = QueryWorkflowRequest {
763 namespace: client.namespace(),
764 execution: Some(ProtoWorkflowExecution {
765 workflow_id,
766 run_id,
767 }),
768 query: Some(WorkflowQuery {
769 query_type: query_name,
770 query_args: Some(Payloads { payloads }),
771 header: options.header,
772 }),
773 query_reject_condition: options
774 .reject_condition
775 .map(|condition| condition as i32)
776 .unwrap_or(1),
777 }
778 .into_request();
779 options.rpc_options.apply_to(&mut request);
780 let response = client
781 .query_workflow(request)
782 .await
783 .map_err(WorkflowQueryError::from_status)?
784 .into_inner();
785 Ok(QueryWorkflowOutput::new(response))
786 })
787 }
788 }),
789 )
790 .await?;
791 let response = output.response;
792
793 if let Some(rejected) = response.query_rejected {
794 return Err(WorkflowQueryError::Rejected {
795 status: (rejected.status != 0)
796 .then(|| WorkflowExecutionStatus::from_raw(rejected.status)),
797 });
798 }
799
800 let result_payloads = response
801 .query_result
802 .map(|p| p.payloads)
803 .unwrap_or_default();
804
805 self.client
806 .data_converter()
807 .from_payloads(&SerializationContextData::Workflow, result_payloads)
808 .await
809 .map_err(WorkflowQueryError::from)
810 }
811
812 pub async fn execute_update<U>(
814 &self,
815 update: U,
816 input: U::Input,
817 options: WorkflowExecuteUpdateOptions,
818 ) -> Result<U::Output, WorkflowUpdateError>
819 where
820 CT: WorkflowService + NamespacedClient + Clone,
821 U: UpdateDefinition<Workflow = W::Run>,
822 U::Input: Send,
823 U::Output: 'static,
824 {
825 let rpc_options = options.rpc_options.clone();
826 let handle = self
827 .start_update(
828 update,
829 input,
830 WorkflowStartUpdateOptions::builder()
831 .maybe_update_id(options.update_id)
832 .maybe_header(options.header)
833 .wait_for_stage(WorkflowUpdateWaitStage::Completed)
834 .rpc_options(rpc_options.clone())
835 .build(),
836 )
837 .await?;
838 handle.get_result(rpc_options).await
839 }
840
841 pub async fn start_update<U>(
844 &self,
845 update: U,
846 input: U::Input,
847 options: WorkflowStartUpdateOptions,
848 ) -> Result<WorkflowUpdateHandle<CT, U::Output>, WorkflowUpdateError>
849 where
850 CT: WorkflowService + NamespacedClient + Clone,
851 U: UpdateDefinition<Workflow = W::Run>,
852 U::Input: Send,
853 {
854 let output = interceptors::call_start_workflow_update(
855 self.client.client_interceptors(),
856 StartWorkflowUpdateInput::new(
857 self.info().workflow_id.clone(),
858 self.info().run_id.clone().unwrap_or_default(),
859 update.name().to_string(),
860 input,
861 options,
862 ),
863 Next::new({
864 let mut client = self.client.clone();
865 move |input: StartWorkflowUpdateInput| -> BoxFuture<
866 '_,
867 Result<StartWorkflowUpdateOutput, WorkflowUpdateError>,
868 > {
869 Box::pin(async move {
870 let (workflow_id, run_id, update_name, args, options) = input.into_parts();
871 let data_converter = client.data_converter().clone();
872 let unencoded_payloads = {
873 let payload_converter = data_converter.payload_converter();
874 let context = SerializationContext {
875 data: &SerializationContextData::Workflow,
876 converter: payload_converter,
877 };
878 args.serialize_payloads(&context)
879 };
880 drop(args);
881 let payloads = data_converter
882 .codec()
883 .encode(&SerializationContextData::Workflow, unencoded_payloads?)
884 .await?;
885 let lifecycle_stage = match options.wait_for_stage {
886 WorkflowUpdateWaitStage::Admitted => {
887 UpdateWorkflowExecutionLifecycleStage::Admitted
888 }
889 WorkflowUpdateWaitStage::Accepted => {
890 UpdateWorkflowExecutionLifecycleStage::Accepted
891 }
892 WorkflowUpdateWaitStage::Completed => {
893 UpdateWorkflowExecutionLifecycleStage::Completed
894 }
895 };
896 let update_id = options
897 .update_id
898 .unwrap_or_else(|| Uuid::new_v4().to_string());
899 let mut request = UpdateWorkflowExecutionRequest {
900 namespace: client.namespace(),
901 workflow_execution: Some(ProtoWorkflowExecution {
902 workflow_id: workflow_id.clone(),
903 run_id,
904 }),
905 wait_policy: Some(WaitPolicy {
906 lifecycle_stage: lifecycle_stage.into(),
907 }),
908 request: Some(update::v1::Request {
909 meta: Some(update::v1::Meta {
910 update_id: update_id.clone(),
911 identity: client.identity(),
912 }),
913 input: Some(update::v1::Input {
914 header: options.header,
915 name: update_name,
916 args: Some(Payloads { payloads }),
917 }),
918 ..Default::default()
919 }),
920 ..Default::default()
921 }
922 .into_request();
923 options.rpc_options.apply_to(&mut request);
924 let response = WorkflowService::update_workflow_execution(
925 &mut client,
926 request,
927 )
928 .await
929 .map_err(WorkflowUpdateError::from_status)?
930 .into_inner();
931 let run_id = response
932 .update_ref
933 .as_ref()
934 .and_then(|reference| reference.workflow_execution.as_ref())
935 .map(|execution| execution.run_id.clone())
936 .filter(|run_id| !run_id.is_empty());
937 Ok(StartWorkflowUpdateOutput::new(
938 update_id,
939 workflow_id,
940 run_id,
941 response.outcome,
942 ))
943 })
944 }
945 }),
946 )
947 .await?;
948
949 Ok(WorkflowUpdateHandle {
950 client: self.client.clone(),
951 update_id: output.update_id,
952 workflow_id: output.workflow_id,
953 run_id: output.run_id.or_else(|| self.info().run_id.clone()),
954 known_outcome: output.known_outcome,
955 _output: PhantomData,
956 })
957 }
958
959 pub async fn cancel(&self, opts: WorkflowCancelOptions) -> Result<(), WorkflowInteractionError>
961 where
962 CT: NamespacedClient,
963 {
964 interceptors::call_cancel_workflow(
965 self.client.client_interceptors(),
966 CancelWorkflowInput {
967 workflow_id: self.info.workflow_id.clone(),
968 run_id: self.info.run_id.clone().unwrap_or_default(),
969 first_execution_run_id: self
970 .info
971 .first_execution_run_id
972 .clone()
973 .unwrap_or_default(),
974 options: opts,
975 },
976 Next::new({
977 let mut client = self.client.clone();
978 move |input: CancelWorkflowInput| -> BoxFuture<
979 '_,
980 Result<(), WorkflowInteractionError>,
981 > {
982 Box::pin(async move {
983 let mut request = RequestCancelWorkflowExecutionRequest {
984 namespace: client.namespace(),
985 workflow_execution: Some(ProtoWorkflowExecution {
986 workflow_id: input.workflow_id,
987 run_id: input.run_id,
988 }),
989 identity: client.identity(),
990 request_id: input
991 .options
992 .request_id
993 .clone()
994 .unwrap_or_else(|| Uuid::new_v4().to_string()),
995 first_execution_run_id: input.first_execution_run_id,
996 reason: input.options.reason.clone(),
997 links: vec![],
998 }
999 .into_request();
1000 input.options.rpc_options.apply_to(&mut request);
1001 WorkflowService::request_cancel_workflow_execution(&mut client, request)
1002 .await
1003 .map_err(WorkflowInteractionError::from_status)?;
1004 Ok(())
1005 })
1006 }
1007 }),
1008 )
1009 .await
1010 }
1011
1012 pub async fn terminate(
1014 &self,
1015 opts: WorkflowTerminateOptions,
1016 ) -> Result<(), WorkflowInteractionError>
1017 where
1018 CT: NamespacedClient,
1019 {
1020 interceptors::call_terminate_workflow(
1021 self.client.client_interceptors(),
1022 TerminateWorkflowInput {
1023 workflow_id: self.info.workflow_id.clone(),
1024 run_id: self.info.run_id.clone().unwrap_or_default(),
1025 first_execution_run_id: self
1026 .info
1027 .first_execution_run_id
1028 .clone()
1029 .unwrap_or_default(),
1030 options: opts,
1031 },
1032 Next::new({
1033 let mut client = self.client.clone();
1034 move |input: TerminateWorkflowInput| -> BoxFuture<
1035 '_,
1036 Result<(), WorkflowInteractionError>,
1037 > {
1038 Box::pin(async move {
1039 let mut request = TerminateWorkflowExecutionRequest {
1040 namespace: client.namespace(),
1041 workflow_execution: Some(ProtoWorkflowExecution {
1042 workflow_id: input.workflow_id,
1043 run_id: input.run_id,
1044 }),
1045 reason: input.options.reason.clone(),
1046 details: input.options.details.clone(),
1047 identity: client.identity(),
1048 first_execution_run_id: input.first_execution_run_id,
1049 links: vec![],
1050 }
1051 .into_request();
1052 input.options.rpc_options.apply_to(&mut request);
1053 WorkflowService::terminate_workflow_execution(&mut client, request)
1054 .await
1055 .map_err(WorkflowInteractionError::from_status)?;
1056 Ok(())
1057 })
1058 }
1059 }),
1060 )
1061 .await
1062 }
1063
1064 pub async fn describe(
1066 &self,
1067 opts: WorkflowDescribeOptions,
1068 ) -> Result<WorkflowExecutionDescription, WorkflowInteractionError>
1069 where
1070 CT: NamespacedClient,
1071 {
1072 let output = interceptors::call_describe_workflow(
1073 self.client.client_interceptors(),
1074 DescribeWorkflowInput {
1075 workflow_id: self.info.workflow_id.clone(),
1076 run_id: self.info.run_id.clone().unwrap_or_default(),
1077 options: opts,
1078 },
1079 Next::new({
1080 let mut client = self.client.clone();
1081 move |input: DescribeWorkflowInput| -> BoxFuture<
1082 '_,
1083 Result<DescribeWorkflowOutput, WorkflowInteractionError>,
1084 > {
1085 Box::pin(async move {
1086 let mut request = DescribeWorkflowExecutionRequest {
1087 namespace: client.namespace(),
1088 execution: Some(ProtoWorkflowExecution {
1089 workflow_id: input.workflow_id,
1090 run_id: input.run_id,
1091 }),
1092 }
1093 .into_request();
1094 input.options.rpc_options.apply_to(&mut request);
1095 let response =
1096 WorkflowService::describe_workflow_execution(&mut client, request)
1097 .await
1098 .map_err(WorkflowInteractionError::from_status)?
1099 .into_inner();
1100 Ok(DescribeWorkflowOutput::new(response))
1101 })
1102 }
1103 }),
1104 )
1105 .await?;
1106 WorkflowExecutionDescription::new(output.response, self.client.data_converter())
1107 .await
1108 .map_err(WorkflowInteractionError::from)
1109 }
1110 pub async fn fetch_history(
1112 &self,
1113 opts: WorkflowFetchHistoryOptions,
1114 ) -> Result<WorkflowHistory, WorkflowInteractionError>
1115 where
1116 CT: NamespacedClient,
1117 {
1118 let run_id = self.info.run_id.clone().unwrap_or_default();
1119 self.fetch_history_for_run(&run_id, &opts).await
1120 }
1121
1122 async fn fetch_history_for_run(
1124 &self,
1125 run_id: &str,
1126 opts: &WorkflowFetchHistoryOptions,
1127 ) -> Result<WorkflowHistory, WorkflowInteractionError>
1128 where
1129 CT: NamespacedClient,
1130 {
1131 let mut all_events = Vec::new();
1132 let mut next_page_token = vec![];
1133
1134 loop {
1135 let output = interceptors::call_fetch_workflow_history_page(
1136 self.client.client_interceptors(),
1137 FetchWorkflowHistoryPageInput {
1138 workflow_id: self.info.workflow_id.clone(),
1139 run_id: run_id.to_string(),
1140 next_page_token,
1141 options: opts.clone(),
1142 },
1143 Next::new({
1144 let mut client = self.client.clone();
1145 move |input: FetchWorkflowHistoryPageInput| -> BoxFuture<
1146 '_,
1147 Result<FetchWorkflowHistoryPageOutput, WorkflowInteractionError>,
1148 > {
1149 Box::pin(async move {
1150 let mut request = GetWorkflowExecutionHistoryRequest {
1151 namespace: client.namespace(),
1152 execution: Some(ProtoWorkflowExecution {
1153 workflow_id: input.workflow_id,
1154 run_id: input.run_id,
1155 }),
1156 next_page_token: input.next_page_token,
1157 skip_archival: input.options.skip_archival,
1158 wait_new_event: input.options.wait_new_event,
1159 history_event_filter_type: input.options.event_filter_type as i32,
1160 ..Default::default()
1161 }
1162 .into_request();
1163 input.options.rpc_options.apply_to(&mut request);
1164 let response = WorkflowService::get_workflow_execution_history(
1165 &mut client,
1166 request,
1167 )
1168 .await
1169 .map_err(WorkflowInteractionError::from_status)?
1170 .into_inner();
1171 Ok(FetchWorkflowHistoryPageOutput::new(
1172 response
1173 .history
1174 .map(|history| history.events)
1175 .unwrap_or_default(),
1176 response.next_page_token,
1177 ))
1178 })
1179 }
1180 }),
1181 )
1182 .await?;
1183
1184 all_events.extend(output.events);
1185 if output.next_page_token.is_empty() {
1186 break;
1187 }
1188 next_page_token = output.next_page_token;
1189 }
1190
1191 Ok(WorkflowHistory::new(all_events))
1192 }
1193}
1194
1195pub struct WorkflowUpdateHandle<CT, T> {
1199 client: CT,
1200 update_id: String,
1201 workflow_id: String,
1202 run_id: Option<String>,
1203 known_outcome: Option<update::v1::Outcome>,
1205 _output: PhantomData<T>,
1206}
1207
1208impl<CT, T> WorkflowUpdateHandle<CT, T> {
1209 pub fn id(&self) -> &str {
1211 &self.update_id
1212 }
1213
1214 pub fn workflow_id(&self) -> &str {
1216 &self.workflow_id
1217 }
1218
1219 pub fn workflow_run_id(&self) -> Option<&str> {
1221 self.run_id.as_deref()
1222 }
1223}
1224
1225impl<CT, T: 'static> WorkflowUpdateHandle<CT, T>
1226where
1227 CT: WorkflowService + NamespacedClient + Clone,
1228{
1229 pub async fn get_result(&self, rpc_options: RpcOptions) -> Result<T, WorkflowUpdateError>
1231 where
1232 T: temporalio_common::data_converters::TemporalDeserializable,
1233 {
1234 let output = interceptors::call_poll_workflow_update(
1235 self.client.client_interceptors(),
1236 PollWorkflowUpdateInput {
1237 update_id: self.update_id.clone(),
1238 workflow_id: self.workflow_id.clone(),
1239 run_id: self.run_id.clone().unwrap_or_default(),
1240 rpc_options,
1241 },
1242 Next::new({
1243 let mut client = self.client.clone();
1244 let known_outcome = self.known_outcome.clone();
1245 move |input: PollWorkflowUpdateInput| -> BoxFuture<
1246 '_,
1247 Result<PollWorkflowUpdateOutput, WorkflowUpdateError>,
1248 > {
1249 Box::pin(async move {
1250 if let Some(outcome) = known_outcome {
1251 return Ok(PollWorkflowUpdateOutput::new(outcome));
1252 }
1253 loop {
1257 let mut request = PollWorkflowExecutionUpdateRequest {
1258 namespace: client.namespace(),
1259 update_ref: Some(update::v1::UpdateRef {
1260 workflow_execution: Some(ProtoWorkflowExecution {
1261 workflow_id: input.workflow_id.clone(),
1262 run_id: input.run_id.clone(),
1263 }),
1264 update_id: input.update_id.clone(),
1265 }),
1266 identity: client.identity(),
1267 wait_policy: Some(WaitPolicy {
1268 lifecycle_stage:
1269 UpdateWorkflowExecutionLifecycleStage::Completed.into(),
1270 }),
1271 }
1272 .into_request();
1273 input.rpc_options.apply_to(&mut request);
1274 let response = WorkflowService::poll_workflow_execution_update(
1275 &mut client,
1276 request,
1277 )
1278 .await
1279 .map_err(WorkflowUpdateError::from_status)?
1280 .into_inner();
1281 if let Some(outcome) = response.outcome {
1282 return Ok(PollWorkflowUpdateOutput::new(outcome));
1283 }
1284 }
1285 })
1286 }
1287 }),
1288 )
1289 .await?;
1290 let outcome = output.outcome;
1291
1292 match outcome.value {
1293 Some(update::v1::outcome::Value::Success(success)) => self
1294 .client
1295 .data_converter()
1296 .from_payloads(&SerializationContextData::Workflow, success.payloads)
1297 .await
1298 .map_err(WorkflowUpdateError::from),
1299 Some(update::v1::outcome::Value::Failure(failure)) => {
1300 Err(WorkflowUpdateError::Failed(Box::new(failure)))
1301 }
1302 None => Err(WorkflowUpdateError::Other(
1303 "Update returned no outcome value".into(),
1304 )),
1305 }
1306 }
1307}
1308
1309#[cfg(test)]
1310mod tests {
1311 use super::*;
1312 use crate::test_helpers::XorCodec;
1313 use std::collections::HashMap;
1314 use temporalio_common::{
1315 data_converters::DefaultFailureConverter,
1316 protos::temporal::api::{
1317 common::v1::{Memo, SearchAttributes},
1318 enums::v1::WorkflowExecutionStatus as ProtoWorkflowExecutionStatus,
1319 sdk::v1::UserMetadata,
1320 workflow::v1::WorkflowExecutionConfig,
1321 },
1322 };
1323
1324 #[tokio::test]
1325 async fn workflow_result_details_support_typed_decoding() {
1326 let converter = DataConverter::new(
1327 PayloadConverter::default(),
1328 DefaultFailureConverter,
1329 XorCodec,
1330 );
1331 let payloads = converter
1332 .to_payloads(
1333 &SerializationContextData::Workflow,
1334 &"workflow-result-details".to_owned(),
1335 )
1336 .await
1337 .unwrap();
1338 let details = WorkflowResultDetails::new(payloads.clone(), &converter)
1339 .await
1340 .unwrap();
1341
1342 assert_ne!(details.raw(), payloads);
1343 let decoded_payloads = details.raw().to_vec();
1344 assert_eq!(
1345 details.deserialize::<String>().unwrap(),
1346 "workflow-result-details"
1347 );
1348 assert_eq!(details.into_raw().payloads, decoded_payloads);
1349 }
1350
1351 #[tokio::test]
1352 async fn workflow_result_detail_conversion_errors_are_reported() {
1353 let details =
1354 WorkflowResultDetails::new(vec![Payload::default()], &DataConverter::default())
1355 .await
1356 .unwrap();
1357
1358 assert_eq!(details.raw(), &[Payload::default()]);
1359 assert!(details.deserialize::<String>().is_err());
1360 }
1361
1362 #[tokio::test]
1363 async fn workflow_description_memo_uses_saved_converter() {
1364 let converter = DataConverter::new(
1365 PayloadConverter::default(),
1366 DefaultFailureConverter,
1367 XorCodec,
1368 );
1369 let encoded = converter
1370 .to_payload(
1371 &SerializationContextData::Workflow,
1372 &"memo-value".to_owned(),
1373 )
1374 .await
1375 .unwrap();
1376 let description = WorkflowExecutionDescription::new(
1377 DescribeWorkflowExecutionResponse {
1378 workflow_execution_info: Some(workflow::WorkflowExecutionInfo {
1379 memo: Some(Memo {
1380 fields: HashMap::from([("memo-key".to_owned(), encoded)]),
1381 }),
1382 ..Default::default()
1383 }),
1384 ..Default::default()
1385 },
1386 &converter,
1387 )
1388 .await
1389 .unwrap();
1390 let memo = description.memo();
1391
1392 assert_eq!(
1393 memo.get::<String>("memo-key").unwrap(),
1394 Some("memo-value".to_owned())
1395 );
1396 }
1397
1398 #[tokio::test]
1399 async fn workflow_description_accessors_expose_decoded_fields() {
1400 let converter = DataConverter::default();
1401 let memo_payload = converter
1402 .to_payload(&SerializationContextData::Workflow, &"memo-value")
1403 .await
1404 .unwrap();
1405 let search_attr_payload = converter
1406 .to_payload(&SerializationContextData::Workflow, &"search-value")
1407 .await
1408 .unwrap();
1409 let summary_payload = converter
1410 .to_payload(&SerializationContextData::Workflow, &"workflow summary")
1411 .await
1412 .unwrap();
1413 let details_payload = converter
1414 .to_payload(&SerializationContextData::Workflow, &"workflow details")
1415 .await
1416 .unwrap();
1417 let description = WorkflowExecutionDescription::new(
1418 DescribeWorkflowExecutionResponse {
1419 workflow_execution_info: Some(workflow::WorkflowExecutionInfo {
1420 execution: Some(ProtoWorkflowExecution {
1421 workflow_id: "wf-id".to_string(),
1422 run_id: "run-id".to_string(),
1423 }),
1424 r#type: Some(
1425 temporalio_common::protos::temporal::api::common::v1::WorkflowType {
1426 name: "wf-type".to_string(),
1427 },
1428 ),
1429 status: ProtoWorkflowExecutionStatus::Completed as i32,
1430 task_queue: "task-queue".to_string(),
1431 history_length: 42,
1432 memo: Some(Memo {
1433 fields: HashMap::from([("memo-key".to_string(), memo_payload.clone())]),
1434 }),
1435 parent_execution: Some(ProtoWorkflowExecution {
1436 workflow_id: "parent-id".to_string(),
1437 run_id: "parent-run-id".to_string(),
1438 }),
1439 search_attributes: Some(SearchAttributes {
1440 indexed_fields: HashMap::from([(
1441 "CustomKeywordField".to_string(),
1442 search_attr_payload.clone(),
1443 )]),
1444 }),
1445 ..Default::default()
1446 }),
1447 execution_config: Some(WorkflowExecutionConfig {
1448 user_metadata: Some(UserMetadata {
1449 summary: Some(summary_payload),
1450 details: Some(details_payload),
1451 }),
1452 ..Default::default()
1453 }),
1454 ..Default::default()
1455 },
1456 &converter,
1457 )
1458 .await
1459 .unwrap();
1460
1461 assert_eq!(description.id(), "wf-id");
1462 assert_eq!(description.run_id(), "run-id");
1463 assert_eq!(description.workflow_type(), "wf-type");
1464 assert_eq!(description.status(), WorkflowExecutionStatus::Completed);
1465 let mut unknown_status_description = description.clone();
1466 unknown_status_description
1467 .raw_description
1468 .workflow_execution_info
1469 .as_mut()
1470 .unwrap()
1471 .status = 123_456;
1472 assert_eq!(
1473 unknown_status_description.status(),
1474 WorkflowExecutionStatus::Unknown
1475 );
1476 assert_eq!(description.task_queue(), "task-queue");
1477 assert_eq!(description.history_length(), 42);
1478 assert_eq!(description.parent_id(), Some("parent-id"));
1479 assert_eq!(description.parent_run_id(), Some("parent-run-id"));
1480 let memo = description.memo();
1481 assert_eq!(memo.raw_value("memo-key"), Some(&memo_payload));
1482 assert_eq!(
1483 memo.get::<String>("memo-key").unwrap(),
1484 Some("memo-value".to_owned())
1485 );
1486 let search_attributes = description.search_attributes();
1487 assert_eq!(
1488 search_attributes.raw_payload("CustomKeywordField"),
1489 Some(&search_attr_payload)
1490 );
1491 assert_eq!(description.static_summary(), Some("workflow summary"));
1492 assert_eq!(description.static_details(), Some("workflow details"));
1493 }
1494
1495 #[tokio::test]
1496 async fn workflow_description_rejects_negative_history_length() {
1497 let err = WorkflowExecutionDescription::new(
1498 DescribeWorkflowExecutionResponse {
1499 workflow_execution_info: Some(workflow::WorkflowExecutionInfo {
1500 history_length: -1,
1501 ..Default::default()
1502 }),
1503 ..Default::default()
1504 },
1505 &DataConverter::default(),
1506 )
1507 .await
1508 .unwrap_err();
1509
1510 assert_eq!(
1511 err.to_string(),
1512 "Encoding error: workflow history_length must be non-negative, got -1"
1513 );
1514 }
1515}