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    pub timeout: u64,
45}
46
47/// Configuration for the MQTT adapter.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct MqttConfig {
50    pub broker: String,
51    pub port: u16,
52    pub client_id: String,
53    pub topic: String,
54    pub max_retries: u32,
55}
56
57/// Configuration for the notification system.
58#[derive(Debug, Clone, Serialize, Deserialize)]
59#[serde(tag = "type")]
60pub enum AdapterConfig {
61    Webhook(WebhookConfig),
62    Kafka(KafkaConfig),
63    Mqtt(MqttConfig),
64}
65
66/// http producer configuration
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct HttpProducerConfig {
69    #[serde(default = "default_http_port")]
70    pub port: u16,
71}
72
73impl Default for HttpProducerConfig {
74    fn default() -> Self {
75        Self {
76            port: default_http_port(),
77        }
78    }
79}
80
81fn default_http_port() -> u16 {
82    3000
83}
84
85/// Configuration for the notification system.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct NotificationConfig {
88    #[serde(default = "default_store_path")]
89    pub store_path: String,
90    #[serde(default = "default_channel_capacity")]
91    pub channel_capacity: usize,
92    pub adapters: Vec<AdapterConfig>,
93    #[serde(default)]
94    pub http: HttpProducerConfig,
95}
96
97impl Default for NotificationConfig {
98    fn default() -> Self {
99        Self {
100            store_path: default_store_path(),
101            channel_capacity: default_channel_capacity(),
102            adapters: Vec::new(),
103            http: HttpProducerConfig::default(),
104        }
105    }
106}
107
108impl NotificationConfig {
109    /// create a new configuration with default values
110    pub fn new() -> Self {
111        Self::default()
112    }
113
114    /// create a configuration from a configuration file
115    pub fn from_file(path: &str) -> Result<Self, Error> {
116        let config = figment::Figment::new()
117            .merge(figment::providers::Toml::file(path))
118            .extract()?;
119
120        Ok(config)
121    }
122
123    /// Read configuration from multiple sources (support TOML, YAML, .env)
124    pub fn load() -> Result<Self, Error> {
125        let figment = figment::Figment::new()
126            // First try to read the config.toml of the current directory
127            .merge(figment::providers::Toml::file("event.toml"))
128            // Then try to read the config.yaml of the current directory
129            .merge(figment::providers::Yaml::file("event.yaml"))
130            // Finally read the environment variable and overwrite the previous value
131            .merge(figment::providers::Env::prefixed("EVENT_NOTIF_"));
132
133        Ok(figment.extract()?)
134    }
135
136    /// loading configuration from env file
137    pub fn from_env_file(path: &str) -> Result<Self, Error> {
138        // loading env files
139        dotenv::from_path(path)
140            .map_err(|e| Error::ConfigError(format!("unable to load env file: {}", e)))?;
141
142        // Extract configuration from environment variables using figurement
143        let figment =
144            figment::Figment::new().merge(figment::providers::Env::prefixed("EVENT_NOTIF_"));
145
146        Ok(figment.extract()?)
147    }
148}
149
150/// Provide temporary directories as default storage paths
151fn default_store_path() -> String {
152    std::env::temp_dir()
153        .join("event-notification")
154        .to_string_lossy()
155        .to_string()
156}
157
158/// Provides the recommended default channel capacity for high concurrency systems
159fn default_channel_capacity() -> usize {
160    10000 // Reasonable default values for high concurrency systems
161}