Skip to main content

reinhardt_conf/settings/
email.rs

1//! Email settings fragment
2//!
3//! Provides composable email configuration as a [`SettingsFragment`](crate::settings::fragment::SettingsFragment).
4
5use reinhardt_core::macros::settings;
6use serde::{Deserialize, Serialize};
7use std::path::PathBuf;
8
9fn default_backend() -> String {
10	"console".to_string()
11}
12
13fn default_host() -> String {
14	"localhost".to_string()
15}
16
17fn default_port() -> u16 {
18	25
19}
20
21fn default_from_email() -> String {
22	"noreply@example.com".to_string()
23}
24
25fn default_server_email() -> String {
26	"root@localhost".to_string()
27}
28
29/// Email configuration fragment.
30///
31/// Controls email backend, SMTP connection, and notification settings.
32#[settings(fragment = true, section = "email")]
33#[non_exhaustive]
34#[derive(Clone, Debug, Serialize, Deserialize)]
35pub struct EmailSettings {
36	/// Email backend type (e.g., `"smtp"`, `"console"`, `"file"`, `"memory"`).
37	#[serde(default = "default_backend")]
38	pub backend: String,
39	/// SMTP server hostname.
40	#[serde(default = "default_host")]
41	pub host: String,
42	/// SMTP server port number.
43	#[serde(default = "default_port")]
44	pub port: u16,
45	/// Optional SMTP authentication username.
46	pub username: Option<String>,
47	/// Optional SMTP authentication password.
48	pub password: Option<String>,
49	/// Whether to use STARTTLS for the SMTP connection.
50	pub use_tls: bool,
51	/// Whether to use direct TLS/SSL for the SMTP connection.
52	pub use_ssl: bool,
53	/// Default sender email address for outgoing emails.
54	#[serde(default = "default_from_email")]
55	pub from_email: String,
56
57	/// List of (name, email) tuples for site administrators.
58	/// Used by mail_admins() helper.
59	#[serde(default)]
60	pub admins: Vec<(String, String)>,
61
62	/// List of (name, email) tuples for site managers.
63	/// Used by mail_managers() helper.
64	#[serde(default)]
65	pub managers: Vec<(String, String)>,
66
67	/// Email address for server error notifications.
68	#[serde(default = "default_server_email")]
69	pub server_email: String,
70
71	/// Prefix for email subjects (e.g., `"[Reinhardt]"`).
72	#[serde(default)]
73	pub subject_prefix: String,
74
75	/// Connection timeout in seconds.
76	pub timeout: Option<u64>,
77
78	/// Path to SSL certificate file.
79	pub ssl_certfile: Option<PathBuf>,
80
81	/// Path to SSL key file.
82	pub ssl_keyfile: Option<PathBuf>,
83
84	/// Directory path for file-based email backend.
85	/// Required when backend is `"file"`.
86	#[serde(default)]
87	pub file_path: Option<PathBuf>,
88}
89
90impl Default for EmailSettings {
91	fn default() -> Self {
92		Self {
93			backend: "console".to_string(),
94			host: "localhost".to_string(),
95			port: 25,
96			username: None,
97			password: None,
98			use_tls: false,
99			use_ssl: false,
100			from_email: "noreply@example.com".to_string(),
101			admins: Vec::new(),
102			managers: Vec::new(),
103			server_email: default_server_email(),
104			subject_prefix: String::new(),
105			timeout: None,
106			ssl_certfile: None,
107			ssl_keyfile: None,
108			file_path: None,
109		}
110	}
111}
112
113#[cfg(test)]
114mod tests {
115	use super::*;
116	use crate::settings::fragment::SettingsFragment;
117	use rstest::rstest;
118
119	#[rstest]
120	fn test_email_section_name() {
121		// Arrange / Act
122		let section = EmailSettings::section();
123
124		// Assert
125		assert_eq!(section, "email");
126	}
127
128	#[rstest]
129	fn test_email_default_values() {
130		// Arrange / Act
131		let settings = EmailSettings::default();
132
133		// Assert
134		assert_eq!(settings.backend, "console");
135		assert_eq!(settings.host, "localhost");
136		assert_eq!(settings.port, 25);
137		assert!(settings.username.is_none());
138		assert!(settings.password.is_none());
139		assert!(!settings.use_tls);
140		assert!(!settings.use_ssl);
141		assert_eq!(settings.from_email, "noreply@example.com");
142		assert!(settings.admins.is_empty());
143		assert!(settings.managers.is_empty());
144		assert_eq!(settings.server_email, "root@localhost");
145		assert!(settings.subject_prefix.is_empty());
146		assert!(settings.timeout.is_none());
147		assert!(settings.ssl_certfile.is_none());
148		assert!(settings.ssl_keyfile.is_none());
149		assert!(settings.file_path.is_none());
150	}
151}