@temporalio/core-bridge 1.1.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/Cargo.lock +765 -128
  2. package/Cargo.toml +2 -2
  3. package/common.js +7 -3
  4. package/index.d.ts +118 -5
  5. package/index.js +2 -6
  6. package/package.json +2 -3
  7. package/releases/aarch64-apple-darwin/index.node +0 -0
  8. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  9. package/releases/x86_64-apple-darwin/index.node +0 -0
  10. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  11. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  12. package/scripts/build.js +4 -3
  13. package/sdk-core/.buildkite/docker/Dockerfile +2 -1
  14. package/sdk-core/.buildkite/pipeline.yml +2 -0
  15. package/sdk-core/.cargo/config.toml +1 -1
  16. package/sdk-core/ARCHITECTURE.md +2 -2
  17. package/sdk-core/README.md +12 -0
  18. package/sdk-core/bridge-ffi/Cargo.toml +2 -2
  19. package/sdk-core/bridge-ffi/src/lib.rs +2 -2
  20. package/sdk-core/client/Cargo.toml +7 -5
  21. package/sdk-core/client/src/lib.rs +354 -226
  22. package/sdk-core/client/src/metrics.rs +13 -11
  23. package/sdk-core/client/src/raw.rs +352 -107
  24. package/sdk-core/client/src/retry.rs +188 -147
  25. package/sdk-core/client/src/workflow_handle/mod.rs +1 -1
  26. package/sdk-core/core/Cargo.toml +28 -15
  27. package/sdk-core/core/src/core_tests/activity_tasks.rs +98 -33
  28. package/sdk-core/core/src/core_tests/child_workflows.rs +125 -3
  29. package/sdk-core/core/src/core_tests/local_activities.rs +6 -6
  30. package/sdk-core/core/src/core_tests/workers.rs +3 -2
  31. package/sdk-core/core/src/core_tests/workflow_tasks.rs +70 -2
  32. package/sdk-core/core/src/ephemeral_server/mod.rs +515 -0
  33. package/sdk-core/core/src/lib.rs +62 -28
  34. package/sdk-core/core/src/pollers/mod.rs +2 -0
  35. package/sdk-core/core/src/pollers/poll_buffer.rs +4 -4
  36. package/sdk-core/core/src/replay/mod.rs +3 -3
  37. package/sdk-core/core/src/retry_logic.rs +10 -9
  38. package/sdk-core/core/src/telemetry/metrics.rs +48 -39
  39. package/sdk-core/core/src/telemetry/mod.rs +46 -12
  40. package/sdk-core/core/src/telemetry/prometheus_server.rs +17 -13
  41. package/sdk-core/core/src/test_help/mod.rs +18 -8
  42. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +10 -10
  43. package/sdk-core/core/src/worker/activities/local_activities.rs +13 -13
  44. package/sdk-core/core/src/worker/activities.rs +6 -12
  45. package/sdk-core/core/src/worker/client/mocks.rs +1 -0
  46. package/sdk-core/core/src/worker/client.rs +193 -64
  47. package/sdk-core/core/src/worker/mod.rs +14 -19
  48. package/sdk-core/core/src/worker/workflow/driven_workflow.rs +3 -0
  49. package/sdk-core/core/src/worker/workflow/history_update.rs +5 -5
  50. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +133 -85
  51. package/sdk-core/core/src/worker/workflow/machines/mod.rs +3 -2
  52. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +160 -105
  53. package/sdk-core/core/src/worker/workflow/managed_run.rs +2 -1
  54. package/sdk-core/core/src/worker/workflow/mod.rs +62 -58
  55. package/sdk-core/core/src/worker/workflow/run_cache.rs +5 -3
  56. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +7 -5
  57. package/sdk-core/core-api/Cargo.toml +3 -3
  58. package/sdk-core/core-api/src/errors.rs +3 -11
  59. package/sdk-core/core-api/src/worker.rs +7 -0
  60. package/sdk-core/protos/api_upstream/.buildkite/Dockerfile +1 -1
  61. package/sdk-core/protos/api_upstream/.github/CODEOWNERS +1 -1
  62. package/sdk-core/protos/api_upstream/.github/PULL_REQUEST_TEMPLATE.md +2 -6
  63. package/sdk-core/protos/api_upstream/.github/workflows/trigger-api-go-update.yml +29 -0
  64. package/sdk-core/protos/api_upstream/Makefile +2 -2
  65. package/sdk-core/protos/api_upstream/buf.yaml +1 -0
  66. package/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +86 -0
  67. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +26 -0
  68. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +46 -0
  69. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +7 -0
  70. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +14 -0
  71. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +51 -0
  72. package/sdk-core/protos/api_upstream/temporal/api/failure/v1/message.proto +18 -0
  73. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +57 -1
  74. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +1 -3
  75. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +4 -2
  76. package/sdk-core/protos/api_upstream/temporal/api/replication/v1/message.proto +11 -0
  77. package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +23 -0
  78. package/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +46 -0
  79. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +1 -0
  80. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +172 -0
  81. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +30 -0
  82. package/sdk-core/protos/grpc/health/v1/health.proto +63 -0
  83. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +18 -15
  84. package/sdk-core/protos/testsrv_upstream/Makefile +80 -0
  85. package/sdk-core/protos/testsrv_upstream/api-linter.yaml +38 -0
  86. package/sdk-core/protos/testsrv_upstream/buf.yaml +13 -0
  87. package/sdk-core/protos/testsrv_upstream/dependencies/gogoproto/gogo.proto +141 -0
  88. package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/request_response.proto +63 -0
  89. package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/service.proto +90 -0
  90. package/sdk-core/sdk/Cargo.toml +2 -2
  91. package/sdk-core/sdk/src/lib.rs +2 -2
  92. package/sdk-core/sdk/src/workflow_context/options.rs +36 -8
  93. package/sdk-core/sdk/src/workflow_context.rs +30 -6
  94. package/sdk-core/sdk/src/workflow_future.rs +4 -4
  95. package/sdk-core/sdk-core-protos/Cargo.toml +5 -5
  96. package/sdk-core/sdk-core-protos/build.rs +9 -1
  97. package/sdk-core/sdk-core-protos/src/history_builder.rs +6 -1
  98. package/sdk-core/sdk-core-protos/src/lib.rs +93 -32
  99. package/sdk-core/test-utils/Cargo.toml +3 -3
  100. package/sdk-core/test-utils/src/canned_histories.rs +58 -0
  101. package/sdk-core/test-utils/src/lib.rs +35 -12
  102. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +128 -0
  103. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +55 -5
  104. package/sdk-core/tests/integ_tests/polling_tests.rs +2 -1
  105. package/sdk-core/tests/integ_tests/queries_tests.rs +5 -5
  106. package/sdk-core/tests/integ_tests/visibility_tests.rs +93 -0
  107. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +93 -10
  108. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +1 -1
  109. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +14 -14
  110. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +2 -6
  111. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +12 -12
  112. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +12 -1
  113. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +3 -3
  114. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +8 -2
  115. package/sdk-core/tests/integ_tests/workflow_tests.rs +19 -4
  116. package/sdk-core/tests/load_tests.rs +2 -1
  117. package/sdk-core/tests/main.rs +17 -0
  118. package/sdk-core/tests/runner.rs +93 -0
  119. package/src/conversions.rs +157 -94
  120. package/src/helpers.rs +190 -0
  121. package/src/lib.rs +10 -912
  122. package/src/runtime.rs +436 -0
  123. package/src/testing.rs +67 -0
  124. package/src/worker.rs +465 -0
