Skip to main content

lark_webhook_notify/
config.rs

1// lark-webhook-notify/src/config.rs
2use figment::{
3    Figment,
4    providers::{Env, Format, Serialized, Toml},
5};
6use serde::{Deserialize, Serialize};
7
8use crate::error::{LarkWebhookError, Result};
9
10/// Webhook credentials used to construct a [`LarkWebhookNotifier`].
11///
12/// Load via [`LarkWebhookSettings::load`] (supports env vars + TOML file),
13/// or set fields directly.
14///
15/// # Required fields
16///
17/// Both `webhook_url` and `webhook_secret` must be `Some` before passing to
18/// [`LarkWebhookNotifier::new`].
19#[derive(Deserialize, Serialize, Debug, Clone, Default)]
20pub struct LarkWebhookSettings {
21    /// The full incoming-webhook URL from the Lark bot configuration page.
22    pub webhook_url: Option<String>,
23    /// The signing secret used to generate HMAC-SHA256 request signatures.
24    pub webhook_secret: Option<String>,
25}
26
27impl LarkWebhookSettings {
28    /// Load config with figment hierarchy (lowest → highest priority):
29    /// 1. Defaults (all None)
30    /// 2. TOML file (lark_webhook.toml or custom path)
31    /// 3. LARK_WEBHOOK_URL / LARK_WEBHOOK_SECRET env vars
32    /// 4. Direct params
33    pub fn load(
34        toml_file: Option<&str>,
35        webhook_url: Option<String>,
36        webhook_secret: Option<String>,
37    ) -> Result<Self> {
38        let path = toml_file.unwrap_or("lark_webhook.toml");
39        let mut settings: LarkWebhookSettings =
40            Figment::from(Serialized::defaults(LarkWebhookSettings::default()))
41                .merge(Toml::file(path))
42                .merge(Env::prefixed("LARK_"))
43                .extract()
44                .map_err(|e| LarkWebhookError::Config(e.to_string()))?;
45
46        if let Some(url) = webhook_url {
47            settings.webhook_url = Some(url);
48        }
49        if let Some(secret) = webhook_secret {
50            settings.webhook_secret = Some(secret);
51        }
52        Ok(settings)
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use std::sync::Mutex;
60
61    // Serialize tests that read/write env vars to prevent parallel interference.
62    static ENV_MUTEX: Mutex<()> = Mutex::new(());
63
64    #[test]
65    fn test_defaults_are_none() {
66        let _guard = ENV_MUTEX.lock().unwrap();
67        // Run without any env vars or files set
68        // Use a non-existent toml file path so figment skips it gracefully
69        let s = LarkWebhookSettings::load(Some("nonexistent_xyz.toml"), None, None).unwrap();
70        assert!(s.webhook_url.is_none());
71        assert!(s.webhook_secret.is_none());
72    }
73
74    #[test]
75    fn test_direct_params_override() {
76        let _guard = ENV_MUTEX.lock().unwrap();
77        let s = LarkWebhookSettings::load(
78            None,
79            Some("https://direct.url".to_owned()),
80            Some("direct_secret".to_owned()),
81        )
82        .unwrap();
83        assert_eq!(s.webhook_url.as_deref(), Some("https://direct.url"));
84        assert_eq!(s.webhook_secret.as_deref(), Some("direct_secret"));
85    }
86
87    #[test]
88    fn test_toml_file_loading() {
89        let _guard = ENV_MUTEX.lock().unwrap();
90        use std::io::Write;
91        let mut f = tempfile::NamedTempFile::new().unwrap();
92        write!(
93            f,
94            "webhook_url = \"https://toml.url\"\nwebhook_secret = \"toml_secret\"\n"
95        )
96        .unwrap();
97        let path = f.path().to_str().unwrap().to_owned();
98        let s = LarkWebhookSettings::load(Some(&path), None, None).unwrap();
99        assert_eq!(s.webhook_url.as_deref(), Some("https://toml.url"));
100        assert_eq!(s.webhook_secret.as_deref(), Some("toml_secret"));
101    }
102
103    #[test]
104    fn test_env_var_loading() {
105        let _guard = ENV_MUTEX.lock().unwrap();
106        // NOTE: env var tests can interfere with each other if run in parallel.
107        // The ENV_MUTEX above serializes all env-sensitive tests in this module.
108        // This test cleans up after itself before asserting.
109        unsafe {
110            std::env::set_var("LARK_WEBHOOK_URL", "https://env.url");
111            std::env::set_var("LARK_WEBHOOK_SECRET", "env_secret");
112        }
113        let result = LarkWebhookSettings::load(Some("nonexistent_xyz.toml"), None, None);
114        unsafe {
115            std::env::remove_var("LARK_WEBHOOK_URL");
116            std::env::remove_var("LARK_WEBHOOK_SECRET");
117        }
118        let s = result.unwrap();
119        assert_eq!(s.webhook_url.as_deref(), Some("https://env.url"));
120        assert_eq!(s.webhook_secret.as_deref(), Some("env_secret"));
121    }
122}