@temporalio/core-bridge 1.11.8 → 1.12.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.
- package/Cargo.lock +219 -193
- package/Cargo.toml +27 -8
- package/README.md +5 -0
- package/index.js +72 -12
- package/lib/errors.d.ts +25 -0
- package/lib/errors.js +76 -1
- package/lib/errors.js.map +1 -1
- package/lib/index.d.ts +11 -478
- package/lib/index.js +28 -5
- package/lib/index.js.map +1 -1
- package/lib/native.d.ts +330 -0
- package/lib/{worker-tuner.js → native.js} +1 -1
- package/lib/native.js.map +1 -0
- package/package.json +7 -3
- package/releases/aarch64-apple-darwin/index.node +0 -0
- package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
- package/releases/x86_64-apple-darwin/index.node +0 -0
- package/releases/x86_64-pc-windows-msvc/index.node +0 -0
- package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
- package/sdk-core/.cargo/config.toml +8 -2
- package/sdk-core/.cargo/multi-worker-manual-test +15 -0
- package/sdk-core/.github/workflows/per-pr.yml +40 -11
- package/sdk-core/AGENTS.md +73 -0
- package/sdk-core/ARCHITECTURE.md +71 -23
- package/sdk-core/Cargo.toml +1 -1
- package/sdk-core/README.md +4 -4
- package/sdk-core/arch_docs/workflow_task_chunking.md +51 -0
- package/sdk-core/client/Cargo.toml +1 -1
- package/sdk-core/client/src/lib.rs +49 -13
- package/sdk-core/client/src/metrics.rs +15 -16
- package/sdk-core/client/src/proxy.rs +2 -2
- package/sdk-core/client/src/raw.rs +54 -8
- package/sdk-core/client/src/retry.rs +109 -13
- package/sdk-core/client/src/worker_registry/mod.rs +4 -4
- package/sdk-core/client/src/workflow_handle/mod.rs +1 -1
- package/sdk-core/core/Cargo.toml +28 -8
- package/sdk-core/core/src/abstractions.rs +62 -10
- package/sdk-core/core/src/core_tests/activity_tasks.rs +180 -8
- package/sdk-core/core/src/core_tests/mod.rs +4 -4
- package/sdk-core/core/src/core_tests/queries.rs +18 -4
- package/sdk-core/core/src/core_tests/workers.rs +3 -3
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +191 -25
- package/sdk-core/core/src/ephemeral_server/mod.rs +10 -3
- package/sdk-core/core/src/internal_flags.rs +14 -14
- package/sdk-core/core/src/lib.rs +5 -2
- package/sdk-core/core/src/pollers/mod.rs +1 -1
- package/sdk-core/core/src/pollers/poll_buffer.rs +495 -164
- package/sdk-core/core/src/protosext/mod.rs +3 -3
- package/sdk-core/core/src/replay/mod.rs +3 -3
- package/sdk-core/core/src/telemetry/metrics.rs +13 -4
- package/sdk-core/core/src/telemetry/mod.rs +72 -70
- package/sdk-core/core/src/telemetry/otel.rs +51 -54
- package/sdk-core/core/src/test_help/mod.rs +9 -3
- package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +31 -11
- package/sdk-core/core/src/worker/activities/local_activities.rs +35 -28
- package/sdk-core/core/src/worker/activities.rs +58 -30
- package/sdk-core/core/src/worker/client/mocks.rs +3 -3
- package/sdk-core/core/src/worker/client.rs +155 -53
- package/sdk-core/core/src/worker/mod.rs +103 -95
- package/sdk-core/core/src/worker/nexus.rs +100 -73
- package/sdk-core/core/src/worker/tuner/resource_based.rs +14 -6
- package/sdk-core/core/src/worker/tuner.rs +4 -13
- package/sdk-core/core/src/worker/workflow/history_update.rs +47 -51
- package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -4
- package/sdk-core/core/src/worker/workflow/machines/nexus_operation_state_machine.rs +127 -32
- package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +1 -2
- package/sdk-core/core/src/worker/workflow/machines/update_state_machine.rs +1 -1
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +55 -42
- package/sdk-core/core/src/worker/workflow/managed_run.rs +45 -35
- package/sdk-core/core/src/worker/workflow/mod.rs +200 -97
- package/sdk-core/core/src/worker/workflow/wft_poller.rs +175 -4
- package/sdk-core/core/src/worker/workflow/workflow_stream.rs +38 -36
- package/sdk-core/core-api/Cargo.toml +8 -0
- package/sdk-core/core-api/src/envconfig.rs +1544 -0
- package/sdk-core/core-api/src/lib.rs +2 -0
- package/sdk-core/core-api/src/telemetry/metrics.rs +8 -0
- package/sdk-core/core-api/src/telemetry.rs +36 -3
- package/sdk-core/core-api/src/worker.rs +301 -75
- package/sdk-core/docker/docker-compose-telem.yaml +1 -0
- package/sdk-core/etc/prometheus.yaml +6 -2
- package/sdk-core/histories/long_local_activity_with_update-0_history.bin +0 -0
- package/sdk-core/histories/long_local_activity_with_update-1_history.bin +0 -0
- package/sdk-core/histories/long_local_activity_with_update-2_history.bin +0 -0
- package/sdk-core/histories/long_local_activity_with_update-3_history.bin +0 -0
- package/sdk-core/sdk/src/activity_context.rs +5 -0
- package/sdk-core/sdk/src/interceptors.rs +73 -3
- package/sdk-core/sdk/src/lib.rs +15 -16
- package/sdk-core/sdk/src/workflow_context/options.rs +10 -0
- package/sdk-core/sdk/src/workflow_context.rs +48 -29
- package/sdk-core/sdk/src/workflow_future.rs +5 -6
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/.github/workflows/push-to-buf.yml +20 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/CODEOWNERS +6 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/README.md +17 -6
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/VERSION +1 -1
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.lock +7 -2
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.yaml +2 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/request_response.proto +78 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/service.proto +29 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/identity/v1/message.proto +74 -32
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/namespace/v1/message.proto +45 -15
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/nexus/v1/message.proto +7 -1
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/operation/v1/message.proto +3 -3
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/region/v1/message.proto +3 -3
- package/sdk-core/sdk-core-protos/protos/api_upstream/LICENSE +1 -1
- package/sdk-core/sdk-core-protos/protos/api_upstream/buf.yaml +2 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +1103 -88
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +1233 -151
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/activity/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/batch/v1/message.proto +19 -24
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/command/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +12 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/deployment/v1/message.proto +45 -45
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/command_type.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/common.proto +15 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/deployment.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/event_type.proto +4 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/namespace.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/nexus.proto +0 -20
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/query.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/reset.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/schedule.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/update.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/workflow.proto +4 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/errordetails/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/export/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/failure/v1/message.proto +2 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/filter/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +75 -49
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/namespace/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +0 -20
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/protocol/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/query/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/replication/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/rules/v1/message.proto +90 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/schedule/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/enhanced_stack_trace.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/user_metadata.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/workflow_metadata.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +17 -38
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/update/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/version/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +151 -44
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +172 -65
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +69 -28
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/activity_task/activity_task.proto +18 -0
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/common/common.proto +5 -0
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/nexus/nexus.proto +16 -1
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +21 -15
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +3 -0
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +3 -0
- package/sdk-core/sdk-core-protos/src/lib.rs +60 -16
- package/sdk-core/test-utils/src/lib.rs +157 -39
- package/sdk-core/tests/cloud_tests.rs +86 -0
- package/sdk-core/tests/fuzzy_workflow.rs +23 -26
- package/sdk-core/tests/global_metric_tests.rs +116 -0
- package/sdk-core/tests/heavy_tests.rs +127 -7
- package/sdk-core/tests/integ_tests/client_tests.rs +2 -8
- package/sdk-core/tests/integ_tests/metrics_tests.rs +100 -106
- package/sdk-core/tests/integ_tests/polling_tests.rs +94 -8
- package/sdk-core/tests/integ_tests/update_tests.rs +75 -6
- package/sdk-core/tests/integ_tests/worker_tests.rs +54 -5
- package/sdk-core/tests/integ_tests/worker_versioning_tests.rs +240 -0
- package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +41 -3
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +168 -8
- package/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +285 -15
- package/sdk-core/tests/integ_tests/workflow_tests/priority.rs +12 -4
- package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +3 -2
- package/sdk-core/tests/integ_tests/workflow_tests.rs +124 -74
- package/sdk-core/tests/main.rs +3 -51
- package/sdk-core/tests/manual_tests.rs +430 -0
- package/sdk-core/tests/runner.rs +28 -2
- package/src/client.rs +565 -0
- package/src/helpers/abort_controller.rs +204 -0
- package/src/helpers/callbacks.rs +299 -0
- package/src/helpers/errors.rs +302 -0
- package/src/helpers/future.rs +44 -0
- package/src/helpers/handles.rs +191 -0
- package/src/helpers/inspect.rs +18 -0
- package/src/helpers/json_string.rs +58 -0
- package/src/helpers/mod.rs +20 -0
- package/src/helpers/properties.rs +71 -0
- package/src/helpers/try_from_js.rs +213 -0
- package/src/helpers/try_into_js.rs +129 -0
- package/src/lib.rs +28 -40
- package/src/logs.rs +111 -0
- package/src/metrics.rs +325 -0
- package/src/runtime.rs +409 -498
- package/src/testing.rs +315 -57
- package/src/worker.rs +907 -378
- package/ts/errors.ts +57 -0
- package/ts/index.ts +10 -596
- package/ts/native.ts +496 -0
- package/lib/worker-tuner.d.ts +0 -167
- package/lib/worker-tuner.js.map +0 -1
- package/src/conversions/slot_supplier_bridge.rs +0 -291
- package/src/conversions.rs +0 -618
- package/src/errors.rs +0 -38
- package/src/helpers.rs +0 -297
- package/ts/worker-tuner.ts +0 -193
|
@@ -0,0 +1,1544 @@
|
|
|
1
|
+
//! Environment and file-based configuration for Temporal clients.
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides utilities to load Temporal client configuration from TOML files
|
|
4
|
+
//! and environment variables. The configuration supports multiple profiles and various
|
|
5
|
+
//! connection settings including TLS, authentication, and codec configuration.
|
|
6
|
+
//!
|
|
7
|
+
//! ## Environment Variables
|
|
8
|
+
//!
|
|
9
|
+
//! The following environment variables are supported:
|
|
10
|
+
//! - `TEMPORAL_CONFIG_FILE`: Path to the TOML configuration file
|
|
11
|
+
//! - `TEMPORAL_PROFILE`: Profile name to use from the configuration file
|
|
12
|
+
//! - `TEMPORAL_ADDRESS`: Temporal server address
|
|
13
|
+
//! - `TEMPORAL_NAMESPACE`: Temporal namespace
|
|
14
|
+
//! - `TEMPORAL_API_KEY`: API key for authentication
|
|
15
|
+
//! - `TEMPORAL_TLS`: A boolean (`true`/`false`) string to enable/disable TLS.
|
|
16
|
+
//! - `TEMPORAL_TLS_CLIENT_CERT_PATH`: Path to a client certificate file. Mutually exclusive with TEMPORAL_TLS_CLIENT_CERT_DATA, only supply one.
|
|
17
|
+
//! - `TEMPORAL_TLS_CLIENT_CERT_DATA`: The raw client certificate data. Mutually exclusive with TEMPORAL_TLS_CLIENT_CERT_PATH, only supply one.
|
|
18
|
+
//! - `TEMPORAL_TLS_CLIENT_KEY_PATH`: Path to a client key file. Mutually exclusive with TEMPORAL_TLS_CLIENT_KEY_DATA, only supply one.
|
|
19
|
+
//! - `TEMPORAL_TLS_CLIENT_KEY_DATA`: The raw client key data. Mutually exclusive with TEMPORAL_TLS_CLIENT_KEY_PATH, only supply one.
|
|
20
|
+
//! - `TEMPORAL_TLS_SERVER_CA_CERT_PATH`: Path to a server CA certificate file. Mutually exclusive with TEMPORAL_TLS_SERVER_CA_CERT_DATA, only supply one.
|
|
21
|
+
//! - `TEMPORAL_TLS_SERVER_CA_CERT_DATA`: The raw server CA certificate data. Mutually exclusive with TEMPORAL_TLS_SERVER_CA_CERT_PATH, only supply one.
|
|
22
|
+
//! - `TEMPORAL_TLS_SERVER_NAME`: The server name to use for SNI.
|
|
23
|
+
//! - `TEMPORAL_TLS_DISABLE_HOST_VERIFICATION`: A boolean (`true`/`false`) string to disable host verification.
|
|
24
|
+
//! - `TEMPORAL_CODEC_ENDPOINT`: The endpoint for a remote data converter.
|
|
25
|
+
//! - `TEMPORAL_CODEC_AUTH`: The authorization header value for a remote data converter.
|
|
26
|
+
//! - `TEMPORAL_GRPC_META_*`: gRPC metadata headers. Any variables with this prefix will be
|
|
27
|
+
//! converted to gRPC headers. The part of the name after the prefix is converted to the header
|
|
28
|
+
//! name by lowercasing it and replacing underscores with hyphens. For example
|
|
29
|
+
//! `TEMPORAL_GRPC_META_SOME_KEY` becomes `some-key`.
|
|
30
|
+
//!
|
|
31
|
+
//! ## TOML Configuration Format
|
|
32
|
+
//!
|
|
33
|
+
//! ```toml
|
|
34
|
+
//! [profile.default]
|
|
35
|
+
//! address = "localhost:7233"
|
|
36
|
+
//! namespace = "default"
|
|
37
|
+
//! api_key = "your-api-key"
|
|
38
|
+
//!
|
|
39
|
+
//! [profile.default.tls]
|
|
40
|
+
//! disabled = false
|
|
41
|
+
//! client_cert_path = "/path/to/cert.pem"
|
|
42
|
+
//! client_key_path = "/path/to/key.pem"
|
|
43
|
+
//!
|
|
44
|
+
//! [profile.default.codec]
|
|
45
|
+
//! endpoint = "http://localhost:8080"
|
|
46
|
+
//! auth = "Bearer token"
|
|
47
|
+
//!
|
|
48
|
+
//! [profile.default.grpc_meta]
|
|
49
|
+
//! custom_header = "value"
|
|
50
|
+
//! ```
|
|
51
|
+
|
|
52
|
+
use serde::{Deserialize, Serialize};
|
|
53
|
+
use std::{collections::HashMap, fs, path::Path};
|
|
54
|
+
use thiserror::Error;
|
|
55
|
+
|
|
56
|
+
/// Default profile name when none is specified
|
|
57
|
+
pub const DEFAULT_PROFILE: &str = "default";
|
|
58
|
+
|
|
59
|
+
/// Default configuration file name
|
|
60
|
+
pub const DEFAULT_CONFIG_FILE: &str = "temporal.toml";
|
|
61
|
+
|
|
62
|
+
/// Errors that can occur during configuration loading
|
|
63
|
+
#[derive(Debug, Error)]
|
|
64
|
+
pub enum ConfigError {
|
|
65
|
+
#[error("Profile '{0}' not found")]
|
|
66
|
+
ProfileNotFound(String),
|
|
67
|
+
|
|
68
|
+
#[error("Invalid configuration: {0}")]
|
|
69
|
+
InvalidConfig(String),
|
|
70
|
+
|
|
71
|
+
#[error("Configuration loading error: {0}")]
|
|
72
|
+
LoadError(anyhow::Error),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
impl From<std::str::Utf8Error> for ConfigError {
|
|
76
|
+
fn from(e: std::str::Utf8Error) -> Self {
|
|
77
|
+
Self::LoadError(e.into())
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
impl From<toml::de::Error> for ConfigError {
|
|
82
|
+
fn from(e: toml::de::Error) -> Self {
|
|
83
|
+
Self::LoadError(e.into())
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
impl From<toml::ser::Error> for ConfigError {
|
|
88
|
+
fn from(e: toml::ser::Error) -> Self {
|
|
89
|
+
Self::LoadError(e.into())
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// A source for configuration or a TLS certificate/key, from a path or raw data.
|
|
94
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
95
|
+
pub enum DataSource {
|
|
96
|
+
Path(String),
|
|
97
|
+
Data(Vec<u8>),
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// ClientConfig represents a client config file.
|
|
101
|
+
#[derive(Debug, Clone, PartialEq, Default)]
|
|
102
|
+
pub struct ClientConfig {
|
|
103
|
+
/// Profiles, keyed by profile name
|
|
104
|
+
pub profiles: HashMap<String, ClientConfigProfile>,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/// ClientConfigProfile is profile-level configuration for a client.
|
|
108
|
+
#[derive(Debug, Clone, PartialEq, Default)]
|
|
109
|
+
pub struct ClientConfigProfile {
|
|
110
|
+
/// Client address
|
|
111
|
+
pub address: Option<String>,
|
|
112
|
+
|
|
113
|
+
/// Client namespace
|
|
114
|
+
pub namespace: Option<String>,
|
|
115
|
+
|
|
116
|
+
/// Client API key. If present and TLS field is None or present and not disabled (i.e. without Disabled as true),
|
|
117
|
+
/// TLS is defaulted to enabled.
|
|
118
|
+
pub api_key: Option<String>,
|
|
119
|
+
|
|
120
|
+
/// Optional client TLS config.
|
|
121
|
+
pub tls: Option<ClientConfigTLS>,
|
|
122
|
+
|
|
123
|
+
/// Optional client codec config.
|
|
124
|
+
pub codec: Option<ClientConfigCodec>,
|
|
125
|
+
|
|
126
|
+
/// Client gRPC metadata (aka headers). When loading from TOML and env var, or writing to TOML, the keys are
|
|
127
|
+
/// lowercased and underscores are replaced with hyphens. This is used for deduplicating/overriding too, so manually
|
|
128
|
+
/// set values that are not normalized may not get overridden with [ClientConfigProfile::apply_env_vars].
|
|
129
|
+
pub grpc_meta: HashMap<String, String>,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// ClientConfigTLS is TLS configuration for a client.
|
|
133
|
+
#[derive(Debug, Clone, PartialEq, Default)]
|
|
134
|
+
pub struct ClientConfigTLS {
|
|
135
|
+
/// If true, TLS is explicitly disabled. If false/unset, whether TLS is enabled or not depends on other factors such
|
|
136
|
+
/// as whether this struct is present or None, and whether API key exists (which enables TLS by default).
|
|
137
|
+
pub disabled: bool,
|
|
138
|
+
|
|
139
|
+
/// Client certificate source.
|
|
140
|
+
pub client_cert: Option<DataSource>,
|
|
141
|
+
|
|
142
|
+
/// Client key source.
|
|
143
|
+
pub client_key: Option<DataSource>,
|
|
144
|
+
|
|
145
|
+
/// Server CA certificate source.
|
|
146
|
+
pub server_ca_cert: Option<DataSource>,
|
|
147
|
+
|
|
148
|
+
/// SNI override
|
|
149
|
+
pub server_name: Option<String>,
|
|
150
|
+
|
|
151
|
+
/// True if host verification should be skipped
|
|
152
|
+
pub disable_host_verification: bool,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/// Codec configuration for a client
|
|
156
|
+
#[derive(Debug, Clone, PartialEq, Default)]
|
|
157
|
+
pub struct ClientConfigCodec {
|
|
158
|
+
/// Remote endpoint for the codec
|
|
159
|
+
pub endpoint: Option<String>,
|
|
160
|
+
|
|
161
|
+
/// Auth for the codec
|
|
162
|
+
pub auth: Option<String>,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// Options for loading client configuration
|
|
166
|
+
#[derive(Debug, Default)]
|
|
167
|
+
pub struct LoadClientConfigOptions {
|
|
168
|
+
/// Where to load config from. If unset, will try env vars then default path.
|
|
169
|
+
pub config_source: Option<DataSource>,
|
|
170
|
+
|
|
171
|
+
/// If true, will error if there are unrecognized keys
|
|
172
|
+
pub config_file_strict: bool,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// Options for loading a client configuration profile
|
|
176
|
+
#[derive(Debug, Default)]
|
|
177
|
+
pub struct LoadClientConfigProfileOptions {
|
|
178
|
+
/// Where to load config from. If unset, will try env vars then default path.
|
|
179
|
+
pub config_source: Option<DataSource>,
|
|
180
|
+
|
|
181
|
+
/// Specific profile to use
|
|
182
|
+
pub config_file_profile: Option<String>,
|
|
183
|
+
|
|
184
|
+
/// If true, will error if there are unrecognized keys.
|
|
185
|
+
pub config_file_strict: bool,
|
|
186
|
+
|
|
187
|
+
/// Disable loading from file
|
|
188
|
+
pub disable_file: bool,
|
|
189
|
+
|
|
190
|
+
/// Disable loading from environment variables
|
|
191
|
+
pub disable_env: bool,
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// Options for parsing TOML configuration
|
|
195
|
+
#[derive(Debug, Default)]
|
|
196
|
+
pub struct ClientConfigFromTOMLOptions {
|
|
197
|
+
/// If true, will error if there are unrecognized keys.
|
|
198
|
+
pub strict: bool,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/// Read bytes from a file path, returning Ok(None) if it doesn't exist
|
|
202
|
+
fn read_path_bytes(path: &str) -> Result<Option<Vec<u8>>, ConfigError> {
|
|
203
|
+
if !Path::new(path).exists() {
|
|
204
|
+
return Ok(None);
|
|
205
|
+
}
|
|
206
|
+
match fs::read(path) {
|
|
207
|
+
Ok(data) => Ok(Some(data)),
|
|
208
|
+
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
|
209
|
+
Err(e) => Err(ConfigError::LoadError(e.into())),
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
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.
|
|
216
|
+
pub fn load_client_config(
|
|
217
|
+
options: LoadClientConfigOptions,
|
|
218
|
+
env_vars: Option<&HashMap<String, String>>,
|
|
219
|
+
) -> Result<ClientConfig, ConfigError> {
|
|
220
|
+
// Get which bytes to load from TOML
|
|
221
|
+
let toml_data = match options.config_source {
|
|
222
|
+
Some(DataSource::Data(d)) => Some(d),
|
|
223
|
+
Some(DataSource::Path(p)) => read_path_bytes(&p)?,
|
|
224
|
+
None => {
|
|
225
|
+
let file_path = env_vars
|
|
226
|
+
.and_then(|vars| vars.get("TEMPORAL_CONFIG_FILE"))
|
|
227
|
+
.filter(|p| !p.is_empty())
|
|
228
|
+
.cloned()
|
|
229
|
+
.map(Ok)
|
|
230
|
+
.unwrap_or_else(get_default_config_file_path)?;
|
|
231
|
+
read_path_bytes(&file_path)?
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
if let Some(data) = toml_data {
|
|
236
|
+
ClientConfig::from_toml(
|
|
237
|
+
&data,
|
|
238
|
+
ClientConfigFromTOMLOptions {
|
|
239
|
+
strict: options.config_file_strict,
|
|
240
|
+
},
|
|
241
|
+
)
|
|
242
|
+
} else {
|
|
243
|
+
Ok(ClientConfig::default())
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// Load a specific client configuration profile
|
|
248
|
+
pub fn load_client_config_profile(
|
|
249
|
+
options: LoadClientConfigProfileOptions,
|
|
250
|
+
env_vars: Option<&HashMap<String, String>>,
|
|
251
|
+
) -> Result<ClientConfigProfile, ConfigError> {
|
|
252
|
+
if options.disable_file && options.disable_env {
|
|
253
|
+
return Err(ConfigError::InvalidConfig(
|
|
254
|
+
"Cannot disable both file and environment loading".to_string(),
|
|
255
|
+
));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let mut profile = if options.disable_file {
|
|
259
|
+
ClientConfigProfile::default()
|
|
260
|
+
} else {
|
|
261
|
+
// Load the full config
|
|
262
|
+
let config = load_client_config(
|
|
263
|
+
LoadClientConfigOptions {
|
|
264
|
+
config_source: options.config_source,
|
|
265
|
+
config_file_strict: options.config_file_strict,
|
|
266
|
+
},
|
|
267
|
+
env_vars,
|
|
268
|
+
)?;
|
|
269
|
+
|
|
270
|
+
// 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)),
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
if let Some(prof) = config.profiles.get(&profile_name) {
|
|
281
|
+
Ok(prof.clone())
|
|
282
|
+
} else if !profile_unset {
|
|
283
|
+
Err(ConfigError::ProfileNotFound(profile_name))
|
|
284
|
+
} else {
|
|
285
|
+
Ok(ClientConfigProfile::default())
|
|
286
|
+
}?
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// Apply environment variables if not disabled
|
|
290
|
+
if !options.disable_env {
|
|
291
|
+
if let Some(vars) = env_vars {
|
|
292
|
+
profile.apply_env_vars(vars)?;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Apply API key → TLS auto-enabling logic
|
|
297
|
+
profile.apply_api_key_tls_logic();
|
|
298
|
+
|
|
299
|
+
Ok(profile)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
impl ClientConfig {
|
|
303
|
+
/// Load configuration from a TOML string with options. This will replace all profiles within,
|
|
304
|
+
/// it does not do any form of merging.
|
|
305
|
+
pub fn from_toml(
|
|
306
|
+
toml_bytes: &[u8],
|
|
307
|
+
options: ClientConfigFromTOMLOptions,
|
|
308
|
+
) -> Result<Self, ConfigError> {
|
|
309
|
+
use strict::StrictTomlClientConfig;
|
|
310
|
+
let toml_str = std::str::from_utf8(toml_bytes)?;
|
|
311
|
+
let mut conf = ClientConfig::default();
|
|
312
|
+
if toml_str.trim().is_empty() {
|
|
313
|
+
return Ok(conf);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if options.strict {
|
|
317
|
+
let toml_conf: StrictTomlClientConfig = toml::from_str(toml_str)?;
|
|
318
|
+
toml_conf.apply_to_client_config(&mut conf)?;
|
|
319
|
+
} else {
|
|
320
|
+
let toml_conf: TomlClientConfig = toml::from_str(toml_str)?;
|
|
321
|
+
toml_conf.apply_to_client_config(&mut conf)?;
|
|
322
|
+
}
|
|
323
|
+
Ok(conf)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/// Convert configuration to TOML string.
|
|
327
|
+
pub fn to_toml(&self) -> Result<Vec<u8>, ConfigError> {
|
|
328
|
+
let mut toml_conf = TomlClientConfig::new();
|
|
329
|
+
toml_conf.populate_from_client_config(self);
|
|
330
|
+
Ok(toml::to_string_pretty(&toml_conf)?.into_bytes())
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
impl ClientConfigProfile {
|
|
335
|
+
/// Apply environment variable overrides to this profile
|
|
336
|
+
pub fn apply_env_vars(
|
|
337
|
+
&mut self,
|
|
338
|
+
env_vars: &HashMap<String, String>,
|
|
339
|
+
) -> Result<(), ConfigError> {
|
|
340
|
+
// Apply basic settings
|
|
341
|
+
if let Some(address) = env_vars.get("TEMPORAL_ADDRESS") {
|
|
342
|
+
self.address = Some(address.clone());
|
|
343
|
+
}
|
|
344
|
+
if let Some(namespace) = env_vars.get("TEMPORAL_NAMESPACE") {
|
|
345
|
+
self.namespace = Some(namespace.clone());
|
|
346
|
+
}
|
|
347
|
+
if let Some(api_key) = env_vars.get("TEMPORAL_API_KEY") {
|
|
348
|
+
self.api_key = Some(api_key.clone());
|
|
349
|
+
}
|
|
350
|
+
|
|
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)?;
|
|
354
|
+
|
|
355
|
+
Ok(())
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
fn apply_tls_env_vars(
|
|
359
|
+
&mut self,
|
|
360
|
+
env_vars: &HashMap<String, String>,
|
|
361
|
+
) -> Result<(), ConfigError> {
|
|
362
|
+
const TLS_ENV_VARS: &[&str] = &[
|
|
363
|
+
"TEMPORAL_TLS",
|
|
364
|
+
"TEMPORAL_TLS_CLIENT_CERT_PATH",
|
|
365
|
+
"TEMPORAL_TLS_CLIENT_CERT_DATA",
|
|
366
|
+
"TEMPORAL_TLS_CLIENT_KEY_PATH",
|
|
367
|
+
"TEMPORAL_TLS_CLIENT_KEY_DATA",
|
|
368
|
+
"TEMPORAL_TLS_SERVER_CA_CERT_PATH",
|
|
369
|
+
"TEMPORAL_TLS_SERVER_CA_CERT_DATA",
|
|
370
|
+
"TEMPORAL_TLS_SERVER_NAME",
|
|
371
|
+
"TEMPORAL_TLS_DISABLE_HOST_VERIFICATION",
|
|
372
|
+
];
|
|
373
|
+
|
|
374
|
+
if TLS_ENV_VARS.iter().any(|&k| env_vars.contains_key(k)) && self.tls.is_none() {
|
|
375
|
+
self.tls = Some(ClientConfigTLS::default());
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
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
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
apply_data_source_env_var(
|
|
386
|
+
env_vars,
|
|
387
|
+
"cert",
|
|
388
|
+
"TEMPORAL_TLS_CLIENT_CERT_PATH",
|
|
389
|
+
"TEMPORAL_TLS_CLIENT_CERT_DATA",
|
|
390
|
+
&mut tls.client_cert,
|
|
391
|
+
)?;
|
|
392
|
+
apply_data_source_env_var(
|
|
393
|
+
env_vars,
|
|
394
|
+
"key",
|
|
395
|
+
"TEMPORAL_TLS_CLIENT_KEY_PATH",
|
|
396
|
+
"TEMPORAL_TLS_CLIENT_KEY_DATA",
|
|
397
|
+
&mut tls.client_key,
|
|
398
|
+
)?;
|
|
399
|
+
apply_data_source_env_var(
|
|
400
|
+
env_vars,
|
|
401
|
+
"server CA cert",
|
|
402
|
+
"TEMPORAL_TLS_SERVER_CA_CERT_PATH",
|
|
403
|
+
"TEMPORAL_TLS_SERVER_CA_CERT_DATA",
|
|
404
|
+
&mut tls.server_ca_cert,
|
|
405
|
+
)?;
|
|
406
|
+
|
|
407
|
+
if let Some(v) = env_vars.get("TEMPORAL_TLS_SERVER_NAME") {
|
|
408
|
+
tls.server_name = Some(v.clone());
|
|
409
|
+
}
|
|
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
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
Ok(())
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
fn apply_codec_env_vars(
|
|
420
|
+
&mut self,
|
|
421
|
+
env_vars: &HashMap<String, String>,
|
|
422
|
+
) -> Result<(), ConfigError> {
|
|
423
|
+
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() {
|
|
425
|
+
self.codec = Some(ClientConfigCodec::default());
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
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());
|
|
431
|
+
}
|
|
432
|
+
if let Some(auth) = env_vars.get("TEMPORAL_CODEC_AUTH") {
|
|
433
|
+
codec.auth = Some(auth.clone());
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
Ok(())
|
|
437
|
+
}
|
|
438
|
+
|
|
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());
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
Ok(())
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/// Apply automatic TLS enabling when API key is present
|
|
457
|
+
pub fn apply_api_key_tls_logic(&mut self) {
|
|
458
|
+
if self.api_key.is_some() && self.tls.is_none() {
|
|
459
|
+
// If API key is present but no TLS config exists, create one with TLS enabled
|
|
460
|
+
self.tls = Some(ClientConfigTLS::default());
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/// Helper for applying env vars to a data source.
|
|
466
|
+
fn apply_data_source_env_var(
|
|
467
|
+
env_vars: &HashMap<String, String>,
|
|
468
|
+
name: &str,
|
|
469
|
+
path_var: &str,
|
|
470
|
+
data_var: &str,
|
|
471
|
+
dest: &mut Option<DataSource>,
|
|
472
|
+
) -> Result<(), ConfigError> {
|
|
473
|
+
let has_path_env = env_vars.contains_key(path_var);
|
|
474
|
+
let has_data_env = env_vars.contains_key(data_var);
|
|
475
|
+
|
|
476
|
+
if has_path_env && has_data_env {
|
|
477
|
+
return Err(ConfigError::InvalidConfig(format!(
|
|
478
|
+
"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
|
+
)));
|
|
490
|
+
}
|
|
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
|
+
)));
|
|
502
|
+
}
|
|
503
|
+
*dest = Some(DataSource::Path(env_vars.get(path_var).unwrap().clone()));
|
|
504
|
+
}
|
|
505
|
+
Ok(())
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/// Parse a boolean value from string (supports "true", "false", "1", "0")
|
|
509
|
+
fn env_var_to_bool(s: &str) -> Option<bool> {
|
|
510
|
+
match s.to_lowercase().as_str() {
|
|
511
|
+
"true" | "1" => Some(true),
|
|
512
|
+
"false" | "0" => Some(false),
|
|
513
|
+
_ => None,
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/// Normalize gRPC metadata key (lowercase and replace underscores with hyphens)
|
|
518
|
+
fn normalize_grpc_meta_key(key: &str) -> String {
|
|
519
|
+
key.to_lowercase().replace('_', "-")
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/// Get the default configuration file path
|
|
523
|
+
fn get_default_config_file_path() -> Result<String, ConfigError> {
|
|
524
|
+
// Try to get user config directory
|
|
525
|
+
let config_dir = dirs::config_dir()
|
|
526
|
+
.ok_or_else(|| ConfigError::InvalidConfig("failed getting user config dir".to_string()))?;
|
|
527
|
+
|
|
528
|
+
let path = config_dir.join("temporalio").join(DEFAULT_CONFIG_FILE);
|
|
529
|
+
Ok(path.to_string_lossy().to_string())
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
533
|
+
struct TomlClientConfig {
|
|
534
|
+
#[serde(default, rename = "profile")]
|
|
535
|
+
profiles: HashMap<String, TomlClientConfigProfile>,
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
impl TomlClientConfig {
|
|
539
|
+
fn new() -> Self {
|
|
540
|
+
Self {
|
|
541
|
+
profiles: HashMap::new(),
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
fn apply_to_client_config(&self, conf: &mut ClientConfig) -> Result<(), ConfigError> {
|
|
546
|
+
conf.profiles = HashMap::with_capacity(self.profiles.len());
|
|
547
|
+
for (k, v) in &self.profiles {
|
|
548
|
+
conf.profiles.insert(k.clone(), v.to_client_config()?);
|
|
549
|
+
}
|
|
550
|
+
Ok(())
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
fn populate_from_client_config(&mut self, conf: &ClientConfig) {
|
|
554
|
+
self.profiles = HashMap::with_capacity(conf.profiles.len());
|
|
555
|
+
for (k, v) in &conf.profiles {
|
|
556
|
+
let mut prof = TomlClientConfigProfile::new();
|
|
557
|
+
prof.populate_from_client_config(v);
|
|
558
|
+
self.profiles.insert(k.clone(), prof);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
564
|
+
struct TomlClientConfigProfile {
|
|
565
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
566
|
+
address: Option<String>,
|
|
567
|
+
|
|
568
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
569
|
+
namespace: Option<String>,
|
|
570
|
+
|
|
571
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
572
|
+
api_key: Option<String>,
|
|
573
|
+
|
|
574
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
575
|
+
tls: Option<TomlClientConfigTLS>,
|
|
576
|
+
|
|
577
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
578
|
+
codec: Option<TomlClientConfigCodec>,
|
|
579
|
+
|
|
580
|
+
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
|
581
|
+
grpc_meta: HashMap<String, String>,
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
impl TomlClientConfigProfile {
|
|
585
|
+
fn new() -> Self {
|
|
586
|
+
Self {
|
|
587
|
+
address: None,
|
|
588
|
+
namespace: None,
|
|
589
|
+
api_key: None,
|
|
590
|
+
tls: None,
|
|
591
|
+
codec: None,
|
|
592
|
+
grpc_meta: HashMap::new(),
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
fn to_client_config(&self) -> Result<ClientConfigProfile, ConfigError> {
|
|
596
|
+
let mut ret = ClientConfigProfile {
|
|
597
|
+
address: self.address.clone(),
|
|
598
|
+
namespace: self.namespace.clone(),
|
|
599
|
+
api_key: self.api_key.clone(),
|
|
600
|
+
tls: self
|
|
601
|
+
.tls
|
|
602
|
+
.as_ref()
|
|
603
|
+
.map(|tls| tls.to_client_config())
|
|
604
|
+
.transpose()?,
|
|
605
|
+
codec: self.codec.as_ref().map(|codec| codec.to_client_config()),
|
|
606
|
+
grpc_meta: HashMap::new(),
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
if !self.grpc_meta.is_empty() {
|
|
610
|
+
ret.grpc_meta = HashMap::with_capacity(self.grpc_meta.len());
|
|
611
|
+
for (k, v) in &self.grpc_meta {
|
|
612
|
+
ret.grpc_meta.insert(normalize_grpc_meta_key(k), v.clone());
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
Ok(ret)
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
fn populate_from_client_config(&mut self, conf: &ClientConfigProfile) {
|
|
619
|
+
self.address = conf.address.clone();
|
|
620
|
+
self.namespace = conf.namespace.clone();
|
|
621
|
+
self.api_key = conf.api_key.clone();
|
|
622
|
+
|
|
623
|
+
if let Some(ref tls_conf) = conf.tls {
|
|
624
|
+
let mut toml_tls = TomlClientConfigTLS::new();
|
|
625
|
+
toml_tls.populate_from_client_config(tls_conf);
|
|
626
|
+
self.tls = Some(toml_tls);
|
|
627
|
+
} else {
|
|
628
|
+
self.tls = None;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if let Some(ref codec_conf) = conf.codec {
|
|
632
|
+
let mut toml_codec = TomlClientConfigCodec::new();
|
|
633
|
+
toml_codec.populate_from_client_config(codec_conf);
|
|
634
|
+
self.codec = Some(toml_codec);
|
|
635
|
+
} else {
|
|
636
|
+
self.codec = None;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if !conf.grpc_meta.is_empty() {
|
|
640
|
+
self.grpc_meta = HashMap::with_capacity(conf.grpc_meta.len());
|
|
641
|
+
for (k, v) in &conf.grpc_meta {
|
|
642
|
+
self.grpc_meta.insert(normalize_grpc_meta_key(k), v.clone());
|
|
643
|
+
}
|
|
644
|
+
} else {
|
|
645
|
+
self.grpc_meta.clear();
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
651
|
+
struct TomlClientConfigTLS {
|
|
652
|
+
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
|
653
|
+
disabled: bool,
|
|
654
|
+
|
|
655
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
656
|
+
client_cert_path: Option<String>,
|
|
657
|
+
|
|
658
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
659
|
+
client_cert_data: Option<String>, // String in TOML, not Vec<u8>
|
|
660
|
+
|
|
661
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
662
|
+
client_key_path: Option<String>,
|
|
663
|
+
|
|
664
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
665
|
+
client_key_data: Option<String>, // String in TOML, not Vec<u8>
|
|
666
|
+
|
|
667
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
668
|
+
server_ca_cert_path: Option<String>,
|
|
669
|
+
|
|
670
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
671
|
+
server_ca_cert_data: Option<String>, // String in TOML, not Vec<u8>
|
|
672
|
+
|
|
673
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
674
|
+
server_name: Option<String>,
|
|
675
|
+
|
|
676
|
+
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
|
677
|
+
disable_host_verification: bool,
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
impl TomlClientConfigTLS {
|
|
681
|
+
fn new() -> Self {
|
|
682
|
+
Self {
|
|
683
|
+
disabled: false,
|
|
684
|
+
client_cert_path: None,
|
|
685
|
+
client_cert_data: None,
|
|
686
|
+
client_key_path: None,
|
|
687
|
+
client_key_data: None,
|
|
688
|
+
server_ca_cert_path: None,
|
|
689
|
+
server_ca_cert_data: None,
|
|
690
|
+
server_name: None,
|
|
691
|
+
disable_host_verification: false,
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
fn to_client_config(&self) -> Result<ClientConfigTLS, ConfigError> {
|
|
696
|
+
if self.client_cert_path.is_some() && self.client_cert_data.is_some() {
|
|
697
|
+
return Err(ConfigError::InvalidConfig(
|
|
698
|
+
"Cannot specify both client_cert_path and client_cert_data".to_string(),
|
|
699
|
+
));
|
|
700
|
+
}
|
|
701
|
+
if self.client_key_path.is_some() && self.client_key_data.is_some() {
|
|
702
|
+
return Err(ConfigError::InvalidConfig(
|
|
703
|
+
"Cannot specify both client_key_path and client_key_data".to_string(),
|
|
704
|
+
));
|
|
705
|
+
}
|
|
706
|
+
if self.server_ca_cert_path.is_some() && self.server_ca_cert_data.is_some() {
|
|
707
|
+
return Err(ConfigError::InvalidConfig(
|
|
708
|
+
"Cannot specify both server_ca_cert_path and server_ca_cert_data".to_string(),
|
|
709
|
+
));
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
let string_to_bytes = |s: &Option<String>| {
|
|
713
|
+
s.as_ref().and_then(|val| {
|
|
714
|
+
if val.is_empty() {
|
|
715
|
+
None
|
|
716
|
+
} else {
|
|
717
|
+
Some(val.as_bytes().to_vec())
|
|
718
|
+
}
|
|
719
|
+
})
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
Ok(ClientConfigTLS {
|
|
723
|
+
disabled: self.disabled,
|
|
724
|
+
client_cert: self
|
|
725
|
+
.client_cert_path
|
|
726
|
+
.clone()
|
|
727
|
+
.map(DataSource::Path)
|
|
728
|
+
.or_else(|| string_to_bytes(&self.client_cert_data).map(DataSource::Data)),
|
|
729
|
+
client_key: self
|
|
730
|
+
.client_key_path
|
|
731
|
+
.clone()
|
|
732
|
+
.map(DataSource::Path)
|
|
733
|
+
.or_else(|| string_to_bytes(&self.client_key_data).map(DataSource::Data)),
|
|
734
|
+
server_ca_cert: self
|
|
735
|
+
.server_ca_cert_path
|
|
736
|
+
.clone()
|
|
737
|
+
.map(DataSource::Path)
|
|
738
|
+
.or_else(|| string_to_bytes(&self.server_ca_cert_data).map(DataSource::Data)),
|
|
739
|
+
server_name: self.server_name.clone(),
|
|
740
|
+
disable_host_verification: self.disable_host_verification,
|
|
741
|
+
})
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
fn populate_from_client_config(&mut self, conf: &ClientConfigTLS) {
|
|
745
|
+
self.disabled = conf.disabled;
|
|
746
|
+
if let Some(ref cert_source) = conf.client_cert {
|
|
747
|
+
match cert_source {
|
|
748
|
+
DataSource::Path(p) => self.client_cert_path = Some(p.clone()),
|
|
749
|
+
DataSource::Data(d) => {
|
|
750
|
+
self.client_cert_data = Some(String::from_utf8_lossy(d).into_owned())
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if let Some(ref key_source) = conf.client_key {
|
|
755
|
+
match key_source {
|
|
756
|
+
DataSource::Path(p) => self.client_key_path = Some(p.clone()),
|
|
757
|
+
DataSource::Data(d) => {
|
|
758
|
+
self.client_key_data = Some(String::from_utf8_lossy(d).into_owned())
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if let Some(ref ca_source) = conf.server_ca_cert {
|
|
763
|
+
match ca_source {
|
|
764
|
+
DataSource::Path(p) => self.server_ca_cert_path = Some(p.clone()),
|
|
765
|
+
DataSource::Data(d) => {
|
|
766
|
+
self.server_ca_cert_data = Some(String::from_utf8_lossy(d).into_owned())
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
self.server_name = conf.server_name.clone();
|
|
771
|
+
self.disable_host_verification = conf.disable_host_verification;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
776
|
+
struct TomlClientConfigCodec {
|
|
777
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
778
|
+
endpoint: Option<String>,
|
|
779
|
+
|
|
780
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
781
|
+
auth: Option<String>,
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
impl TomlClientConfigCodec {
|
|
785
|
+
fn new() -> Self {
|
|
786
|
+
Self {
|
|
787
|
+
endpoint: None,
|
|
788
|
+
auth: None,
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
fn to_client_config(&self) -> ClientConfigCodec {
|
|
792
|
+
ClientConfigCodec {
|
|
793
|
+
endpoint: self.endpoint.clone(),
|
|
794
|
+
auth: self.auth.clone(),
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
fn populate_from_client_config(&mut self, conf: &ClientConfigCodec) {
|
|
799
|
+
self.endpoint = conf.endpoint.clone();
|
|
800
|
+
self.auth = conf.auth.clone();
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
mod strict {
|
|
805
|
+
use super::{
|
|
806
|
+
ClientConfig, ClientConfigCodec, ClientConfigProfile, ClientConfigTLS, ConfigError,
|
|
807
|
+
DataSource, normalize_grpc_meta_key,
|
|
808
|
+
};
|
|
809
|
+
use serde::Deserialize;
|
|
810
|
+
use std::collections::HashMap;
|
|
811
|
+
|
|
812
|
+
#[derive(Debug, Deserialize)]
|
|
813
|
+
#[serde(deny_unknown_fields)]
|
|
814
|
+
pub(crate) struct StrictTomlClientConfig {
|
|
815
|
+
#[serde(default, rename = "profile")]
|
|
816
|
+
profiles: HashMap<String, StrictTomlClientConfigProfile>,
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
impl StrictTomlClientConfig {
|
|
820
|
+
pub(crate) fn apply_to_client_config(
|
|
821
|
+
self,
|
|
822
|
+
conf: &mut ClientConfig,
|
|
823
|
+
) -> Result<(), ConfigError> {
|
|
824
|
+
conf.profiles = HashMap::with_capacity(self.profiles.len());
|
|
825
|
+
for (k, v) in self.profiles {
|
|
826
|
+
conf.profiles.insert(k, v.into_client_config()?);
|
|
827
|
+
}
|
|
828
|
+
Ok(())
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
#[derive(Debug, Deserialize)]
|
|
833
|
+
#[serde(deny_unknown_fields)]
|
|
834
|
+
struct StrictTomlClientConfigProfile {
|
|
835
|
+
#[serde(default)]
|
|
836
|
+
address: Option<String>,
|
|
837
|
+
#[serde(default)]
|
|
838
|
+
namespace: Option<String>,
|
|
839
|
+
#[serde(default)]
|
|
840
|
+
api_key: Option<String>,
|
|
841
|
+
#[serde(default)]
|
|
842
|
+
tls: Option<StrictTomlClientConfigTLS>,
|
|
843
|
+
#[serde(default)]
|
|
844
|
+
codec: Option<StrictTomlClientConfigCodec>,
|
|
845
|
+
#[serde(default)]
|
|
846
|
+
grpc_meta: HashMap<String, String>,
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
impl StrictTomlClientConfigProfile {
|
|
850
|
+
fn into_client_config(self) -> Result<ClientConfigProfile, ConfigError> {
|
|
851
|
+
let mut ret = ClientConfigProfile {
|
|
852
|
+
address: self.address,
|
|
853
|
+
namespace: self.namespace,
|
|
854
|
+
api_key: self.api_key,
|
|
855
|
+
tls: self.tls.map(|tls| tls.into_client_config()).transpose()?,
|
|
856
|
+
codec: self.codec.map(|codec| codec.into_client_config()),
|
|
857
|
+
grpc_meta: HashMap::new(),
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
if !self.grpc_meta.is_empty() {
|
|
861
|
+
ret.grpc_meta = HashMap::with_capacity(self.grpc_meta.len());
|
|
862
|
+
for (k, v) in self.grpc_meta {
|
|
863
|
+
ret.grpc_meta.insert(normalize_grpc_meta_key(&k), v);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
Ok(ret)
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
#[derive(Debug, Deserialize)]
|
|
871
|
+
#[serde(deny_unknown_fields)]
|
|
872
|
+
struct StrictTomlClientConfigTLS {
|
|
873
|
+
#[serde(default)]
|
|
874
|
+
disabled: bool,
|
|
875
|
+
#[serde(default)]
|
|
876
|
+
client_cert_path: Option<String>,
|
|
877
|
+
#[serde(default)]
|
|
878
|
+
client_cert_data: Option<String>,
|
|
879
|
+
#[serde(default)]
|
|
880
|
+
client_key_path: Option<String>,
|
|
881
|
+
#[serde(default)]
|
|
882
|
+
client_key_data: Option<String>,
|
|
883
|
+
#[serde(default)]
|
|
884
|
+
server_ca_cert_path: Option<String>,
|
|
885
|
+
#[serde(default)]
|
|
886
|
+
server_ca_cert_data: Option<String>,
|
|
887
|
+
#[serde(default)]
|
|
888
|
+
server_name: Option<String>,
|
|
889
|
+
#[serde(default)]
|
|
890
|
+
disable_host_verification: bool,
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
impl StrictTomlClientConfigTLS {
|
|
894
|
+
fn into_client_config(self) -> Result<ClientConfigTLS, ConfigError> {
|
|
895
|
+
if self.client_cert_path.is_some() && self.client_cert_data.is_some() {
|
|
896
|
+
return Err(ConfigError::InvalidConfig(
|
|
897
|
+
"Cannot specify both client_cert_path and client_cert_data".to_string(),
|
|
898
|
+
));
|
|
899
|
+
}
|
|
900
|
+
if self.client_key_path.is_some() && self.client_key_data.is_some() {
|
|
901
|
+
return Err(ConfigError::InvalidConfig(
|
|
902
|
+
"Cannot specify both client_key_path and client_key_data".to_string(),
|
|
903
|
+
));
|
|
904
|
+
}
|
|
905
|
+
if self.server_ca_cert_path.is_some() && self.server_ca_cert_data.is_some() {
|
|
906
|
+
return Err(ConfigError::InvalidConfig(
|
|
907
|
+
"Cannot specify both server_ca_cert_path and server_ca_cert_data".to_string(),
|
|
908
|
+
));
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
let string_to_bytes = |s: Option<String>| {
|
|
912
|
+
s.and_then(|val| {
|
|
913
|
+
if val.is_empty() {
|
|
914
|
+
None
|
|
915
|
+
} else {
|
|
916
|
+
Some(val.as_bytes().to_vec())
|
|
917
|
+
}
|
|
918
|
+
})
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
Ok(ClientConfigTLS {
|
|
922
|
+
disabled: self.disabled,
|
|
923
|
+
client_cert: self
|
|
924
|
+
.client_cert_path
|
|
925
|
+
.map(DataSource::Path)
|
|
926
|
+
.or_else(|| string_to_bytes(self.client_cert_data).map(DataSource::Data)),
|
|
927
|
+
client_key: self
|
|
928
|
+
.client_key_path
|
|
929
|
+
.map(DataSource::Path)
|
|
930
|
+
.or_else(|| string_to_bytes(self.client_key_data).map(DataSource::Data)),
|
|
931
|
+
server_ca_cert: self
|
|
932
|
+
.server_ca_cert_path
|
|
933
|
+
.map(DataSource::Path)
|
|
934
|
+
.or_else(|| string_to_bytes(self.server_ca_cert_data).map(DataSource::Data)),
|
|
935
|
+
server_name: self.server_name,
|
|
936
|
+
disable_host_verification: self.disable_host_verification,
|
|
937
|
+
})
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
#[derive(Debug, Deserialize)]
|
|
942
|
+
#[serde(deny_unknown_fields)]
|
|
943
|
+
struct StrictTomlClientConfigCodec {
|
|
944
|
+
#[serde(default)]
|
|
945
|
+
endpoint: Option<String>,
|
|
946
|
+
#[serde(default)]
|
|
947
|
+
auth: Option<String>,
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
impl StrictTomlClientConfigCodec {
|
|
951
|
+
fn into_client_config(self) -> ClientConfigCodec {
|
|
952
|
+
ClientConfigCodec {
|
|
953
|
+
endpoint: self.endpoint,
|
|
954
|
+
auth: self.auth,
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
#[cfg(test)]
|
|
961
|
+
mod tests {
|
|
962
|
+
use super::*;
|
|
963
|
+
use std::io::Write;
|
|
964
|
+
use tempfile::NamedTempFile;
|
|
965
|
+
|
|
966
|
+
#[test]
|
|
967
|
+
fn test_client_config_toml_multiple_profiles() {
|
|
968
|
+
let toml_str = r#"
|
|
969
|
+
[profile.default]
|
|
970
|
+
address = "localhost:7233"
|
|
971
|
+
namespace = "default"
|
|
972
|
+
api_key = "test-key"
|
|
973
|
+
|
|
974
|
+
[profile.default.tls]
|
|
975
|
+
disabled = false
|
|
976
|
+
client_cert_path = "/path/to/cert"
|
|
977
|
+
|
|
978
|
+
[profile.prod]
|
|
979
|
+
address = "prod.temporal.io:7233"
|
|
980
|
+
namespace = "production"
|
|
981
|
+
"#;
|
|
982
|
+
|
|
983
|
+
let config = ClientConfig::from_toml(toml_str.as_bytes(), Default::default()).unwrap();
|
|
984
|
+
|
|
985
|
+
let default_profile = config.profiles.get("default").unwrap();
|
|
986
|
+
assert_eq!(default_profile.address.as_ref().unwrap(), "localhost:7233");
|
|
987
|
+
assert_eq!(default_profile.namespace.as_ref().unwrap(), "default");
|
|
988
|
+
assert_eq!(default_profile.api_key.as_ref().unwrap(), "test-key");
|
|
989
|
+
|
|
990
|
+
let tls = default_profile.tls.as_ref().unwrap();
|
|
991
|
+
assert!(!tls.disabled);
|
|
992
|
+
assert_eq!(
|
|
993
|
+
tls.client_cert,
|
|
994
|
+
Some(DataSource::Path("/path/to/cert".to_string()))
|
|
995
|
+
);
|
|
996
|
+
|
|
997
|
+
let prod_profile = config.profiles.get("prod").unwrap();
|
|
998
|
+
assert_eq!(
|
|
999
|
+
prod_profile.address.as_ref().unwrap(),
|
|
1000
|
+
"prod.temporal.io:7233"
|
|
1001
|
+
);
|
|
1002
|
+
assert_eq!(prod_profile.namespace.as_ref().unwrap(), "production");
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
#[test]
|
|
1006
|
+
fn test_client_config_toml_roundtrip() {
|
|
1007
|
+
let mut prof = ClientConfigProfile {
|
|
1008
|
+
address: Some("addr".to_string()),
|
|
1009
|
+
namespace: Some("ns".to_string()),
|
|
1010
|
+
api_key: Some("key".to_string()),
|
|
1011
|
+
..Default::default()
|
|
1012
|
+
};
|
|
1013
|
+
prof.grpc_meta.insert("k".to_string(), "v".to_string());
|
|
1014
|
+
|
|
1015
|
+
let tls = ClientConfigTLS {
|
|
1016
|
+
client_cert: Some(DataSource::Data(b"cert".to_vec())),
|
|
1017
|
+
server_ca_cert: Some(DataSource::Data(b"ca".to_vec())),
|
|
1018
|
+
..Default::default()
|
|
1019
|
+
};
|
|
1020
|
+
prof.tls = Some(tls);
|
|
1021
|
+
|
|
1022
|
+
let mut conf = ClientConfig::default();
|
|
1023
|
+
conf.profiles.insert("default".to_string(), prof);
|
|
1024
|
+
|
|
1025
|
+
let toml_bytes = conf.to_toml().unwrap();
|
|
1026
|
+
let new_conf = ClientConfig::from_toml(&toml_bytes, Default::default()).unwrap();
|
|
1027
|
+
assert_eq!(conf, new_conf);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
#[test]
|
|
1031
|
+
fn test_load_client_config_profile_from_file() {
|
|
1032
|
+
// Create temporary file with test data
|
|
1033
|
+
let mut temp_file = NamedTempFile::new().unwrap();
|
|
1034
|
+
writeln!(
|
|
1035
|
+
temp_file,
|
|
1036
|
+
r#"
|
|
1037
|
+
[profile.default]
|
|
1038
|
+
address = "my-address"
|
|
1039
|
+
namespace = "my-namespace"
|
|
1040
|
+
"#
|
|
1041
|
+
)
|
|
1042
|
+
.unwrap();
|
|
1043
|
+
|
|
1044
|
+
// Test explicit file path
|
|
1045
|
+
let options = LoadClientConfigProfileOptions {
|
|
1046
|
+
config_source: Some(DataSource::Path(
|
|
1047
|
+
temp_file.path().to_string_lossy().to_string(),
|
|
1048
|
+
)),
|
|
1049
|
+
..Default::default()
|
|
1050
|
+
};
|
|
1051
|
+
|
|
1052
|
+
let profile = load_client_config_profile(options, None).unwrap();
|
|
1053
|
+
assert_eq!(profile.address.as_ref().unwrap(), "my-address");
|
|
1054
|
+
assert_eq!(profile.namespace.as_ref().unwrap(), "my-namespace");
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
#[test]
|
|
1058
|
+
fn test_load_client_config_profile_from_env_file_path() {
|
|
1059
|
+
// Create temporary file
|
|
1060
|
+
let mut temp_file = NamedTempFile::new().unwrap();
|
|
1061
|
+
writeln!(
|
|
1062
|
+
temp_file,
|
|
1063
|
+
r#"
|
|
1064
|
+
[profile.default]
|
|
1065
|
+
address = "my-address"
|
|
1066
|
+
namespace = "my-namespace"
|
|
1067
|
+
"#
|
|
1068
|
+
)
|
|
1069
|
+
.unwrap();
|
|
1070
|
+
|
|
1071
|
+
// Test loading via TEMPORAL_CONFIG_FILE env var
|
|
1072
|
+
let mut vars = HashMap::new();
|
|
1073
|
+
vars.insert(
|
|
1074
|
+
"TEMPORAL_CONFIG_FILE".to_string(),
|
|
1075
|
+
temp_file.path().to_string_lossy().to_string(),
|
|
1076
|
+
);
|
|
1077
|
+
|
|
1078
|
+
let options = LoadClientConfigProfileOptions {
|
|
1079
|
+
..Default::default()
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
let profile = load_client_config_profile(options, Some(&vars)).unwrap();
|
|
1083
|
+
assert_eq!(profile.address.as_ref().unwrap(), "my-address");
|
|
1084
|
+
assert_eq!(profile.namespace.as_ref().unwrap(), "my-namespace");
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
#[test]
|
|
1088
|
+
fn test_load_client_config_profile_with_env_overrides() {
|
|
1089
|
+
let toml_str = r#"
|
|
1090
|
+
[profile.default]
|
|
1091
|
+
address = "my-address"
|
|
1092
|
+
namespace = "my-namespace"
|
|
1093
|
+
api_key = "my-api-key"
|
|
1094
|
+
|
|
1095
|
+
[profile.default.tls]
|
|
1096
|
+
disabled = true
|
|
1097
|
+
client_cert_path = "my-client-cert-path"
|
|
1098
|
+
client_key_path = "my-client-key-path"
|
|
1099
|
+
server_name = "my-server-name"
|
|
1100
|
+
disable_host_verification = true
|
|
1101
|
+
|
|
1102
|
+
[profile.default.codec]
|
|
1103
|
+
endpoint = "my-codec-endpoint"
|
|
1104
|
+
auth = "my-codec-auth"
|
|
1105
|
+
|
|
1106
|
+
[profile.default.grpc_meta]
|
|
1107
|
+
some-header = "some-value"
|
|
1108
|
+
some-other-header = "some-value2"
|
|
1109
|
+
"#;
|
|
1110
|
+
let mut vars = HashMap::new();
|
|
1111
|
+
vars.insert("TEMPORAL_ADDRESS".to_string(), "my-address-new".to_string());
|
|
1112
|
+
vars.insert(
|
|
1113
|
+
"TEMPORAL_NAMESPACE".to_string(),
|
|
1114
|
+
"my-namespace-new".to_string(),
|
|
1115
|
+
);
|
|
1116
|
+
vars.insert("TEMPORAL_API_KEY".to_string(), "my-api-key-new".to_string());
|
|
1117
|
+
vars.insert("TEMPORAL_TLS".to_string(), "true".to_string());
|
|
1118
|
+
vars.insert(
|
|
1119
|
+
"TEMPORAL_TLS_CLIENT_CERT_PATH".to_string(),
|
|
1120
|
+
"my-client-cert-path-new".to_string(),
|
|
1121
|
+
);
|
|
1122
|
+
vars.insert(
|
|
1123
|
+
"TEMPORAL_TLS_CLIENT_KEY_PATH".to_string(),
|
|
1124
|
+
"my-client-key-path-new".to_string(),
|
|
1125
|
+
);
|
|
1126
|
+
vars.insert(
|
|
1127
|
+
"TEMPORAL_TLS_SERVER_NAME".to_string(),
|
|
1128
|
+
"my-server-name-new".to_string(),
|
|
1129
|
+
);
|
|
1130
|
+
vars.insert(
|
|
1131
|
+
"TEMPORAL_TLS_DISABLE_HOST_VERIFICATION".to_string(),
|
|
1132
|
+
"false".to_string(),
|
|
1133
|
+
);
|
|
1134
|
+
vars.insert(
|
|
1135
|
+
"TEMPORAL_CODEC_ENDPOINT".to_string(),
|
|
1136
|
+
"my-codec-endpoint-new".to_string(),
|
|
1137
|
+
);
|
|
1138
|
+
vars.insert(
|
|
1139
|
+
"TEMPORAL_CODEC_AUTH".to_string(),
|
|
1140
|
+
"my-codec-auth-new".to_string(),
|
|
1141
|
+
);
|
|
1142
|
+
vars.insert(
|
|
1143
|
+
"TEMPORAL_GRPC_META_SOME_HEADER".to_string(),
|
|
1144
|
+
"some-value-new".to_string(),
|
|
1145
|
+
);
|
|
1146
|
+
vars.insert(
|
|
1147
|
+
"TEMPORAL_GRPC_META_SOME_THIRD_HEADER".to_string(),
|
|
1148
|
+
"some-value3-new".to_string(),
|
|
1149
|
+
);
|
|
1150
|
+
vars.insert(
|
|
1151
|
+
"TEMPORAL_GRPC_META_some_value4".to_string(),
|
|
1152
|
+
"some-value4-new".to_string(),
|
|
1153
|
+
);
|
|
1154
|
+
|
|
1155
|
+
let options = LoadClientConfigProfileOptions {
|
|
1156
|
+
config_source: Some(DataSource::Data(toml_str.as_bytes().to_vec())),
|
|
1157
|
+
config_file_profile: Some("default".to_string()),
|
|
1158
|
+
..Default::default()
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1161
|
+
let profile = load_client_config_profile(options, Some(&vars)).unwrap();
|
|
1162
|
+
assert_eq!(profile.address.as_ref().unwrap(), "my-address-new");
|
|
1163
|
+
assert_eq!(profile.namespace.as_ref().unwrap(), "my-namespace-new");
|
|
1164
|
+
assert_eq!(profile.api_key.as_ref().unwrap(), "my-api-key-new");
|
|
1165
|
+
|
|
1166
|
+
let tls = profile.tls.as_ref().unwrap();
|
|
1167
|
+
assert!(!tls.disabled); // TLS enabled via env var
|
|
1168
|
+
assert_eq!(
|
|
1169
|
+
tls.client_cert,
|
|
1170
|
+
Some(DataSource::Path("my-client-cert-path-new".to_string()))
|
|
1171
|
+
);
|
|
1172
|
+
assert_eq!(
|
|
1173
|
+
tls.client_key,
|
|
1174
|
+
Some(DataSource::Path("my-client-key-path-new".to_string()))
|
|
1175
|
+
);
|
|
1176
|
+
assert_eq!(tls.server_name.as_ref().unwrap(), "my-server-name-new");
|
|
1177
|
+
assert!(!tls.disable_host_verification);
|
|
1178
|
+
let codec = profile.codec.as_ref().unwrap();
|
|
1179
|
+
assert_eq!(codec.endpoint.as_ref().unwrap(), "my-codec-endpoint-new");
|
|
1180
|
+
assert_eq!(codec.auth.as_ref().unwrap(), "my-codec-auth-new");
|
|
1181
|
+
assert_eq!(
|
|
1182
|
+
profile.grpc_meta.get("some-header").unwrap(),
|
|
1183
|
+
"some-value-new"
|
|
1184
|
+
);
|
|
1185
|
+
assert_eq!(
|
|
1186
|
+
profile.grpc_meta.get("some-other-header").unwrap(),
|
|
1187
|
+
"some-value2"
|
|
1188
|
+
);
|
|
1189
|
+
assert_eq!(
|
|
1190
|
+
profile.grpc_meta.get("some-third-header").unwrap(),
|
|
1191
|
+
"some-value3-new"
|
|
1192
|
+
);
|
|
1193
|
+
assert_eq!(
|
|
1194
|
+
profile.grpc_meta.get("some-value4").unwrap(),
|
|
1195
|
+
"some-value4-new"
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
#[test]
|
|
1200
|
+
fn test_client_config_toml_full() {
|
|
1201
|
+
let toml_str = r#"
|
|
1202
|
+
[profile.foo]
|
|
1203
|
+
address = "my-address"
|
|
1204
|
+
namespace = "my-namespace"
|
|
1205
|
+
api_key = "my-api-key"
|
|
1206
|
+
some_future_key = "some future value not handled"
|
|
1207
|
+
|
|
1208
|
+
[profile.foo.tls]
|
|
1209
|
+
disabled = true
|
|
1210
|
+
client_cert_path = "my-client-cert-path"
|
|
1211
|
+
client_key_path = "my-client-key-path"
|
|
1212
|
+
server_ca_cert_path = "my-server-ca-cert-path"
|
|
1213
|
+
server_name = "my-server-name"
|
|
1214
|
+
disable_host_verification = true
|
|
1215
|
+
|
|
1216
|
+
[profile.foo.codec]
|
|
1217
|
+
endpoint = "my-endpoint"
|
|
1218
|
+
auth = "my-auth"
|
|
1219
|
+
|
|
1220
|
+
[profile.foo.grpc_meta]
|
|
1221
|
+
sOme-hEader_key = "some-value"
|
|
1222
|
+
"#;
|
|
1223
|
+
|
|
1224
|
+
let config = ClientConfig::from_toml(toml_str.as_bytes(), Default::default()).unwrap();
|
|
1225
|
+
let profile = config.profiles.get("foo").unwrap();
|
|
1226
|
+
|
|
1227
|
+
assert_eq!(profile.address.as_ref().unwrap(), "my-address");
|
|
1228
|
+
assert_eq!(profile.namespace.as_ref().unwrap(), "my-namespace");
|
|
1229
|
+
assert_eq!(profile.api_key.as_ref().unwrap(), "my-api-key");
|
|
1230
|
+
|
|
1231
|
+
let codec = profile.codec.as_ref().unwrap();
|
|
1232
|
+
assert_eq!(codec.endpoint.as_ref().unwrap(), "my-endpoint");
|
|
1233
|
+
assert_eq!(codec.auth.as_ref().unwrap(), "my-auth");
|
|
1234
|
+
|
|
1235
|
+
let tls = profile.tls.as_ref().unwrap();
|
|
1236
|
+
assert!(tls.disabled);
|
|
1237
|
+
assert_eq!(
|
|
1238
|
+
tls.client_cert,
|
|
1239
|
+
Some(DataSource::Path("my-client-cert-path".to_string()))
|
|
1240
|
+
);
|
|
1241
|
+
assert_eq!(
|
|
1242
|
+
tls.client_key,
|
|
1243
|
+
Some(DataSource::Path("my-client-key-path".to_string()))
|
|
1244
|
+
);
|
|
1245
|
+
assert_eq!(
|
|
1246
|
+
tls.server_ca_cert,
|
|
1247
|
+
Some(DataSource::Path("my-server-ca-cert-path".to_string()))
|
|
1248
|
+
);
|
|
1249
|
+
assert_eq!(tls.server_name.as_ref().unwrap(), "my-server-name");
|
|
1250
|
+
assert!(tls.disable_host_verification);
|
|
1251
|
+
|
|
1252
|
+
// Note: gRPC meta keys get normalized
|
|
1253
|
+
assert_eq!(profile.grpc_meta.len(), 1);
|
|
1254
|
+
assert_eq!(
|
|
1255
|
+
profile.grpc_meta.get("some-header-key").unwrap(),
|
|
1256
|
+
"some-value"
|
|
1257
|
+
);
|
|
1258
|
+
|
|
1259
|
+
// Test round-trip serialization
|
|
1260
|
+
let toml_out = config.to_toml().unwrap();
|
|
1261
|
+
let config2 = ClientConfig::from_toml(&toml_out, Default::default()).unwrap();
|
|
1262
|
+
assert_eq!(config, config2);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
#[test]
|
|
1266
|
+
fn test_client_config_toml_partial() {
|
|
1267
|
+
let toml_str = r#"
|
|
1268
|
+
[profile.foo]
|
|
1269
|
+
api_key = "my-api-key"
|
|
1270
|
+
|
|
1271
|
+
[profile.foo.tls]
|
|
1272
|
+
"#;
|
|
1273
|
+
|
|
1274
|
+
let config = ClientConfig::from_toml(toml_str.as_bytes(), Default::default()).unwrap();
|
|
1275
|
+
let profile = config.profiles.get("foo").unwrap();
|
|
1276
|
+
|
|
1277
|
+
assert!(profile.address.is_none());
|
|
1278
|
+
assert!(profile.namespace.is_none());
|
|
1279
|
+
assert_eq!(profile.api_key.as_ref().unwrap(), "my-api-key");
|
|
1280
|
+
assert!(profile.codec.is_none());
|
|
1281
|
+
assert!(profile.tls.is_some());
|
|
1282
|
+
|
|
1283
|
+
let tls = profile.tls.as_ref().unwrap();
|
|
1284
|
+
assert!(!tls.disabled); // default value
|
|
1285
|
+
assert!(tls.client_cert.is_none());
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
#[test]
|
|
1289
|
+
fn test_client_config_toml_empty() {
|
|
1290
|
+
let config = ClientConfig::from_toml("".as_bytes(), Default::default()).unwrap();
|
|
1291
|
+
assert!(config.profiles.is_empty());
|
|
1292
|
+
|
|
1293
|
+
// Test round-trip
|
|
1294
|
+
let toml_out = config.to_toml().unwrap();
|
|
1295
|
+
let config2 = ClientConfig::from_toml(&toml_out, Default::default()).unwrap();
|
|
1296
|
+
assert_eq!(config, config2);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
#[test]
|
|
1300
|
+
fn test_profile_not_found() {
|
|
1301
|
+
let toml_str = r#"
|
|
1302
|
+
[profile.existing]
|
|
1303
|
+
address = "localhost:7233"
|
|
1304
|
+
"#;
|
|
1305
|
+
|
|
1306
|
+
let options = LoadClientConfigProfileOptions {
|
|
1307
|
+
config_source: Some(DataSource::Data(toml_str.as_bytes().to_vec())),
|
|
1308
|
+
config_file_profile: Some("nonexistent".to_string()),
|
|
1309
|
+
..Default::default()
|
|
1310
|
+
};
|
|
1311
|
+
|
|
1312
|
+
// Should error because we explicitly asked for a profile that doesn't exist
|
|
1313
|
+
let result = load_client_config_profile(options, None);
|
|
1314
|
+
assert!(result.is_err());
|
|
1315
|
+
assert!(matches!(
|
|
1316
|
+
result.unwrap_err(),
|
|
1317
|
+
ConfigError::ProfileNotFound(p) if p == "nonexistent"
|
|
1318
|
+
));
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
#[test]
|
|
1322
|
+
fn test_client_config_toml_strict_unrecognized_field() {
|
|
1323
|
+
let toml_str = r#"
|
|
1324
|
+
[profile.default]
|
|
1325
|
+
unrecognized_field = "is-bad"
|
|
1326
|
+
"#;
|
|
1327
|
+
let err = ClientConfig::from_toml(
|
|
1328
|
+
toml_str.as_bytes(),
|
|
1329
|
+
ClientConfigFromTOMLOptions { strict: true },
|
|
1330
|
+
)
|
|
1331
|
+
.unwrap_err();
|
|
1332
|
+
let err_str = err.to_string();
|
|
1333
|
+
assert!(err_str.contains("unrecognized_field"));
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
#[test]
|
|
1337
|
+
fn test_client_config_toml_strict_unrecognized_table() {
|
|
1338
|
+
let toml_str = r#"
|
|
1339
|
+
[unrecognized_table]
|
|
1340
|
+
foo = "bar"
|
|
1341
|
+
"#;
|
|
1342
|
+
let err = ClientConfig::from_toml(
|
|
1343
|
+
toml_str.as_bytes(),
|
|
1344
|
+
ClientConfigFromTOMLOptions { strict: true },
|
|
1345
|
+
)
|
|
1346
|
+
.unwrap_err();
|
|
1347
|
+
let err_str = err.to_string();
|
|
1348
|
+
assert!(err_str.contains("unrecognized_table"));
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
#[test]
|
|
1352
|
+
fn test_client_config_both_path_and_data_fails() {
|
|
1353
|
+
// Env vars
|
|
1354
|
+
let mut vars = HashMap::new();
|
|
1355
|
+
vars.insert(
|
|
1356
|
+
"TEMPORAL_TLS_CLIENT_CERT_PATH".to_string(),
|
|
1357
|
+
"some-path".to_string(),
|
|
1358
|
+
);
|
|
1359
|
+
vars.insert(
|
|
1360
|
+
"TEMPORAL_TLS_CLIENT_CERT_DATA".to_string(),
|
|
1361
|
+
"some-data".to_string(),
|
|
1362
|
+
);
|
|
1363
|
+
let options = LoadClientConfigProfileOptions {
|
|
1364
|
+
..Default::default()
|
|
1365
|
+
};
|
|
1366
|
+
let err = load_client_config_profile(options, Some(&vars)).unwrap_err();
|
|
1367
|
+
assert!(matches!(err, ConfigError::InvalidConfig(_)));
|
|
1368
|
+
assert!(err.to_string().contains("Cannot specify both"));
|
|
1369
|
+
|
|
1370
|
+
// TOML
|
|
1371
|
+
let toml_str = r#"
|
|
1372
|
+
[profile.default]
|
|
1373
|
+
[profile.default.tls]
|
|
1374
|
+
client_cert_path = "some-path"
|
|
1375
|
+
client_cert_data = "some-data"
|
|
1376
|
+
"#;
|
|
1377
|
+
let err = ClientConfig::from_toml(toml_str.as_bytes(), Default::default()).unwrap_err();
|
|
1378
|
+
assert!(matches!(err, ConfigError::InvalidConfig(_)));
|
|
1379
|
+
assert!(err.to_string().contains("Cannot specify both"));
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
#[test]
|
|
1383
|
+
fn test_client_config_path_data_conflict_across_sources() {
|
|
1384
|
+
// Path in TOML, data in env var should fail
|
|
1385
|
+
let toml_str = r#"
|
|
1386
|
+
[profile.default]
|
|
1387
|
+
[profile.default.tls]
|
|
1388
|
+
client_cert_path = "some-path"
|
|
1389
|
+
"#;
|
|
1390
|
+
let mut vars = HashMap::new();
|
|
1391
|
+
vars.insert(
|
|
1392
|
+
"TEMPORAL_TLS_CLIENT_CERT_DATA".to_string(),
|
|
1393
|
+
"some-data".to_string(),
|
|
1394
|
+
);
|
|
1395
|
+
let options = LoadClientConfigProfileOptions {
|
|
1396
|
+
config_source: Some(DataSource::Data(toml_str.as_bytes().to_vec())),
|
|
1397
|
+
..Default::default()
|
|
1398
|
+
};
|
|
1399
|
+
let err = load_client_config_profile(options, Some(&vars)).unwrap_err();
|
|
1400
|
+
assert!(matches!(err, ConfigError::InvalidConfig(_)));
|
|
1401
|
+
assert!(
|
|
1402
|
+
err.to_string()
|
|
1403
|
+
.contains("when cert path is already specified")
|
|
1404
|
+
);
|
|
1405
|
+
|
|
1406
|
+
// Data in TOML, path in env var should fail
|
|
1407
|
+
let toml_str = r#"
|
|
1408
|
+
[profile.default]
|
|
1409
|
+
[profile.default.tls]
|
|
1410
|
+
client_cert_data = "some-data"
|
|
1411
|
+
"#;
|
|
1412
|
+
let mut vars = HashMap::new();
|
|
1413
|
+
vars.insert(
|
|
1414
|
+
"TEMPORAL_TLS_CLIENT_CERT_PATH".to_string(),
|
|
1415
|
+
"some-path".to_string(),
|
|
1416
|
+
);
|
|
1417
|
+
let options = LoadClientConfigProfileOptions {
|
|
1418
|
+
config_source: Some(DataSource::Data(toml_str.as_bytes().to_vec())),
|
|
1419
|
+
..Default::default()
|
|
1420
|
+
};
|
|
1421
|
+
let err = load_client_config_profile(options, Some(&vars)).unwrap_err();
|
|
1422
|
+
assert!(matches!(err, ConfigError::InvalidConfig(_)));
|
|
1423
|
+
assert!(
|
|
1424
|
+
err.to_string()
|
|
1425
|
+
.contains("when cert data is already specified")
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
#[test]
|
|
1430
|
+
fn test_default_profile_not_found_is_ok() {
|
|
1431
|
+
let toml_str = r#"
|
|
1432
|
+
[profile.existing]
|
|
1433
|
+
address = "localhost:7233"
|
|
1434
|
+
"#;
|
|
1435
|
+
|
|
1436
|
+
let options = LoadClientConfigProfileOptions {
|
|
1437
|
+
config_source: Some(DataSource::Data(toml_str.as_bytes().to_vec())),
|
|
1438
|
+
..Default::default()
|
|
1439
|
+
};
|
|
1440
|
+
|
|
1441
|
+
// Should not error, just returns an empty profile
|
|
1442
|
+
let profile = load_client_config_profile(options, None).unwrap();
|
|
1443
|
+
assert_eq!(profile, ClientConfigProfile::default());
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
#[test]
|
|
1447
|
+
fn test_normalize_grpc_meta_key() {
|
|
1448
|
+
assert_eq!(normalize_grpc_meta_key("SOME_HEADER"), "some-header");
|
|
1449
|
+
assert_eq!(normalize_grpc_meta_key("some_header"), "some-header");
|
|
1450
|
+
assert_eq!(normalize_grpc_meta_key("Some_Header"), "some-header");
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
#[test]
|
|
1454
|
+
fn test_env_var_to_bool() {
|
|
1455
|
+
assert_eq!(env_var_to_bool("true"), Some(true));
|
|
1456
|
+
assert_eq!(env_var_to_bool("TRUE"), Some(true));
|
|
1457
|
+
assert_eq!(env_var_to_bool("1"), Some(true));
|
|
1458
|
+
|
|
1459
|
+
assert_eq!(env_var_to_bool("false"), Some(false));
|
|
1460
|
+
assert_eq!(env_var_to_bool("FALSE"), Some(false));
|
|
1461
|
+
assert_eq!(env_var_to_bool("0"), Some(false));
|
|
1462
|
+
|
|
1463
|
+
assert_eq!(env_var_to_bool("invalid"), None);
|
|
1464
|
+
assert_eq!(env_var_to_bool("yes"), None);
|
|
1465
|
+
assert_eq!(env_var_to_bool("no"), None);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
#[test]
|
|
1469
|
+
fn test_load_client_config_profile_disables_are_an_error() {
|
|
1470
|
+
let options = LoadClientConfigProfileOptions {
|
|
1471
|
+
disable_file: true,
|
|
1472
|
+
disable_env: true,
|
|
1473
|
+
..Default::default()
|
|
1474
|
+
};
|
|
1475
|
+
|
|
1476
|
+
let result = load_client_config_profile(options, None);
|
|
1477
|
+
assert!(result.is_err());
|
|
1478
|
+
assert!(
|
|
1479
|
+
result
|
|
1480
|
+
.unwrap_err()
|
|
1481
|
+
.to_string()
|
|
1482
|
+
.contains("Cannot disable both file and environment")
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
#[test]
|
|
1487
|
+
fn test_load_client_config_profile_from_env_only() {
|
|
1488
|
+
let mut vars = HashMap::new();
|
|
1489
|
+
vars.insert("TEMPORAL_ADDRESS".to_string(), "env-address".to_string());
|
|
1490
|
+
vars.insert(
|
|
1491
|
+
"TEMPORAL_NAMESPACE".to_string(),
|
|
1492
|
+
"env-namespace".to_string(),
|
|
1493
|
+
);
|
|
1494
|
+
|
|
1495
|
+
let options = LoadClientConfigProfileOptions {
|
|
1496
|
+
disable_file: true,
|
|
1497
|
+
..Default::default()
|
|
1498
|
+
};
|
|
1499
|
+
|
|
1500
|
+
let profile = load_client_config_profile(options, Some(&vars)).unwrap();
|
|
1501
|
+
assert_eq!(profile.address.as_ref().unwrap(), "env-address");
|
|
1502
|
+
assert_eq!(profile.namespace.as_ref().unwrap(), "env-namespace");
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
#[test]
|
|
1506
|
+
fn test_api_key_tls_auto_enable() {
|
|
1507
|
+
// Test 1: When API key is present, TLS should be automatically enabled
|
|
1508
|
+
let toml_str = r#"
|
|
1509
|
+
[profile.default]
|
|
1510
|
+
api_key = "my-api-key"
|
|
1511
|
+
"#;
|
|
1512
|
+
|
|
1513
|
+
let options = LoadClientConfigProfileOptions {
|
|
1514
|
+
config_source: Some(DataSource::Data(toml_str.as_bytes().to_vec())),
|
|
1515
|
+
..Default::default()
|
|
1516
|
+
};
|
|
1517
|
+
|
|
1518
|
+
let profile = load_client_config_profile(options, None).unwrap();
|
|
1519
|
+
|
|
1520
|
+
// TLS should be enabled due to API key presence
|
|
1521
|
+
assert!(profile.tls.is_some());
|
|
1522
|
+
let tls = profile.tls.as_ref().unwrap();
|
|
1523
|
+
assert!(!tls.disabled);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
#[test]
|
|
1527
|
+
fn test_no_api_key_no_tls_is_none() {
|
|
1528
|
+
// Test that if no API key is present and no TLS block exists, TLS config is None
|
|
1529
|
+
let toml_str = r#"
|
|
1530
|
+
[profile.default]
|
|
1531
|
+
address = "some-address"
|
|
1532
|
+
"#;
|
|
1533
|
+
|
|
1534
|
+
let options = LoadClientConfigProfileOptions {
|
|
1535
|
+
config_source: Some(DataSource::Data(toml_str.as_bytes().to_vec())),
|
|
1536
|
+
..Default::default()
|
|
1537
|
+
};
|
|
1538
|
+
|
|
1539
|
+
let profile = load_client_config_profile(options, None).unwrap();
|
|
1540
|
+
|
|
1541
|
+
// TLS should not be enabled
|
|
1542
|
+
assert!(profile.tls.is_none());
|
|
1543
|
+
}
|
|
1544
|
+
}
|