@@ -13,17 +13,25 @@ mod retry;
13
13
  mod workflow_handle;
14
14
 
15
15
  pub use crate::retry::{CallType, RetryClient, RETRYABLE_ERROR_CODES};
16
- pub use raw::WorkflowService;
16
+ pub use raw::{HealthService, OperatorService, TestService, WorkflowService};
17
+ pub use temporal_sdk_core_protos::temporal::api::{
18
+ filter::v1::{StartTimeFilter, StatusFilter, WorkflowExecutionFilter, WorkflowTypeFilter},
19
+ workflowservice::v1::{
20
+ list_closed_workflow_executions_request::Filters as ListClosedFilters,
21
+ list_open_workflow_executions_request::Filters as ListOpenFilters,
22
+ },
23
+ };
17
24
  pub use workflow_handle::{WorkflowExecutionInfo, WorkflowExecutionResult};
18
25
 
19
26
  use crate::{
20
27
  metrics::{GrpcMetricSvc, MetricsContext},
21
28
  raw::{sealed::RawClientLike, AttachMetricLabels},
22
- sealed::{RawClientLikeUser, WfHandleClient},
29
+ sealed::WfHandleClient,
23
30
  workflow_handle::UntypedWorkflowHandle,
24
31
  };
25
- use backoff::{ExponentialBackoff, SystemClock};
32
+ use backoff::{exponential, ExponentialBackoff, SystemClock};
26
33
  use http::uri::InvalidUri;
34
+ use once_cell::sync::OnceCell;
27
35
  use opentelemetry::metrics::Meter;
28
36
  use parking_lot::RwLock;
29
37
  use std::{
@@ -36,18 +44,23 @@ use std::{
36
44
  };
37
45
  use temporal_sdk_core_protos::{
38
46
  coresdk::{workflow_commands::QueryResult, IntoPayloadsExt},
47
+ grpc::health::v1::health_client::HealthClient,
39
48
  temporal::api::{
40
49
  command::v1::Command,
41
- common::v1::{Payload, Payloads, WorkflowExecution, WorkflowType},
42
- enums::v1::{TaskQueueKind, WorkflowTaskFailedCause},
50
+ common::v1::{Header, Payload, Payloads, WorkflowExecution, WorkflowType},
51
+ enums::v1::{TaskQueueKind, WorkflowIdReusePolicy, WorkflowTaskFailedCause},
43
52
  failure::v1::Failure,
44
- query::v1::{WorkflowQuery, WorkflowQueryResult},
45
- taskqueue::v1::{StickyExecutionAttributes, TaskQueue, TaskQueueMetadata},
53
+ operatorservice::v1::operator_service_client::OperatorServiceClient,
54
+ query::v1::WorkflowQuery,
55
+ taskqueue::v1::{StickyExecutionAttributes, TaskQueue},
56
+ testservice::v1::test_service_client::TestServiceClient,
46
57
  workflowservice::v1::{workflow_service_client::WorkflowServiceClient, *},
47
58
  },
48
59
  TaskToken,
49
60
  };
50
61
  use tonic::{
62
+ body::BoxBody,
63
+ client::GrpcService,
51
64
  codegen::InterceptedService,
52
65
  metadata::{MetadataKey, MetadataValue},
53
66
  service::Interceptor,
@@ -58,6 +71,9 @@ use tower::ServiceBuilder;
58
71
  use url::Url;
59
72
  use uuid::Uuid;
60
73
 
74
+ static CLIENT_NAME_HEADER_KEY: &str = "client-name";
75
+ static CLIENT_VERSION_HEADER_KEY: &str = "client-version";
76
+ /// These must match the gRPC method names, not the snake case versions that exist in the Rust code.
61
77
  static LONG_POLL_METHOD_NAMES: [&str; 2] = ["PollWorkflowTaskQueue", "PollActivityTaskQueue"];
62
78
  /// The server times out polls after 60 seconds. Set our timeout to be slightly beyond that.
63
79
  const LONG_POLL_TIMEOUT: Duration = Duration::from_secs(70);
@@ -164,20 +180,24 @@ impl RetryConfig {
164
180
  max_retries: 0,
165
181
  }
166
182
  }
183
+
184
+ pub(crate) fn into_exp_backoff<C>(self, clock: C) -> exponential::ExponentialBackoff<C> {
185
+ exponential::ExponentialBackoff {
186
+ current_interval: self.initial_interval,
187
+ initial_interval: self.initial_interval,
188
+ randomization_factor: self.randomization_factor,
189
+ multiplier: self.multiplier,
190
+ max_interval: self.max_interval,
191
+ max_elapsed_time: self.max_elapsed_time,
192
+ clock,
193
+ start_time: Instant::now(),
194
+ }
195
+ }
167
196
  }
