//! SMTP client configuration types. use serde::Deserialize; /// Configuration for connecting to an SMTP server. #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SmtpClientConfig { /// Target SMTP server hostname. pub host: String, /// Target port (25 = SMTP, 465 = implicit TLS, 587 = submission). pub port: u16, /// Use implicit TLS (port 465). If false, STARTTLS is attempted. #[serde(default)] pub secure: bool, /// Domain to use in EHLO command. Defaults to "localhost". #[serde(default = "default_domain")] pub domain: String, /// Authentication credentials (optional). pub auth: Option, /// Connection timeout in seconds. Default: 30. #[serde(default = "default_connection_timeout")] pub connection_timeout_secs: u64, /// Socket read/write timeout in seconds. Default: 120. #[serde(default = "default_socket_timeout")] pub socket_timeout_secs: u64, /// Pool key override. Defaults to "host:port". pub pool_key: Option, /// Maximum connections per pool. Default: 10. #[serde(default = "default_max_pool_connections")] pub max_pool_connections: usize, } /// Authentication configuration. #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SmtpAuthConfig { /// Username. pub user: String, /// Password. pub pass: String, /// Method: "PLAIN" or "LOGIN". Default: "PLAIN". #[serde(default = "default_auth_method")] pub method: String, } /// DKIM signing configuration (applied before sending). #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DkimSignConfig { /// Signing domain (e.g. "example.com"). pub domain: String, /// DKIM selector (e.g. "default" or "mta"). pub selector: String, /// PEM-encoded RSA private key. pub private_key: String, } impl SmtpClientConfig { /// Get the effective pool key for this config. pub fn effective_pool_key(&self) -> String { self.pool_key .clone() .unwrap_or_else(|| format!("{}:{}", self.host, self.port)) } } fn default_domain() -> String { "localhost".to_string() } fn default_connection_timeout() -> u64 { 30 } fn default_socket_timeout() -> u64 { 120 } fn default_max_pool_connections() -> usize { 10 } fn default_auth_method() -> String { "PLAIN".to_string() } #[cfg(test)] mod tests { use super::*; #[test] fn test_deserialize_minimal_config() { let json = r#"{"host":"mail.example.com","port":25}"#; let config: SmtpClientConfig = serde_json::from_str(json).unwrap(); assert_eq!(config.host, "mail.example.com"); assert_eq!(config.port, 25); assert!(!config.secure); assert_eq!(config.domain, "localhost"); assert!(config.auth.is_none()); assert_eq!(config.connection_timeout_secs, 30); assert_eq!(config.socket_timeout_secs, 120); assert_eq!(config.max_pool_connections, 10); } #[test] fn test_deserialize_full_config() { let json = r#"{ "host": "smtp.gmail.com", "port": 465, "secure": true, "domain": "myserver.com", "auth": { "user": "u", "pass": "p", "method": "LOGIN" }, "connectionTimeoutSecs": 60, "socketTimeoutSecs": 300, "poolKey": "gmail", "maxPoolConnections": 5 }"#; let config: SmtpClientConfig = serde_json::from_str(json).unwrap(); assert_eq!(config.host, "smtp.gmail.com"); assert_eq!(config.port, 465); assert!(config.secure); assert_eq!(config.domain, "myserver.com"); assert_eq!(config.connection_timeout_secs, 60); assert_eq!(config.socket_timeout_secs, 300); assert_eq!(config.effective_pool_key(), "gmail"); assert_eq!(config.max_pool_connections, 5); let auth = config.auth.unwrap(); assert_eq!(auth.user, "u"); assert_eq!(auth.pass, "p"); assert_eq!(auth.method, "LOGIN"); } #[test] fn test_effective_pool_key_default() { let json = r#"{"host":"mx.example.com","port":587}"#; let config: SmtpClientConfig = serde_json::from_str(json).unwrap(); assert_eq!(config.effective_pool_key(), "mx.example.com:587"); } #[test] fn test_dkim_config_deserialize() { let json = r#"{"domain":"example.com","selector":"mta","privateKey":"-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----"}"#; let dkim: DkimSignConfig = serde_json::from_str(json).unwrap(); assert_eq!(dkim.domain, "example.com"); assert_eq!(dkim.selector, "mta"); assert!(dkim.private_key.contains("RSA PRIVATE KEY")); } }