1use crate::{
4 CompleteAsyncActivityInput, FailAsyncActivityInput, HeartbeatAsyncActivityInput,
5 NamespacedClient, Next, ReportAsyncActivityCancellationInput, RpcOptions, TemporalClientValue,
6 errors::AsyncActivityError, grpc::WorkflowService, interceptors,
7};
8use futures_util::future::BoxFuture;
9use temporalio_common::{
10 data_converters::{
11 ActivitySerializationContext, SerializationContext, SerializationContextData,
12 TemporalSerializable,
13 },
14 error::{ApplicationFailure, OutgoingActivityError, OutgoingError},
15 payload_visitor::encode_payloads,
16 protos::{
17 TaskToken,
18 temporal::api::{
19 common::v1::Payloads,
20 workflowservice::v1::{
21 RecordActivityTaskHeartbeatByIdRequest, RecordActivityTaskHeartbeatByIdResponse,
22 RecordActivityTaskHeartbeatRequest, RecordActivityTaskHeartbeatResponse,
23 RespondActivityTaskCanceledByIdRequest, RespondActivityTaskCanceledRequest,
24 RespondActivityTaskCompletedByIdRequest, RespondActivityTaskCompletedRequest,
25 RespondActivityTaskFailedByIdRequest, RespondActivityTaskFailedRequest,
26 },
27 },
28 },
29};
30use tonic::IntoRequest;
31
32async fn encode_optional_value(
33 value: Option<Box<dyn TemporalClientValue>>,
34 data_converter: &temporalio_common::data_converters::DataConverter,
35) -> Result<Option<Payloads>, AsyncActivityError> {
36 let Some(value) = value else {
37 return Ok(None);
38 };
39 let unencoded_payloads = {
40 let payload_converter = data_converter.payload_converter();
41 let context_data = SerializationContextData::Activity(ActivitySerializationContext::new());
42 let context = SerializationContext::new(&context_data, payload_converter);
43 value.serialize_payloads(&context)?
44 };
45 drop(value);
46 let payloads = data_converter
47 .codec()
48 .encode(
49 &SerializationContextData::Activity(ActivitySerializationContext::new()),
50 unencoded_payloads,
51 )
52 .await?;
53 Ok(Some(Payloads { payloads }))
54}
55
56#[derive(Debug, Clone)]
58pub enum ActivityIdentifier {
59 TaskToken(TaskToken),
61 ByIdWorkflow {
63 workflow_id: String,
65 run_id: String,
67 activity_id: String,
69 },
70 ByIdStandalone {
72 activity_id: String,
74 run_id: String,
76 },
77}
78
79impl ActivityIdentifier {
80 pub fn from_task_token(token: TaskToken) -> Self {
82 Self::TaskToken(token)
83 }
84
85 pub fn by_id_workflow(
88 workflow_id: impl Into<String>,
89 run_id: impl Into<String>,
90 activity_id: impl Into<String>,
91 ) -> Self {
92 Self::ByIdWorkflow {
93 workflow_id: workflow_id.into(),
94 run_id: run_id.into(),
95 activity_id: activity_id.into(),
96 }
97 }
98
99 pub fn by_id_standalone(activity_id: impl Into<String>, run_id: impl Into<String>) -> Self {
102 Self::ByIdStandalone {
103 activity_id: activity_id.into(),
104 run_id: run_id.into(),
105 }
106 }
107
108 fn into_parts(self) -> Option<(String, String, String)> {
110 match self {
111 Self::TaskToken(_) => None,
112 Self::ByIdWorkflow {
113 workflow_id,
114 run_id,
115 activity_id,
116 } => Some((workflow_id, run_id, activity_id)),
117 Self::ByIdStandalone {
118 activity_id,
119 run_id,
120 } => Some((String::new(), run_id, activity_id)),
121 }
122 }
123}
124
125pub struct AsyncActivityHandle<CT> {
127 client: CT,
128 identifier: ActivityIdentifier,
129}
130
131impl<CT> AsyncActivityHandle<CT> {
132 pub fn new(client: CT, identifier: ActivityIdentifier) -> Self {
134 Self { client, identifier }
135 }
136
137 pub fn identifier(&self) -> &ActivityIdentifier {
139 &self.identifier
140 }
141
142 pub fn client(&self) -> &CT {
144 &self.client
145 }
146}
147
148impl<CT: WorkflowService + NamespacedClient + Clone> AsyncActivityHandle<CT> {
149 pub async fn complete<T>(
151 &self,
152 result: Option<T>,
153 rpc_options: RpcOptions,
154 ) -> Result<(), AsyncActivityError>
155 where
156 T: TemporalSerializable + Send + 'static,
157 {
158 interceptors::call_complete_async_activity(
159 self.client.client_interceptors(),
160 CompleteAsyncActivityInput::new(self.identifier.clone(), result, rpc_options),
161 Next::new({
162 let mut client = self.client.clone();
163 move |input: CompleteAsyncActivityInput| -> BoxFuture<
164 '_,
165 Result<(), AsyncActivityError>,
166 > {
167 Box::pin(async move {
168 let (identifier, result, rpc_options) = input.into_parts();
169 let result = encode_optional_value(result, client.data_converter()).await?;
170 if let ActivityIdentifier::TaskToken(token) = identifier {
171 let mut request = RespondActivityTaskCompletedRequest {
172 task_token: token.into_inner(),
173 result,
174 identity: client.identity(),
175 namespace: client.namespace(),
176 ..Default::default()
177 }
178 .into_request();
179 rpc_options.apply_to(&mut request);
180 WorkflowService::respond_activity_task_completed(
181 &mut client,
182 request,
183 )
184 .await
185 .map_err(AsyncActivityError::from_status)?;
186 } else {
187 let (workflow_id, run_id, activity_id) = identifier.into_parts().unwrap();
188 let mut request = RespondActivityTaskCompletedByIdRequest {
189 namespace: client.namespace(),
190 workflow_id,
191 run_id,
192 activity_id,
193 result,
194 identity: client.identity(),
195 resource_id: Default::default(),
196 }
197 .into_request();
198 rpc_options.apply_to(&mut request);
199 WorkflowService::respond_activity_task_completed_by_id(
200 &mut client,
201 request,
202 )
203 .await
204 .map_err(AsyncActivityError::from_status)?;
205 }
206 Ok(())
207 })
208 }
209 }),
210 )
211 .await
212 }
213
214 pub async fn fail<E, T>(
216 &self,
217 failure: E,
218 last_heartbeat_details: Option<T>,
219 rpc_options: RpcOptions,
220 ) -> Result<(), AsyncActivityError>
221 where
222 E: Into<ApplicationFailure>,
223 T: TemporalSerializable + Send + 'static,
224 {
225 interceptors::call_fail_async_activity(
226 self.client.client_interceptors(),
227 FailAsyncActivityInput::new(
228 self.identifier.clone(),
229 failure.into(),
230 last_heartbeat_details,
231 rpc_options,
232 ),
233 Next::new({
234 let mut client = self.client.clone();
235 move |input: FailAsyncActivityInput| -> BoxFuture<
236 '_,
237 Result<(), AsyncActivityError>,
238 > {
239 Box::pin(async move {
240 let (identifier, application_failure, details, rpc_options) =
241 input.into_parts();
242 let data_converter = client.data_converter().clone();
243 let mut failure = data_converter.to_failure(
244 &SerializationContextData::Activity(ActivitySerializationContext::new()),
245 OutgoingError::Activity(OutgoingActivityError::Application(Box::new(
246 application_failure,
247 ))),
248 );
249 encode_payloads(
250 &mut failure,
251 data_converter.codec(),
252 &SerializationContextData::Activity(ActivitySerializationContext::new()),
253 )
254 .await?;
255 let last_heartbeat_details =
256 encode_optional_value(details, &data_converter).await?;
257 if let ActivityIdentifier::TaskToken(token) = identifier {
258 let mut request = RespondActivityTaskFailedRequest {
259 task_token: token.into_inner(),
260 failure: Some(failure),
261 identity: client.identity(),
262 namespace: client.namespace(),
263 last_heartbeat_details,
264 ..Default::default()
265 }
266 .into_request();
267 rpc_options.apply_to(&mut request);
268 WorkflowService::respond_activity_task_failed(
269 &mut client,
270 request,
271 )
272 .await
273 .map_err(AsyncActivityError::from_status)?;
274 } else {
275 let (workflow_id, run_id, activity_id) = identifier.into_parts().unwrap();
276 let mut request = RespondActivityTaskFailedByIdRequest {
277 namespace: client.namespace(),
278 workflow_id,
279 run_id,
280 activity_id,
281 failure: Some(failure),
282 identity: client.identity(),
283 last_heartbeat_details,
284 resource_id: Default::default(),
285 ..Default::default()
286 }
287 .into_request();
288 rpc_options.apply_to(&mut request);
289 WorkflowService::respond_activity_task_failed_by_id(
290 &mut client,
291 request,
292 )
293 .await
294 .map_err(AsyncActivityError::from_status)?;
295 }
296 Ok(())
297 })
298 }
299 }),
300 )
301 .await
302 }
303
304 pub async fn report_cancelation<T>(
306 &self,
307 details: Option<T>,
308 rpc_options: RpcOptions,
309 ) -> Result<(), AsyncActivityError>
310 where
311 T: TemporalSerializable + Send + 'static,
312 {
313 interceptors::call_report_async_activity_cancellation(
314 self.client.client_interceptors(),
315 ReportAsyncActivityCancellationInput::new(
316 self.identifier.clone(),
317 details,
318 rpc_options,
319 ),
320 Next::new({
321 let mut client = self.client.clone();
322 move |input: ReportAsyncActivityCancellationInput| -> BoxFuture<
323 '_,
324 Result<(), AsyncActivityError>,
325 > {
326 Box::pin(async move {
327 let (identifier, details, rpc_options) = input.into_parts();
328 let details = encode_optional_value(details, client.data_converter()).await?;
329 if let ActivityIdentifier::TaskToken(token) = identifier {
330 let mut request = RespondActivityTaskCanceledRequest {
331 task_token: token.into_inner(),
332 details,
333 identity: client.identity(),
334 namespace: client.namespace(),
335 ..Default::default()
336 }
337 .into_request();
338 rpc_options.apply_to(&mut request);
339 WorkflowService::respond_activity_task_canceled(
340 &mut client,
341 request,
342 )
343 .await
344 .map_err(AsyncActivityError::from_status)?;
345 } else {
346 let (workflow_id, run_id, activity_id) = identifier.into_parts().unwrap();
347 let mut request = RespondActivityTaskCanceledByIdRequest {
348 namespace: client.namespace(),
349 workflow_id,
350 run_id,
351 activity_id,
352 details,
353 identity: client.identity(),
354 ..Default::default()
355 }
356 .into_request();
357 rpc_options.apply_to(&mut request);
358 WorkflowService::respond_activity_task_canceled_by_id(
359 &mut client,
360 request,
361 )
362 .await
363 .map_err(AsyncActivityError::from_status)?;
364 }
365 Ok(())
366 })
367 }
368 }),
369 )
370 .await
371 }
372
373 pub async fn heartbeat<T>(
378 &self,
379 details: Option<T>,
380 rpc_options: RpcOptions,
381 ) -> Result<ActivityHeartbeatResponse, AsyncActivityError>
382 where
383 T: TemporalSerializable + Send + 'static,
384 {
385 interceptors::call_heartbeat_async_activity(
386 self.client.client_interceptors(),
387 HeartbeatAsyncActivityInput::new(self.identifier.clone(), details, rpc_options),
388 Next::new({
389 let mut client = self.client.clone();
390 move |input: HeartbeatAsyncActivityInput| -> BoxFuture<
391 '_,
392 Result<ActivityHeartbeatResponse, AsyncActivityError>,
393 > {
394 Box::pin(async move {
395 let (identifier, details, rpc_options) = input.into_parts();
396 let details = encode_optional_value(details, client.data_converter()).await?;
397 if let ActivityIdentifier::TaskToken(token) = identifier {
398 let mut request = RecordActivityTaskHeartbeatRequest {
399 task_token: token.into_inner(),
400 details,
401 identity: client.identity(),
402 namespace: client.namespace(),
403 resource_id: Default::default(),
404 }
405 .into_request();
406 rpc_options.apply_to(&mut request);
407 let response = WorkflowService::record_activity_task_heartbeat(
408 &mut client,
409 request,
410 )
411 .await
412 .map_err(AsyncActivityError::from_status)?
413 .into_inner();
414 Ok(ActivityHeartbeatResponse::from(response))
415 } else {
416 let (workflow_id, run_id, activity_id) = identifier.into_parts().unwrap();
417 let mut request = RecordActivityTaskHeartbeatByIdRequest {
418 namespace: client.namespace(),
419 workflow_id,
420 run_id,
421 activity_id,
422 details,
423 identity: client.identity(),
424 resource_id: Default::default(),
425 }
426 .into_request();
427 rpc_options.apply_to(&mut request);
428 let response =
429 WorkflowService::record_activity_task_heartbeat_by_id(
430 &mut client,
431 request,
432 )
433 .await
434 .map_err(AsyncActivityError::from_status)?
435 .into_inner();
436 Ok(ActivityHeartbeatResponse::from(response))
437 }
438 })
439 }
440 }),
441 )
442 .await
443 }
444}
445
446#[derive(Debug, Clone)]
448#[non_exhaustive]
449pub struct ActivityHeartbeatResponse {
450 pub cancel_requested: bool,
452 pub activity_paused: bool,
454 pub activity_reset: bool,
456}
457
458impl From<RecordActivityTaskHeartbeatResponse> for ActivityHeartbeatResponse {
459 fn from(resp: RecordActivityTaskHeartbeatResponse) -> Self {
460 Self {
461 cancel_requested: resp.cancel_requested,
462 activity_paused: resp.activity_paused,
463 activity_reset: resp.activity_reset,
464 }
465 }
466}
467
468impl From<RecordActivityTaskHeartbeatByIdResponse> for ActivityHeartbeatResponse {
469 fn from(resp: RecordActivityTaskHeartbeatByIdResponse) -> Self {
470 Self {
471 cancel_requested: resp.cancel_requested,
472 activity_paused: resp.activity_paused,
473 activity_reset: resp.activity_reset,
474 }
475 }
476}