//! Service configuration. use std::path::PathBuf; use std::time::Duration; use federation_net::NetworkId; use crate::error::{ArtistDhtError, Result}; /// Default interval between republish rounds. pub const DEFAULT_REPUBLISH_INTERVAL: Duration = Duration::from_secs(10 * 60); /// Default interval between expired-record sweeps. pub const DEFAULT_EXPIRE_INTERVAL: Duration = Duration::from_secs(60); /// Default timeout of a single DHT request. pub const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(5); /// Default timeout of a whole iterative lookup. pub const DEFAULT_LOOKUP_TIMEOUT: Duration = Duration::from_secs(15); /// Default timeout for transport operations (dialing, handshakes, sends). pub const DEFAULT_TRANSPORT_TIMEOUT: Duration = Duration::from_secs(15); /// Configuration for an [`crate::ArtistDhtService`]. /// /// Use [`ArtistDhtConfig::builder`] to construct a validated instance. #[derive(Debug, Clone)] pub struct ArtistDhtConfig { /// Directory for the peer identity and the SQLite database. pub data_dir: PathBuf, /// Network this peer participates in. pub network_id: NetworkId, /// Interval between automatic republish rounds. pub republish_interval: Duration, /// Interval between sweeps of expired DHT records. pub expire_interval: Duration, /// Timeout of a single DHT request. pub request_timeout: Duration, /// Timeout of a whole iterative lookup. pub lookup_timeout: Duration, /// Timeout for transport operations: dialing a peer, handshakes and /// message delivery. Kept separate from `request_timeout` because /// establishing a connection through relays can take much longer than a /// request over an existing one. pub transport_timeout: Duration, } impl ArtistDhtConfig { /// Returns a new [`ArtistDhtConfigBuilder`]. pub fn builder() -> ArtistDhtConfigBuilder { ArtistDhtConfigBuilder::default() } } /// Builder for [`ArtistDhtConfig`]. /// /// `data_dir` and `network_id` are required; the timers default to the /// production values and are configurable mainly for tests. #[derive(Debug, Default, Clone)] pub struct ArtistDhtConfigBuilder { data_dir: Option, network_id: Option, republish_interval: Option, expire_interval: Option, request_timeout: Option, lookup_timeout: Option, transport_timeout: Option, } impl ArtistDhtConfigBuilder { /// Sets the data directory. pub fn data_dir(mut self, dir: impl Into) -> Self { self.data_dir = Some(dir.into()); self } /// Sets the network identifier. pub fn network_id(mut self, network_id: NetworkId) -> Self { self.network_id = Some(network_id); self } /// Sets the republish interval. pub fn republish_interval(mut self, interval: Duration) -> Self { self.republish_interval = Some(interval); self } /// Sets the expired-record sweep interval. pub fn expire_interval(mut self, interval: Duration) -> Self { self.expire_interval = Some(interval); self } /// Sets the timeout of a single DHT request. pub fn request_timeout(mut self, timeout: Duration) -> Self { self.request_timeout = Some(timeout); self } /// Sets the timeout of a whole iterative lookup. pub fn lookup_timeout(mut self, timeout: Duration) -> Self { self.lookup_timeout = Some(timeout); self } /// Sets the timeout for transport operations (dialing, handshakes). pub fn transport_timeout(mut self, timeout: Duration) -> Self { self.transport_timeout = Some(timeout); self } /// Validates and builds the configuration. pub fn build(self) -> Result { let data_dir = self .data_dir .ok_or_else(|| ArtistDhtError::Database("data_dir is required".into()))?; if data_dir.as_os_str().is_empty() { return Err(ArtistDhtError::Database( "data_dir must not be empty".into(), )); } let network_id = self .network_id .ok_or_else(|| ArtistDhtError::Network("network_id is required".into()))?; let config = ArtistDhtConfig { data_dir, network_id, republish_interval: self .republish_interval .unwrap_or(DEFAULT_REPUBLISH_INTERVAL), expire_interval: self.expire_interval.unwrap_or(DEFAULT_EXPIRE_INTERVAL), request_timeout: self.request_timeout.unwrap_or(DEFAULT_REQUEST_TIMEOUT), lookup_timeout: self.lookup_timeout.unwrap_or(DEFAULT_LOOKUP_TIMEOUT), transport_timeout: self.transport_timeout.unwrap_or(DEFAULT_TRANSPORT_TIMEOUT), }; for (name, value) in [ ("republish_interval", config.republish_interval), ("expire_interval", config.expire_interval), ("request_timeout", config.request_timeout), ("lookup_timeout", config.lookup_timeout), ("transport_timeout", config.transport_timeout), ] { if value.is_zero() { return Err(ArtistDhtError::Database(format!( "{name} must be greater than zero" ))); } } Ok(config) } } #[cfg(test)] mod tests { use super::*; #[test] fn builder_applies_defaults() { let config = ArtistDhtConfig::builder() .data_dir("./dir") .network_id(NetworkId::from_name("test")) .build() .expect("valid"); assert_eq!(config.republish_interval, DEFAULT_REPUBLISH_INTERVAL); assert_eq!(config.expire_interval, DEFAULT_EXPIRE_INTERVAL); assert_eq!(config.request_timeout, DEFAULT_REQUEST_TIMEOUT); assert_eq!(config.lookup_timeout, DEFAULT_LOOKUP_TIMEOUT); } #[test] fn builder_rejects_missing_or_invalid() { assert!(ArtistDhtConfig::builder().build().is_err()); assert!( ArtistDhtConfig::builder() .data_dir("./dir") .network_id(NetworkId::from_name("test")) .request_timeout(Duration::ZERO) .build() .is_err() ); } }