@temporalio/core-bridge 1.12.0 → 1.12.2

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 (116) hide show
  1. package/Cargo.lock +64 -119
  2. package/Cargo.toml +1 -1
  3. package/index.js +3 -2
  4. package/package.json +3 -3
  5. package/releases/aarch64-apple-darwin/index.node +0 -0
  6. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  7. package/releases/x86_64-apple-darwin/index.node +0 -0
  8. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  9. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  10. package/sdk-core/.cargo/config.toml +1 -2
  11. package/sdk-core/.github/workflows/per-pr.yml +2 -0
  12. package/sdk-core/AGENTS.md +7 -0
  13. package/sdk-core/Cargo.toml +9 -5
  14. package/sdk-core/README.md +6 -5
  15. package/sdk-core/client/Cargo.toml +3 -2
  16. package/sdk-core/client/src/lib.rs +17 -8
  17. package/sdk-core/client/src/metrics.rs +57 -23
  18. package/sdk-core/client/src/raw.rs +33 -15
  19. package/sdk-core/core/Cargo.toml +11 -9
  20. package/sdk-core/core/benches/workflow_replay.rs +114 -15
  21. package/sdk-core/core/src/core_tests/activity_tasks.rs +18 -18
  22. package/sdk-core/core/src/core_tests/child_workflows.rs +4 -4
  23. package/sdk-core/core/src/core_tests/determinism.rs +6 -6
  24. package/sdk-core/core/src/core_tests/local_activities.rs +20 -20
  25. package/sdk-core/core/src/core_tests/mod.rs +40 -5
  26. package/sdk-core/core/src/core_tests/queries.rs +25 -16
  27. package/sdk-core/core/src/core_tests/replay_flag.rs +3 -3
  28. package/sdk-core/core/src/core_tests/updates.rs +3 -3
  29. package/sdk-core/core/src/core_tests/workers.rs +9 -7
  30. package/sdk-core/core/src/core_tests/workflow_tasks.rs +40 -42
  31. package/sdk-core/core/src/ephemeral_server/mod.rs +1 -19
  32. package/sdk-core/core/src/lib.rs +10 -1
  33. package/sdk-core/core/src/pollers/poll_buffer.rs +2 -2
  34. package/sdk-core/core/src/replay/mod.rs +3 -3
  35. package/sdk-core/core/src/telemetry/metrics.rs +306 -152
  36. package/sdk-core/core/src/telemetry/mod.rs +11 -4
  37. package/sdk-core/core/src/telemetry/otel.rs +134 -131
  38. package/sdk-core/core/src/telemetry/prometheus_meter.rs +885 -0
  39. package/sdk-core/core/src/telemetry/prometheus_server.rs +48 -28
  40. package/sdk-core/core/src/test_help/mod.rs +27 -12
  41. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +7 -7
  42. package/sdk-core/core/src/worker/activities.rs +4 -4
  43. package/sdk-core/core/src/worker/client/mocks.rs +10 -3
  44. package/sdk-core/core/src/worker/client.rs +68 -5
  45. package/sdk-core/core/src/worker/heartbeat.rs +229 -0
  46. package/sdk-core/core/src/worker/mod.rs +35 -14
  47. package/sdk-core/core/src/worker/tuner/resource_based.rs +4 -4
  48. package/sdk-core/core/src/worker/workflow/history_update.rs +71 -19
  49. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +1 -2
  50. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -1
  51. package/sdk-core/core/src/worker/workflow/machines/nexus_operation_state_machine.rs +31 -48
  52. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +1 -2
  53. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +3 -3
  54. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +4 -1
  55. package/sdk-core/core/src/worker/workflow/managed_run.rs +1 -1
  56. package/sdk-core/core/src/worker/workflow/mod.rs +15 -15
  57. package/sdk-core/core-api/Cargo.toml +2 -2
  58. package/sdk-core/core-api/src/envconfig.rs +204 -99
  59. package/sdk-core/core-api/src/lib.rs +9 -0
  60. package/sdk-core/core-api/src/telemetry/metrics.rs +548 -100
  61. package/sdk-core/core-api/src/worker.rs +11 -5
  62. package/sdk-core/core-c-bridge/Cargo.toml +49 -0
  63. package/sdk-core/core-c-bridge/build.rs +26 -0
  64. package/sdk-core/core-c-bridge/include/temporal-sdk-core-c-bridge.h +817 -0
  65. package/sdk-core/core-c-bridge/src/client.rs +679 -0
  66. package/sdk-core/core-c-bridge/src/lib.rs +245 -0
  67. package/sdk-core/core-c-bridge/src/metric.rs +682 -0
  68. package/sdk-core/core-c-bridge/src/random.rs +61 -0
  69. package/sdk-core/core-c-bridge/src/runtime.rs +445 -0
  70. package/sdk-core/core-c-bridge/src/testing.rs +282 -0
  71. package/sdk-core/core-c-bridge/src/tests/context.rs +644 -0
  72. package/sdk-core/core-c-bridge/src/tests/mod.rs +178 -0
  73. package/sdk-core/core-c-bridge/src/tests/utils.rs +108 -0
  74. package/sdk-core/core-c-bridge/src/worker.rs +1069 -0
  75. package/sdk-core/etc/deps.svg +64 -64
  76. package/sdk-core/sdk/src/activity_context.rs +6 -4
  77. package/sdk-core/sdk/src/lib.rs +49 -27
  78. package/sdk-core/sdk/src/workflow_future.rs +18 -25
  79. package/sdk-core/sdk-core-protos/protos/api_upstream/README.md +4 -0
  80. package/sdk-core/sdk-core-protos/protos/api_upstream/buf.yaml +0 -2
  81. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +630 -83
  82. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +632 -78
  83. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/batch/v1/message.proto +4 -4
  84. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/command/v1/message.proto +6 -4
  85. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +2 -2
  86. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/deployment/v1/message.proto +32 -2
  87. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/common.proto +10 -1
  88. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/deployment.proto +26 -0
  89. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
  90. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/reset.proto +4 -4
  91. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/failure/v1/message.proto +2 -2
  92. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +47 -31
  93. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +4 -4
  94. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/schedule/v1/message.proto +7 -1
  95. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/worker/v1/message.proto +134 -0
  96. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +14 -11
  97. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +148 -37
  98. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +21 -0
  99. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +4 -4
  100. package/sdk-core/sdk-core-protos/src/history_builder.rs +9 -5
  101. package/sdk-core/sdk-core-protos/src/lib.rs +96 -6
  102. package/sdk-core/test-utils/src/lib.rs +11 -3
  103. package/sdk-core/tests/cloud_tests.rs +3 -3
  104. package/sdk-core/tests/heavy_tests.rs +11 -3
  105. package/sdk-core/tests/integ_tests/client_tests.rs +12 -13
  106. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +1 -1
  107. package/sdk-core/tests/integ_tests/metrics_tests.rs +188 -83
  108. package/sdk-core/tests/integ_tests/polling_tests.rs +1 -1
  109. package/sdk-core/tests/integ_tests/queries_tests.rs +56 -40
  110. package/sdk-core/tests/integ_tests/update_tests.rs +2 -7
  111. package/sdk-core/tests/integ_tests/worker_tests.rs +3 -4
  112. package/sdk-core/tests/integ_tests/worker_versioning_tests.rs +3 -7
  113. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +3 -5
  114. package/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +24 -17
  115. package/src/client.rs +6 -0
  116. package/src/metrics.rs +6 -6
