Skip to main content

torrust_tracker_deployer_lib/domain/tracker/
protocol.rs

1//! Network protocol types for tracker services
2//!
3//! This module defines the protocol types used by tracker services
4//! to distinguish between UDP and TCP based services.
5
6use std::fmt;
7use std::str::FromStr;
8
9/// Network protocol used by tracker services
10///
11/// Distinguishes between UDP and TCP protocols for socket binding validation.
12/// UDP and TCP maintain separate port spaces in the operating system, allowing
13/// the same port number to be used by both protocols simultaneously.
14///
15/// # Examples
16///
17/// ```rust
18/// use torrust_tracker_deployer_lib::domain::tracker::Protocol;
19///
20/// let udp = Protocol::Udp;
21/// let tcp = Protocol::Tcp;
22///
23/// assert_eq!(udp.to_string(), "UDP");
24/// assert_eq!(tcp.to_string(), "TCP");
25/// ```
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub enum Protocol {
28    /// User Datagram Protocol - connectionless protocol
29    Udp,
30    /// Transmission Control Protocol - connection-oriented protocol
31    Tcp,
32}
33
34/// Error type for protocol parsing failures
35#[derive(Debug, Clone, PartialEq)]
36pub enum ProtocolParseError {
37    /// Unknown protocol string provided
38    UnknownProtocol(String),
39}
40
41impl fmt::Display for Protocol {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        match self {
44            Self::Udp => write!(f, "UDP"),
45            Self::Tcp => write!(f, "TCP"),
46        }
47    }
48}
49
50impl FromStr for Protocol {
51    type Err = ProtocolParseError;
52
53    fn from_str(s: &str) -> Result<Self, Self::Err> {
54        match s.to_uppercase().as_str() {
55            "UDP" => Ok(Self::Udp),
56            "TCP" => Ok(Self::Tcp),
57            _ => Err(ProtocolParseError::UnknownProtocol(s.to_string())),
58        }
59    }
60}
61
62impl fmt::Display for ProtocolParseError {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        match self {
65            Self::UnknownProtocol(proto) => {
66                write!(f, "Unknown protocol: '{proto}'. Expected 'UDP' or 'TCP'")
67            }
68        }
69    }
70}
71
72impl std::error::Error for ProtocolParseError {}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    mod protocol_enum {
79        use super::*;
80
81        #[test]
82        fn it_should_display_udp_as_uppercase_string() {
83            assert_eq!(Protocol::Udp.to_string(), "UDP");
84        }
85
86        #[test]
87        fn it_should_display_tcp_as_uppercase_string() {
88            assert_eq!(Protocol::Tcp.to_string(), "TCP");
89        }
90
91        #[test]
92        fn it_should_parse_udp_from_uppercase_string() {
93            assert_eq!("UDP".parse::<Protocol>().unwrap(), Protocol::Udp);
94        }
95
96        #[test]
97        fn it_should_parse_udp_from_lowercase_string() {
98            assert_eq!("udp".parse::<Protocol>().unwrap(), Protocol::Udp);
99        }
100
101        #[test]
102        fn it_should_parse_udp_from_mixed_case_string() {
103            assert_eq!("Udp".parse::<Protocol>().unwrap(), Protocol::Udp);
104        }
105
106        #[test]
107        fn it_should_parse_tcp_from_uppercase_string() {
108            assert_eq!("TCP".parse::<Protocol>().unwrap(), Protocol::Tcp);
109        }
110
111        #[test]
112        fn it_should_parse_tcp_from_lowercase_string() {
113            assert_eq!("tcp".parse::<Protocol>().unwrap(), Protocol::Tcp);
114        }
115
116        #[test]
117        fn it_should_parse_tcp_from_mixed_case_string() {
118            assert_eq!("Tcp".parse::<Protocol>().unwrap(), Protocol::Tcp);
119        }
120
121        #[test]
122        fn it_should_return_error_when_parsing_unknown_protocol() {
123            let result = "HTTP".parse::<Protocol>();
124            assert!(result.is_err());
125            assert_eq!(
126                result.unwrap_err(),
127                ProtocolParseError::UnknownProtocol("HTTP".to_string())
128            );
129        }
130
131        #[test]
132        fn it_should_return_error_when_parsing_empty_string() {
133            let result = "".parse::<Protocol>();
134            assert!(result.is_err());
135            assert_eq!(
136                result.unwrap_err(),
137                ProtocolParseError::UnknownProtocol(String::new())
138            );
139        }
140
141        #[test]
142        fn it_should_be_equal_when_same_protocol() {
143            assert_eq!(Protocol::Udp, Protocol::Udp);
144            assert_eq!(Protocol::Tcp, Protocol::Tcp);
145        }
146
147        #[test]
148        fn it_should_not_be_equal_when_different_protocols() {
149            assert_ne!(Protocol::Udp, Protocol::Tcp);
150        }
151
152        #[test]
153        fn it_should_be_hashable() {
154            use std::collections::HashSet;
155
156            let mut set = HashSet::new();
157            set.insert(Protocol::Udp);
158            set.insert(Protocol::Tcp);
159            set.insert(Protocol::Udp); // Duplicate
160
161            assert_eq!(set.len(), 2); // Only two unique protocols
162        }
163    }
164
165    mod protocol_parse_error {
166        use super::*;
167
168        #[test]
169        fn it_should_display_helpful_error_message_for_unknown_protocol() {
170            let error = ProtocolParseError::UnknownProtocol("HTTP".to_string());
171            assert_eq!(
172                error.to_string(),
173                "Unknown protocol: 'HTTP'. Expected 'UDP' or 'TCP'"
174            );
175        }
176    }
177}