apollo-rust-client 0.7.0

A Rust client for Apollo configuration center
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
//! Configuration management for the Apollo client.
//!
//! This module provides the `ClientConfig` struct and related functionality for configuring
//! the Apollo client. It supports both direct configuration and environment variable-based
//! configuration, with platform-specific optimizations for native Rust and WebAssembly targets.
//!
//! # Configuration Sources
//!
//! - **Direct Configuration**: Manually specify all configuration fields
//! - **Environment Variables**: Automatically load configuration from environment variables
//! - **Mixed Approach**: Load from environment variables and override specific fields
//!
//! ## Environment Variables
//!
//! The following environment variables are supported:
//! - `APP_ID`: Your application identifier in Apollo
//! - `APOLLO_CONFIG_SERVICE`: The Apollo configuration server URL
//! - `IDC`: The cluster name (defaults to "default")
//! - `APOLLO_ACCESS_KEY_SECRET`: Authentication secret key
//! - `APOLLO_LABEL`: Labels for grayscale release targeting
//! - `APOLLO_CACHE_DIR`: Local cache directory
//! - `APOLLO_CACHE_TTL`: Cache time-to-live in seconds
//! - `APOLLO_ALLOW_INSECURE_HTTPS`: Whether to allow insecure HTTPS connections
//!
//! # Platform Support
//!
//! - **Native Rust**: Full feature set including file caching and environment variable support
//! - **WebAssembly**: Optimized for browser environments with persistent localStorage caching (and Node.js in-memory fallback)
//!
//! # Examples
//!
//! ## Direct Configuration
//!
//! ```rust
//! use apollo_rust_client::client_config::ClientConfig;
//!
//! let config = ClientConfig {
//!     app_id: "my-app".to_string(),
//!     config_server: "http://apollo-server:8080".to_string(),
//!     cluster: "default".to_string(),
//!     secret: Some("secret-key".to_string()),
//!     cache_dir: None, // Uses default
//!     label: Some("production".to_string()),
//!     ip: Some("192.168.1.100".to_string()),
//!     allow_insecure_https: None,
//!     #[cfg(not(target_arch = "wasm32"))]
//!     cache_ttl: None,
//! };
//! ```
//!
//! ## Environment Variable Configuration
//!
//! ```rust,no_run
//! use apollo_rust_client::client_config::ClientConfig;
//!
//! // Requires APP_ID and APOLLO_CONFIG_SERVICE environment variables
//! let config = ClientConfig::from_env()?;
//! # Ok::<(), apollo_rust_client::client_config::Error>(())
//! ```

use cfg_if::cfg_if;
use wasm_bindgen::prelude::*;

/// Comprehensive error types that can occur during client configuration.
///
/// This enum covers all possible error conditions that may arise during
/// client configuration operations, from environment variable access to
/// configuration validation.
///
/// # Error Categories
///
/// - **Environment Variable Errors**: Issues with accessing or parsing environment variables
///
/// # Examples
///
/// ```rust
/// use apollo_rust_client::client_config::{ClientConfig, Error};
///
/// match ClientConfig::from_env() {
///     Ok(config) => {
///         // Handle successful configuration creation
///     }
///     Err(Error::EnvVar(var_error, var_name)) => {
///         // Handle missing or invalid environment variables
///         eprintln!("Environment variable '{}' error: {}", var_name, var_error);
///     }
///     Err(e) => {
///         // Handle other errors
///         eprintln!("Configuration error: {}", e);
///     }
/// }
/// ```
#[derive(Debug, thiserror::Error)]
pub enum Error {
    /// An environment variable access error occurred.
    ///
    /// This error occurs when attempting to read an environment variable
    /// that is not set or cannot be accessed. The error includes both
    /// the underlying system error and the name of the variable that failed.
    #[error("Environment variable is not set: {1}")]
    EnvVar(std::env::VarError, String),
}

