1use super::defaults;
6use crate::types::{
7 CacheCapacity, HostName, MaxConnections, MaxErrors, Port, ServerName, ThreadCount,
8 duration_serde, option_duration_serde,
9};
10use serde::{Deserialize, Serialize};
11use std::time::Duration;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, clap::ValueEnum)]
15#[serde(rename_all = "kebab-case")]
16#[value(rename_all = "kebab-case")]
17pub enum RoutingMode {
18 Stateful,
20 #[serde(alias = "percommand")]
22 PerCommand,
23 Hybrid,
25}
26
27impl Default for RoutingMode {
28 fn default() -> Self {
32 Self::Hybrid
33 }
34}
35
36impl RoutingMode {
37 #[must_use]
39 pub const fn supports_per_command_routing(&self) -> bool {
40 matches!(self, Self::PerCommand | Self::Hybrid)
41 }
42
43 #[must_use]
45 pub const fn supports_stateful_commands(&self) -> bool {
46 matches!(self, Self::Stateful | Self::Hybrid)
47 }
48
49 #[must_use]
51 pub const fn short_name(&self) -> &'static str {
52 match self {
53 Self::Stateful => "stateful",
54 Self::PerCommand => "per-command",
55 Self::Hybrid => "hybrid",
56 }
57 }
58
59 #[must_use]
61 pub const fn as_str(&self) -> &'static str {
62 match self {
63 Self::Stateful => "stateful 1:1 mode",
64 Self::PerCommand => "per-command routing mode (stateless)",
65 Self::Hybrid => "hybrid routing mode",
66 }
67 }
68}
69
70impl std::fmt::Display for RoutingMode {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 f.write_str(self.as_str())
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, clap::ValueEnum)]
78#[serde(rename_all = "kebab-case")]
79pub enum BackendSelectionStrategy {
80 #[serde(alias = "round-robin")]
82 WeightedRoundRobin,
83 #[serde(alias = "adaptive-weighted")]
85 LeastLoaded,
86}
87
88impl Default for BackendSelectionStrategy {
89 fn default() -> Self {
91 Self::LeastLoaded
92 }
93}
94
95impl BackendSelectionStrategy {
96 #[must_use]
98 pub const fn as_str(&self) -> &'static str {
99 match self {
100 Self::WeightedRoundRobin => "weighted round-robin",
101 Self::LeastLoaded => "least-loaded",
102 }
103 }
104}
105
106impl std::fmt::Display for BackendSelectionStrategy {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 f.write_str(self.as_str())
109 }
110}
111
112#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
114pub struct Config {
115 #[serde(default)]
117 pub proxy: Proxy,
118 #[serde(default)]
120 pub routing: Routing,
121 #[serde(default)]
123 pub memory: Memory,
124 #[serde(default, skip_serializing_if = "Option::is_none")]
131 pub cache: Option<Cache>,
132 #[serde(default)]
134 pub health_check: HealthCheck,
135 #[serde(default)]
137 pub client_auth: ClientAuth,
138 #[serde(default)]
140 pub servers: Vec<Server>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
145#[serde(default)]
146pub struct Proxy {
147 pub host: String,
149 pub port: Port,
151 pub threads: ThreadCount,
153 #[serde(skip_serializing)]
155 pub routing_mode: RoutingMode,
156 #[serde(skip_serializing)]
158 pub backend_selection: BackendSelectionStrategy,
159 pub validate_yenc: bool,
161 #[serde(default = "super::defaults::log_file_level")]
164 pub log_file_level: String,
165 #[serde(default)]
169 pub stats_file: Option<std::path::PathBuf>,
170 #[serde(default, skip_serializing)]
172 pub buffer_pool_count: usize,
173 #[serde(default, skip_serializing)]
175 pub capture_pool_count: usize,
176}
177
178impl Proxy {
179 pub const DEFAULT_HOST: &'static str = "0.0.0.0";
181}
182
183impl Default for Proxy {
184 fn default() -> Self {
185 Self {
186 host: Self::DEFAULT_HOST.to_string(),
187 port: Port::default(),
188 threads: ThreadCount::default(),
189 validate_yenc: true,
190 log_file_level: defaults::log_file_level(),
191 stats_file: None,
192 routing_mode: RoutingMode::default(),
193 backend_selection: BackendSelectionStrategy::default(),
194 buffer_pool_count: defaults::buffer_pool_count(),
195 capture_pool_count: defaults::capture_pool_count(),
196 }
197 }
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
202#[serde(default)]
203pub struct Routing {
204 #[serde(rename = "mode", alias = "routing_mode")]
206 pub routing_mode: RoutingMode,
207 #[serde(alias = "strategy")]
209 pub backend_selection: BackendSelectionStrategy,
210 #[serde(default = "super::defaults::adaptive_precheck")]
212 pub adaptive_precheck: bool,
213}
214
215impl Default for Routing {
216 fn default() -> Self {
217 Self {
218 routing_mode: RoutingMode::default(),
219 backend_selection: BackendSelectionStrategy::default(),
220 adaptive_precheck: defaults::adaptive_precheck(),
221 }
222 }
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
227#[serde(default)]
228pub struct Memory {
229 #[serde(default = "super::defaults::socket_recv_buffer_size")]
231 pub socket_recv_buffer_size: usize,
232 #[serde(default = "super::defaults::socket_send_buffer_size")]
234 pub socket_send_buffer_size: usize,
235 #[serde(default = "super::defaults::buffer_pool_size")]
237 pub buffer_pool_size: usize,
238 #[serde(default = "super::defaults::buffer_pool_count")]
240 pub buffer_pool_count: usize,
241 #[serde(default = "super::defaults::capture_pool_size")]
243 pub capture_pool_size: usize,
244 #[serde(default = "super::defaults::capture_pool_count")]
246 pub capture_pool_count: usize,
247}
248
249impl Default for Memory {
250 fn default() -> Self {
251 Self {
252 socket_recv_buffer_size: defaults::socket_recv_buffer_size(),
253 socket_send_buffer_size: defaults::socket_send_buffer_size(),
254 buffer_pool_size: defaults::buffer_pool_size(),
255 buffer_pool_count: defaults::buffer_pool_count(),
256 capture_pool_size: defaults::capture_pool_size(),
257 capture_pool_count: defaults::capture_pool_count(),
258 }
259 }
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
264#[serde(default)]
265pub struct Cache {
266 #[serde(
274 default = "super::defaults::cache_max_capacity",
275 rename = "article_cache_capacity",
276 alias = "cache_capacity",
277 alias = "max_capacity"
278 )]
279 pub article_cache_capacity: CacheCapacity,
280 #[serde(
282 with = "duration_serde",
283 default = "super::defaults::cache_ttl",
284 rename = "article_cache_ttl_secs",
285 alias = "cache_ttl",
286 alias = "ttl_secs",
287 alias = "ttl"
288 )]
289 pub article_cache_ttl_secs: Duration,
290 #[serde(
302 default = "super::defaults::cache_articles",
303 rename = "store_article_bodies",
304 alias = "store_articles",
305 alias = "cache_articles"
306 )]
307 pub store_article_bodies: bool,
308
309 #[serde(default, skip_serializing_if = "Option::is_none")]
314 pub disk: Option<DiskCache>,
315
316 #[serde(
322 default,
323 skip_serializing_if = "Option::is_none",
324 rename = "availability_index_path",
325 alias = "availability_path",
326 alias = "availability_file"
327 )]
328 pub availability_index_path: Option<std::path::PathBuf>,
329 #[serde(default, skip_serializing)]
331 pub adaptive_precheck: bool,
332}
333
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, clap::ValueEnum)]
336#[serde(rename_all = "lowercase")]
337pub enum CompressionCodec {
338 None,
340 #[default]
345 Lz4,
346 Zstd,
351}
352
353impl std::fmt::Display for CompressionCodec {
354 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
355 match self {
356 Self::None => write!(f, "none"),
357 Self::Lz4 => write!(f, "lz4"),
358 Self::Zstd => write!(f, "zstd"),
359 }
360 }
361}
362
363#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
371pub struct DiskCache {
372 #[serde(default = "super::defaults::disk_cache_path")]
377 pub path: std::path::PathBuf,
378
379 #[serde(default = "super::defaults::disk_cache_capacity")]
386 pub capacity: CacheCapacity,
387
388 #[serde(default = "super::defaults::disk_cache_compression_codec")]
399 pub compression: CompressionCodec,
400
401 #[serde(default = "super::defaults::disk_cache_shards")]
405 pub shards: usize,
406}
407
408impl Default for DiskCache {
409 fn default() -> Self {
410 Self {
411 path: defaults::disk_cache_path(),
412 capacity: defaults::disk_cache_capacity(),
413 compression: defaults::disk_cache_compression_codec(),
414 shards: defaults::disk_cache_shards(),
415 }
416 }
417}
418
419impl Default for Cache {
420 fn default() -> Self {
421 Self {
422 article_cache_capacity: defaults::cache_max_capacity(),
423 article_cache_ttl_secs: defaults::cache_ttl(),
424 store_article_bodies: defaults::cache_articles(),
425 disk: None,
426 availability_index_path: None,
427 adaptive_precheck: defaults::adaptive_precheck(),
428 }
429 }
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
434pub struct HealthCheck {
435 #[serde(
437 with = "duration_serde",
438 default = "super::defaults::health_check_interval"
439 )]
440 pub interval: Duration,
441 #[serde(
443 with = "duration_serde",
444 default = "super::defaults::health_check_timeout"
445 )]
446 pub timeout: Duration,
447 #[serde(default = "super::defaults::unhealthy_threshold")]
449 pub unhealthy_threshold: MaxErrors,
450}
451
452impl Default for HealthCheck {
453 fn default() -> Self {
454 Self {
455 interval: super::defaults::health_check_interval(),
456 timeout: super::defaults::health_check_timeout(),
457 unhealthy_threshold: super::defaults::unhealthy_threshold(),
458 }
459 }
460}
461
462#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
464pub struct ClientAuth {
465 #[serde(skip_serializing_if = "Option::is_none")]
467 pub greeting: Option<String>,
468 #[serde(default, skip_serializing_if = "Vec::is_empty")]
470 pub users: Vec<UserCredentials>,
471}
472
473#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
475pub struct UserCredentials {
476 pub username: String,
477 pub password: String,
478}
479
480impl ClientAuth {
481 #[must_use]
483 pub const fn is_enabled(&self) -> bool {
484 !self.users.is_empty()
485 }
486
487 #[must_use]
489 pub fn all_users(&self) -> Vec<(&str, &str)> {
490 self.users
491 .iter()
492 .map(|user| (user.username.as_str(), user.password.as_str()))
493 .collect()
494 }
495}
496
497#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
499pub struct Server {
500 pub host: HostName,
501 pub port: Port,
502 pub name: ServerName,
503 #[serde(skip_serializing_if = "Option::is_none")]
504 pub username: Option<String>,
505 #[serde(skip_serializing_if = "Option::is_none")]
506 pub password: Option<String>,
507 #[serde(default = "super::defaults::max_connections")]
509 pub max_connections: MaxConnections,
510
511 #[serde(default)]
513 pub use_tls: bool,
514 #[serde(default = "super::defaults::tls_verify_cert")]
516 pub tls_verify_cert: bool,
517 #[serde(skip_serializing_if = "Option::is_none")]
519 pub tls_cert_path: Option<String>,
520 #[serde(
523 with = "option_duration_serde",
524 default,
525 skip_serializing_if = "Option::is_none"
526 )]
527 pub connection_keepalive: Option<Duration>,
528 #[serde(
533 with = "option_duration_serde",
534 default = "super::defaults::replacement_cooldown_option",
535 skip_serializing_if = "Option::is_none"
536 )]
537 pub replacement_cooldown: Option<Duration>,
538 #[serde(default = "super::defaults::health_check_max_per_cycle")]
541 pub health_check_max_per_cycle: usize,
542 #[serde(
545 with = "duration_serde",
546 default = "super::defaults::health_check_pool_timeout"
547 )]
548 pub health_check_pool_timeout: Duration,
549 #[serde(default)]
552 pub tier: u8,
553 #[serde(default, skip_serializing_if = "Option::is_none")]
556 pub compress: Option<bool>,
557 #[serde(default, skip_serializing_if = "Option::is_none")]
559 pub compress_level: Option<u32>,
560
561 #[serde(
565 with = "duration_serde",
566 default = "super::defaults::backend_idle_timeout"
567 )]
568 pub backend_idle_timeout: Duration,
569}
570
571pub struct ServerBuilder {
598 host: String,
599 port: Port,
600 name: Option<String>,
601 username: Option<String>,
602 password: Option<String>,
603 max_connections: Option<MaxConnections>,
604 use_tls: bool,
605 tls_verify_cert: bool,
606 tls_cert_path: Option<String>,
607 connection_keepalive: Option<Duration>,
608 replacement_cooldown: Option<Duration>,
609 health_check_max_per_cycle: Option<usize>,
610 health_check_pool_timeout: Option<Duration>,
611 tier: u8,
612 compress: Option<bool>,
613 compress_level: Option<u32>,
614 backend_idle_timeout: Option<Duration>,
615}
616
617impl ServerBuilder {
618 #[must_use]
624 pub fn new(host: impl Into<String>, port: Port) -> Self {
625 Self {
626 host: host.into(),
627 port,
628 name: None,
629 username: None,
630 password: None,
631 max_connections: None,
632 use_tls: false,
633 tls_verify_cert: true, tls_cert_path: None,
635 connection_keepalive: None,
636 replacement_cooldown: None,
637 health_check_max_per_cycle: None,
638 health_check_pool_timeout: None,
639 tier: 0,
640 compress: None,
641 compress_level: None,
642 backend_idle_timeout: None,
643 }
644 }
645
646 #[must_use]
648 pub fn name(mut self, name: impl Into<String>) -> Self {
649 self.name = Some(name.into());
650 self
651 }
652
653 #[must_use]
655 pub fn username(mut self, username: impl Into<String>) -> Self {
656 self.username = Some(username.into());
657 self
658 }
659
660 #[must_use]
662 pub fn password(mut self, password: impl Into<String>) -> Self {
663 self.password = Some(password.into());
664 self
665 }
666
667 #[must_use]
669 pub const fn max_connections(mut self, max: MaxConnections) -> Self {
670 self.max_connections = Some(max);
671 self
672 }
673
674 #[must_use]
676 pub const fn use_tls(mut self, enabled: bool) -> Self {
677 self.use_tls = enabled;
678 self
679 }
680
681 #[must_use]
683 pub const fn tls_verify_cert(mut self, verify: bool) -> Self {
684 self.tls_verify_cert = verify;
685 self
686 }
687
688 #[must_use]
690 pub fn tls_cert_path(mut self, path: impl Into<String>) -> Self {
691 self.tls_cert_path = Some(path.into());
692 self
693 }
694
695 #[must_use]
697 pub const fn connection_keepalive(mut self, interval: Duration) -> Self {
698 self.connection_keepalive = Some(interval);
699 self
700 }
701
702 #[must_use]
704 pub const fn replacement_cooldown(mut self, cooldown: Duration) -> Self {
705 self.replacement_cooldown = Some(cooldown);
706 self
707 }
708
709 #[must_use]
711 pub const fn health_check_max_per_cycle(mut self, max: usize) -> Self {
712 self.health_check_max_per_cycle = Some(max);
713 self
714 }
715
716 #[must_use]
718 pub const fn health_check_pool_timeout(mut self, timeout: Duration) -> Self {
719 self.health_check_pool_timeout = Some(timeout);
720 self
721 }
722
723 #[must_use]
725 pub const fn tier(mut self, tier: u8) -> Self {
726 self.tier = tier;
727 self
728 }
729
730 #[must_use]
732 pub const fn compress(mut self, compress: Option<bool>) -> Self {
733 self.compress = compress;
734 self
735 }
736
737 #[must_use]
743 pub fn compress_level(mut self, level: u32) -> Self {
744 assert!(level <= 9, "compress_level must be 0-9, got {level}");
745 self.compress_level = Some(level);
746 self
747 }
748
749 #[must_use]
754 pub const fn backend_idle_timeout(mut self, timeout: Duration) -> Self {
755 self.backend_idle_timeout = Some(timeout);
756 self
757 }
758
759 pub fn build(self) -> Result<Server, anyhow::Error> {
769 use crate::types::{HostName, ServerName};
770
771 let host = HostName::try_new(self.host.clone())?;
772 let port = self.port; let name_str = self
774 .name
775 .unwrap_or_else(|| format!("{}:{}", self.host, self.port.get()));
776 let name = ServerName::try_new(name_str)?;
777
778 let max_connections = self
779 .max_connections
780 .unwrap_or_else(super::defaults::max_connections);
781
782 let health_check_max_per_cycle = self
783 .health_check_max_per_cycle
784 .unwrap_or_else(super::defaults::health_check_max_per_cycle);
785
786 let health_check_pool_timeout = self
787 .health_check_pool_timeout
788 .unwrap_or_else(super::defaults::health_check_pool_timeout);
789
790 Ok(Server {
791 host,
792 port,
793 name,
794 username: self.username,
795 password: self.password,
796 max_connections,
797 use_tls: self.use_tls,
798 tls_verify_cert: self.tls_verify_cert,
799 tls_cert_path: self.tls_cert_path,
800 connection_keepalive: self.connection_keepalive,
801 replacement_cooldown: self
802 .replacement_cooldown
803 .or_else(super::defaults::replacement_cooldown_option),
804 health_check_max_per_cycle,
805 health_check_pool_timeout,
806 tier: self.tier,
807 compress: self.compress,
808 compress_level: self.compress_level,
809 backend_idle_timeout: self
810 .backend_idle_timeout
811 .unwrap_or_else(super::defaults::backend_idle_timeout),
812 })
813 }
814}
815
816impl Server {
817 #[must_use]
832 pub fn builder(host: impl Into<String>, port: Port) -> ServerBuilder {
833 ServerBuilder::new(host, port)
834 }
835}
836
837#[cfg(test)]
838mod tests {
839 use super::*;
840
841 #[test]
843 fn test_routing_mode_default() {
844 assert_eq!(RoutingMode::default(), RoutingMode::Hybrid);
845 }
846
847 #[test]
848 fn test_routing_mode_supports_per_command() {
849 assert!(RoutingMode::PerCommand.supports_per_command_routing());
850 assert!(RoutingMode::Hybrid.supports_per_command_routing());
851 assert!(!RoutingMode::Stateful.supports_per_command_routing());
852 }
853
854 #[test]
855 fn test_routing_mode_supports_stateful() {
856 assert!(RoutingMode::Stateful.supports_stateful_commands());
857 assert!(RoutingMode::Hybrid.supports_stateful_commands());
858 assert!(!RoutingMode::PerCommand.supports_stateful_commands());
859 }
860
861 #[test]
862 fn test_routing_mode_as_str() {
863 assert_eq!(RoutingMode::Stateful.as_str(), "stateful 1:1 mode");
864 assert_eq!(
865 RoutingMode::PerCommand.as_str(),
866 "per-command routing mode (stateless)"
867 );
868 assert_eq!(RoutingMode::Hybrid.as_str(), "hybrid routing mode");
869 }
870
871 #[test]
872 fn test_routing_mode_display() {
873 assert_eq!(RoutingMode::Stateful.to_string(), "stateful 1:1 mode");
874 assert_eq!(RoutingMode::Hybrid.to_string(), "hybrid routing mode");
875 }
876
877 #[test]
879 fn test_proxy_default() {
880 let proxy = Proxy::default();
881 assert_eq!(proxy.host, "0.0.0.0");
882 assert_eq!(proxy.port.get(), 8119);
883 }
884
885 #[test]
886 fn test_proxy_default_host_constant() {
887 assert_eq!(Proxy::DEFAULT_HOST, "0.0.0.0");
888 }
889
890 #[test]
892 fn test_cache_default() {
893 let cache = Cache::default();
894 assert_eq!(cache.article_cache_capacity.get(), 64 * 1024 * 1024); assert_eq!(
896 cache.article_cache_ttl_secs,
897 crate::constants::duration_polyfill::from_hours(1)
898 );
899 assert!(!cache.store_article_bodies);
900 }
901
902 #[test]
903 fn test_memory_default() {
904 let memory = Memory::default();
905 assert_eq!(
906 memory.socket_recv_buffer_size,
907 crate::constants::socket::HIGH_THROUGHPUT_RECV_BUFFER
908 );
909 assert_eq!(
910 memory.socket_send_buffer_size,
911 crate::constants::socket::HIGH_THROUGHPUT_SEND_BUFFER
912 );
913 assert_eq!(memory.buffer_pool_size, crate::constants::buffer::POOL);
914 assert_eq!(
915 memory.buffer_pool_count,
916 crate::constants::buffer::POOL_COUNT
917 );
918 assert_eq!(memory.capture_pool_size, crate::constants::buffer::CAPTURE);
919 assert_eq!(
920 memory.capture_pool_count,
921 crate::constants::buffer::CAPTURE_COUNT
922 );
923 }
924
925 #[test]
927 fn test_health_check_default() {
928 let hc = HealthCheck::default();
929 assert_eq!(hc.interval, Duration::from_secs(30));
930 assert_eq!(hc.timeout, Duration::from_secs(5));
931 assert_eq!(hc.unhealthy_threshold.get(), 3);
932 }
933
934 #[test]
936 fn test_client_auth_is_enabled() {
937 let mut auth = ClientAuth::default();
938 assert!(!auth.is_enabled());
939
940 auth.users.push(UserCredentials {
941 username: "user".to_string(),
942 password: "pass".to_string(),
943 });
944 assert!(auth.is_enabled());
945 }
946
947 #[test]
948 fn test_client_auth_is_enabled_multi_user() {
949 let mut auth = ClientAuth::default();
950 auth.users.push(UserCredentials {
951 username: "alice".to_string(),
952 password: "secret".to_string(),
953 });
954 assert!(auth.is_enabled());
955 }
956
957 #[test]
958 fn test_client_auth_all_users_single() {
959 let mut auth = ClientAuth::default();
960 auth.users.push(UserCredentials {
961 username: "user".to_string(),
962 password: "pass".to_string(),
963 });
964
965 let users = auth.all_users();
966 assert_eq!(users.len(), 1);
967 assert_eq!(users[0], ("user", "pass"));
968 }
969
970 #[test]
971 fn test_client_auth_all_users_multi() {
972 let mut auth = ClientAuth::default();
973 auth.users.push(UserCredentials {
974 username: "alice".to_string(),
975 password: "alice_pw".to_string(),
976 });
977 auth.users.push(UserCredentials {
978 username: "bob".to_string(),
979 password: "bob_pw".to_string(),
980 });
981
982 let users = auth.all_users();
983 assert_eq!(users.len(), 2);
984 assert_eq!(users[0], ("alice", "alice_pw"));
985 assert_eq!(users[1], ("bob", "bob_pw"));
986 }
987
988 #[test]
990 fn test_server_builder_minimal() {
991 let server = Server::builder("news.example.com", Port::try_new(119).unwrap())
992 .build()
993 .unwrap();
994
995 assert_eq!(server.host.as_str(), "news.example.com");
996 assert_eq!(server.port.get(), 119);
997 assert_eq!(server.name.as_str(), "news.example.com:119");
998 assert_eq!(server.max_connections.get(), 10);
999 assert!(!server.use_tls);
1000 assert!(server.tls_verify_cert); }
1002
1003 #[test]
1004 fn test_server_builder_with_name() {
1005 let server = Server::builder("localhost", Port::try_new(119).unwrap())
1006 .name("Test Server")
1007 .build()
1008 .unwrap();
1009
1010 assert_eq!(server.name.as_str(), "Test Server");
1011 }
1012
1013 #[test]
1014 fn test_server_builder_with_auth() {
1015 let server = Server::builder("news.example.com", Port::try_new(119).unwrap())
1016 .username("testuser")
1017 .password("testpass")
1018 .build()
1019 .unwrap();
1020
1021 assert_eq!(server.username.as_ref().unwrap(), "testuser");
1022 assert_eq!(server.password.as_ref().unwrap(), "testpass");
1023 }
1024
1025 #[test]
1026 fn test_server_builder_with_max_connections() {
1027 let server = Server::builder("localhost", Port::try_new(119).unwrap())
1028 .max_connections(MaxConnections::try_new(20).unwrap())
1029 .build()
1030 .unwrap();
1031
1032 assert_eq!(server.max_connections.get(), 20);
1033 }
1034
1035 #[test]
1036 fn test_server_builder_with_tls() {
1037 let server = Server::builder("secure.example.com", Port::try_new(563).unwrap())
1038 .use_tls(true)
1039 .tls_verify_cert(false)
1040 .tls_cert_path("/path/to/cert.pem")
1041 .build()
1042 .unwrap();
1043
1044 assert!(server.use_tls);
1045 assert!(!server.tls_verify_cert);
1046 assert_eq!(server.tls_cert_path.as_ref().unwrap(), "/path/to/cert.pem");
1047 }
1048
1049 #[test]
1050 fn test_server_builder_with_keepalive() {
1051 let keepalive = crate::constants::duration_polyfill::from_minutes(5);
1052 let server = Server::builder("localhost", Port::try_new(119).unwrap())
1053 .connection_keepalive(keepalive)
1054 .build()
1055 .unwrap();
1056
1057 assert_eq!(server.connection_keepalive, Some(keepalive));
1058 }
1059
1060 #[test]
1061 fn test_server_builder_default_replacement_cooldown() {
1062 let server = Server::builder("localhost", Port::try_new(119).unwrap())
1063 .build()
1064 .unwrap();
1065
1066 assert_eq!(
1067 server.replacement_cooldown,
1068 super::defaults::replacement_cooldown_option()
1069 );
1070 }
1071
1072 #[test]
1073 fn test_server_builder_with_replacement_cooldown() {
1074 let cooldown = Duration::from_secs(31);
1075 let server = Server::builder("localhost", Port::try_new(119).unwrap())
1076 .replacement_cooldown(cooldown)
1077 .build()
1078 .unwrap();
1079
1080 assert_eq!(server.replacement_cooldown, Some(cooldown));
1081 }
1082
1083 #[test]
1084 fn test_server_builder_with_health_check_settings() {
1085 let timeout = Duration::from_millis(500);
1086 let server = Server::builder("localhost", Port::try_new(119).unwrap())
1087 .health_check_max_per_cycle(5)
1088 .health_check_pool_timeout(timeout)
1089 .build()
1090 .unwrap();
1091
1092 assert_eq!(server.health_check_max_per_cycle, 5);
1093 assert_eq!(server.health_check_pool_timeout, timeout);
1094 }
1095
1096 #[test]
1097 fn test_server_builder_chaining() {
1098 let server = Server::builder("news.example.com", Port::try_new(563).unwrap())
1099 .name("Production Server")
1100 .username("admin")
1101 .password("secret")
1102 .max_connections(MaxConnections::try_new(25).unwrap())
1103 .use_tls(true)
1104 .tls_verify_cert(true)
1105 .build()
1106 .unwrap();
1107
1108 assert_eq!(server.name.as_str(), "Production Server");
1109 assert_eq!(server.max_connections.get(), 25);
1110 assert!(server.use_tls);
1111 }
1112
1113 #[test]
1115 fn test_config_default() {
1116 let config = Config::default();
1117 assert!(config.servers.is_empty());
1118 assert_eq!(config.proxy.host, "0.0.0.0");
1119 assert!(config.cache.is_none());
1120 assert!(!config.client_auth.is_enabled());
1121 }
1122
1123 #[test]
1125 fn test_compression_codec_serde_lz4() {
1126 let json = r#""lz4""#;
1127 let codec: CompressionCodec = serde_json::from_str(json).unwrap();
1128 assert_eq!(codec, CompressionCodec::Lz4);
1129 assert_eq!(serde_json::to_string(&codec).unwrap(), json);
1130 }
1131
1132 #[test]
1133 fn test_compression_codec_serde_zstd() {
1134 let json = r#""zstd""#;
1135 let codec: CompressionCodec = serde_json::from_str(json).unwrap();
1136 assert_eq!(codec, CompressionCodec::Zstd);
1137 }
1138
1139 #[test]
1140 fn test_compression_codec_serde_none() {
1141 let json = r#""none""#;
1142 let codec: CompressionCodec = serde_json::from_str(json).unwrap();
1143 assert_eq!(codec, CompressionCodec::None);
1144 }
1145
1146 #[test]
1147 fn test_compression_codec_default_is_lz4() {
1148 assert_eq!(CompressionCodec::default(), CompressionCodec::Lz4);
1149 }
1150
1151 #[test]
1152 fn test_compression_codec_display() {
1153 assert_eq!(CompressionCodec::Lz4.to_string(), "lz4");
1154 assert_eq!(CompressionCodec::Zstd.to_string(), "zstd");
1155 assert_eq!(CompressionCodec::None.to_string(), "none");
1156 }
1157
1158 #[test]
1160 fn test_disk_cache_default_compression_is_lz4() {
1161 let disk_cache = DiskCache::default();
1162 assert_eq!(disk_cache.compression, CompressionCodec::Lz4);
1163 }
1164
1165 #[test]
1166 fn test_disk_cache_deserialize_compression_codec() {
1167 let toml = r#"
1168 path = "/tmp/cache"
1169 capacity = "100mb"
1170 compression = "zstd"
1171 shards = 4
1172 "#;
1173 let disk_cache: DiskCache = toml::from_str(toml).unwrap();
1174 assert_eq!(disk_cache.compression, CompressionCodec::Zstd);
1175 }
1176
1177 #[test]
1178 fn test_disk_cache_deserialize_compression_none() {
1179 let toml = r#"
1180 path = "/tmp/cache"
1181 capacity = "100mb"
1182 compression = "none"
1183 shards = 4
1184 "#;
1185 let disk_cache: DiskCache = toml::from_str(toml).unwrap();
1186 assert_eq!(disk_cache.compression, CompressionCodec::None);
1187 }
1188}