1#[cfg(unix)]
8use std::path::PathBuf;
9use std::{str::FromStr, time::Duration};
10
11use derive_more::Display;
12use eyre::{bail, Result};
13use serde::{
14 de::{self, Deserializer},
15 Deserialize, Serialize,
16};
17
18#[derive(Debug, Clone, Deserialize)]
30pub struct Config {
31 #[serde(default)]
37 pub listen: Vec<Transport>,
38 #[serde(default)]
40 pub discovery: DiscoveryConfig, #[serde(default)]
43 pub compression: CompressionConfig,
44 #[serde(with = "humantime_serde", default = "default_ping_interval")]
51 pub ping_interval: Duration,
52 #[serde(with = "humantime_serde", default = "default_idle_timeout")]
62 pub idle_timeout: Duration,
63 #[serde(default = "default_transport_specific_metrics")]
69 pub transport_specific_metrics: bool,
70}
71
72fn default_ping_interval() -> Duration {
73 Duration::from_secs(5)
74}
75
76fn default_idle_timeout() -> Duration {
77 Duration::from_secs(30)
78}
79
80fn default_transport_specific_metrics() -> bool {
81 false
82}
83
84#[derive(Debug, Deserialize, Clone)]
86#[serde(default)]
87pub struct DiscoveryConfig {
88 pub predefined: Vec<Transport>,
94 #[serde(with = "humantime_serde")]
98 pub attempt_interval: Duration,
99}
100
101impl Default for DiscoveryConfig {
102 fn default() -> Self {
103 Self {
104 predefined: Vec::new(),
105 attempt_interval: Duration::from_secs(10),
106 }
107 }
108}
109
110#[derive(Debug, Clone, Hash, PartialEq, Eq, Display, Serialize)]
112pub enum Transport {
113 #[display("tcp://{_0}")]
115 Tcp(String),
116 #[cfg(unix)]
120 #[display("uds://{}", _0.display())]
121 Uds(PathBuf),
122 #[cfg(feature = "turmoil06")]
128 #[display("turmoil06://{_0}")]
129 Turmoil06(String),
130 #[cfg(feature = "turmoil07")]
136 #[display("turmoil07://{_0}")]
137 Turmoil07(String),
138}
139
140impl FromStr for Transport {
141 type Err = eyre::Error;
142
143 fn from_str(s: &str) -> Result<Self> {
144 #[cfg(unix)]
145 const PROTOCOLS: &str = "tcp or uds";
146 #[cfg(not(unix))]
147 const PROTOCOLS: &str = "tcp";
148
149 let (protocol, addr) = s.split_once("://").unwrap_or_default();
150
151 match protocol {
152 "" => bail!("protocol must be specified ({PROTOCOLS})"),
153 "tcp" => Ok(Transport::Tcp(addr.into())),
154 #[cfg(unix)]
155 "uds" => {
156 eyre::ensure!(
157 !addr.ends_with('/'),
158 "path to UDS socket cannot be directory"
159 );
160 Ok(Transport::Uds(PathBuf::from(addr)))
161 }
162 #[cfg(feature = "turmoil06")]
163 "turmoil06" => Ok(Transport::Turmoil06(addr.into())),
164 #[cfg(feature = "turmoil07")]
165 "turmoil07" => Ok(Transport::Turmoil07(addr.into())),
166 proto => bail!("unknown protocol: {proto}"),
167 }
168 }
169}
170
171impl<'de> Deserialize<'de> for Transport {
172 fn deserialize<D>(deserializer: D) -> Result<Transport, D::Error>
173 where
174 D: Deserializer<'de>,
175 {
176 let s: String = Deserialize::deserialize(deserializer)?;
178
179 s.parse::<Transport>()
180 .map_err(|err| de::Error::custom(format!(r#"unsupported transport: "{s}", {err}"#)))
181 }
182}
183
184#[derive(Debug, Default, Deserialize, Clone)]
202pub struct CompressionConfig {
203 #[serde(default)]
205 pub lz4: Preference,
206}
207
208#[derive(Debug, Clone, Copy, Default, Deserialize)]
215pub enum Preference {
216 Preferred,
218
219 #[default]
221 Supported,
222
223 Disabled,
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn transport_parsing() {
233 assert!(Transport::from_str("")
235 .unwrap_err()
236 .to_string()
237 .starts_with("protocol must be specified"));
238 assert!(Transport::from_str("://a/b")
239 .unwrap_err()
240 .to_string()
241 .starts_with("protocol must be specified"));
242
243 assert!(Transport::from_str("foo://a")
245 .unwrap_err()
246 .to_string()
247 .starts_with("unknown protocol"));
248 #[cfg(not(unix))]
249 assert!(Transport::from_str("uds://a")
250 .unwrap_err()
251 .to_string()
252 .starts_with("unknown protocol"));
253
254 assert_eq!(
256 Transport::from_str("tcp://127.0.0.1:4242").unwrap(),
257 Transport::Tcp("127.0.0.1:4242".into())
258 );
259 assert_eq!(
260 Transport::from_str("tcp://alice:4242").unwrap(),
261 Transport::Tcp("alice:4242".into())
262 );
263
264 #[cfg(unix)]
266 {
267 assert_eq!(
268 Transport::from_str("uds:///a/b").unwrap(),
269 Transport::Uds("/a/b".into())
270 );
271 assert_eq!(
272 Transport::from_str("uds://rel/a/b").unwrap(),
273 Transport::Uds("rel/a/b".into())
274 );
275 assert_eq!(
276 Transport::from_str("uds:///a/").unwrap_err().to_string(),
277 "path to UDS socket cannot be directory"
278 );
279 }
280
281 #[cfg(feature = "turmoil06")]
283 assert_eq!(
284 Transport::from_str("turmoil06://alice").unwrap(),
285 Transport::Turmoil06("alice".into())
286 );
287
288 #[cfg(feature = "turmoil07")]
290 assert_eq!(
291 Transport::from_str("turmoil07://alice").unwrap(),
292 Transport::Turmoil07("alice".into())
293 );
294 }
295
296 #[test]
297 fn transport_display() {
298 #[cfg(unix)]
299 assert_eq!(
300 Transport::Uds(PathBuf::from("/some/path")).to_string(),
301 "uds:///some/path"
302 );
303 }
304}