/// Configuration settings for the Apollo client.
///
/// This struct contains all the necessary information to connect to and interact with
/// an Apollo Configuration Center. It supports various configuration options including
/// authentication, caching, and grayscale release targeting.
///
/// # Required Fields
///
/// - `app_id`: Your application identifier in Apollo
/// - `config_server`: The Apollo configuration server URL
/// - `cluster`: The cluster name (typically "default")
///
/// # Optional Fields
///
/// - `secret`: Authentication secret key for secure access
/// - `cache_dir`: Local cache directory (native targets only)
/// - `label`: Labels for grayscale release targeting
/// - `ip`: IP address for grayscale release targeting
/// - `allow_insecure_https`: Whether to allow insecure HTTPS connections (self-signed certificates)
///
/// # Examples
///
/// ## Minimal Configuration
///
/// ```rust
/// use apollo_rust_client::client_config::ClientConfig;
///
/// let config = ClientConfig {
///     app_id: "my-app".to_string(),
///     config_server: "http://apollo-server:8080".to_string(),
///     cluster: "default".to_string(),
///     secret: None,
///     cache_dir: None,
///     label: None,
///     ip: None,
///     allow_insecure_https: None,
///     #[cfg(not(target_arch = "wasm32"))]
///     cache_ttl: None,
/// };
/// ```
///
/// ## Full Configuration
///
/// ```rust
/// use apollo_rust_client::client_config::ClientConfig;
///
/// let config = ClientConfig {
///     app_id: "my-app".to_string(),
///     config_server: "http://apollo-server:8080".to_string(),
///     cluster: "production".to_string(),
///     secret: Some("secret-key".to_string()),
///     cache_dir: Some("/custom/cache/path".to_string()),
///     label: Some("canary,beta".to_string()),
///     ip: Some("192.168.1.100".to_string()),
///     allow_insecure_https: Some(true), // Allow self-signed certificates
///     #[cfg(not(target_arch = "wasm32"))]
///     cache_ttl: None,
/// };
/// ```
#[derive(Clone, Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct ClientConfig {
    /// The unique identifier for your application in Apollo.
    ///
    /// This is used to identify which application's configuration to retrieve
    /// from the Apollo Configuration Center.
    pub app_id: String,

    /// The cluster name to connect to.
    ///
    /// Clusters allow you to organize different environments or deployment
    /// groups. Common values include "default", "production", "staging", etc.
    pub cluster: String,

    /// The directory to store local cache files (native targets only).
    ///
    /// On native Rust targets, this specifies where configuration files should
    /// be cached locally. If `None`, defaults to `/opt/data/{app_id}/config-cache`.
    /// On WebAssembly targets, this is always `None` as file system access is not available.
    pub cache_dir: Option<String>,

    /// The Apollo configuration server URL.
    ///
    /// This should be the base URL of your Apollo Configuration Center server,
    /// including the protocol (http/https) and port if necessary.
    /// Example: "http://apollo-server:8080"
    #[allow(clippy::doc_markdown)]
    pub config_server: String,

    /// Optional secret key for authentication with the Apollo server.
    ///
    /// If your Apollo namespace requires authentication, provide the secret key here.
    /// This is used to generate HMAC-SHA1 signatures for secure access to protected
    /// configuration namespaces.
    pub secret: Option<String>,

    /// Labels for grayscale release targeting.
    ///
    /// Comma-separated list of labels that identify this client instance.
    /// Apollo can use these labels to determine which configuration version
    /// to serve during grayscale releases. Example: "canary,beta"
    pub label: Option<String>,

    /// IP address for grayscale release targeting.
    ///
    /// The IP address of this client instance. Apollo can use this IP address
    /// to determine which configuration version to serve during grayscale releases
    /// based on IP-based targeting rules.
    pub ip: Option<String>,

    /// Whether to allow insecure HTTPS connections (self-signed certificates).
    ///
    /// When set to `true`, the client will accept self-signed SSL certificates
    /// and other insecure HTTPS connections. This is useful in company internal
    /// networks or development environments where self-signed certificates are used.
    ///
    /// **Warning**: Setting this to `true` reduces security by bypassing SSL
    /// certificate validation. Only use this in trusted internal networks.
    pub allow_insecure_https: Option<bool>,

    /// Time-to-live for the cache, in seconds (native targets only).
    ///
    /// When using `from_env`, this defaults to 600 seconds (10 minutes) if
    /// the `APOLLO_CACHE_TTL` environment variable is not set.
    /// This field is not available on WebAssembly targets as disk caching is not supported.
    #[cfg(not(target_arch = "wasm32"))]
    pub cache_ttl: Option<u64>,

    /// The refresh interval in seconds for the background namespace cache refresh loop (native targets only).
    ///
    /// When using `from_env`, this defaults to 30 seconds if
    /// the `APOLLO_REFRESH_INTERVAL` environment variable is not set.
    /// This field is not available on WebAssembly targets as background refresh is not supported.
    #[cfg(not(target_arch = "wasm32"))]
    pub refresh_interval: Option<u64>,

    /// A pre-configured `reqwest::Client` (native targets only) to allow custom HTTP pools, proxies, headers, or tracers.
    ///
    /// If not specified, defaults to standard client construction.
    #[cfg(not(target_arch = "wasm32"))]
    #[wasm_bindgen(skip)]
    pub http_client: Option<reqwest::Client>,
}