168
197
 
169
198
  impl From<RetryConfig> for ExponentialBackoff {
170
199
  fn from(c: RetryConfig) -> Self {
171
- Self {
172
- current_interval: c.initial_interval,
173
- initial_interval: c.initial_interval,
174
- randomization_factor: c.randomization_factor,
175
- multiplier: c.multiplier,
176
- max_interval: c.max_interval,
177
- max_elapsed_time: c.max_elapsed_time,
178
- clock: SystemClock::default(),
179
- start_time: Instant::now(),
180
- }
200
+ c.into_exp_backoff(SystemClock::default())
181
201
  }
182
202
  }
183
203
 
@@ -203,51 +223,12 @@ pub enum ClientInitError {
203
223
  SystemInfoCallError(tonic::Status),
204
224
  }
205
225
 
206
- #[doc(hidden)]
207
- /// Allows passing different kinds of clients into things that want to be flexible. Motivating
208
- /// use-case was worker initialization.
209
- ///
210
- /// Needs to exist in this crate to avoid blanket impl conflicts.
211
- pub enum AnyClient {
212
- /// A high level client, like the type workers work with
213
- HighLevel(Arc<dyn WorkflowClientTrait + Send + Sync>),
214
- /// A low level gRPC client, wrapped with the typical interceptors
215
- LowLevel(Box<ConfiguredClient<WorkflowServiceClientWithMetrics>>),
216
- }
217
-
218
- impl<SGA> From<SGA> for AnyClient
219
- where
220
- SGA: WorkflowClientTrait + Send + Sync + 'static,
221
- {
222
- fn from(s: SGA) -> Self {
223
- Self::HighLevel(Arc::new(s))
224
- }
225
- }
226
- impl<SGA> From<Arc<SGA>> for AnyClient
227
- where
228
- SGA: WorkflowClientTrait + Send + Sync + 'static,
229
- {
230
- fn from(s: Arc<SGA>) -> Self {
231
- Self::HighLevel(s)
232
- }
233
- }
234
- impl From<RetryClient<ConfiguredClient<WorkflowServiceClientWithMetrics>>> for AnyClient {
235
- fn from(c: RetryClient<ConfiguredClient<WorkflowServiceClientWithMetrics>>) -> Self {
236
- Self::LowLevel(Box::new(c.into_inner()))
237
- }
238
- }
239
- impl From<ConfiguredClient<WorkflowServiceClientWithMetrics>> for AnyClient {
240
- fn from(c: ConfiguredClient<WorkflowServiceClientWithMetrics>) -> Self {
241
- Self::LowLevel(Box::new(c))
242
- }
243
- }
244
-
245
226
  /// A client with [ClientOptions] attached, which can be passed to initialize workers,
246
- /// or can be used directly.
227
+ /// or can be used directly. Is cheap to clone.
247
228
  #[derive(Clone, Debug)]
