Files
frid/crates/artist-dht/src/config.rs
T
2026-07-10 15:10:03 +03:00

182 lines
6.2 KiB
Rust

//! 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<PathBuf>,
network_id: Option<NetworkId>,
republish_interval: Option<Duration>,
expire_interval: Option<Duration>,
request_timeout: Option<Duration>,
lookup_timeout: Option<Duration>,
transport_timeout: Option<Duration>,
}
impl ArtistDhtConfigBuilder {
/// Sets the data directory.
pub fn data_dir(mut self, dir: impl Into<PathBuf>) -> 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<ArtistDhtConfig> {
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()
);
}
}