-121
Cargo.lock
-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
-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
+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
+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
/// # }