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 }
286 .into_request();
287 rpc_options.apply_to(&mut request);
288 WorkflowService::respond_activity_task_failed_by_id(
289 &mut client,
290 request,
291 )
292 .await
293 .map_err(AsyncActivityError::from_status)?;
294 }
295 Ok(())
296 })
297 }
298 }),
299 )
300 .await
301 }
302
303 pub async fn report_cancelation<T>(
305 &self,
306 details: Option<T>,
307 rpc_options: RpcOptions,
308 ) -> Result<(), AsyncActivityError>
309 where
310 T: TemporalSerializable + Send + 'static,
311 {
312 interceptors::call_report_async_activity_cancellation(
313 self.client.client_interceptors(),
314 ReportAsyncActivityCancellationInput::new(
315 self.identifier.clone(),
316 details,
317 rpc_options,
318 ),
319 Next::new({
320 let mut client = self.client.clone();
321 move |input: ReportAsyncActivityCancellationInput| -> BoxFuture<
322 '_,
323 Result<(), AsyncActivityError>,
324 > {
325 Box::pin(async move {
326 let (identifier, details, rpc_options) = input.into_parts();
327 let details = encode_optional_value(details, client.data_converter()).await?;
328 if let ActivityIdentifier::TaskToken(token) = identifier {
329 let mut request = RespondActivityTaskCanceledRequest {
330 task_token: token.into_inner(),
331 details,
332 identity: client.identity(),
333 namespace: client.namespace(),
334 ..Default::default()
335 }
336 .into_request();
337 rpc_options.apply_to(&mut request);
338 WorkflowService::respond_activity_task_canceled(
339 &mut client,
340 request,
341 )
342 .await
343 .map_err(AsyncActivityError::from_status)?;
344 } else {
345 let (workflow_id, run_id, activity_id) = identifier.into_parts().unwrap();
346 let mut request = RespondActivityTaskCanceledByIdRequest {
347 namespace: client.namespace(),
348 workflow_id,
349 run_id,
350 activity_id,
351 details,
352 identity: client.identity(),
353 ..Default::default()
354 }
355 .into_request();
356 rpc_options.apply_to(&mut request);
357 WorkflowService::respond_activity_task_canceled_by_id(
358 &mut client,
359 request,
360 )
361 .await
362 .map_err(AsyncActivityError::from_status)?;
363 }
364 Ok(())
365 })
366 }
367 }),
368 )
369 .await
370 }
371
372 pub async fn heartbeat<T>(
377 &self,
378 details: Option<T>,
379 rpc_options: RpcOptions,
380 ) -> Result<ActivityHeartbeatResponse, AsyncActivityError>
381 where
382 T: TemporalSerializable + Send + 'static,
383 {
384 interceptors::call_heartbeat_async_activity(
385 self.client.client_interceptors(),
386 HeartbeatAsyncActivityInput::new(self.identifier.clone(), details, rpc_options),
387 Next::new({
388 let mut client = self.client.clone();
389 move |input: HeartbeatAsyncActivityInput| -> BoxFuture<
390 '_,
391 Result<ActivityHeartbeatResponse, AsyncActivityError>,
392 > {
393 Box::pin(async move {
394 let (identifier, details, rpc_options) = input.into_parts();
395 let details = encode_optional_value(details, client.data_converter()).await?;
396 if let ActivityIdentifier::TaskToken(token) = identifier {
397 let mut request = RecordActivityTaskHeartbeatRequest {
398 task_token: token.into_inner(),
399 details,
400 identity: client.identity(),
401 namespace: client.namespace(),
402 resource_id: Default::default(),
403 }
404 .into_request();
405 rpc_options.apply_to(&mut request);
406 let response = WorkflowService::record_activity_task_heartbeat(
407 &mut client,
408 request,
409 )
410 .await
411 .map_err(AsyncActivityError::from_status)?
412 .into_inner();
413 Ok(ActivityHeartbeatResponse::from(response))
414 } else {
415 let (workflow_id, run_id, activity_id) = identifier.into_parts().unwrap();
416 let mut request = RecordActivityTaskHeartbeatByIdRequest {
417 namespace: client.namespace(),
418 workflow_id,
419 run_id,
420 activity_id,
421 details,
422 identity: client.identity(),
423 resource_id: Default::default(),
424 }
425 .into_request();
426 rpc_options.apply_to(&mut request);
427 let response =
428 WorkflowService::record_activity_task_heartbeat_by_id(
429 &mut client,
430 request,
431 )
432 .await
433 .map_err(AsyncActivityError::from_status)?
434 .into_inner();
435 Ok(ActivityHeartbeatResponse::from(response))
436 }
437 })
438 }
439 }),
440 )
441 .await
442 }
443}
444
445#[derive(Debug, Clone)]
447#[non_exhaustive]
448pub struct ActivityHeartbeatResponse {
449 pub cancel_requested: bool,
451 pub activity_paused: bool,
453 pub activity_reset: bool,
455}
456
457impl From<RecordActivityTaskHeartbeatResponse> for ActivityHeartbeatResponse {
458 fn from(resp: RecordActivityTaskHeartbeatResponse) -> Self {
459 Self {
460 cancel_requested: resp.cancel_requested,
461 activity_paused: resp.activity_paused,
462 activity_reset: resp.activity_reset,
463 }
464 }
465}
466
467impl From<RecordActivityTaskHeartbeatByIdResponse> for ActivityHeartbeatResponse {
468 fn from(resp: RecordActivityTaskHeartbeatByIdResponse) -> Self {
469 Self {
470 cancel_requested: resp.cancel_requested,
471 activity_paused: resp.activity_paused,
472 activity_reset: resp.activity_reset,
473 }
474 }
475}