From e21643063ef0f9252a8eaf92ad7142f9644f823f Mon Sep 17 00:00:00 2001 From: YoursFunny Date: Thu, 13 Aug 2026 22:26:23 +0800 Subject: [PATCH] feat(config): fail fast on misspelled env values A typo in EDIT_MESSAGE_TTL_SECONDS / WEBHOOK_PORT etc. used to silently fall back to a default, so the bot ran with different behavior than the operator intended (or failed much later on a bare .expect). Unparseable values now log a loud warning naming the variable; invalid webhook settings still surface as a hard .expect in webhook mode. --- crates/xmedia-bot/src/config.rs | 76 ++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 21 deletions(-) diff --git a/crates/xmedia-bot/src/config.rs b/crates/xmedia-bot/src/config.rs index 7176f8d..4449392 100644 --- a/crates/xmedia-bot/src/config.rs +++ b/crates/xmedia-bot/src/config.rs @@ -23,32 +23,66 @@ pub struct Config { impl Config { pub fn load() -> Config { - let admin_ids = env::var("BOT_ADMIN") - .ok() - .map(|s| { - s.split(',') - .filter_map(|part| part.trim().parse::().ok()) - .collect() - }) - .unwrap_or_default(); + // Fail-fast helpers: a misspelled value must not silently fall back + // to a default and run with different behavior than the operator + // intended — log a loud warning naming the variable instead. + fn parse_u64(name: &str, default: u64) -> u64 { + match env::var(name) { + Ok(v) => v.parse::().unwrap_or_else(|_| { + log::warn!("invalid {name}={v:?}; using default {default}"); + default + }), + Err(_) => default, + } + } - let edit_message_ttl = env::var("EDIT_MESSAGE_TTL_SECONDS") - .ok() - .and_then(|s| s.parse::().ok()) - .map(Duration::from_secs) - .unwrap_or(Duration::from_secs(86400)); + let admin_ids = match env::var("BOT_ADMIN") { + Ok(s) => { + let (ids, bad): (Vec<_>, Vec<_>) = s + .split(',') + .map(str::trim) + .filter(|part| !part.is_empty()) + .partition(|part| part.parse::().is_ok()); + if !bad.is_empty() { + log::warn!("BOT_ADMIN: ignoring non-numeric ids: {bad:?}"); + } + ids.into_iter().filter_map(|p| p.parse::().ok()).collect() + } + Err(_) => Vec::new(), + }; - let link_cache_ttl = env::var("LINK_CACHE_TTL_SECONDS") - .ok() - .and_then(|s| s.parse::().ok()) - .map(Duration::from_secs) - .unwrap_or(Duration::from_secs(7 * 24 * 3600)); + let edit_message_ttl = Duration::from_secs(parse_u64( + "EDIT_MESSAGE_TTL_SECONDS", + 24 * 3600, + )); + let link_cache_ttl = Duration::from_secs(parse_u64( + "LINK_CACHE_TTL_SECONDS", + 7 * 24 * 3600, + )); let webhook_enabled = env::var("WEBHOOK") .is_ok_and(|v| matches!(v.to_lowercase().as_str(), "true" | "yes" | "1")); - let webhook_url = env::var("WEBHOOK_URL").ok().and_then(|s| s.parse().ok()); - let webhook_listen = env::var("WEBHOOK_LISTEN").ok().and_then(|s| s.parse().ok()); - let webhook_port = env::var("WEBHOOK_PORT").ok().and_then(|s| s.parse().ok()); + // The webhook settings are consumed by `.expect()` in main when + // WEBHOOK=true, so an unparseable value fails fast at startup with a + // clear message; still log here for the WEBHOOK=false case. + let webhook_url = env::var("WEBHOOK_URL").ok().and_then(|s| { + s.parse::().ok().or_else(|| { + log::warn!("invalid WEBHOOK_URL={s:?}"); + None + }) + }); + let webhook_listen = env::var("WEBHOOK_LISTEN").ok().and_then(|s| { + s.parse::().ok().or_else(|| { + log::warn!("invalid WEBHOOK_LISTEN={s:?}"); + None + }) + }); + let webhook_port = env::var("WEBHOOK_PORT").ok().and_then(|s| { + s.parse::().ok().or_else(|| { + log::warn!("invalid WEBHOOK_PORT={s:?}"); + None + }) + }); // Empty strings count as unset (e.g. `-e WEBHOOK_CERT=` to disable a // value that would otherwise come from `.env`). let webhook_cert = env::var("WEBHOOK_CERT").ok().filter(|s| !s.is_empty());