248
229
  pub struct ConfiguredClient<C> {
249
230
  client: C,
250
- options: ClientOptions,
231
+ options: Arc<ClientOptions>,
251
232
  headers: Arc<RwLock<HashMap<String, String>>>,
252
233
  /// Capabilities as read from the `get_system_info` RPC call made on client connection
253
234
  capabilities: Option<get_system_info_response::Capabilities>,
@@ -270,11 +251,6 @@ impl<C> ConfiguredClient<C> {
270
251
  pub fn capabilities(&self) -> Option<&get_system_info_response::Capabilities> {
271
252
  self.capabilities.as_ref()
272
253
  }
273
-
274
- /// De-constitute this type
275
- pub fn into_parts(self) -> (C, ClientOptions) {
276
- (self.client, self.options)
277
- }
278
254
  }
279
255
 
280
256
  // The configured client is effectively a "smart" (dumb) pointer
@@ -317,7 +293,7 @@ impl ClientOptions {
317
293
  &self,
318
294
  metrics_meter: Option<&Meter>,
319
295
  headers: Option<Arc<RwLock<HashMap<String, String>>>>,
320
- ) -> Result<RetryClient<ConfiguredClient<WorkflowServiceClientWithMetrics>>, ClientInitError>
296
+ ) -> Result<RetryClient<ConfiguredClient<TemporalServiceClientWithMetrics>>, ClientInitError>
321
297
  {
322
298
  let channel = Channel::from_shared(self.target_url.to_string())?;
323
299
  let channel = self.add_tls_to_channel(channel).await?;
@@ -333,11 +309,12 @@ impl ClientOptions {
333
309
  opts: self.clone(),
334
310
  headers: headers.clone(),
335
311
  };
312
+ let svc = InterceptedService::new(service, interceptor);
336
313
 
337
314
  let mut client = ConfiguredClient {
338
315
  headers,
339
- client: WorkflowServiceClient::with_interceptor(service, interceptor),
340
- options: self.clone(),
316
+ client: TemporalServiceClient::new(svc),
317
+ options: Arc::new(self.clone()),
341
318
  capabilities: None,
342
319
  };
343
320
  match client
@@ -416,27 +393,35 @@ impl Interceptor for ServiceCallInterceptor {
416
393
  /// cancel the request and have that status returned to the caller.
417
394
  fn call(&mut self, mut request: tonic::Request<()>) -> Result<tonic::Request<()>, Status> {
418
395
  let metadata = request.metadata_mut();
419
- metadata.insert(
420
- "client-name",
421
- self.opts
422
- .client_name
423
- .parse()
424
- .unwrap_or_else(|_| MetadataValue::from_static("")),
425
- );
426
- metadata.insert(
427
- "client-version",
428
- self.opts
429
- .client_version
430
- .parse()
431
- .unwrap_or_else(|_| MetadataValue::from_static("")),
432
- );
396
+ if !metadata.contains_key(CLIENT_NAME_HEADER_KEY) {
397
+ metadata.insert(
398
+ CLIENT_NAME_HEADER_KEY,
399
+ self.opts
400
+ .client_name
401
+ .parse()
402
+ .unwrap_or_else(|_| MetadataValue::from_static("")),
403
+ );
404
+ }
405
+ if !metadata.contains_key(CLIENT_VERSION_HEADER_KEY) {
406
+ metadata.insert(
407
+ CLIENT_VERSION_HEADER_KEY,
408
+ self.opts
409
+ .client_version
410
+ .parse()
411
+ .unwrap_or_else(|_| MetadataValue::from_static("")),
412
+ );
413
+ }
433
414
  let headers = &*self.headers.read();
434
415
  for (k, v) in headers {
435
- if let (Ok(k), Ok(v)) = (MetadataKey::from_str(k), MetadataValue::from_str(v)) {
416
+ if metadata.contains_key(k) {
417
+ // Don't overwrite per-request specified headers
418
+ continue;
419
+ }
420
+ if let (Ok(k), Ok(v)) = (MetadataKey::from_str(k), v.parse()) {
436
421
  metadata.insert(k, v);
437
422
  }
438
423
  }
439
- if metadata.get("grpc-timeout").is_none() {
424
+ if !metadata.contains_key("grpc-timeout") {
440
425
  request.set_timeout(OTHER_CALL_TIMEOUT);
441
426
  }
442
427
 
@@ -444,15 +429,88 @@ impl Interceptor for ServiceCallInterceptor {
444
429
  }
445
430
  }
446
431
 
432
+ /// Aggregates various services exposed by the Temporal server
433
+ #[derive(Debug, Clone)]
434
+ pub struct TemporalServiceClient<T> {
435
+ svc: T,
436
+ workflow_svc_client: OnceCell<WorkflowServiceClient<T>>,
437
+ operator_svc_client: OnceCell<OperatorServiceClient<T>>,
438
+ test_svc_client: OnceCell<TestServiceClient<T>>,
439
+ health_svc_client: OnceCell<HealthClient<T>>,
440
+ }
441
+ impl<T> TemporalServiceClient<T>
442
+ where
443
+ T: Clone,
444
+ T: GrpcService<BoxBody> + Send + Clone + 'static,
445
+ T::ResponseBody: tonic::codegen::Body<Data = tonic::codegen::Bytes> + Send + 'static,
446
+ T::Error: Into<tonic::codegen::StdError>,
447
+ <T::ResponseBody as tonic::codegen::Body>::Error: Into<tonic::codegen::StdError> + Send,
448
+ {
449
+ fn new(svc: T) -> Self {
450
+ Self {
451
+ svc,
452
+ workflow_svc_client: OnceCell::new(),
453
+ operator_svc_client: OnceCell::new(),
454
+ test_svc_client: OnceCell::new(),
455
+ health_svc_client: OnceCell::new(),
456
+ }
457
+ }
458
+ /// Get the underlying workflow service client
459
+ pub fn workflow_svc(&self) -> &WorkflowServiceClient<T> {
460
+ self.workflow_svc_client
461
+ .get_or_init(|| WorkflowServiceClient::new(self.svc.clone()))
462
+ }
463
+ /// Get the underlying operator service client
464
+ pub fn operator_svc(&self) -> &OperatorServiceClient<T> {
465
+ self.operator_svc_client
466
+ .get_or_init(|| OperatorServiceClient::new(self.svc.clone()))
467
+ }
468
+ /// Get the underlying test service client
469
+ pub fn test_svc(&self) -> &TestServiceClient<T> {
470
+ self.test_svc_client
471
+ .get_or_init(|| TestServiceClient::new(self.svc.clone()))
472
+ }
473
+ /// Get the underlying health service client
474
+ pub fn health_svc(&self) -> &HealthClient<T> {
475
+ self.health_svc_client
476
+ .get_or_init(|| HealthClient::new(self.svc.clone()))
477
+ }
478
+ /// Get the underlying workflow service client mutably
479
+ pub fn workflow_svc_mut(&mut self) -> &mut WorkflowServiceClient<T> {
480
+ let _ = self.workflow_svc();
481
+ self.workflow_svc_client.get_mut().unwrap()
482
+ }
483
+ /// Get the underlying operator service client mutably
484
+ pub fn operator_svc_mut(&mut self) -> &mut OperatorServiceClient<T> {
485
+ let _ = self.operator_svc();
486
+ self.operator_svc_client.get_mut().unwrap()
487
+ }
488
+ /// Get the underlying test service client mutably
489
+ pub fn test_svc_mut(&mut self) -> &mut TestServiceClient<T> {
490
+ let _ = self.test_svc();
491
+ self.test_svc_client.get_mut().unwrap()
492
+ }
493
+ /// Get the underlying health service client mutably
494
+ pub fn health_svc_mut(&mut self) -> &mut HealthClient<T> {
495
+ let _ = self.health_svc();
496
+ self.health_svc_client.get_mut().unwrap()
497
+ }
498
+ }
447
499
  /// A [WorkflowServiceClient] with the default interceptors attached.
448
500
  pub type WorkflowServiceClientWithMetrics = WorkflowServiceClient<InterceptedMetricsSvc>;
501
+ /// An [OperatorServiceClient] with the default interceptors attached.
502
+ pub type OperatorServiceClientWithMetrics = OperatorServiceClient<InterceptedMetricsSvc>;
503
+ /// An [TestServiceClient] with the default interceptors attached.
504
+ pub type TestServiceClientWithMetrics = TestServiceClient<InterceptedMetricsSvc>;
505
+ /// A [TemporalServiceClient] with the default interceptors attached.
506
+ pub type TemporalServiceClientWithMetrics = TemporalServiceClient<InterceptedMetricsSvc>;
449
507
  type InterceptedMetricsSvc = InterceptedService<GrpcMetricSvc, ServiceCallInterceptor>;
450
508
 
451
509
  /// Contains an instance of a namespace-bound client for interacting with the Temporal server
452
510
  #[derive(Debug, Clone)]
453
511
  pub struct Client {
454
512
  /// Client for interacting with workflow service
455
- inner: ConfiguredClient<WorkflowServiceClientWithMetrics>,
513
+ inner: ConfiguredClient<TemporalServiceClientWithMetrics>,
456
514
  /// The namespace this client interacts with
457
515
  namespace: String,
458
516
  /// If set, attach as the worker build id to relevant calls
@@ -462,7 +520,7 @@ pub struct Client {
462
520
  impl Client {
463
521
  /// Create a new client from an existing configured lower level client and a namespace
464
522
  pub fn new(
465
- client: ConfiguredClient<WorkflowServiceClientWithMetrics>,
523
+ client: ConfiguredClient<TemporalServiceClientWithMetrics>,
466
524
  namespace: String,
467
525
  ) -> Self {
468
526
  Client {
@@ -489,7 +547,7 @@ impl Client {
489
547
  /// Note that it is reasonably cheap to clone the returned type if you need to own it. Such
490
548
  /// clones will keep re-using the same channel.
491
549
  pub fn raw_client(&self) -> &WorkflowServiceClientWithMetrics {
492
- self.inner.deref()
550
+ self.inner.workflow_svc()
493
551
  }
494
552
 
495
553
  /// Return the options this client was initialized with
@@ -499,7 +557,7 @@ impl Client {
499
557
 
500
558
  /// Return the options this client was initialized with mutably
501
559
  pub fn options_mut(&mut self) -> &mut ClientOptions {
502
- &mut self.inner.options
560
+ Arc::make_mut(&mut self.inner.options)
503
561
  }
504
562
 
505
563
  /// Set a worker build id to be attached to relevant requests. Unlikely to be useful outside
@@ -507,6 +565,39 @@ impl Client {
507
565
  pub fn set_worker_build_id(&mut self, id: String) {
508
566
  self.bound_worker_build_id = Some(id)
509
567
  }
568
+
569
+ /// Returns a reference to the underlying client
570
+ pub fn inner(&self) -> &ConfiguredClient<TemporalServiceClientWithMetrics> {
571
+ &self.inner
572
+ }
573
+
574
+ /// Consumes self and returns the underlying client
575
+ pub fn into_inner(self) -> ConfiguredClient<TemporalServiceClientWithMetrics> {
576
+ self.inner
577
+ }
578
+
579
+ fn wf_svc(&self) -> WorkflowServiceClientWithMetrics {
580
+ self.inner.workflow_svc().clone()
581
+ }
582
+ }
583
+
584
+ /// Enum to help reference a namespace by either the namespace name or the namespace id
585
+ #[derive(Clone)]
586
+ pub enum Namespace {
587
+ /// Namespace name
588
+ Name(String),
589
+ /// Namespace id
590
+ Id(String),
591
+ }
592
+
593
+ impl Namespace {
594
+ fn into_describe_namespace_request(self) -> DescribeNamespaceRequest {
595
+ let (namespace, id) = match self {
596
+ Namespace::Name(n) => (n, "".to_owned()),
597
+ Namespace::Id(n) => ("".to_owned(), n),
598
+ };
599
+ DescribeNamespaceRequest { namespace, id }
600
+ }
510
601
  }
511
602
 
512
603
  /// This trait provides higher-level friendlier interaction with the server.
@@ -521,25 +612,10 @@ pub trait WorkflowClientTrait {
521
612
  task_queue: String,
522
613
  workflow_id: String,
523
614
  workflow_type: String,
615
+ request_id: Option<String>,
524
616
  options: WorkflowOptions,
525
617
  ) -> Result<StartWorkflowExecutionResponse>;
526
618
 
527
- /// Fetch new workflow tasks from the provided queue. Should block indefinitely if there is no
528
- /// work.
529
- async fn poll_workflow_task(
530
- &self,
531
- task_queue: String,
532
- is_sticky: bool,
533
- ) -> Result<PollWorkflowTaskQueueResponse>;
534
-
535
- /// Fetch new activity tasks from the provided queue. Should block indefinitely if there is no
536
- /// work.
537
- async fn poll_activity_task(
538
- &self,
539
- task_queue: String,
540
- max_tasks_per_sec: Option<f64>,
541
- ) -> Result<PollActivityTaskQueueResponse>;
542
-
543
619
  /// Notifies the server that workflow tasks for a given workflow should be sent to the normal
544
620
  /// non-sticky task queue. This normally happens when workflow has been evicted from the cache.
545
621
  async fn reset_sticky_task_queue(
@@ -548,12 +624,6 @@ pub trait WorkflowClientTrait {
548
624
  run_id: String,
549
625
  ) -> Result<ResetStickyTaskQueueResponse>;
550
626
 
551
- /// Complete a workflow activation.
552
- async fn complete_workflow_task(
553
- &self,
554
- request: WorkflowTaskCompletion,
555
- ) -> Result<RespondWorkflowTaskCompletedResponse>;
556
-
557
627
  /// Complete activity task by sending response to the server. `task_token` contains activity
558
628
  /// identifier that would've been received from polling for an activity task. `result` is a blob
559
629
  /// that contains activity response.
@@ -607,6 +677,7 @@ pub trait WorkflowClientTrait {
607
677
  run_id: String,
608
678
  signal_name: String,
609
679
  payloads: Option<Payloads>,
680
+ request_id: Option<String>,
610
681
  ) -> Result<SignalWorkflowExecutionResponse>;
611
682
 
612
683
  /// Send signal and start workflow transcationally
@@ -618,9 +689,11 @@ pub trait WorkflowClientTrait {
618
689
  task_queue: String,
619
690
  workflow_id: String,
620
691
  workflow_type: String,
692
+ request_id: Option<String>,
621
693
  options: WorkflowOptions,
622
694
  signal_name: String,
623
695
  signal_input: Option<Payloads>,
696
+ signal_header: Option<Header>,
624
697
  ) -> Result<SignalWithStartWorkflowExecutionResponse>;
625
698
 
626
699
  /// Request a query of a certain workflow instance
@@ -659,6 +732,7 @@ pub trait WorkflowClientTrait {
659
732
  workflow_id: String,
660
733
  run_id: Option<String>,
661
734
  reason: String,
735
+ request_id: Option<String>,
662
736
  ) -> Result<RequestCancelWorkflowExecutionResponse>;
663
737
 
664
738
  /// Terminate a currently executing workflow
@@ -671,6 +745,35 @@ pub trait WorkflowClientTrait {
671
745
  /// Lists all available namespaces
672
746
  async fn list_namespaces(&self) -> Result<ListNamespacesResponse>;
673
747
 
748
+ /// Query namespace details
749
+ async fn describe_namespace(&self, namespace: Namespace) -> Result<DescribeNamespaceResponse>;
750
+
751
+ /// List open workflows with Standard Visibility filtering
752
+ async fn list_open_workflow_executions(
753
+ &self,
754
+ max_page_size: i32,
755
+ next_page_token: Vec<u8>,
756
+ start_time_filter: Option<StartTimeFilter>,
757
+ filters: Option<ListOpenFilters>,
758
+ ) -> Result<ListOpenWorkflowExecutionsResponse>;
759
+
760
+ /// List closed workflows Standard Visibility filtering
761
+ async fn list_closed_workflow_executions(
762
+ &self,
763
+ max_page_size: i32,
764
+ next_page_token: Vec<u8>,
765
+ start_time_filter: Option<StartTimeFilter>,
766
+ filters: Option<ListClosedFilters>,
767
+ ) -> Result<ListClosedWorkflowExecutionsResponse>;
768
+
769
+ /// List workflows with Advanced Visibility filtering
770
+ async fn list_workflow_executions(
771
+ &self,
772
+ max_page_size: i32,
773
+ next_page_token: Vec<u8>,
774
+ query: String,
775
+ ) -> Result<ListWorkflowExecutionsResponse>;
776
+
674
777
  /// Returns options that were used to initialize the client
675
778
  fn get_options(&self) -> &ClientOptions;
676
779
 
@@ -681,9 +784,22 @@ pub trait WorkflowClientTrait {
681
784
  /// Optional fields supplied at the start of workflow execution
682
785
  #[derive(Debug, Clone, Default)]
683
786
  pub struct WorkflowOptions {
684
- /// Optionally indicates the default task timeout for workflow tasks
787
+ /// Set the policy for reusing the workflow id
788
+ pub id_reuse_policy: WorkflowIdReusePolicy,
789
+
790
+ /// Optionally set the execution timeout for the workflow
791
+ /// <https://docs.temporal.io/workflows/#workflow-execution-timeout>
792
+ pub execution_timeout: Option<Duration>,
793
+
794
+ /// Optionally indicates the default run timeout for a workflow run
795
+ pub run_timeout: Option<Duration>,
796
+
797
+ /// Optionally indicates the default task timeout for a workflow run
685
798
  pub task_timeout: Option<Duration>,
686
799
 
800
+ /// Optionally set a cron schedule for the workflow
801
+ pub cron_schedule: Option<String>,
802
+
687
803
  /// Optionally associate extra search attributes with a workflow
688
804
  pub search_attributes: Option<HashMap<String, Payload>>,
689
805
  }
@@ -696,10 +812,9 @@ impl WorkflowClientTrait for Client {
696
812
  task_queue: String,
697
813
  workflow_id: String,
698
814
  workflow_type: String,
815
+ request_id: Option<String>,
699
816
  options: WorkflowOptions,
700
817
  ) -> Result<StartWorkflowExecutionResponse> {
701
- let request_id = Uuid::new_v4().to_string();
702
-
703
818
  Ok(self
704
819
  .wf_svc()
705
820
  .start_workflow_execution(StartWorkflowExecutionRequest {
@@ -713,65 +828,21 @@ impl WorkflowClientTrait for Client {
713
828
  name: task_queue,
714
829
  kind: TaskQueueKind::Unspecified as i32,
715
830
  }),
716
- request_id,
717
- workflow_task_timeout: options.task_timeout.map(Into::into),
718
- search_attributes: options.search_attributes.map(Into::into),
831
+ request_id: request_id.unwrap_or_else(|| Uuid::new_v4().to_string()),
832
+ workflow_id_reuse_policy: options.id_reuse_policy as i32,
833
+ workflow_execution_timeout: options
834
+ .execution_timeout
835
+ .and_then(|d| d.try_into().ok()),
836
+ workflow_run_timeout: options.execution_timeout.and_then(|d| d.try_into().ok()),
837
+ workflow_task_timeout: options.task_timeout.and_then(|d| d.try_into().ok()),
838
+ search_attributes: options.search_attributes.and_then(|d| d.try_into().ok()),
839
+ cron_schedule: options.cron_schedule.unwrap_or_default(),
719
840
  ..Default::default()
720
841
  })
721
842
  .await?
722
843
  .into_inner())
723
844
  }
724
845
 
725
- async fn poll_workflow_task(
726
- &self,
727
- task_queue: String,
728
- is_sticky: bool,
729
- ) -> Result<PollWorkflowTaskQueueResponse> {
730
- let request = PollWorkflowTaskQueueRequest {
731
- namespace: self.namespace.clone(),
732
- task_queue: Some(TaskQueue {
733
- name: task_queue,
734
- kind: if is_sticky {
735
- TaskQueueKind::Sticky
736
- } else {
737
- TaskQueueKind::Normal
738
- } as i32,
739
- }),
740
- identity: self.inner.options.identity.clone(),
741
- binary_checksum: self.bound_worker_build_id.clone().unwrap_or_default(),
742
- };
743
-
744
- Ok(self
745
- .wf_svc()
746
- .poll_workflow_task_queue(request)
747
- .await?
748
- .into_inner())
749
- }
750
-
751
- async fn poll_activity_task(
752
- &self,
753
- task_queue: String,
754
- max_tasks_per_sec: Option<f64>,
755
- ) -> Result<PollActivityTaskQueueResponse> {
756
- let request = PollActivityTaskQueueRequest {
757
- namespace: self.namespace.clone(),
758
- task_queue: Some(TaskQueue {
759
- name: task_queue,
760
- kind: TaskQueueKind::Normal as i32,
761
- }),
762
- identity: self.inner.options.identity.clone(),
763
- task_queue_metadata: max_tasks_per_sec.map(|tps| TaskQueueMetadata {
764
- max_tasks_per_second: Some(tps),
765
- }),
766
- };
767
-
768
- Ok(self
769
- .wf_svc()
770
- .poll_activity_task_queue(request)
771
- .await?
772
- .into_inner())
773
- }
774
-
775
846
  async fn reset_sticky_task_queue(
776
847
  &self,
777
848
  workflow_id: String,
@@ -791,42 +862,6 @@ impl WorkflowClientTrait for Client {
791
862
  .into_inner())
792
863
  }
793
864
 
794
- async fn complete_workflow_task(
795
- &self,
796
- request: WorkflowTaskCompletion,
797
- ) -> Result<RespondWorkflowTaskCompletedResponse> {
798
- let request = RespondWorkflowTaskCompletedRequest {
799
- task_token: request.task_token.into(),
800
- commands: request.commands,
801
- identity: self.inner.options.identity.clone(),
802
- sticky_attributes: request.sticky_attributes,
803
- return_new_workflow_task: request.return_new_workflow_task,
804
- force_create_new_workflow_task: request.force_create_new_workflow_task,
805
- binary_checksum: self.bound_worker_build_id.clone().unwrap_or_default(),
806
- query_results: request
807
- .query_responses
808
- .into_iter()
809
- .map(|qr| {
810
- let (id, completed_type, query_result, error_message) = qr.into_components();
811
- (
812
- id,
813
- WorkflowQueryResult {
814
- result_type: completed_type as i32,
815
- answer: query_result,
816
- error_message,
817
- },
818
- )
819
- })
820
- .collect(),
821
- namespace: self.namespace.clone(),
822
- };
823
- Ok(self
824
- .wf_svc()
825
- .respond_workflow_task_completed(request)
826
- .await?
827
- .into_inner())
828
- }
829
-
830
865
  async fn complete_activity_task(
831
866
  &self,
832
867
  task_token: TaskToken,
@@ -924,6 +959,7 @@ impl WorkflowClientTrait for Client {
924
959
  run_id: String,
925
960
  signal_name: String,
926
961
  payloads: Option<Payloads>,
962
+ request_id: Option<String>,
927
963
  ) -> Result<SignalWorkflowExecutionResponse> {
928
964
  Ok(self
929
965
  .wf_svc()
@@ -936,6 +972,7 @@ impl WorkflowClientTrait for Client {
936
972
  signal_name,
937
973
  input: payloads,
938
974
  identity: self.inner.options.identity.clone(),
975
+ request_id: request_id.unwrap_or_else(|| Uuid::new_v4().to_string()),
939
976
  ..Default::default()
940
977
  })
941
978
  .await?
@@ -948,11 +985,12 @@ impl WorkflowClientTrait for Client {
948
985
  task_queue: String,
949
986
  workflow_id: String,
950
987
  workflow_type: String,
988
+ request_id: Option<String>,
951
989
  options: WorkflowOptions,
952
990
  signal_name: String,
953
991
  signal_input: Option<Payloads>,
992
+ signal_header: Option<Header>,
954
993
  ) -> Result<SignalWithStartWorkflowExecutionResponse> {
955
- let request_id = Uuid::new_v4().to_string();
956
994
  Ok(self
957
995
  .wf_svc()
958
996
  .signal_with_start_workflow_execution(SignalWithStartWorkflowExecutionRequest {
@@ -965,13 +1003,20 @@ impl WorkflowClientTrait for Client {
965
1003
  name: task_queue,
966
1004
  kind: TaskQueueKind::Normal as i32,
967
1005
  }),
968
- request_id,
969
1006
  input,
970
1007
  signal_name,
971
1008
  signal_input,
972
1009
  identity: self.inner.options.identity.clone(),
973
- workflow_task_timeout: options.task_timeout.map(Into::into),
974
- search_attributes: options.search_attributes.map(Into::into),
1010
+ request_id: request_id.unwrap_or_else(|| Uuid::new_v4().to_string()),
1011
+ workflow_id_reuse_policy: options.id_reuse_policy as i32,
1012
+ workflow_execution_timeout: options
1013
+ .execution_timeout
1014
+ .and_then(|d| d.try_into().ok()),
1015
+ workflow_run_timeout: options.execution_timeout.and_then(|d| d.try_into().ok()),
1016
+ workflow_task_timeout: options.task_timeout.and_then(|d| d.try_into().ok()),
1017
+ search_attributes: options.search_attributes.and_then(|d| d.try_into().ok()),
1018
+ cron_schedule: options.cron_schedule.unwrap_or_default(),
1019
+ header: signal_header,
975
1020
  ..Default::default()
976
1021
  })
977
1022
  .await?
@@ -1062,6 +1107,7 @@ impl WorkflowClientTrait for Client {
1062
1107
  workflow_id: String,
1063
1108
  run_id: Option<String>,
1064
1109
  reason: String,
1110
+ request_id: Option<String>,
1065
1111
  ) -> Result<RequestCancelWorkflowExecutionResponse> {
1066
1112
  Ok(self
1067
1113
  .wf_svc()
@@ -1072,7 +1118,7 @@ impl WorkflowClientTrait for Client {
1072
1118
  run_id: run_id.unwrap_or_default(),
1073
1119
  }),
1074
1120
  identity: self.inner.options.identity.clone(),
1075
- request_id: "".to_string(),
1121
+ request_id: request_id.unwrap_or_else(|| Uuid::new_v4().to_string()),
1076
1122
  first_execution_run_id: "".to_string(),
1077
1123
  reason,
1078
1124
  })
@@ -1110,6 +1156,72 @@ impl WorkflowClientTrait for Client {
1110
1156
  .into_inner())
1111
1157
  }
1112
1158
 
1159
+ async fn describe_namespace(&self, namespace: Namespace) -> Result<DescribeNamespaceResponse> {
1160
+ Ok(self
1161
+ .wf_svc()
1162
+ .describe_namespace(namespace.into_describe_namespace_request())
1163
+ .await?
1164
+ .into_inner())
1165
+ }
1166
+
1167
+ async fn list_open_workflow_executions(
1168
+ &self,
1169
+ maximum_page_size: i32,
1170
+ next_page_token: Vec<u8>,
1171
+ start_time_filter: Option<StartTimeFilter>,
1172
+ filters: Option<ListOpenFilters>,
1173
+ ) -> Result<ListOpenWorkflowExecutionsResponse> {
1174
+ Ok(self
1175
+ .wf_svc()
1176
+ .list_open_workflow_executions(ListOpenWorkflowExecutionsRequest {
1177
+ namespace: self.namespace.clone(),
1178
+ maximum_page_size,
1179
+ next_page_token,
1180
+ start_time_filter,
1181
+ filters,
1182
+ })
1183
+ .await?
1184
+ .into_inner())
1185
+ }
1186
+
1187
+ async fn list_closed_workflow_executions(
1188
+ &self,
1189
+ maximum_page_size: i32,
1190
+ next_page_token: Vec<u8>,
1191
+ start_time_filter: Option<StartTimeFilter>,
1192
+ filters: Option<ListClosedFilters>,
1193
+ ) -> Result<ListClosedWorkflowExecutionsResponse> {
1194
+ Ok(self
1195
+ .wf_svc()
1196
+ .list_closed_workflow_executions(ListClosedWorkflowExecutionsRequest {
1197
+ namespace: self.namespace.clone(),
1198
+ maximum_page_size,
1199
+ next_page_token,
1200
+ start_time_filter,
1201
+ filters,
1202
+ })
1203
+ .await?
1204
+ .into_inner())
1205
+ }
1206
+
1207
+ async fn list_workflow_executions(
1208
+ &self,
1209
+ page_size: i32,
1210
+ next_page_token: Vec<u8>,
1211
+ query: String,
1212
+ ) -> Result<ListWorkflowExecutionsResponse> {
1213
+ Ok(self
1214
+ .wf_svc()
1215
+ .list_workflow_executions(ListWorkflowExecutionsRequest {
1216
+ namespace: self.namespace.clone(),
1217
+ page_size,
1218
+ next_page_token,
1219
+ query,
1220
+ })
1221
+ .await?
1222
+ .into_inner())
1223
+ }
1224
+
1113
1225
  fn get_options(&self) -> &ClientOptions {
1114
1226
  &self.inner.options
1115
1227
  }
@@ -1122,39 +1234,28 @@ impl WorkflowClientTrait for Client {
1122
1234
  mod sealed {
1123
1235
  use crate::{InterceptedMetricsSvc, RawClientLike, WorkflowClientTrait};
1124
1236
 
1125
- pub trait RawClientLikeUser {
1126
- type RawClientT: RawClientLike<SvcType = InterceptedMetricsSvc>;
1127
- /// Used to access the client as a [WorkflowService] implementor rather than the raw struct
1128
- fn wf_svc(&self) -> Self::RawClientT;
1237
+ pub trait WfHandleClient:
1238
+ WorkflowClientTrait + RawClientLike<SvcType = InterceptedMetricsSvc>
1239
+ {
1129
1240
  }
1130
-
1131
- pub trait WfHandleClient: WorkflowClientTrait + RawClientLikeUser {}
1132
- impl<T> WfHandleClient for T where T: WorkflowClientTrait + RawClientLikeUser {}
1133
- }
1134
-
1135
- impl RawClientLikeUser for Client {
1136
- type RawClientT = WorkflowServiceClientWithMetrics;
1137
-
1138
- fn wf_svc(&self) -> Self::RawClientT {
1139
- self.raw_client().clone()
1241
+ impl<T> WfHandleClient for T where
1242
+ T: WorkflowClientTrait + RawClientLike<SvcType = InterceptedMetricsSvc>
1243
+ {
1140
1244
  }
1141
1245
  }
1142
1246
 
1143
1247
  /// Additional methods for workflow clients
1144
- pub trait WfClientExt: WfHandleClient + Sized {
1248
+ pub trait WfClientExt: WfHandleClient + Sized + Clone {
1145
1249
  /// Create an untyped handle for a workflow execution, which can be used to do things like
1146
1250
  /// wait for that workflow's result. `run_id` may be left blank to target the latest run.
1147
1251
  fn get_untyped_workflow_handle(
1148
1252
  &self,
1149
1253
  workflow_id: impl Into<String>,
1150
1254
  run_id: impl Into<String>,
1151
- ) -> UntypedWorkflowHandle<Self::RawClientT>
1152
- where
1153
- Self::RawClientT: Clone,
1154
- {
1255
+ ) -> UntypedWorkflowHandle<Self> {
1155
1256
  let rid = run_id.into();
1156
1257
  UntypedWorkflowHandle::new(
1157
- self.wf_svc(),
1258
+ self.clone(),
1158
1259
  WorkflowExecutionInfo {
1159
1260
  namespace: self.namespace().to_string(),
1160
1261
  workflow_id: workflow_id.into(),
@@ -1163,4 +1264,31 @@ pub trait WfClientExt: WfHandleClient + Sized {
1163
1264
  )
1164
1265
  }
1165
1266
  }
1166
- impl<T> WfClientExt for T where T: WfHandleClient + Sized {}
1267
+ impl<T> WfClientExt for T where T: WfHandleClient + Clone + Sized {}
1268
+
1269
+ #[cfg(test)]
1270
+ mod tests {
1271
+ use super::*;
1272
+
1273
+ #[test]
1274
+ fn respects_per_call_headers() {
1275
+ let opts = ClientOptionsBuilder::default()
1276
+ .identity("enchicat".to_string())
1277
+ .target_url(Url::parse("https://smolkitty").unwrap())
1278
+ .client_name("cute-kitty".to_string())
1279
+ .client_version("0.1.0".to_string())
1280
+ .build()
1281
+ .unwrap();
1282
+
1283
+ let mut static_headers = HashMap::new();
1284
+ static_headers.insert("enchi".to_string(), "kitty".to_string());
1285
+ let mut iceptor = ServiceCallInterceptor {
1286
+ opts,
1287
+ headers: Arc::new(RwLock::new(static_headers)),
1288
+ };
1289
+ let mut req = tonic::Request::new(());
1290
+ req.metadata_mut().insert("enchi", "cat".parse().unwrap());
1291
+ let next_req = iceptor.call(req).unwrap();
1292
+ assert_eq!(next_req.metadata().get("enchi").unwrap(), "cat");
1293
+ }
1294
+ }