event_notification/
config.rs

1use crate::Error;
2use figment::providers::Format;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Configuration for the notification system.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct WebhookConfig {
9    pub endpoint: String,
10    pub auth_token: Option<String>,
11    pub custom_headers: Option<HashMap<String, String>>,
12    pub max_retries: u32,
13    pub timeout: u64,
14}
15
16impl WebhookConfig {
17    /// verify that the configuration is valid
18    pub fn validate(&self) -> Result<(), String> {
19        // verify that endpoint cannot be empty
20        if self.endpoint.trim().is_empty() {
21            return Err("Webhook endpoint cannot be empty".to_string());
22        }
23
24        // verification timeout must be reasonable
25        if self.timeout == 0 {
26            return Err("Webhook timeout must be greater than 0".to_string());
27        }
28
29        // Verify that the maximum number of retry is reasonable
30        if self.max_retries > 10 {
31            return Err("Maximum retry count cannot exceed 10".to_string());
32        }
33
34        Ok(())
35    }
36}
37
38/// Configuration for the Kafka adapter.
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct KafkaConfig {
41    pub brokers: String,
42    pub topic: String,
43    pub max_retries: u32,
44}
45
46/// Configuration for the MQTT adapter.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct MqttConfig {
49    pub broker: String,
50    pub port: u16,
51    pub client_id: String,
52    pub topic: String,
53    pub max_retries: u32,
54}
55
56/// Configuration for the notification system.
57#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(tag = "type")]
59pub enum AdapterConfig {
60    Webhook(WebhookConfig),
61    Kafka(KafkaConfig),
62    Mqtt(MqttConfig),
63}
64
65/// http producer configuration
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct HttpProducerConfig {
68    #[serde(default = "default_http_port")]
69    pub port: u16,
70}
71
72impl Default for HttpProducerConfig {
73    fn default() -> Self {
74        Self {
75            port: default_http_port(),
76        }
77    }
78}
79
80fn default_http_port() -> u16 {
81    3000
82}
83
84/// Configuration for the notification system.
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct NotificationConfig {
87    #[serde(default = "default_store_path")]
88    pub store_path: String,
89    #[serde(default = "default_channel_capacity")]
90    pub channel_capacity: usize,
91    pub adapters: Vec<AdapterConfig>,
92    #[serde(default)]
93    pub http: HttpProducerConfig,
94}
95
96impl Default for NotificationConfig {
97    fn default() -> Self {
98        Self {
99            store_path: default_store_path(),
100            channel_capacity: default_channel_capacity(),
101            adapters: Vec::new(),
102            http: HttpProducerConfig::default(),
103        }
104    }
105}
106
107impl NotificationConfig {
108    /// create a new configuration with default values
109    pub fn new() -> Self {
110        Self::default()
111    }
112
113    /// create a configuration from a configuration file
114    pub fn from_file(path: &str) -> Result<Self, Error> {
115        let config = figment::Figment::new()
116            .merge(figment::providers::Toml::file(path))
117            .extract()?;
118
119        Ok(config)
120    }
121
122    /// Read configuration from multiple sources (support TOML, YAML, .env)
123    pub fn load() -> Result<Self, Error> {
124        let figment = figment::Figment::new()
125            // First try to read the config.toml of the current directory
126            .merge(figment::providers::Toml::file("event.toml"))
127            // Then try to read the config.yaml of the current directory
128            .merge(figment::providers::Yaml::file("event.yaml"))
129            // Finally read the environment variable and overwrite the previous value
130            .merge(figment::providers::Env::prefixed("EVENT_NOTIF_"));
131
132        Ok(figment.extract()?)
133    }
134
135    /// loading configuration from env file
136    pub fn from_env_file(path: &str) -> Result<Self, Error> {
137        // loading env files
138        dotenv::from_path(path)
139            .map_err(|e| Error::ConfigError(format!("unable to load env file: {}", e)))?;
140
141        // Extract configuration from environment variables using figurement
142        let figment =
143            figment::Figment::new().merge(figment::providers::Env::prefixed("EVENT_NOTIF_"));
144
145        Ok(figment.extract()?)
146    }
147}
148
149/// Provide temporary directories as default storage paths
150fn default_store_path() -> String {
151    std::env::temp_dir()
152        .join("event-notification")
153        .to_string_lossy()
154        .to_string()
155}
156
157/// Provides the recommended default channel capacity for high concurrency systems
158fn default_channel_capacity() -> usize {
159    10000 // Reasonable default values for high concurrency systems
160}