@@ -69,7 +69,13 @@ pub enum ConfigError {
69
69
  InvalidConfig(String),
70
70
 
71
71
  #[error("Configuration loading error: {0}")]
72
- LoadError(anyhow::Error),
72
+ LoadError(Box<dyn std::error::Error>),
73
+ }
74
+
75
+ impl From<std::env::VarError> for ConfigError {
76
+ fn from(e: std::env::VarError) -> Self {
77
+ Self::LoadError(e.into())
78
+ }
73
79
  }
74
80
 
75
81
  impl From<std::str::Utf8Error> for ConfigError {
@@ -198,6 +204,33 @@ pub struct ClientConfigFromTOMLOptions {
198
204
  pub strict: bool,
199
205
  }
200
206
 
207
+ /// A source for environment variables, which can be either a provided HashMap or the system's
208
+ /// environment. This allows for deferred/lazy reading of system env vars.
209
+ enum EnvProvider<'a> {
210
+ Map(&'a HashMap<String, String>),
211
+ System,
212
+ }
213
+
214
+ impl<'a> EnvProvider<'a> {
215
+ fn get(&self, key: &str) -> Result<Option<String>, ConfigError> {
216
+ match self {
217
+ EnvProvider::Map(map) => Ok(map.get(key).cloned()),
218
+ EnvProvider::System => match std::env::var(key) {
219
+ Ok(v) => Ok(Some(v)),
220
+ Err(std::env::VarError::NotPresent) => Ok(None),
221
+ Err(e) => Err(e.into()),
222
+ },
223
+ }
224
+ }
225
+
226
+ fn contains_key(&self, key: &str) -> Result<bool, ConfigError> {
227
+ match self {
228
+ EnvProvider::Map(map) => Ok(map.contains_key(key)),
229
+ EnvProvider::System => Ok(std::env::var(key).is_ok()),
230
+ }
231
+ }
232
+ }
233
+
201
234
  /// Read bytes from a file path, returning Ok(None) if it doesn't exist
202
235
  fn read_path_bytes(path: &str) -> Result<Option<Vec<u8>>, ConfigError> {
203
236
  if !Path::new(path).exists() {
@@ -210,24 +243,32 @@ fn read_path_bytes(path: &str) -> Result<Option<Vec<u8>>, ConfigError> {
210
243
  }
211
244
  }
212
245
 
213
- /// Load client configuration from TOML. Does not load values from environment variables
214
- /// (but may use environment variables to get which config file to load). This will not fail
215
- /// if the file does not exist.
246
+ /// Load client configuration from TOML. This function uses environment variables (which are
247
+ /// taken from the system if not provided) to locate the configuration file. It does not apply
248
+ /// other environment variable values; that is handled by [load_client_config_profile]. This will
249
+ /// not fail if the file does not exist.
216
250
  pub fn load_client_config(
217
251
  options: LoadClientConfigOptions,
218
252
  env_vars: Option<&HashMap<String, String>>,
219
253
  ) -> Result<ClientConfig, ConfigError> {
254
+ let env_provider = match env_vars {
255
+ Some(map) => EnvProvider::Map(map),
256
+ None => EnvProvider::System,
257
+ };
258
+
220
259
  // Get which bytes to load from TOML
221
260
  let toml_data = match options.config_source {
222
261
  Some(DataSource::Data(d)) => Some(d),
223
262
  Some(DataSource::Path(p)) => read_path_bytes(&p)?,
224
263
  None => {
225
- let file_path = env_vars
226
- .and_then(|vars| vars.get("TEMPORAL_CONFIG_FILE"))
264
+ let file_path = if let Some(path) = env_provider
265
+ .get("TEMPORAL_CONFIG_FILE")?
227
266
  .filter(|p| !p.is_empty())
228
- .cloned()
229
- .map(Ok)
230
- .unwrap_or_else(get_default_config_file_path)?;
267
+ {
268
+ path
269
+ } else {
270
+ get_default_config_file_path()?
271
+ };
231
272
  read_path_bytes(&file_path)?
232
273
  }
233
274
  };
@@ -244,7 +285,20 @@ pub fn load_client_config(
244
285
  }
245
286
  }
246
287
 
247
- /// Load a specific client configuration profile
288
+ /// Load a specific client configuration profile.
289
+ ///
290
+ /// This function is the primary entry point for loading client configuration. It orchestrates loading
291
+ /// from a TOML file (if not disabled) and then applies overrides from environment variables (if not disabled).
292
+ ///
293
+ /// The resolution order is as follows:
294
+ /// 1. A profile is loaded from a TOML file. The file is located by checking `options.config_source`,
295
+ /// then the `TEMPORAL_CONFIG_FILE` environment variable, then a default path. The profile within
296
+ /// the file is determined by `options.config_file_profile`, then the `TEMPORAL_PROFILE`
297
+ /// environment variable, then the "default" profile.
298
+ /// 2. Environment variables are applied on top of the loaded profile.
299
+ ///
300
+ /// If `env_vars` is provided as a `HashMap`, it will be used as the source for environment
301
+ /// variables. If it is `None`, the function will fall back to using the system's environment variables.
248
302
  pub fn load_client_config_profile(
249
303
  options: LoadClientConfigProfileOptions,
250
304
  env_vars: Option<&HashMap<String, String>>,
@@ -255,6 +309,15 @@ pub fn load_client_config_profile(
255
309
  ));
256
310
  }
257
311
 
312
+ let env_provider = if options.disable_env {
313
+ None
314
+ } else {
315
+ Some(match env_vars {
316
+ Some(map) => EnvProvider::Map(map),
317
+ None => EnvProvider::System,
318
+ })
319
+ };
320
+
258
321
  let mut profile = if options.disable_file {
259
322
  ClientConfigProfile::default()
260
323
  } else {
@@ -268,13 +331,17 @@ pub fn load_client_config_profile(
268
331
  )?;
269
332
 
270
333
  // Determine profile name
271
- let (profile_name, profile_unset) = match &options.config_file_profile {
272
- Some(profile) => (profile.clone(), false),
273
- None => env_vars
274
- .and_then(|vars| vars.get("TEMPORAL_PROFILE"))
275
- .filter(|p| !p.is_empty())
276
- .map(|p| (p.clone(), false))
277
- .unwrap_or((DEFAULT_PROFILE.to_string(), true)),
334
+ let (profile_name, profile_unset) = if let Some(p) = options.config_file_profile.as_deref()
335
+ {
336
+ (p.to_string(), false)
337
+ } else {
338
+ match env_provider.as_ref() {
339
+ Some(provider) => match provider.get("TEMPORAL_PROFILE")? {
340
+ Some(p) if !p.is_empty() => (p, false),
341
+ _ => (DEFAULT_PROFILE.to_string(), true),
342
+ },
343
+ None => (DEFAULT_PROFILE.to_string(), true),
344
+ }
278
345
  };
279
346
 
280
347
  if let Some(prof) = config.profiles.get(&profile_name) {
@@ -288,9 +355,7 @@ pub fn load_client_config_profile(
288
355
 
289
356
  // Apply environment variables if not disabled
290
357
  if !options.disable_env {
291
- if let Some(vars) = env_vars {
292
- profile.apply_env_vars(vars)?;
293
- }
358
+ profile.load_from_env(env_vars)?;
294
359
  }
295
360
 
296
361
  // Apply API key → TLS auto-enabling logic
@@ -332,33 +397,35 @@ impl ClientConfig {
332
397
  }
333
398
 
334
399
  impl ClientConfigProfile {
335
- /// Apply environment variable overrides to this profile
336
- pub fn apply_env_vars(
400
+ /// Apply environment variable overrides to this profile.
401
+ /// If `env_vars` is `None`, the system's environment variables will be used as the source.
402
+ pub fn load_from_env(
337
403
  &mut self,
338
- env_vars: &HashMap<String, String>,
404
+ env_vars: Option<&HashMap<String, String>>,
339
405
  ) -> Result<(), ConfigError> {
406
+ let env_provider = match env_vars {
407
+ Some(map) => EnvProvider::Map(map),
408
+ None => EnvProvider::System,
409
+ };
340
410
  // Apply basic settings
341
- if let Some(address) = env_vars.get("TEMPORAL_ADDRESS") {
342
- self.address = Some(address.clone());
411
+ if let Some(address) = env_provider.get("TEMPORAL_ADDRESS")? {
412
+ self.address = Some(address);
343
413
  }
344
- if let Some(namespace) = env_vars.get("TEMPORAL_NAMESPACE") {
345
- self.namespace = Some(namespace.clone());
414
+ if let Some(namespace) = env_provider.get("TEMPORAL_NAMESPACE")? {
415
+ self.namespace = Some(namespace);
346
416
  }
347
- if let Some(api_key) = env_vars.get("TEMPORAL_API_KEY") {
348
- self.api_key = Some(api_key.clone());
417
+ if let Some(api_key) = env_provider.get("TEMPORAL_API_KEY")? {
418
+ self.api_key = Some(api_key);
349
419
  }
350
420
 
351
- self.apply_tls_env_vars(env_vars)?;
352
- self.apply_codec_env_vars(env_vars)?;
353
- self.apply_grpc_meta_env_vars(env_vars)?;
421
+ self.apply_tls_env_vars(&env_provider)?;
422
+ self.apply_codec_env_vars(&env_provider)?;
423
+ self.apply_grpc_meta_env_vars(&env_provider)?;
354
424
 
355
425
  Ok(())
356
426
  }
357
427
 
358
- fn apply_tls_env_vars(
359
- &mut self,
360
- env_vars: &HashMap<String, String>,
361
- ) -> Result<(), ConfigError> {
428
+ fn apply_tls_env_vars(&mut self, env_provider: &EnvProvider) -> Result<(), ConfigError> {
362
429
  const TLS_ENV_VARS: &[&str] = &[
363
430
  "TEMPORAL_TLS",
364
431
  "TEMPORAL_TLS_CLIENT_CERT_PATH",
@@ -371,82 +438,91 @@ impl ClientConfigProfile {
371
438
  "TEMPORAL_TLS_DISABLE_HOST_VERIFICATION",
372
439
  ];
373
440
 
374
- if TLS_ENV_VARS.iter().any(|&k| env_vars.contains_key(k)) && self.tls.is_none() {
441
+ if self.tls.is_none() && has_any_env_var(env_provider, TLS_ENV_VARS)? {
375
442
  self.tls = Some(ClientConfigTLS::default());
376
443
  }
377
444
 
378
445
  if let Some(ref mut tls) = self.tls {
379
- if let Some(disabled_str) = env_vars.get("TEMPORAL_TLS") {
380
- if let Some(disabled) = env_var_to_bool(disabled_str) {
381
- tls.disabled = !disabled;
382
- }
446
+ if let Some(disabled_str) = env_provider.get("TEMPORAL_TLS")?
447
+ && let Some(disabled) = env_var_to_bool(&disabled_str)
448
+ {
449
+ tls.disabled = !disabled;
383
450
  }
384
451
 
385
452
  apply_data_source_env_var(
386
- env_vars,
453
+ env_provider,
387
454
  "cert",
388
455
  "TEMPORAL_TLS_CLIENT_CERT_PATH",
389
456
  "TEMPORAL_TLS_CLIENT_CERT_DATA",
390
457
  &mut tls.client_cert,
391
458
  )?;
392
459
  apply_data_source_env_var(
393
- env_vars,
460
+ env_provider,
394
461
  "key",
395
462
  "TEMPORAL_TLS_CLIENT_KEY_PATH",
396
463
  "TEMPORAL_TLS_CLIENT_KEY_DATA",
397
464
  &mut tls.client_key,
398
465
  )?;
399
466
  apply_data_source_env_var(
400
- env_vars,
467
+ env_provider,
401
468
  "server CA cert",
402
469
  "TEMPORAL_TLS_SERVER_CA_CERT_PATH",
403
470
  "TEMPORAL_TLS_SERVER_CA_CERT_DATA",
404
471
  &mut tls.server_ca_cert,
405
472
  )?;
406
473
 
407
- if let Some(v) = env_vars.get("TEMPORAL_TLS_SERVER_NAME") {
408
- tls.server_name = Some(v.clone());
474
+ if let Some(v) = env_provider.get("TEMPORAL_TLS_SERVER_NAME")? {
475
+ tls.server_name = Some(v);
409
476
  }
410
- if let Some(v) = env_vars.get("TEMPORAL_TLS_DISABLE_HOST_VERIFICATION") {
411
- if let Some(b) = env_var_to_bool(v) {
412
- tls.disable_host_verification = b;
413
- }
477
+ if let Some(v) = env_provider.get("TEMPORAL_TLS_DISABLE_HOST_VERIFICATION")?
478
+ && let Some(b) = env_var_to_bool(&v)
479
+ {
480
+ tls.disable_host_verification = b;
414
481
  }
415
482
  }
416
483
  Ok(())
417
484
  }
418
485
 
419
- fn apply_codec_env_vars(
420
- &mut self,
421
- env_vars: &HashMap<String, String>,
422
- ) -> Result<(), ConfigError> {
486
+ fn apply_codec_env_vars(&mut self, env_provider: &EnvProvider) -> Result<(), ConfigError> {
423
487
  const CODEC_ENV_VARS: &[&str] = &["TEMPORAL_CODEC_ENDPOINT", "TEMPORAL_CODEC_AUTH"];
424
- if CODEC_ENV_VARS.iter().any(|&k| env_vars.contains_key(k)) && self.codec.is_none() {
488
+ if self.codec.is_none() && has_any_env_var(env_provider, CODEC_ENV_VARS)? {
425
489
  self.codec = Some(ClientConfigCodec::default());
426
490
  }
427
491
 
428
492
  if let Some(ref mut codec) = self.codec {
429
- if let Some(endpoint) = env_vars.get("TEMPORAL_CODEC_ENDPOINT") {
430
- codec.endpoint = Some(endpoint.clone());
493
+ if let Some(endpoint) = env_provider.get("TEMPORAL_CODEC_ENDPOINT")? {
494
+ codec.endpoint = Some(endpoint);
431
495
  }
432
- if let Some(auth) = env_vars.get("TEMPORAL_CODEC_AUTH") {
433
- codec.auth = Some(auth.clone());
496
+ if let Some(auth) = env_provider.get("TEMPORAL_CODEC_AUTH")? {
497
+ codec.auth = Some(auth);
434
498
  }
435
499
  }
436
500
  Ok(())
437
501
  }
438
502
 
439
- fn apply_grpc_meta_env_vars(
440
- &mut self,
441
- env_vars: &HashMap<String, String>,
442
- ) -> Result<(), ConfigError> {
443
- for (key, value) in env_vars {
444
- if let Some(header_name) = key.strip_prefix("TEMPORAL_GRPC_META_") {
445
- let normalized_name = normalize_grpc_meta_key(header_name);
446
- if value.is_empty() {
447
- self.grpc_meta.remove(&normalized_name);
448
- } else {
449
- self.grpc_meta.insert(normalized_name, value.clone());
503
+ fn apply_grpc_meta_env_vars(&mut self, env_provider: &EnvProvider) -> Result<(), ConfigError> {
504
+ let mut handle_meta_var = |header_name: &str, value: &str| {
505
+ let normalized_name = normalize_grpc_meta_key(header_name);
506
+ if value.is_empty() {
507
+ self.grpc_meta.remove(&normalized_name);
508
+ } else {
509
+ self.grpc_meta.insert(normalized_name, value.to_string());
510
+ }
511
+ };
512
+
513
+ match env_provider {
514
+ EnvProvider::Map(map) => {
515
+ for (key, value) in map.iter() {
516
+ if let Some(header_name) = key.strip_prefix("TEMPORAL_GRPC_META_") {
517
+ handle_meta_var(header_name, value);
518
+ }
519
+ }
520
+ }
521
+ EnvProvider::System => {
522
+ for (key, value) in std::env::vars() {
523
+ if let Some(header_name) = key.strip_prefix("TEMPORAL_GRPC_META_") {
524
+ handle_meta_var(header_name, &value);
525
+ }
450
526
  }
451
527
  }
452
528
  }
@@ -462,47 +538,51 @@ impl ClientConfigProfile {
462
538
  }
463
539
  }
464
540
 
541
+ /// Helper to check if any of the given environment variables are set.
542
+ fn has_any_env_var(env_provider: &EnvProvider, keys: &[&str]) -> Result<bool, ConfigError> {
543
+ for &key in keys {
544
+ if env_provider.contains_key(key)? {
545
+ return Ok(true);
546
+ }
547
+ }
548
+ Ok(false)
549
+ }
550
+
465
551
  /// Helper for applying env vars to a data source.
466
552
  fn apply_data_source_env_var(
467
- env_vars: &HashMap<String, String>,
553
+ env_provider: &EnvProvider,
468
554
  name: &str,
469
555
  path_var: &str,
470
556
  data_var: &str,
471
557
  dest: &mut Option<DataSource>,
472
558
  ) -> Result<(), ConfigError> {
473
- let has_path_env = env_vars.contains_key(path_var);
474
- let has_data_env = env_vars.contains_key(data_var);
559
+ let path_val = env_provider.get(path_var)?;
560
+ let data_val = env_provider.get(data_var)?;
475
561
 
476
- if has_path_env && has_data_env {
477
- return Err(ConfigError::InvalidConfig(format!(
562
+ match (path_val, data_val) {
563
+ (Some(_), Some(_)) => Err(ConfigError::InvalidConfig(format!(
478
564
  "Cannot specify both {path_var} and {data_var}"
479
- )));
480
- }
481
-
482
- if has_data_env {
483
- if dest
484
- .as_ref()
485
- .is_some_and(|s| matches!(s, DataSource::Path(_)))
486
- {
487
- return Err(ConfigError::InvalidConfig(format!(
488
- "Cannot specify {name} data via {data_var} when {name} path is already specified"
489
- )));
565
+ ))),
566
+ (Some(path), None) => {
567
+ if let Some(DataSource::Data(_)) = dest {
568
+ return Err(ConfigError::InvalidConfig(format!(
569
+ "Cannot specify {name} path via {path_var} when {name} data is already specified"
570
+ )));
571
+ }
572
+ *dest = Some(DataSource::Path(path));
573
+ Ok(())
490
574
  }
491
- *dest = Some(DataSource::Data(
492
- env_vars.get(data_var).unwrap().clone().into_bytes(),
493
- ));
494
- } else if has_path_env {
495
- if dest
496
- .as_ref()
497
- .is_some_and(|s| matches!(s, DataSource::Data(_)))
498
- {
499
- return Err(ConfigError::InvalidConfig(format!(
500
- "Cannot specify {name} path via {path_var} when {name} data is already specified"
501
- )));
575
+ (None, Some(data)) => {
576
+ if let Some(DataSource::Path(_)) = dest {
577
+ return Err(ConfigError::InvalidConfig(format!(
578
+ "Cannot specify {name} data via {data_var} when {name} path is already specified"
579
+ )));
580
+ }
581
+ *dest = Some(DataSource::Data(data.into_bytes()));
582
+ Ok(())
502
583
  }
503
- *dest = Some(DataSource::Path(env_vars.get(path_var).unwrap().clone()));
584
+ (None, None) => Ok(()),
504
585
  }
505
- Ok(())
506
586
  }
507
587
 
508
588
  /// Parse a boolean value from string (supports "true", "false", "1", "0")
@@ -1541,4 +1621,29 @@ address = "some-address"
1541
1621
  // TLS should not be enabled
1542
1622
  assert!(profile.tls.is_none());
1543
1623
  }
1624
+
1625
+ #[test]
1626
+ fn test_load_client_config_profile_from_system_env() {
1627
+ // Set up system env vars. These tests can't be run in parallel.
1628
+ unsafe {
1629
+ std::env::set_var("TEMPORAL_ADDRESS", "system-address");
1630
+ std::env::set_var("TEMPORAL_NAMESPACE", "system-namespace");
1631
+ }
1632
+
1633
+ let options = LoadClientConfigProfileOptions {
1634
+ disable_file: true, // Don't load from any files
1635
+ ..Default::default()
1636
+ };
1637
+
1638
+ // Pass None for env_vars to trigger system env var loading
1639
+ let profile = load_client_config_profile(options, None).unwrap();
1640
+ assert_eq!(profile.address.as_ref().unwrap(), "system-address");
1641
+ assert_eq!(profile.namespace.as_ref().unwrap(), "system-namespace");
1642
+
1643
+ // Clean up
1644
+ unsafe {
1645
+ std::env::remove_var("TEMPORAL_ADDRESS");
1646
+ std::env::remove_var("TEMPORAL_NAMESPACE");
1647
+ }
1648
+ }
1544
1649
  }
@@ -138,3 +138,12 @@ pub trait Worker: Send + Sync {
138
138
  /// functions have returned `ShutDown` errors.
139
139
  async fn finalize_shutdown(self);
140
140
  }
141
+
142
+ macro_rules! dbg_panic {
143
+ ($($arg:tt)*) => {
144
+ use tracing::error;
145
+ error!($($arg)*);
146
+ debug_assert!(false, $($arg)*);
147
+ };
148
+ }
149
+ pub(crate) use dbg_panic;