#[cfg(unix)]
use std::path::PathBuf;
use std::{str::FromStr, time::Duration};
use derive_more::Display;
use eyre::{bail, Result};
use serde::{
de::{self, Deserializer},
Deserialize, Serialize,
};
#[derive(Debug, Clone, Deserialize)]
pub struct Config {
#[serde(default)]
pub listen: Vec<Transport>,
#[serde(default)]
pub discovery: DiscoveryConfig, #[serde(default)]
pub compression: CompressionConfig,
#[serde(with = "humantime_serde", default = "default_ping_interval")]
pub ping_interval: Duration,
#[serde(with = "humantime_serde", default = "default_idle_timeout")]
pub idle_timeout: Duration,
#[serde(default = "default_transport_specific_metrics")]
pub transport_specific_metrics: bool,
}
fn default_ping_interval() -> Duration {
Duration::from_secs(5)
}
fn default_idle_timeout() -> Duration {
Duration::from_secs(30)
}
fn default_transport_specific_metrics() -> bool {
false
}
#[derive(Debug, Deserialize, Clone)]
#[serde(default)]
pub struct DiscoveryConfig {
pub predefined: Vec<Transport>,
#[serde(with = "humantime_serde")]
pub attempt_interval: Duration,
}
impl Default for DiscoveryConfig {
fn default() -> Self {
Self {
predefined: Vec::new(),
attempt_interval: Duration::from_secs(10),
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Display, Serialize)]
pub enum Transport {
#[display("tcp://{_0}")]
Tcp(String),
#[cfg(unix)]
#[display("uds://{}", _0.display())]
Uds(PathBuf),
#[cfg(feature = "turmoil06")]
#[display("turmoil06://{_0}")]
Turmoil06(String),
#[cfg(feature = "turmoil07")]
#[display("turmoil07://{_0}")]
Turmoil07(String),
}
impl FromStr for Transport {
type Err = eyre::Error;
fn from_str(s: &str) -> Result<Self> {
#[cfg(unix)]
const PROTOCOLS: &str = "tcp or uds";
#[cfg(not(unix))]
const PROTOCOLS: &str = "tcp";
let (protocol, addr) = s.split_once("://").unwrap_or_default();
match protocol {
"" => bail!("protocol must be specified ({PROTOCOLS})"),
"tcp" => Ok(Transport::Tcp(addr.into())),
#[cfg(unix)]
"uds" => {
eyre::ensure!(
!addr.ends_with('/'),
"path to UDS socket cannot be directory"
);
Ok(Transport::Uds(PathBuf::from(addr)))
}
#[cfg(feature = "turmoil06")]
"turmoil06" => Ok(Transport::Turmoil06(addr.into())),
#[cfg(feature = "turmoil07")]
"turmoil07" => Ok(Transport::Turmoil07(addr.into())),
proto => bail!("unknown protocol: {proto}"),
}
}
}
impl<'de> Deserialize<'de> for Transport {
fn deserialize<D>(deserializer: D) -> Result<Transport, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
s.parse::<Transport>()
.map_err(|err| de::Error::custom(format!(r#"unsupported transport: "{s}", {err}"#)))
}
}
#[derive(Debug, Default, Deserialize, Clone)]
pub struct CompressionConfig {
#[serde(default)]
pub lz4: Preference,
}
#[derive(Debug, Clone, Copy, Default, Deserialize)]
pub enum Preference {
Preferred,
#[default]
Supported,
Disabled,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn transport_parsing() {
assert!(Transport::from_str("")
.unwrap_err()
.to_string()
.starts_with("protocol must be specified"));
assert!(Transport::from_str("://a/b")
.unwrap_err()
.to_string()
.starts_with("protocol must be specified"));
assert!(Transport::from_str("foo://a")
.unwrap_err()
.to_string()
.starts_with("unknown protocol"));
#[cfg(not(unix))]
assert!(Transport::from_str("uds://a")
.unwrap_err()
.to_string()
.starts_with("unknown protocol"));
assert_eq!(
Transport::from_str("tcp://127.0.0.1:4242").unwrap(),
Transport::Tcp("127.0.0.1:4242".into())
);
assert_eq!(
Transport::from_str("tcp://alice:4242").unwrap(),
Transport::Tcp("alice:4242".into())
);
#[cfg(unix)]
{
assert_eq!(
Transport::from_str("uds:///a/b").unwrap(),
Transport::Uds("/a/b".into())
);
assert_eq!(
Transport::from_str("uds://rel/a/b").unwrap(),
Transport::Uds("rel/a/b".into())
);
assert_eq!(
Transport::from_str("uds:///a/").unwrap_err().to_string(),
"path to UDS socket cannot be directory"
);
}
#[cfg(feature = "turmoil06")]
assert_eq!(
Transport::from_str("turmoil06://alice").unwrap(),
Transport::Turmoil06("alice".into())
);
#[cfg(feature = "turmoil07")]
assert_eq!(
Transport::from_str("turmoil07://alice").unwrap(),
Transport::Turmoil07("alice".into())
);
}
#[test]
fn transport_display() {
#[cfg(unix)]
assert_eq!(
Transport::Uds(PathBuf::from("/some/path")).to_string(),
"uds:///some/path"
);
}
}