use super::defaults;
use crate::types::{
CacheCapacity, HostName, MaxConnections, MaxErrors, Port, ServerName, ThreadCount,
duration_serde, option_duration_serde,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, clap::ValueEnum)]
#[serde(rename_all = "kebab-case")]
#[value(rename_all = "kebab-case")]
pub enum RoutingMode {
Stateful,
#[serde(alias = "percommand")]
PerCommand,
Hybrid,
}
impl Default for RoutingMode {
fn default() -> Self {
Self::Hybrid
}
}
impl RoutingMode {
#[must_use]
pub const fn supports_per_command_routing(&self) -> bool {
matches!(self, Self::PerCommand | Self::Hybrid)
}
#[must_use]
pub const fn supports_stateful_commands(&self) -> bool {
matches!(self, Self::Stateful | Self::Hybrid)
}
#[must_use]
pub const fn short_name(&self) -> &'static str {
match self {
Self::Stateful => "stateful",
Self::PerCommand => "per-command",
Self::Hybrid => "hybrid",
}
}
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Stateful => "stateful 1:1 mode",
Self::PerCommand => "per-command routing mode (stateless)",
Self::Hybrid => "hybrid routing mode",
}
}
}
impl std::fmt::Display for RoutingMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, clap::ValueEnum)]
#[serde(rename_all = "kebab-case")]
pub enum BackendSelectionStrategy {
#[serde(alias = "round-robin")]
WeightedRoundRobin,
#[serde(alias = "adaptive-weighted")]
LeastLoaded,
}
impl Default for BackendSelectionStrategy {
fn default() -> Self {
Self::LeastLoaded
}
}
impl BackendSelectionStrategy {
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::WeightedRoundRobin => "weighted round-robin",
Self::LeastLoaded => "least-loaded",
}
}
}
impl std::fmt::Display for BackendSelectionStrategy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
pub struct Config {
#[serde(default)]
pub proxy: Proxy,
#[serde(default)]
pub routing: Routing,
#[serde(default)]
pub memory: Memory,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cache: Option<Cache>,
#[serde(default)]
pub health_check: HealthCheck,
#[serde(default)]
pub client_auth: ClientAuth,
#[serde(default)]
pub servers: Vec<Server>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default)]
pub struct Proxy {
pub host: String,
pub port: Port,
pub threads: ThreadCount,
#[serde(skip_serializing)]
pub routing_mode: RoutingMode,
#[serde(skip_serializing)]
pub backend_selection: BackendSelectionStrategy,
pub validate_yenc: bool,
#[serde(default = "super::defaults::log_file_level")]
pub log_file_level: String,
#[serde(default)]
pub stats_file: Option<std::path::PathBuf>,
#[serde(default, skip_serializing)]
pub buffer_pool_count: usize,
#[serde(default, skip_serializing)]
pub capture_pool_count: usize,
}
impl Proxy {
pub const DEFAULT_HOST: &'static str = "0.0.0.0";
}
impl Default for Proxy {
fn default() -> Self {
Self {
host: Self::DEFAULT_HOST.to_string(),
port: Port::default(),
threads: ThreadCount::default(),
validate_yenc: true,
log_file_level: defaults::log_file_level(),
stats_file: None,
routing_mode: RoutingMode::default(),
backend_selection: BackendSelectionStrategy::default(),
buffer_pool_count: defaults::buffer_pool_count(),
capture_pool_count: defaults::capture_pool_count(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default)]
pub struct Routing {
#[serde(rename = "mode", alias = "routing_mode")]
pub routing_mode: RoutingMode,
#[serde(alias = "strategy")]
pub backend_selection: BackendSelectionStrategy,
#[serde(default = "super::defaults::adaptive_precheck")]
pub adaptive_precheck: bool,
}
impl Default for Routing {
fn default() -> Self {
Self {
routing_mode: RoutingMode::default(),
backend_selection: BackendSelectionStrategy::default(),
adaptive_precheck: defaults::adaptive_precheck(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default)]
pub struct Memory {
#[serde(default = "super::defaults::socket_recv_buffer_size")]
pub socket_recv_buffer_size: usize,
#[serde(default = "super::defaults::socket_send_buffer_size")]
pub socket_send_buffer_size: usize,
#[serde(default = "super::defaults::buffer_pool_size")]
pub buffer_pool_size: usize,
#[serde(default = "super::defaults::buffer_pool_count")]
pub buffer_pool_count: usize,
#[serde(default = "super::defaults::capture_pool_size")]
pub capture_pool_size: usize,
#[serde(default = "super::defaults::capture_pool_count")]
pub capture_pool_count: usize,
}
impl Default for Memory {
fn default() -> Self {
Self {
socket_recv_buffer_size: defaults::socket_recv_buffer_size(),
socket_send_buffer_size: defaults::socket_send_buffer_size(),
buffer_pool_size: defaults::buffer_pool_size(),
buffer_pool_count: defaults::buffer_pool_count(),
capture_pool_size: defaults::capture_pool_size(),
capture_pool_count: defaults::capture_pool_count(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default)]
pub struct Cache {
#[serde(
default = "super::defaults::cache_max_capacity",
rename = "article_cache_capacity",
alias = "cache_capacity",
alias = "max_capacity"
)]
pub article_cache_capacity: CacheCapacity,
#[serde(
with = "duration_serde",
default = "super::defaults::cache_ttl",
rename = "article_cache_ttl_secs",
alias = "cache_ttl",
alias = "ttl_secs",
alias = "ttl"
)]
pub article_cache_ttl_secs: Duration,
#[serde(
default = "super::defaults::cache_articles",
rename = "store_article_bodies",
alias = "store_articles",
alias = "cache_articles"
)]
pub store_article_bodies: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub disk: Option<DiskCache>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
rename = "availability_index_path",
alias = "availability_path",
alias = "availability_file"
)]
pub availability_index_path: Option<std::path::PathBuf>,
#[serde(default, skip_serializing)]
pub adaptive_precheck: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, clap::ValueEnum)]
#[serde(rename_all = "lowercase")]
pub enum CompressionCodec {
None,
#[default]
Lz4,
Zstd,
}
impl std::fmt::Display for CompressionCodec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::Lz4 => write!(f, "lz4"),
Self::Zstd => write!(f, "zstd"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DiskCache {
#[serde(default = "super::defaults::disk_cache_path")]
pub path: std::path::PathBuf,
#[serde(default = "super::defaults::disk_cache_capacity")]
pub capacity: CacheCapacity,
#[serde(default = "super::defaults::disk_cache_compression_codec")]
pub compression: CompressionCodec,
#[serde(default = "super::defaults::disk_cache_shards")]
pub shards: usize,
}
impl Default for DiskCache {
fn default() -> Self {
Self {
path: defaults::disk_cache_path(),
capacity: defaults::disk_cache_capacity(),
compression: defaults::disk_cache_compression_codec(),
shards: defaults::disk_cache_shards(),
}
}
}
impl Default for Cache {
fn default() -> Self {
Self {
article_cache_capacity: defaults::cache_max_capacity(),
article_cache_ttl_secs: defaults::cache_ttl(),
store_article_bodies: defaults::cache_articles(),
disk: None,
availability_index_path: None,
adaptive_precheck: defaults::adaptive_precheck(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct HealthCheck {
#[serde(
with = "duration_serde",
default = "super::defaults::health_check_interval"
)]
pub interval: Duration,
#[serde(
with = "duration_serde",
default = "super::defaults::health_check_timeout"
)]
pub timeout: Duration,
#[serde(default = "super::defaults::unhealthy_threshold")]
pub unhealthy_threshold: MaxErrors,
}
impl Default for HealthCheck {
fn default() -> Self {
Self {
interval: super::defaults::health_check_interval(),
timeout: super::defaults::health_check_timeout(),
unhealthy_threshold: super::defaults::unhealthy_threshold(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct ClientAuth {
#[serde(skip_serializing_if = "Option::is_none")]
pub greeting: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub users: Vec<UserCredentials>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UserCredentials {
pub username: String,
pub password: String,
}
impl ClientAuth {
#[must_use]
pub const fn is_enabled(&self) -> bool {
!self.users.is_empty()
}
#[must_use]
pub fn all_users(&self) -> Vec<(&str, &str)> {
self.users
.iter()
.map(|user| (user.username.as_str(), user.password.as_str()))
.collect()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Server {
pub host: HostName,
pub port: Port,
pub name: ServerName,
#[serde(skip_serializing_if = "Option::is_none")]
pub username: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
#[serde(default = "super::defaults::max_connections")]
pub max_connections: MaxConnections,
#[serde(default)]
pub use_tls: bool,
#[serde(default = "super::defaults::tls_verify_cert")]
pub tls_verify_cert: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub tls_cert_path: Option<String>,
#[serde(
with = "option_duration_serde",
default,
skip_serializing_if = "Option::is_none"
)]
pub connection_keepalive: Option<Duration>,
#[serde(
with = "option_duration_serde",
default = "super::defaults::replacement_cooldown_option",
skip_serializing_if = "Option::is_none"
)]
pub replacement_cooldown: Option<Duration>,
#[serde(default = "super::defaults::health_check_max_per_cycle")]
pub health_check_max_per_cycle: usize,
#[serde(
with = "duration_serde",
default = "super::defaults::health_check_pool_timeout"
)]
pub health_check_pool_timeout: Duration,
#[serde(default)]
pub tier: u8,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub compress: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub compress_level: Option<u32>,
#[serde(
with = "duration_serde",
default = "super::defaults::backend_idle_timeout"
)]
pub backend_idle_timeout: Duration,
}
pub struct ServerBuilder {
host: String,
port: Port,
name: Option<String>,
username: Option<String>,
password: Option<String>,
max_connections: Option<MaxConnections>,
use_tls: bool,
tls_verify_cert: bool,
tls_cert_path: Option<String>,
connection_keepalive: Option<Duration>,
replacement_cooldown: Option<Duration>,
health_check_max_per_cycle: Option<usize>,
health_check_pool_timeout: Option<Duration>,
tier: u8,
compress: Option<bool>,
compress_level: Option<u32>,
backend_idle_timeout: Option<Duration>,
}
impl ServerBuilder {
#[must_use]
pub fn new(host: impl Into<String>, port: Port) -> Self {
Self {
host: host.into(),
port,
name: None,
username: None,
password: None,
max_connections: None,
use_tls: false,
tls_verify_cert: true, tls_cert_path: None,
connection_keepalive: None,
replacement_cooldown: None,
health_check_max_per_cycle: None,
health_check_pool_timeout: None,
tier: 0,
compress: None,
compress_level: None,
backend_idle_timeout: None,
}
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn username(mut self, username: impl Into<String>) -> Self {
self.username = Some(username.into());
self
}
#[must_use]
pub fn password(mut self, password: impl Into<String>) -> Self {
self.password = Some(password.into());
self
}
#[must_use]
pub const fn max_connections(mut self, max: MaxConnections) -> Self {
self.max_connections = Some(max);
self
}
#[must_use]
pub const fn use_tls(mut self, enabled: bool) -> Self {
self.use_tls = enabled;
self
}
#[must_use]
pub const fn tls_verify_cert(mut self, verify: bool) -> Self {
self.tls_verify_cert = verify;
self
}
#[must_use]
pub fn tls_cert_path(mut self, path: impl Into<String>) -> Self {
self.tls_cert_path = Some(path.into());
self
}
#[must_use]
pub const fn connection_keepalive(mut self, interval: Duration) -> Self {
self.connection_keepalive = Some(interval);
self
}
#[must_use]
pub const fn replacement_cooldown(mut self, cooldown: Duration) -> Self {
self.replacement_cooldown = Some(cooldown);
self
}
#[must_use]
pub const fn health_check_max_per_cycle(mut self, max: usize) -> Self {
self.health_check_max_per_cycle = Some(max);
self
}
#[must_use]
pub const fn health_check_pool_timeout(mut self, timeout: Duration) -> Self {
self.health_check_pool_timeout = Some(timeout);
self
}
#[must_use]
pub const fn tier(mut self, tier: u8) -> Self {
self.tier = tier;
self
}
#[must_use]
pub const fn compress(mut self, compress: Option<bool>) -> Self {
self.compress = compress;
self
}
#[must_use]
pub fn compress_level(mut self, level: u32) -> Self {
assert!(level <= 9, "compress_level must be 0-9, got {level}");
self.compress_level = Some(level);
self
}
#[must_use]
pub const fn backend_idle_timeout(mut self, timeout: Duration) -> Self {
self.backend_idle_timeout = Some(timeout);
self
}
pub fn build(self) -> Result<Server, anyhow::Error> {
use crate::types::{HostName, ServerName};
let host = HostName::try_new(self.host.clone())?;
let port = self.port; let name_str = self
.name
.unwrap_or_else(|| format!("{}:{}", self.host, self.port.get()));
let name = ServerName::try_new(name_str)?;
let max_connections = self
.max_connections
.unwrap_or_else(super::defaults::max_connections);
let health_check_max_per_cycle = self
.health_check_max_per_cycle
.unwrap_or_else(super::defaults::health_check_max_per_cycle);
let health_check_pool_timeout = self
.health_check_pool_timeout
.unwrap_or_else(super::defaults::health_check_pool_timeout);
Ok(Server {
host,
port,
name,
username: self.username,
password: self.password,
max_connections,
use_tls: self.use_tls,
tls_verify_cert: self.tls_verify_cert,
tls_cert_path: self.tls_cert_path,
connection_keepalive: self.connection_keepalive,
replacement_cooldown: self
.replacement_cooldown
.or_else(super::defaults::replacement_cooldown_option),
health_check_max_per_cycle,
health_check_pool_timeout,
tier: self.tier,
compress: self.compress,
compress_level: self.compress_level,
backend_idle_timeout: self
.backend_idle_timeout
.unwrap_or_else(super::defaults::backend_idle_timeout),
})
}
}
impl Server {
#[must_use]
pub fn builder(host: impl Into<String>, port: Port) -> ServerBuilder {
ServerBuilder::new(host, port)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_routing_mode_default() {
assert_eq!(RoutingMode::default(), RoutingMode::Hybrid);
}
#[test]
fn test_routing_mode_supports_per_command() {
assert!(RoutingMode::PerCommand.supports_per_command_routing());
assert!(RoutingMode::Hybrid.supports_per_command_routing());
assert!(!RoutingMode::Stateful.supports_per_command_routing());
}
#[test]
fn test_routing_mode_supports_stateful() {
assert!(RoutingMode::Stateful.supports_stateful_commands());
assert!(RoutingMode::Hybrid.supports_stateful_commands());
assert!(!RoutingMode::PerCommand.supports_stateful_commands());
}
#[test]
fn test_routing_mode_as_str() {
assert_eq!(RoutingMode::Stateful.as_str(), "stateful 1:1 mode");
assert_eq!(
RoutingMode::PerCommand.as_str(),
"per-command routing mode (stateless)"
);
assert_eq!(RoutingMode::Hybrid.as_str(), "hybrid routing mode");
}
#[test]
fn test_routing_mode_display() {
assert_eq!(RoutingMode::Stateful.to_string(), "stateful 1:1 mode");
assert_eq!(RoutingMode::Hybrid.to_string(), "hybrid routing mode");
}
#[test]
fn test_proxy_default() {
let proxy = Proxy::default();
assert_eq!(proxy.host, "0.0.0.0");
assert_eq!(proxy.port.get(), 8119);
}
#[test]
fn test_proxy_default_host_constant() {
assert_eq!(Proxy::DEFAULT_HOST, "0.0.0.0");
}
#[test]
fn test_cache_default() {
let cache = Cache::default();
assert_eq!(cache.article_cache_capacity.get(), 64 * 1024 * 1024); assert_eq!(
cache.article_cache_ttl_secs,
crate::constants::duration_polyfill::from_hours(1)
);
assert!(!cache.store_article_bodies);
}
#[test]
fn test_memory_default() {
let memory = Memory::default();
assert_eq!(
memory.socket_recv_buffer_size,
crate::constants::socket::HIGH_THROUGHPUT_RECV_BUFFER
);
assert_eq!(
memory.socket_send_buffer_size,
crate::constants::socket::HIGH_THROUGHPUT_SEND_BUFFER
);
assert_eq!(memory.buffer_pool_size, crate::constants::buffer::POOL);
assert_eq!(
memory.buffer_pool_count,
crate::constants::buffer::POOL_COUNT
);
assert_eq!(memory.capture_pool_size, crate::constants::buffer::CAPTURE);
assert_eq!(
memory.capture_pool_count,
crate::constants::buffer::CAPTURE_COUNT
);
}
#[test]
fn test_health_check_default() {
let hc = HealthCheck::default();
assert_eq!(hc.interval, Duration::from_secs(30));
assert_eq!(hc.timeout, Duration::from_secs(5));
assert_eq!(hc.unhealthy_threshold.get(), 3);
}
#[test]
fn test_client_auth_is_enabled() {
let mut auth = ClientAuth::default();
assert!(!auth.is_enabled());
auth.users.push(UserCredentials {
username: "user".to_string(),
password: "pass".to_string(),
});
assert!(auth.is_enabled());
}
#[test]
fn test_client_auth_is_enabled_multi_user() {
let mut auth = ClientAuth::default();
auth.users.push(UserCredentials {
username: "alice".to_string(),
password: "secret".to_string(),
});
assert!(auth.is_enabled());
}
#[test]
fn test_client_auth_all_users_single() {
let mut auth = ClientAuth::default();
auth.users.push(UserCredentials {
username: "user".to_string(),
password: "pass".to_string(),
});
let users = auth.all_users();
assert_eq!(users.len(), 1);
assert_eq!(users[0], ("user", "pass"));
}
#[test]
fn test_client_auth_all_users_multi() {
let mut auth = ClientAuth::default();
auth.users.push(UserCredentials {
username: "alice".to_string(),
password: "alice_pw".to_string(),
});
auth.users.push(UserCredentials {
username: "bob".to_string(),
password: "bob_pw".to_string(),
});
let users = auth.all_users();
assert_eq!(users.len(), 2);
assert_eq!(users[0], ("alice", "alice_pw"));
assert_eq!(users[1], ("bob", "bob_pw"));
}
#[test]
fn test_server_builder_minimal() {
let server = Server::builder("news.example.com", Port::try_new(119).unwrap())
.build()
.unwrap();
assert_eq!(server.host.as_str(), "news.example.com");
assert_eq!(server.port.get(), 119);
assert_eq!(server.name.as_str(), "news.example.com:119");
assert_eq!(server.max_connections.get(), 10);
assert!(!server.use_tls);
assert!(server.tls_verify_cert); }
#[test]
fn test_server_builder_with_name() {
let server = Server::builder("localhost", Port::try_new(119).unwrap())
.name("Test Server")
.build()
.unwrap();
assert_eq!(server.name.as_str(), "Test Server");
}
#[test]
fn test_server_builder_with_auth() {
let server = Server::builder("news.example.com", Port::try_new(119).unwrap())
.username("testuser")
.password("testpass")
.build()
.unwrap();
assert_eq!(server.username.as_ref().unwrap(), "testuser");
assert_eq!(server.password.as_ref().unwrap(), "testpass");
}
#[test]
fn test_server_builder_with_max_connections() {
let server = Server::builder("localhost", Port::try_new(119).unwrap())
.max_connections(MaxConnections::try_new(20).unwrap())
.build()
.unwrap();
assert_eq!(server.max_connections.get(), 20);
}
#[test]
fn test_server_builder_with_tls() {
let server = Server::builder("secure.example.com", Port::try_new(563).unwrap())
.use_tls(true)
.tls_verify_cert(false)
.tls_cert_path("/path/to/cert.pem")
.build()
.unwrap();
assert!(server.use_tls);
assert!(!server.tls_verify_cert);
assert_eq!(server.tls_cert_path.as_ref().unwrap(), "/path/to/cert.pem");
}
#[test]
fn test_server_builder_with_keepalive() {
let keepalive = crate::constants::duration_polyfill::from_minutes(5);
let server = Server::builder("localhost", Port::try_new(119).unwrap())
.connection_keepalive(keepalive)
.build()
.unwrap();
assert_eq!(server.connection_keepalive, Some(keepalive));
}
#[test]
fn test_server_builder_default_replacement_cooldown() {
let server = Server::builder("localhost", Port::try_new(119).unwrap())
.build()
.unwrap();
assert_eq!(
server.replacement_cooldown,
super::defaults::replacement_cooldown_option()
);
}
#[test]
fn test_server_builder_with_replacement_cooldown() {
let cooldown = Duration::from_secs(31);
let server = Server::builder("localhost", Port::try_new(119).unwrap())
.replacement_cooldown(cooldown)
.build()
.unwrap();
assert_eq!(server.replacement_cooldown, Some(cooldown));
}
#[test]
fn test_server_builder_with_health_check_settings() {
let timeout = Duration::from_millis(500);
let server = Server::builder("localhost", Port::try_new(119).unwrap())
.health_check_max_per_cycle(5)
.health_check_pool_timeout(timeout)
.build()
.unwrap();
assert_eq!(server.health_check_max_per_cycle, 5);
assert_eq!(server.health_check_pool_timeout, timeout);
}
#[test]
fn test_server_builder_chaining() {
let server = Server::builder("news.example.com", Port::try_new(563).unwrap())
.name("Production Server")
.username("admin")
.password("secret")
.max_connections(MaxConnections::try_new(25).unwrap())
.use_tls(true)
.tls_verify_cert(true)
.build()
.unwrap();
assert_eq!(server.name.as_str(), "Production Server");
assert_eq!(server.max_connections.get(), 25);
assert!(server.use_tls);
}
#[test]
fn test_config_default() {
let config = Config::default();
assert!(config.servers.is_empty());
assert_eq!(config.proxy.host, "0.0.0.0");
assert!(config.cache.is_none());
assert!(!config.client_auth.is_enabled());
}
#[test]
fn test_compression_codec_serde_lz4() {
let json = r#""lz4""#;
let codec: CompressionCodec = serde_json::from_str(json).unwrap();
assert_eq!(codec, CompressionCodec::Lz4);
assert_eq!(serde_json::to_string(&codec).unwrap(), json);
}
#[test]
fn test_compression_codec_serde_zstd() {
let json = r#""zstd""#;
let codec: CompressionCodec = serde_json::from_str(json).unwrap();
assert_eq!(codec, CompressionCodec::Zstd);
}
#[test]
fn test_compression_codec_serde_none() {
let json = r#""none""#;
let codec: CompressionCodec = serde_json::from_str(json).unwrap();
assert_eq!(codec, CompressionCodec::None);
}
#[test]
fn test_compression_codec_default_is_lz4() {
assert_eq!(CompressionCodec::default(), CompressionCodec::Lz4);
}
#[test]
fn test_compression_codec_display() {
assert_eq!(CompressionCodec::Lz4.to_string(), "lz4");
assert_eq!(CompressionCodec::Zstd.to_string(), "zstd");
assert_eq!(CompressionCodec::None.to_string(), "none");
}
#[test]
fn test_disk_cache_default_compression_is_lz4() {
let disk_cache = DiskCache::default();
assert_eq!(disk_cache.compression, CompressionCodec::Lz4);
}
#[test]
fn test_disk_cache_deserialize_compression_codec() {
let toml = r#"
path = "/tmp/cache"
capacity = "100mb"
compression = "zstd"
shards = 4
"#;
let disk_cache: DiskCache = toml::from_str(toml).unwrap();
assert_eq!(disk_cache.compression, CompressionCodec::Zstd);
}
#[test]
fn test_disk_cache_deserialize_compression_none() {
let toml = r#"
path = "/tmp/cache"
capacity = "100mb"
compression = "none"
shards = 4
"#;
let disk_cache: DiskCache = toml::from_str(toml).unwrap();
assert_eq!(disk_cache.compression, CompressionCodec::None);
}
}