impl From<Error> for JsValue {
    fn from(error: Error) -> Self {
        JsValue::from_str(&error.to_string())
    }
}

cfg_if! {
    if #[cfg(not(target_arch = "wasm32"))] {
        impl ClientConfig {
            /// Creates a new `ClientConfig` by reading environment variables.
            ///
            /// This function loads configuration values from the following environment variables:
            /// - `APP_ID` (required): The Apollo application ID.
            /// - `APOLLO_ACCESS_KEY_SECRET` (optional): Secret key for authentication.
            /// - `IDC` (optional): Cluster name. Defaults to `"default"` if not set.
            /// - `APOLLO_CONFIG_SERVICE` (required): The Apollo config server URL.
            /// - `APOLLO_LABEL` (optional): Comma-separated labels for grayscale release.
            /// - `APOLLO_CACHE_DIR` (optional): Directory for local cache storage.
            /// - `APOLLO_ALLOW_INSECURE_HTTPS` (optional): If set to `"true"`, allows insecure HTTPS.
            /// - `APOLLO_CACHE_TTL` (optional): Cache time-to-live in seconds. Defaults to 600 if not set.
            ///
            /// # Returns
            ///
            /// * `Ok(ClientConfig)` if all required environment variables are present and valid.
            /// * `Err(Error)` if a required environment variable is missing or invalid.
            ///
            /// # Errors
            ///
            /// This function will return an error if:
            /// - The `APP_ID` environment variable is missing.
            /// - The `APOLLO_CONFIG_SERVICE` environment variable is missing.
            /// - Any environment variable that is expected to be a number (such as `APOLLO_CACHE_TTL`)
            ///   cannot be parsed as the correct type.
            /// - Any other required environment variable is missing or invalid.
            pub fn from_env() -> Result<Self, Error> {
                let app_id =
                    std::env::var("APP_ID").map_err(|e| Error::EnvVar(e, "APP_ID".to_string()))?;
                let secret = std::env::var("APOLLO_ACCESS_KEY_SECRET")
                    .map_err(|e| Error::EnvVar(e, "APOLLO_ACCESS_KEY_SECRET".to_string()))
                    .ok();
                let cluster = std::env::var("IDC").unwrap_or("default".to_string());
                let config_server = std::env::var("APOLLO_CONFIG_SERVICE")
                    .map_err(|e| Error::EnvVar(e, "APOLLO_CONFIG_SERVICE".to_string()))?;
                let label = std::env::var("APOLLO_LABEL")
                    .map_err(|e| Error::EnvVar(e, "APOLLO_LABEL".to_string()))
                    .ok();
                let cache_dir = std::env::var("APOLLO_CACHE_DIR").ok();
                let allow_insecure_https = std::env::var("APOLLO_ALLOW_INSECURE_HTTPS")
                    .ok()
                    .and_then(|s| s.parse().ok());
                let cache_ttl = std::env::var("APOLLO_CACHE_TTL")
                    .ok()
                    .and_then(|s| s.parse().ok())
                    .or(Some(600));
                let refresh_interval = std::env::var("APOLLO_REFRESH_INTERVAL")
                    .ok()
                    .and_then(|s| s.parse().ok())
                    .map(|v| {
                        let min_val = if cfg!(test) { 1 } else { 30 };
                        if v < min_val { min_val } else { v }
                    })
                    .or(Some(30));
                Ok(Self {
                    app_id,
                    secret,
                    cluster,
                    config_server,
                    cache_dir,
                    label,
                    ip: None,
                    allow_insecure_https,
                    cache_ttl,
                    refresh_interval,
                    http_client: None,
                })
            }
        }
    } else {
        #[wasm_bindgen]
        impl ClientConfig {
            /// Create a new configuration from environment variables.
            ///
            /// # Returns
            ///
            /// A new configuration instance.
            pub fn from_env() -> Result<Self, Error> {
                let app_id =
                    std::env::var("APP_ID").map_err(|e| Error::EnvVar(e, "APP_ID".to_string()))?;
                let secret = std::env::var("APOLLO_ACCESS_KEY_SECRET")
                    .map_err(|e| Error::EnvVar(e, "APOLLO_ACCESS_KEY_SECRET".to_string()))
                    .ok();
                let cluster = std::env::var("IDC").unwrap_or("default".to_string());
                let config_server = std::env::var("APOLLO_CONFIG_SERVICE")
                    .map_err(|e| Error::EnvVar(e, "APOLLO_CONFIG_SERVICE".to_string()))?;
                let label = std::env::var("APOLLO_LABEL")
                    .map_err(|e| Error::EnvVar(e, "APOLLO_LABEL".to_string()))
                    .ok();
                let cache_dir = std::env::var("APOLLO_CACHE_DIR").ok();
                let allow_insecure_https = std::env::var("APOLLO_ALLOW_INSECURE_HTTPS")
                    .ok()
                    .and_then(|s| s.parse().ok());
                Ok(Self {
                    app_id,
                    secret,
                    cluster,
                    config_server,
                    cache_dir,
                    label,
                    ip: None,
                    allow_insecure_https,
                })
            }
        }
    }
}

