apollo_rust_client/client_config.rs
1//! Configuration management for the Apollo client.
2//!
3//! This module provides the `ClientConfig` struct and related functionality for configuring
4//! the Apollo client. It supports both direct configuration and environment variable-based
5//! configuration, with platform-specific optimizations for native Rust and WebAssembly targets.
6//!
7//! # Configuration Sources
8//!
9//! - **Direct Configuration**: Manually specify all configuration fields
10//! - **Environment Variables**: Automatically load configuration from environment variables
11//! - **Mixed Approach**: Load from environment variables and override specific fields
12//!
13//! ## Environment Variables
14//!
15//! The following environment variables are supported:
16//! - `APP_ID`: Your application identifier in Apollo
17//! - `APOLLO_CONFIG_SERVICE`: The Apollo configuration server URL
18//! - `IDC`: The cluster name (defaults to "default")
19//! - `APOLLO_ACCESS_KEY_SECRET`: Authentication secret key
20//! - `APOLLO_LABEL`: Labels for grayscale release targeting
21//! - `APOLLO_CACHE_DIR`: Local cache directory
22//! - `APOLLO_CACHE_TTL`: Cache time-to-live in seconds
23//! - `APOLLO_ALLOW_INSECURE_HTTPS`: Whether to allow insecure HTTPS connections
24//!
25//! # Platform Support
26//!
27//! - **Native Rust**: Full feature set including file caching and environment variable support
28//! - **WebAssembly**: Optimized for browser environments with memory-only caching
29//!
30//! # Examples
31//!
32//! ## Direct Configuration
33//!
34//! ```rust
35//! use apollo_rust_client::client_config::ClientConfig;
36//!
37//! let config = ClientConfig {
38//! app_id: "my-app".to_string(),
39//! config_server: "http://apollo-server:8080".to_string(),
40//! cluster: "default".to_string(),
41//! secret: Some("secret-key".to_string()),
42//! cache_dir: None, // Uses default
43//! label: Some("production".to_string()),
44//! ip: Some("192.168.1.100".to_string()),
45//! allow_insecure_https: None,
46//! #[cfg(not(target_arch = "wasm32"))]
47//! cache_ttl: None,
48//! };
49//! ```
50//!
51//! ## Environment Variable Configuration
52//!
53//! ```rust,no_run
54//! use apollo_rust_client::client_config::ClientConfig;
55//!
56//! // Requires APP_ID and APOLLO_CONFIG_SERVICE environment variables
57//! let config = ClientConfig::from_env()?;
58//! # Ok::<(), apollo_rust_client::client_config::Error>(())
59//! ```
60
61use cfg_if::cfg_if;
62use wasm_bindgen::prelude::*;
63
64/// Comprehensive error types that can occur during client configuration.
65///
66/// This enum covers all possible error conditions that may arise during
67/// client configuration operations, from environment variable access to
68/// configuration validation.
69///
70/// # Error Categories
71///
72/// - **Environment Variable Errors**: Issues with accessing or parsing environment variables
73///
74/// # Examples
75///
76/// ```rust
77/// use apollo_rust_client::client_config::{ClientConfig, Error};
78///
79/// match ClientConfig::from_env() {
80/// Ok(config) => {
81/// // Handle successful configuration creation
82/// }
83/// Err(Error::EnvVar(var_error, var_name)) => {
84/// // Handle missing or invalid environment variables
85/// eprintln!("Environment variable '{}' error: {}", var_name, var_error);
86/// }
87/// Err(e) => {
88/// // Handle other errors
89/// eprintln!("Configuration error: {}", e);
90/// }
91/// }
92/// ```
93#[derive(Debug, thiserror::Error)]
94pub enum Error {
95 /// An environment variable access error occurred.
96 ///
97 /// This error occurs when attempting to read an environment variable
98 /// that is not set or cannot be accessed. The error includes both
99 /// the underlying system error and the name of the variable that failed.
100 #[error("Environment variable is not set: {1}")]
101 EnvVar(std::env::VarError, String),
102}
103
104/// Configuration settings for the Apollo client.
105///
106/// This struct contains all the necessary information to connect to and interact with
107/// an Apollo Configuration Center. It supports various configuration options including
108/// authentication, caching, and grayscale release targeting.
109///
110/// # Required Fields
111///
112/// - `app_id`: Your application identifier in Apollo
113/// - `config_server`: The Apollo configuration server URL
114/// - `cluster`: The cluster name (typically "default")
115///
116/// # Optional Fields
117///
118/// - `secret`: Authentication secret key for secure access
119/// - `cache_dir`: Local cache directory (native targets only)
120/// - `label`: Labels for grayscale release targeting
121/// - `ip`: IP address for grayscale release targeting
122/// - `allow_insecure_https`: Whether to allow insecure HTTPS connections (self-signed certificates)
123///
124/// # Examples
125///
126/// ## Minimal Configuration
127///
128/// ```rust
129/// use apollo_rust_client::client_config::ClientConfig;
130///
131/// let config = ClientConfig {
132/// app_id: "my-app".to_string(),
133/// config_server: "http://apollo-server:8080".to_string(),
134/// cluster: "default".to_string(),
135/// secret: None,
136/// cache_dir: None,
137/// label: None,
138/// ip: None,
139/// allow_insecure_https: None,
140/// #[cfg(not(target_arch = "wasm32"))]
141/// cache_ttl: None,
142/// };
143/// ```
144///
145/// ## Full Configuration
146///
147/// ```rust
148/// use apollo_rust_client::client_config::ClientConfig;
149///
150/// let config = ClientConfig {
151/// app_id: "my-app".to_string(),
152/// config_server: "http://apollo-server:8080".to_string(),
153/// cluster: "production".to_string(),
154/// secret: Some("secret-key".to_string()),
155/// cache_dir: Some("/custom/cache/path".to_string()),
156/// label: Some("canary,beta".to_string()),
157/// ip: Some("192.168.1.100".to_string()),
158/// allow_insecure_https: Some(true), // Allow self-signed certificates
159/// #[cfg(not(target_arch = "wasm32"))]
160/// cache_ttl: None,
161/// };
162/// ```
163#[derive(Clone, Debug)]
164#[wasm_bindgen(getter_with_clone)]
165pub struct ClientConfig {
166 /// The unique identifier for your application in Apollo.
167 ///
168 /// This is used to identify which application's configuration to retrieve
169 /// from the Apollo Configuration Center.
170 pub app_id: String,
171
172 /// The cluster name to connect to.
173 ///
174 /// Clusters allow you to organize different environments or deployment
175 /// groups. Common values include "default", "production", "staging", etc.
176 pub cluster: String,
177
178 /// The directory to store local cache files (native targets only).
179 ///
180 /// On native Rust targets, this specifies where configuration files should
181 /// be cached locally. If `None`, defaults to `/opt/data/{app_id}/config-cache`.
182 /// On WebAssembly targets, this is always `None` as file system access is not available.
183 pub cache_dir: Option<String>,
184
185 /// The Apollo configuration server URL.
186 ///
187 /// This should be the base URL of your Apollo Configuration Center server,
188 /// including the protocol (http/https) and port if necessary.
189 /// Example: "http://apollo-server:8080"
190 #[allow(clippy::doc_markdown)]
191 pub config_server: String,
192
193 /// Optional secret key for authentication with the Apollo server.
194 ///
195 /// If your Apollo namespace requires authentication, provide the secret key here.
196 /// This is used to generate HMAC-SHA1 signatures for secure access to protected
197 /// configuration namespaces.
198 pub secret: Option<String>,
199
200 /// Labels for grayscale release targeting.
201 ///
202 /// Comma-separated list of labels that identify this client instance.
203 /// Apollo can use these labels to determine which configuration version
204 /// to serve during grayscale releases. Example: "canary,beta"
205 pub label: Option<String>,
206
207 /// IP address for grayscale release targeting.
208 ///
209 /// The IP address of this client instance. Apollo can use this IP address
210 /// to determine which configuration version to serve during grayscale releases
211 /// based on IP-based targeting rules.
212 pub ip: Option<String>,
213
214 /// Whether to allow insecure HTTPS connections (self-signed certificates).
215 ///
216 /// When set to `true`, the client will accept self-signed SSL certificates
217 /// and other insecure HTTPS connections. This is useful in company internal
218 /// networks or development environments where self-signed certificates are used.
219 ///
220 /// **Warning**: Setting this to `true` reduces security by bypassing SSL
221 /// certificate validation. Only use this in trusted internal networks.
222 pub allow_insecure_https: Option<bool>,
223
224 /// Time-to-live for the cache, in seconds (native targets only).
225 ///
226 /// When using `from_env`, this defaults to 600 seconds (10 minutes) if
227 /// the `APOLLO_CACHE_TTL` environment variable is not set.
228 /// This field is not available on WebAssembly targets as disk caching is not supported.
229 #[cfg(not(target_arch = "wasm32"))]
230 pub cache_ttl: Option<u64>,
231}
232
233impl From<Error> for JsValue {
234 fn from(error: Error) -> Self {
235 JsValue::from_str(&error.to_string())
236 }
237}
238
239cfg_if! {
240 if #[cfg(not(target_arch = "wasm32"))] {
241 impl ClientConfig {
242 /// Creates a new `ClientConfig` by reading environment variables.
243 ///
244 /// This function loads configuration values from the following environment variables:
245 /// - `APP_ID` (required): The Apollo application ID.
246 /// - `APOLLO_ACCESS_KEY_SECRET` (optional): Secret key for authentication.
247 /// - `IDC` (optional): Cluster name. Defaults to `"default"` if not set.
248 /// - `APOLLO_CONFIG_SERVICE` (required): The Apollo config server URL.
249 /// - `APOLLO_LABEL` (optional): Comma-separated labels for grayscale release.
250 /// - `APOLLO_CACHE_DIR` (optional): Directory for local cache storage.
251 /// - `APOLLO_ALLOW_INSECURE_HTTPS` (optional): If set to `"true"`, allows insecure HTTPS.
252 /// - `APOLLO_CACHE_TTL` (optional): Cache time-to-live in seconds. Defaults to 600 if not set.
253 ///
254 /// # Returns
255 ///
256 /// * `Ok(ClientConfig)` if all required environment variables are present and valid.
257 /// * `Err(Error)` if a required environment variable is missing or invalid.
258 ///
259 /// # Errors
260 ///
261 /// This function will return an error if:
262 /// - The `APP_ID` environment variable is missing.
263 /// - The `APOLLO_CONFIG_SERVICE` environment variable is missing.
264 /// - Any environment variable that is expected to be a number (such as `APOLLO_CACHE_TTL`)
265 /// cannot be parsed as the correct type.
266 /// - Any other required environment variable is missing or invalid.
267 pub fn from_env() -> Result<Self, Error> {
268 let app_id =
269 std::env::var("APP_ID").map_err(|e| (Error::EnvVar(e, "APP_ID".to_string())))?;
270 let secret = std::env::var("APOLLO_ACCESS_KEY_SECRET")
271 .map_err(|e| (Error::EnvVar(e, "APOLLO_ACCESS_KEY_SECRET".to_string())))
272 .ok();
273 let cluster = std::env::var("IDC").unwrap_or("default".to_string());
274 let config_server = std::env::var("APOLLO_CONFIG_SERVICE")
275 .map_err(|e| (Error::EnvVar(e, "APOLLO_CONFIG_SERVICE".to_string())))?;
276 let label = std::env::var("APOLLO_LABEL")
277 .map_err(|e| (Error::EnvVar(e, "APOLLO_LABEL".to_string())))
278 .ok();
279 let cache_dir = std::env::var("APOLLO_CACHE_DIR").ok();
280 let allow_insecure_https = std::env::var("APOLLO_ALLOW_INSECURE_HTTPS")
281 .ok()
282 .and_then(|s| s.parse().ok());
283 let cache_ttl = std::env::var("APOLLO_CACHE_TTL")
284 .ok()
285 .and_then(|s| s.parse().ok())
286 .or(Some(600));
287 Ok(Self {
288 app_id,
289 secret,
290 cluster,
291 config_server,
292 cache_dir,
293 label,
294 ip: None,
295 allow_insecure_https,
296 cache_ttl,
297 })
298 }
299 }
300 } else {
301 #[wasm_bindgen]
302 impl ClientConfig {
303 /// Create a new configuration from environment variables.
304 ///
305 /// # Returns
306 ///
307 /// A new configuration instance.
308 pub fn from_env() -> Result<Self, Error> {
309 let app_id =
310 std::env::var("APP_ID").map_err(|e| (Error::EnvVar(e, "APP_ID".to_string())))?;
311 let secret = std::env::var("APOLLO_ACCESS_KEY_SECRET")
312 .map_err(|e| (Error::EnvVar(e, "APOLLO_ACCESS_KEY_SECRET".to_string())))
313 .ok();
314 let cluster = std::env::var("IDC").unwrap_or("default".to_string());
315 let config_server = std::env::var("APOLLO_CONFIG_SERVICE")
316 .map_err(|e| (Error::EnvVar(e, "APOLLO_CONFIG_SERVICE".to_string())))?;
317 let label = std::env::var("APOLLO_LABEL")
318 .map_err(|e| (Error::EnvVar(e, "APOLLO_LABEL".to_string())))
319 .ok();
320 let cache_dir = std::env::var("APOLLO_CACHE_DIR").ok();
321 let allow_insecure_https = std::env::var("APOLLO_ALLOW_INSECURE_HTTPS")
322 .ok()
323 .and_then(|s| s.parse().ok());
324 Ok(Self {
325 app_id,
326 secret,
327 cluster,
328 config_server,
329 cache_dir,
330 label,
331 ip: None,
332 allow_insecure_https,
333 })
334 }
335 }
336 }
337}
338
339cfg_if! {
340 if #[cfg(not(target_arch = "wasm32"))] {
341 impl ClientConfig {
342 /// Returns the path to the cache directory for the Apollo client.
343 ///
344 /// This method constructs a `std::path::PathBuf` representing the directory
345 /// where Apollo configuration cache files will be stored. The logic is as follows:
346 ///
347 /// 1. It uses the `cache_dir` field from the `ClientConfig` instance if it's set.
348 /// 2. If `cache_dir` is `None`, it defaults to `/opt/data`.
349 /// 3. It then appends the `app_id` (from `ClientConfig`) as a subdirectory.
350 /// 4. Finally, it appends `config-cache` as another subdirectory.
351 ///
352 /// # Examples
353 ///
354 /// - If `cache_dir` is `Some("/my/custom/path".to_string())` and `app_id` is `"my_app"`,
355 /// the result will be `/my/custom/path/my_app/config-cache`.
356 /// - If `cache_dir` is `None` and `app_id` is `"another_app"`,
357 /// the result will be `/opt/data/another_app/config-cache`.
358 ///
359 /// # Returns
360 ///
361 /// A `std::path::PathBuf` for the cache directory.
362 pub(crate) fn get_cache_dir(&self) -> std::path::PathBuf {
363 let base = std::path::PathBuf::from(
364 &self
365 .cache_dir
366 .clone()
367 .unwrap_or_else(|| String::from("/opt/data")),
368 );
369 base.join(&self.app_id).join("config-cache")
370 }
371 }
372 } else {
373 #[wasm_bindgen]
374 impl ClientConfig {
375 /// Creates a new `ClientConfig` instance specifically for wasm32 targets.
376 ///
377 /// This constructor takes essential configuration parameters (`app_id`, `config_server`, `cluster`)
378 /// directly as arguments. Other configuration fields are initialized to `None` or their
379 /// default values:
380 /// - `cache_dir`: `None` (file system caching is not typically used in wasm32).
381 /// - `secret`: `None`.
382 /// - `label`: `None`.
383 /// - `ip`: `None`.
384 ///
385 /// This is in contrast to the `from_env` method, which attempts to read all
386 /// configuration values from environment variables.
387 ///
388 /// # Arguments
389 ///
390 /// * `app_id` - The unique identifier for your application.
391 /// * `config_server` - The Apollo config server URL.
392 /// * `cluster` - The cluster name (e.g., "default").
393 ///
394 /// # Returns
395 ///
396 /// A new `ClientConfig` instance.
397 #[wasm_bindgen(constructor)]
398 pub fn new(app_id: String, config_server: String, cluster: String) -> Self {
399 Self {
400 app_id,
401 config_server,
402 cluster,
403 cache_dir: None,
404 secret: None,
405 label: None,
406 ip: None,
407 allow_insecure_https: None,
408 }
409 }
410 }
411 }
412}