Skip to main content

rspamd_client/
config.rs

1//!
2//! ## Configuration for rspamd-client
3//!
4//! The `Config` struct allows you to customize various aspects of the client, including the base URL, proxy settings, and TLS settings.
5//!
6
7use std::collections::HashMap;
8use std::iter::IntoIterator;
9use std::time::Duration;
10use typed_builder::TypedBuilder;
11
12/// Custom TLS settings for the Rspamd client
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub struct TlsSettings {
15    /// Path to the TLS certificate file
16    pub cert_path: String,
17
18    /// Path to the TLS key file
19    pub key_path: String,
20
21    /// Optional path to the TLS CA file
22    pub ca_path: Option<String>,
23}
24
25/// Proxy configuration for the Rspamd client
26#[derive(Debug, Clone, PartialEq, Eq, Hash)]
27pub struct ProxyConfig {
28    /// Proxy server URL
29    pub proxy_url: String,
30
31    /// Optional username for proxy authentication
32    pub username: Option<String>,
33
34    /// Optional password for proxy authentication
35    pub password: Option<String>,
36}
37
38#[derive(TypedBuilder, Debug, PartialEq, Default)]
39pub struct EnvelopeData {
40    /// Sender email address
41    #[builder(default, setter(strip_option))]
42    pub from: Option<String>,
43
44    /// Recipients email addresses
45    #[builder(default)]
46    pub rcpt: Vec<String>,
47
48    /// Optional IP address of the sender
49    #[builder(default, setter(strip_option))]
50    pub ip: Option<String>,
51
52    /// Optional IP of the sender
53    #[builder(default, setter(strip_option))]
54    pub user: Option<String>,
55
56    /// Optional HELO string
57    #[builder(default, setter(strip_option))]
58    pub helo: Option<String>,
59
60    /// Optional hostname
61    #[builder(default, setter(strip_option))]
62    pub hostname: Option<String>,
63
64    /// Optional file path for local file scanning (File header)
65    /// When set, the message body is not transmitted and Rspamd reads the file directly from disk
66    /// This is a significant optimization when client and server are on the same host
67    #[builder(default, setter(strip_option))]
68    pub file_path: Option<String>,
69
70    /// Request rewritten body in response (body_block flag)
71    /// When enabled, if Rspamd rewrites the message body, it will be returned
72    /// as a separate part of the reply after the JSON, with Message-Offset header
73    /// indicating where the body starts
74    #[builder(default)]
75    pub body_block: bool,
76
77    /// Optional additional headers
78    #[builder(default)]
79    pub additional_headers: HashMap<String, String>,
80}
81
82impl IntoIterator for EnvelopeData {
83    type Item = (String, String);
84    type IntoIter = std::vec::IntoIter<(String, String)>;
85
86    /// Convert the EnvelopeData struct into an iterator of header pairs.
87    /// Keys may repeat: each recipient is emitted as a separate `Rcpt` header.
88    fn into_iter(self) -> Self::IntoIter {
89        let mut headers = Vec::with_capacity(self.rcpt.len() + self.additional_headers.len() + 7);
90        if let Some(from) = self.from {
91            headers.push(("From".to_string(), from));
92        }
93        if let Some(ip) = self.ip {
94            headers.push(("IP".to_string(), ip));
95        }
96        if let Some(user) = self.user {
97            headers.push(("User".to_string(), user));
98        }
99        if let Some(helo) = self.helo {
100            headers.push(("Helo".to_string(), helo));
101        }
102        if let Some(hostname) = self.hostname {
103            headers.push(("Hostname".to_string(), hostname));
104        }
105        if let Some(file_path) = self.file_path {
106            headers.push(("File".to_string(), file_path));
107        }
108        if self.body_block {
109            headers.push(("Flags".to_string(), "body_block".to_string()));
110        }
111        for rcpt in self.rcpt {
112            headers.push(("Rcpt".to_string(), rcpt));
113        }
114        headers.extend(self.additional_headers);
115        headers.into_iter()
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn envelope_emits_one_rcpt_header_per_recipient() {
125        let envelope = EnvelopeData::builder()
126            .from("sender@example.com".to_string())
127            .rcpt(vec![
128                "one@example.com".to_string(),
129                "two@example.com".to_string(),
130                "three@example.com".to_string(),
131            ])
132            .build();
133
134        let headers: Vec<(String, String)> = envelope.into_iter().collect();
135        let rcpts: Vec<&str> = headers
136            .iter()
137            .filter(|(k, _)| k == "Rcpt")
138            .map(|(_, v)| v.as_str())
139            .collect();
140        assert_eq!(
141            rcpts,
142            ["one@example.com", "two@example.com", "three@example.com"]
143        );
144    }
145}
146
147/// Configuration for Rspamd client
148///
149/// Implements `Clone`, `Eq` and `Hash`, so it can be used directly as a key
150/// in caches of persistent clients (e.g. an LRU of `RspamdAsyncClient`).
151#[derive(TypedBuilder, Debug, Clone, PartialEq, Eq, Hash)]
152pub struct Config {
153    /// Base URL of Rspamd server
154    pub base_url: String,
155
156    /// Optional API key for authentication
157    #[builder(default, setter(strip_option))]
158    pub password: Option<String>,
159
160    /// Timeout duration for requests
161    #[builder(default = Duration::from_secs(30))]
162    pub timeout: Duration,
163
164    /// Number of retries for requests
165    #[builder(default = 1)]
166    pub retries: u32,
167
168    /// Custom TLS settings for the asynchronous client
169    #[builder(default, setter(strip_option))]
170    pub tls_settings: Option<TlsSettings>,
171
172    /// Proxy configuration for the asynchronous client
173    #[builder(default, setter(strip_option))]
174    pub proxy_config: Option<ProxyConfig>,
175
176    /// Use zstd compression
177    #[builder(default = true)]
178    pub zstd: bool,
179
180    /// Encryption key if using native HTTPCrypt encryption (must be in Rspamd base32 format)
181    #[builder(default, setter(strip_option))]
182    pub encryption_key: Option<String>,
183}