cfg_if! {
    if #[cfg(not(target_arch = "wasm32"))] {
        impl ClientConfig {
            /// Returns the path to the cache directory for the Apollo client.
            ///
            /// This method constructs a `std::path::PathBuf` representing the directory
            /// where Apollo configuration cache files will be stored. The logic is as follows:
            ///
            /// 1.  It uses the `cache_dir` field from the `ClientConfig` instance if it's set.
            /// 2.  If `cache_dir` is `None`, it defaults to `/opt/data`.
            /// 3.  It then appends the `app_id` (from `ClientConfig`) as a subdirectory.
            /// 4.  Finally, it appends `config-cache` as another subdirectory.
            ///
            /// # Examples
            ///
            /// - If `cache_dir` is `Some("/my/custom/path".to_string())` and `app_id` is `"my_app"`,
            ///   the result will be `/my/custom/path/my_app/config-cache`.
            /// - If `cache_dir` is `None` and `app_id` is `"another_app"`,
            ///   the result will be `/opt/data/another_app/config-cache`.
            ///
            /// # Returns
            ///
            /// A `std::path::PathBuf` for the cache directory.
            pub(crate) fn get_cache_dir(&self) -> std::path::PathBuf {
                let base = std::path::PathBuf::from(
                    &self
                        .cache_dir
                        .clone()
                        .unwrap_or_else(|| String::from("/opt/data")),
                );
                base.join(&self.app_id).join("config-cache")
            }
        }
    } else {
        #[wasm_bindgen]
        impl ClientConfig {
            /// Creates a new `ClientConfig` instance specifically for wasm32 targets.
            ///
            /// This constructor takes essential configuration parameters (`app_id`, `config_server`, `cluster`)
            /// directly as arguments. Other configuration fields are initialized to `None` or their
            /// default values:
            /// - `cache_dir`: `None` (file system caching is not typically used in wasm32).
            /// - `secret`: `None`.
            /// - `label`: `None`.
            /// - `ip`: `None`.
            ///
            /// This is in contrast to the `from_env` method, which attempts to read all
            /// configuration values from environment variables.
            ///
            /// # Arguments
            ///
            /// * `app_id` - The unique identifier for your application.
            /// * `config_server` - The Apollo config server URL.
            /// * `cluster` - The cluster name (e.g., "default").
            ///
            /// # Returns
            ///
            /// A new `ClientConfig` instance.
            #[wasm_bindgen(constructor)]
            pub fn new(app_id: String, config_server: String, cluster: String) -> Self {
                Self {
                    app_id,
                    config_server,
                    cluster,
                    cache_dir: None,
                    secret: None,
                    label: None,
                    ip: None,
                    allow_insecure_https: None,
                }
            }
        }
    }
}