QuickDID is a high-performance AT Protocol identity resolution service written in Rust. It provides handle-to-DID resolution with Redis-backed caching and queue processing.

refactor: removing clap dep

Changed files
+119 -473
src
-121
Cargo.lock
··· 48 48 ] 49 49 50 50 [[package]] 51 - name = "anstream" 52 - version = "0.6.20" 53 - source = "registry+https://github.com/rust-lang/crates.io-index" 54 - checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" 55 - dependencies = [ 56 - "anstyle", 57 - "anstyle-parse", 58 - "anstyle-query", 59 - "anstyle-wincon", 60 - "colorchoice", 61 - "is_terminal_polyfill", 62 - "utf8parse", 63 - ] 64 - 65 - [[package]] 66 - name = "anstyle" 67 - version = "1.0.11" 68 - source = "registry+https://github.com/rust-lang/crates.io-index" 69 - checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" 70 - 71 - [[package]] 72 - name = "anstyle-parse" 73 - version = "0.2.7" 74 - source = "registry+https://github.com/rust-lang/crates.io-index" 75 - checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 76 - dependencies = [ 77 - "utf8parse", 78 - ] 79 - 80 - [[package]] 81 - name = "anstyle-query" 82 - version = "1.1.4" 83 - source = "registry+https://github.com/rust-lang/crates.io-index" 84 - checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" 85 - dependencies = [ 86 - "windows-sys 0.60.2", 87 - ] 88 - 89 - [[package]] 90 - name = "anstyle-wincon" 91 - version = "3.0.10" 92 - source = "registry+https://github.com/rust-lang/crates.io-index" 93 - checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" 94 - dependencies = [ 95 - "anstyle", 96 - "once_cell_polyfill", 97 - "windows-sys 0.60.2", 98 - ] 99 - 100 - [[package]] 101 51 name = "anyhow" 102 52 version = "1.0.99" 103 53 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 383 333 ] 384 334 385 335 [[package]] 386 - name = "clap" 387 - version = "4.5.47" 388 - source = "registry+https://github.com/rust-lang/crates.io-index" 389 - checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" 390 - dependencies = [ 391 - "clap_builder", 392 - "clap_derive", 393 - ] 394 - 395 - [[package]] 396 - name = "clap_builder" 397 - version = "4.5.47" 398 - source = "registry+https://github.com/rust-lang/crates.io-index" 399 - checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" 400 - dependencies = [ 401 - "anstream", 402 - "anstyle", 403 - "clap_lex", 404 - "strsim", 405 - ] 406 - 407 - [[package]] 408 - name = "clap_derive" 409 - version = "4.5.47" 410 - source = "registry+https://github.com/rust-lang/crates.io-index" 411 - checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" 412 - dependencies = [ 413 - "heck", 414 - "proc-macro2", 415 - "quote", 416 - "syn", 417 - ] 418 - 419 - [[package]] 420 - name = "clap_lex" 421 - version = "0.7.5" 422 - source = "registry+https://github.com/rust-lang/crates.io-index" 423 - checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" 424 - 425 - [[package]] 426 - name = "colorchoice" 427 - version = "1.0.4" 428 - source = "registry+https://github.com/rust-lang/crates.io-index" 429 - checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 430 - 431 - [[package]] 432 336 name = "combine" 433 337 version = "4.6.7" 434 338 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1452 1356 ] 1453 1357 1454 1358 [[package]] 1455 - name = "is_terminal_polyfill" 1456 - version = "1.70.1" 1457 - source = "registry+https://github.com/rust-lang/crates.io-index" 1458 - checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 1459 - 1460 - [[package]] 1461 1359 name = "itoa" 1462 1360 version = "1.0.15" 1463 1361 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1803 1701 ] 1804 1702 1805 1703 [[package]] 1806 - name = "once_cell_polyfill" 1807 - version = "1.70.1" 1808 - source = "registry+https://github.com/rust-lang/crates.io-index" 1809 - checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" 1810 - 1811 - [[package]] 1812 1704 name = "openssl" 1813 1705 version = "0.10.73" 1814 1706 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2010 1902 "atproto-identity", 2011 1903 "axum", 2012 1904 "bincode", 2013 - "clap", 2014 1905 "deadpool-redis", 2015 1906 "metrohash", 2016 1907 "reqwest", ··· 2876 2767 ] 2877 2768 2878 2769 [[package]] 2879 - name = "strsim" 2880 - version = "0.11.1" 2881 - source = "registry+https://github.com/rust-lang/crates.io-index" 2882 - checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 2883 - 2884 - [[package]] 2885 2770 name = "subtle" 2886 2771 version = "2.6.1" 2887 2772 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3289 3174 version = "1.0.4" 3290 3175 source = "registry+https://github.com/rust-lang/crates.io-index" 3291 3176 checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 3292 - 3293 - [[package]] 3294 - name = "utf8parse" 3295 - version = "0.2.2" 3296 - source = "registry+https://github.com/rust-lang/crates.io-index" 3297 - checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 3298 3177 3299 3178 [[package]] 3300 3179 name = "uuid"
-1
Cargo.toml
··· 19 19 atproto-identity = { version = "0.11.3" } 20 20 axum = { version = "0.8" } 21 21 bincode = { version = "2.0.1", features = ["serde"] } 22 - clap = { version = "4", features = ["derive", "env"] } 23 22 deadpool-redis = { version = "0.22", features = ["connection-manager", "tokio-comp", "tokio-rustls-comp"] } 24 23 metrohash = "1.0.7" 25 24 reqwest = { version = "0.12", features = ["json"] }
+63 -4
src/bin/quickdid.rs
··· 4 4 key::{identify_key, to_public}, 5 5 resolve::HickoryDnsResolver, 6 6 }; 7 - use clap::Parser; 8 7 use quickdid::{ 9 8 cache::create_redis_pool, 10 - config::{Args, Config}, 9 + config::Config, 11 10 handle_resolver::{ 12 11 create_base_resolver, create_caching_resolver, create_redis_resolver_with_ttl, 13 12 create_sqlite_resolver_with_ttl, ··· 55 54 } 56 55 } 57 56 57 + /// Simple command-line argument handling for --version and --help 58 + fn handle_simple_args() -> bool { 59 + let args: Vec<String> = std::env::args().collect(); 60 + 61 + if args.len() > 1 { 62 + match args[1].as_str() { 63 + "--version" | "-V" => { 64 + println!("quickdid {}", env!("CARGO_PKG_VERSION")); 65 + return true; 66 + } 67 + "--help" | "-h" => { 68 + println!("QuickDID - AT Protocol Identity Resolver Service"); 69 + println!("Version: {}", env!("CARGO_PKG_VERSION")); 70 + println!(); 71 + println!("USAGE:"); 72 + println!(" quickdid [OPTIONS]"); 73 + println!(); 74 + println!("OPTIONS:"); 75 + println!(" -h, --help Print help information"); 76 + println!(" -V, --version Print version information"); 77 + println!(); 78 + println!("ENVIRONMENT VARIABLES:"); 79 + println!(" SERVICE_KEY Private key for service identity (required)"); 80 + println!(" HTTP_EXTERNAL External hostname for service endpoints (required)"); 81 + println!(" HTTP_PORT HTTP server port (default: 8080)"); 82 + println!(" PLC_HOSTNAME PLC directory hostname (default: plc.directory)"); 83 + println!(" USER_AGENT HTTP User-Agent header (auto-generated with version)"); 84 + println!(" DNS_NAMESERVERS Custom DNS nameservers (comma-separated IPs)"); 85 + println!(" CERTIFICATE_BUNDLES Additional CA certificates (comma-separated paths)"); 86 + println!(); 87 + println!(" CACHING:"); 88 + println!(" REDIS_URL Redis URL for handle resolution caching"); 89 + println!(" SQLITE_URL SQLite database URL for handle resolution caching"); 90 + println!(" CACHE_TTL_MEMORY TTL for in-memory cache in seconds (default: 600)"); 91 + println!(" CACHE_TTL_REDIS TTL for Redis cache in seconds (default: 7776000)"); 92 + println!(" CACHE_TTL_SQLITE TTL for SQLite cache in seconds (default: 7776000)"); 93 + println!(); 94 + println!(" QUEUE CONFIGURATION:"); 95 + println!(" QUEUE_ADAPTER Queue adapter: 'mpsc', 'redis', 'sqlite', 'noop' (default: mpsc)"); 96 + println!(" QUEUE_REDIS_URL Redis URL for queue adapter"); 97 + println!(" QUEUE_REDIS_PREFIX Redis key prefix for queues (default: queue:handleresolver:)"); 98 + println!(" QUEUE_REDIS_TIMEOUT Queue blocking timeout in seconds (default: 5)"); 99 + println!(" QUEUE_WORKER_ID Worker ID for Redis queue (default: worker1)"); 100 + println!(" QUEUE_BUFFER_SIZE Buffer size for MPSC queue (default: 1000)"); 101 + println!(" QUEUE_SQLITE_MAX_SIZE Maximum SQLite queue size (default: 10000)"); 102 + println!(); 103 + println!("For more information, visit: https://github.com/smokesignal.events/quickdid"); 104 + return true; 105 + } 106 + _ => {} 107 + } 108 + } 109 + 110 + false 111 + } 112 + 58 113 #[tokio::main] 59 114 async fn main() -> Result<()> { 115 + // Handle --version and --help 116 + if handle_simple_args() { 117 + return Ok(()); 118 + } 119 + 60 120 // Initialize tracing 61 121 tracing_subscriber::registry() 62 122 .with( ··· 67 127 .with(tracing_subscriber::fmt::layer()) 68 128 .init(); 69 129 70 - let args = Args::parse(); 71 - let config = Config::from_args(args)?; 130 + let config = Config::from_env()?; 72 131 73 132 // Validate configuration 74 133 config.validate()?;
+56 -347
src/config.rs
··· 5 5 //! 6 6 //! ## Configuration Sources 7 7 //! 8 - //! Configuration can be provided through: 9 - //! - Environment variables (highest priority) 10 - //! - Command-line arguments 11 - //! - Default values (lowest priority) 8 + //! Configuration is provided exclusively through environment variables following 9 + //! the 12-factor app methodology. 12 10 //! 13 11 //! ## Example 14 12 //! ··· 30 28 //! quickdid 31 29 //! ``` 32 30 33 - use atproto_identity::config::optional_env; 34 - use clap::Parser; 31 + use std::env; 35 32 use thiserror::Error; 36 33 37 34 /// Configuration-specific errors following the QuickDID error format ··· 64 61 InvalidTimeout(String), 65 62 } 66 63 67 - #[derive(Parser, Clone)] 68 - #[command( 69 - name = "quickdid", 70 - about = "QuickDID - AT Protocol Identity Resolver Service", 71 - long_about = " 72 - A fast identity resolution service for the AT Protocol ecosystem. 73 - This service provides identity resolution endpoints and handle resolution 74 - capabilities with in-memory caching. 75 - 76 - FEATURES: 77 - - AT Protocol identity resolution and DID document management 78 - - Handle resolution with in-memory caching 79 - - DID:web identity publishing via .well-known endpoints 80 - - Health check endpoint 81 - 82 - ENVIRONMENT VARIABLES: 83 - SERVICE_KEY Private key for service identity (required) 84 - HTTP_EXTERNAL External hostname for service endpoints (required) 85 - HTTP_PORT HTTP server port (default: 8080) 86 - PLC_HOSTNAME PLC directory hostname (default: plc.directory) 87 - USER_AGENT HTTP User-Agent header (auto-generated with version) 88 - DNS_NAMESERVERS Custom DNS nameservers (comma-separated IPs) 89 - CERTIFICATE_BUNDLES Additional CA certificates (comma-separated paths) 90 - 91 - CACHING: 92 - REDIS_URL Redis URL for handle resolution caching (optional) 93 - SQLITE_URL SQLite database URL for handle resolution caching (optional) 94 - CACHE_TTL_MEMORY TTL for in-memory cache in seconds (default: 600) 95 - CACHE_TTL_REDIS TTL for Redis cache in seconds (default: 7776000 = 90 days) 96 - CACHE_TTL_SQLITE TTL for SQLite cache in seconds (default: 7776000 = 90 days) 97 - 98 - QUEUE CONFIGURATION: 99 - QUEUE_ADAPTER Queue adapter: 'mpsc', 'redis', 'sqlite', 'noop', 'none' (default: mpsc) 100 - QUEUE_REDIS_URL Redis URL for queue adapter (uses REDIS_URL if not set) 101 - QUEUE_REDIS_PREFIX Redis key prefix for queues (default: queue:handleresolver:) 102 - QUEUE_REDIS_TIMEOUT Queue blocking timeout in seconds (default: 5) 103 - QUEUE_WORKER_ID Worker ID for Redis queue (default: worker1) 104 - QUEUE_BUFFER_SIZE Buffer size for MPSC queue (default: 1000) 105 - QUEUE_SQLITE_MAX_SIZE Maximum SQLite queue size for work shedding (default: 10000, 0=unlimited) 106 - " 107 - )] 108 - /// Command-line arguments and environment variables configuration 109 - pub struct Args { 110 - /// HTTP server port to bind to 111 - /// 112 - /// Examples: "8080", "3000", "80" 113 - /// Constraints: Must be a valid port number (1-65535) 114 - #[arg(long, env = "HTTP_PORT", default_value = "8080")] 115 - pub http_port: String, 116 - 117 - /// PLC directory hostname for DID resolution 118 - /// 119 - /// Examples: "plc.directory", "test.plc.directory" 120 - /// Use "plc.directory" for production 121 - #[arg(long, env = "PLC_HOSTNAME", default_value = "plc.directory")] 122 - pub plc_hostname: String, 123 - 124 - /// External hostname for service endpoints (REQUIRED) 125 - /// 126 - /// Examples: 127 - /// - "quickdid.example.com" (standard) 128 - /// - "quickdid.example.com:8080" (with port) 129 - /// - "localhost:3007" (development) 130 - #[arg(long, env = "HTTP_EXTERNAL")] 131 - pub http_external: Option<String>, 132 - 133 - /// Private key for service identity in DID format (REQUIRED) 134 - /// 135 - /// Examples: 136 - /// - "did:key:z42tmZxD2mi1TfMKSFrsRfednwdaaPNZiiWHP4MPgcvXkDWK" 137 - /// - "did:plc:xyz123abc456" 138 - /// 139 - /// SECURITY: Keep this key secure and never commit to version control 140 - #[arg(long, env = "SERVICE_KEY")] 141 - pub service_key: Option<String>, 142 - 143 - /// HTTP User-Agent header for outgoing requests 144 - /// 145 - /// Example: `quickdid/1.0.0 (+https://quickdid.example.com)` 146 - /// Default: Auto-generated with current version 147 - #[arg(long, env = "USER_AGENT")] 148 - pub user_agent: Option<String>, 149 - 150 - /// Custom DNS nameservers (comma-separated IP addresses) 151 - /// 152 - /// Examples: 153 - /// - "8.8.8.8,8.8.4.4" (Google DNS) 154 - /// - "1.1.1.1,1.0.0.1" (Cloudflare DNS) 155 - /// - "192.168.1.1" (Local DNS) 156 - #[arg(long, env = "DNS_NAMESERVERS")] 157 - pub dns_nameservers: Option<String>, 158 - 159 - /// Additional CA certificates (comma-separated file paths) 160 - /// 161 - /// Examples: 162 - /// - "/etc/ssl/certs/custom-ca.pem" 163 - /// - "/certs/ca1.pem,/certs/ca2.pem" 164 - /// 165 - /// Use for custom or internal certificate authorities 166 - #[arg(long, env = "CERTIFICATE_BUNDLES")] 167 - pub certificate_bundles: Option<String>, 64 + /// Helper function to get an environment variable with an optional default 65 + fn get_env_or_default(key: &str, default: Option<&str>) -> Option<String> { 66 + match env::var(key) { 67 + Ok(val) if !val.is_empty() => Some(val), 68 + _ => default.map(String::from), 69 + } 70 + } 168 71 169 - /// Redis connection URL for caching 170 - /// 171 - /// Examples: 172 - /// - "redis://localhost:6379/0" (local, no auth) 173 - /// - "redis://user:pass@redis.example.com:6379/0" (with auth) 174 - /// - "rediss://secure-redis.example.com:6380/0" (TLS) 175 - /// 176 - /// Benefits: Persistent cache, distributed caching, better performance 177 - #[arg(long, env = "REDIS_URL")] 178 - pub redis_url: Option<String>, 179 - 180 - /// SQLite database URL for caching 181 - /// 182 - /// Examples: 183 - /// - "sqlite:./quickdid.db" (file-based database) 184 - /// - "sqlite::memory:" (in-memory database for testing) 185 - /// - "sqlite:/path/to/cache.db" (absolute path) 186 - /// 187 - /// Benefits: Persistent cache, single-file storage, no external dependencies 188 - #[arg(long, env = "SQLITE_URL")] 189 - pub sqlite_url: Option<String>, 190 - 191 - /// Queue adapter type for background processing 192 - /// 193 - /// Values: 194 - /// - "mpsc": In-memory multi-producer single-consumer queue 195 - /// - "redis": Redis-backed distributed queue 196 - /// - "sqlite": SQLite-backed persistent queue 197 - /// - "noop": Disable queue processing (for testing) 198 - /// - "none": Alias for "noop" 199 - /// 200 - /// Default: "mpsc" for single-instance deployments 201 - #[arg(long, env = "QUEUE_ADAPTER", default_value = "mpsc")] 202 - pub queue_adapter: String, 203 - 204 - /// Redis URL specifically for queue operations 205 - /// 206 - /// Falls back to REDIS_URL if not specified 207 - /// Use when separating cache and queue Redis instances 208 - #[arg(long, env = "QUEUE_REDIS_URL")] 209 - pub queue_redis_url: Option<String>, 210 - 211 - /// Redis key prefix for queue operations 212 - /// 213 - /// Examples: 214 - /// - "queue:handleresolver:" (default) 215 - /// - "prod:queue:hr:" (environment-specific) 216 - /// - "quickdid:v1:queue:" (version-specific) 217 - /// 218 - /// Use to namespace queues when sharing Redis 219 - #[arg( 220 - long, 221 - env = "QUEUE_REDIS_PREFIX", 222 - default_value = "queue:handleresolver:" 223 - )] 224 - pub queue_redis_prefix: String, 225 - 226 - /// Worker ID for Redis queue operations 227 - /// 228 - /// Examples: "worker-001", "prod-us-east-1", "quickdid-1" 229 - /// Default: "worker1" 230 - /// 231 - /// Use for identifying specific workers in logs 232 - #[arg(long, env = "QUEUE_WORKER_ID")] 233 - pub queue_worker_id: Option<String>, 234 - 235 - /// Buffer size for MPSC queue 236 - /// 237 - /// Range: 100-100000 (recommended) 238 - /// Default: 1000 239 - /// 240 - /// Increase for high-traffic deployments 241 - #[arg(long, env = "QUEUE_BUFFER_SIZE", default_value = "1000")] 242 - pub queue_buffer_size: usize, 243 - 244 - /// TTL for in-memory cache in seconds 245 - /// 246 - /// Range: 60-3600 (recommended) 247 - /// Default: 600 (10 minutes) 248 - /// 249 - /// Lower values = fresher data, more resolution requests 250 - /// Higher values = better performance, potentially stale data 251 - #[arg(long, env = "CACHE_TTL_MEMORY", default_value = "600")] 252 - pub cache_ttl_memory: u64, 253 - 254 - /// TTL for Redis cache in seconds 255 - /// 256 - /// Range: 3600-31536000 (1 hour to 1 year) 257 - /// Default: 7776000 (90 days) 258 - /// 259 - /// Recommendation: 86400 (1 day) for frequently changing data 260 - #[arg(long, env = "CACHE_TTL_REDIS", default_value = "7776000")] 261 - pub cache_ttl_redis: u64, 262 - 263 - /// TTL for SQLite cache in seconds 264 - /// 265 - /// Range: 3600-31536000 (1 hour to 1 year) 266 - /// Default: 7776000 (90 days) 267 - /// 268 - /// Recommendation: 86400 (1 day) for frequently changing data 269 - #[arg(long, env = "CACHE_TTL_SQLITE", default_value = "7776000")] 270 - pub cache_ttl_sqlite: u64, 271 - 272 - /// Redis blocking timeout for queue operations in seconds 273 - /// 274 - /// Range: 1-60 (recommended) 275 - /// Default: 5 276 - /// 277 - /// Lower values = more responsive to shutdown 278 - /// Higher values = less Redis polling overhead 279 - #[arg(long, env = "QUEUE_REDIS_TIMEOUT", default_value = "5")] 280 - pub queue_redis_timeout: u64, 281 - 282 - /// Maximum queue size for SQLite adapter (work shedding) 283 - /// 284 - /// Range: 100-1000000 (recommended) 285 - /// Default: 10000 286 - /// 287 - /// When the SQLite queue exceeds this limit, the oldest entries are deleted 288 - /// to maintain the queue size. This prevents unbounded queue growth while 289 - /// preserving the most recently queued work items. 290 - /// 291 - /// Set to 0 to disable work shedding (unlimited queue size) 292 - #[arg(long, env = "QUEUE_SQLITE_MAX_SIZE", default_value = "10000")] 293 - pub queue_sqlite_max_size: u64, 72 + /// Helper function to parse an environment variable as a specific type 73 + fn parse_env<T: std::str::FromStr>(key: &str, default: T) -> Result<T, ConfigError> 74 + where 75 + T::Err: std::fmt::Display, 76 + { 77 + match env::var(key) { 78 + Ok(val) if !val.is_empty() => val.parse::<T>() 79 + .map_err(|e| ConfigError::InvalidValue(format!("{}: {}", key, e))), 80 + _ => Ok(default), 81 + } 294 82 } 295 83 296 84 /// Validated configuration for QuickDID service ··· 301 89 /// ## Example 302 90 /// 303 91 /// ```rust,no_run 304 - /// use quickdid::config::{Args, Config}; 305 - /// use clap::Parser; 92 + /// use quickdid::config::Config; 306 93 /// 307 94 /// # fn main() -> Result<(), Box<dyn std::error::Error>> { 308 - /// let args = Args::parse(); 309 - /// let config = Config::from_args(args)?; 95 + /// let config = Config::from_env()?; 310 96 /// config.validate()?; 311 97 /// 312 98 /// println!("Service running at: {}", config.http_external); ··· 381 167 } 382 168 383 169 impl Config { 384 - /// Create a validated Config from command-line arguments and environment variables 170 + /// Create a validated Config from environment variables 385 171 /// 386 172 /// This method: 387 - /// 1. Processes command-line arguments with environment variable fallbacks 173 + /// 1. Reads configuration from environment variables 388 174 /// 2. Validates required fields (HTTP_EXTERNAL and SERVICE_KEY) 389 175 /// 3. Generates derived values (service_did from http_external) 390 176 /// 4. Applies defaults where appropriate 391 177 /// 392 - /// ## Priority Order 393 - /// 394 - /// 1. Command-line arguments (highest priority) 395 - /// 2. Environment variables 396 - /// 3. Default values (lowest priority) 397 - /// 398 178 /// ## Example 399 179 /// 400 180 /// ```rust,no_run 401 - /// use quickdid::config::{Args, Config}; 402 - /// use clap::Parser; 181 + /// use quickdid::config::Config; 403 182 /// 404 183 /// # fn main() -> Result<(), Box<dyn std::error::Error>> { 405 - /// // Parse from environment and command-line 406 - /// let args = Args::parse(); 407 - /// let config = Config::from_args(args)?; 184 + /// // Parse from environment variables 185 + /// let config = Config::from_env()?; 408 186 /// 409 187 /// // The service DID is automatically generated from HTTP_EXTERNAL 410 188 /// assert!(config.service_did.starts_with("did:web:")); ··· 417 195 /// Returns `ConfigError::MissingRequired` if: 418 196 /// - HTTP_EXTERNAL is not provided 419 197 /// - SERVICE_KEY is not provided 420 - pub fn from_args(args: Args) -> Result<Self, ConfigError> { 421 - let http_external = args 422 - .http_external 423 - .or_else(|| { 424 - let env_val = optional_env("HTTP_EXTERNAL"); 425 - if env_val.is_empty() { 426 - None 427 - } else { 428 - Some(env_val) 429 - } 430 - }) 198 + pub fn from_env() -> Result<Self, ConfigError> { 199 + // Required fields 200 + let http_external = env::var("HTTP_EXTERNAL") 201 + .ok() 202 + .filter(|s| !s.is_empty()) 431 203 .ok_or_else(|| ConfigError::MissingRequired("HTTP_EXTERNAL".to_string()))?; 432 204 433 - let service_key = args 434 - .service_key 435 - .or_else(|| { 436 - let env_val = optional_env("SERVICE_KEY"); 437 - if env_val.is_empty() { 438 - None 439 - } else { 440 - Some(env_val) 441 - } 442 - }) 205 + let service_key = env::var("SERVICE_KEY") 206 + .ok() 207 + .filter(|s| !s.is_empty()) 443 208 .ok_or_else(|| ConfigError::MissingRequired("SERVICE_KEY".to_string()))?; 444 209 210 + // Generate default user agent 445 211 let default_user_agent = format!( 446 212 "quickdid/{} (+https://github.com/smokesignal.events/quickdid)", 447 213 env!("CARGO_PKG_VERSION") 448 214 ); 449 215 450 - let user_agent = args 451 - .user_agent 452 - .or_else(|| { 453 - let env_val = optional_env("USER_AGENT"); 454 - if env_val.is_empty() { 455 - None 456 - } else { 457 - Some(env_val) 458 - } 459 - }) 460 - .unwrap_or(default_user_agent); 461 - 216 + // Generate service DID from http_external 462 217 let service_did = if http_external.contains(':') { 463 218 let encoded_external = http_external.replace(':', "%3A"); 464 219 format!("did:web:{}", encoded_external) ··· 467 222 }; 468 223 469 224 Ok(Config { 470 - http_port: args.http_port, 471 - plc_hostname: args.plc_hostname, 225 + http_port: get_env_or_default("HTTP_PORT", Some("8080")).unwrap(), 226 + plc_hostname: get_env_or_default("PLC_HOSTNAME", Some("plc.directory")).unwrap(), 472 227 http_external, 473 228 service_key, 474 - user_agent, 229 + user_agent: get_env_or_default("USER_AGENT", None).unwrap_or(default_user_agent), 475 230 service_did, 476 - dns_nameservers: args.dns_nameservers.or_else(|| { 477 - let env_val = optional_env("DNS_NAMESERVERS"); 478 - if env_val.is_empty() { 479 - None 480 - } else { 481 - Some(env_val) 482 - } 483 - }), 484 - certificate_bundles: args.certificate_bundles.or_else(|| { 485 - let env_val = optional_env("CERTIFICATE_BUNDLES"); 486 - if env_val.is_empty() { 487 - None 488 - } else { 489 - Some(env_val) 490 - } 491 - }), 492 - redis_url: args.redis_url.or_else(|| { 493 - let env_val = optional_env("REDIS_URL"); 494 - if env_val.is_empty() { 495 - None 496 - } else { 497 - Some(env_val) 498 - } 499 - }), 500 - sqlite_url: args.sqlite_url.or_else(|| { 501 - let env_val = optional_env("SQLITE_URL"); 502 - if env_val.is_empty() { 503 - None 504 - } else { 505 - Some(env_val) 506 - } 507 - }), 508 - queue_adapter: args.queue_adapter, 509 - queue_redis_url: args.queue_redis_url.or_else(|| { 510 - let env_val = optional_env("QUEUE_REDIS_URL"); 511 - if env_val.is_empty() { 512 - None 513 - } else { 514 - Some(env_val) 515 - } 516 - }), 517 - queue_redis_prefix: args.queue_redis_prefix, 518 - queue_worker_id: args.queue_worker_id 519 - .or_else(|| { 520 - let env_val = optional_env("QUEUE_WORKER_ID"); 521 - if env_val.is_empty() { 522 - None 523 - } else { 524 - Some(env_val) 525 - } 526 - }) 527 - .unwrap_or_else(|| "worker1".to_string()), 528 - queue_buffer_size: args.queue_buffer_size, 529 - cache_ttl_memory: args.cache_ttl_memory, 530 - cache_ttl_redis: args.cache_ttl_redis, 531 - cache_ttl_sqlite: args.cache_ttl_sqlite, 532 - queue_redis_timeout: args.queue_redis_timeout, 533 - queue_sqlite_max_size: args.queue_sqlite_max_size, 231 + dns_nameservers: get_env_or_default("DNS_NAMESERVERS", None), 232 + certificate_bundles: get_env_or_default("CERTIFICATE_BUNDLES", None), 233 + redis_url: get_env_or_default("REDIS_URL", None), 234 + sqlite_url: get_env_or_default("SQLITE_URL", None), 235 + queue_adapter: get_env_or_default("QUEUE_ADAPTER", Some("mpsc")).unwrap(), 236 + queue_redis_url: get_env_or_default("QUEUE_REDIS_URL", None), 237 + queue_redis_prefix: get_env_or_default("QUEUE_REDIS_PREFIX", Some("queue:handleresolver:")).unwrap(), 238 + queue_worker_id: get_env_or_default("QUEUE_WORKER_ID", Some("worker1")).unwrap(), 239 + queue_buffer_size: parse_env("QUEUE_BUFFER_SIZE", 1000)?, 240 + cache_ttl_memory: parse_env("CACHE_TTL_MEMORY", 600)?, 241 + cache_ttl_redis: parse_env("CACHE_TTL_REDIS", 7776000)?, 242 + cache_ttl_sqlite: parse_env("CACHE_TTL_SQLITE", 7776000)?, 243 + queue_redis_timeout: parse_env("QUEUE_REDIS_TIMEOUT", 5)?, 244 + queue_sqlite_max_size: parse_env("QUEUE_SQLITE_MAX_SIZE", 10000)?, 534 245 }) 535 246 } 536 247 ··· 544 255 /// ## Example 545 256 /// 546 257 /// ```rust,no_run 547 - /// # use quickdid::config::{Args, Config}; 548 - /// # use clap::Parser; 258 + /// # use quickdid::config::Config; 549 259 /// # fn main() -> Result<(), Box<dyn std::error::Error>> { 550 - /// # let args = Args::parse(); 551 - /// let config = Config::from_args(args)?; 260 + /// let config = Config::from_env()?; 552 261 /// config.validate()?; // Ensures all values are valid 553 262 /// # Ok(()) 554 263 /// # }