Skip to main content

torrust_tracker_deployer_lib/domain/tracker/
binding_address.rs

1//! Binding address value object for tracker services
2//!
3//! This module provides a type-safe representation of socket addresses
4//! with protocol information, used for validating tracker configurations.
5
6use std::fmt;
7use std::net::SocketAddr;
8
9use super::Protocol;
10
11/// A binding address combining socket address and protocol
12///
13/// Represents a complete socket binding specification including both the
14/// network address (IP + port) and the protocol (UDP or TCP). This ensures
15/// that socket address uniqueness validation accounts for protocol differences.
16///
17/// # Examples
18///
19/// ```rust
20/// use torrust_tracker_deployer_lib::domain::tracker::{BindingAddress, Protocol};
21///
22/// let udp_addr = BindingAddress::new(
23///     "0.0.0.0:6969".parse().unwrap(),
24///     Protocol::Udp
25/// );
26///
27/// let tcp_addr = BindingAddress::new(
28///     "0.0.0.0:7070".parse().unwrap(),
29///     Protocol::Tcp
30/// );
31///
32/// assert_eq!(udp_addr.socket().port(), 6969);
33/// assert_eq!(tcp_addr.protocol(), Protocol::Tcp);
34/// ```
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub struct BindingAddress {
37    socket: SocketAddr,
38    protocol: Protocol,
39}
40
41impl BindingAddress {
42    /// Creates a new binding address
43    ///
44    /// # Examples
45    ///
46    /// ```rust
47    /// use torrust_tracker_deployer_lib::domain::tracker::{BindingAddress, Protocol};
48    ///
49    /// let addr = BindingAddress::new(
50    ///     "0.0.0.0:6969".parse().unwrap(),
51    ///     Protocol::Udp
52    /// );
53    /// ```
54    #[must_use]
55    pub fn new(socket: SocketAddr, protocol: Protocol) -> Self {
56        Self { socket, protocol }
57    }
58
59    /// Returns the socket address
60    #[must_use]
61    pub fn socket(&self) -> &SocketAddr {
62        &self.socket
63    }
64
65    /// Returns the protocol
66    #[must_use]
67    pub fn protocol(&self) -> Protocol {
68        self.protocol
69    }
70}
71
72impl fmt::Display for BindingAddress {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        write!(f, "{} ({})", self.socket, self.protocol)
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn it_should_create_binding_address_with_udp_protocol() {
84        let socket: SocketAddr = "0.0.0.0:6969".parse().unwrap();
85        let addr = BindingAddress::new(socket, Protocol::Udp);
86
87        assert_eq!(addr.socket(), &socket);
88        assert_eq!(addr.protocol(), Protocol::Udp);
89    }
90
91    #[test]
92    fn it_should_create_binding_address_with_tcp_protocol() {
93        let socket: SocketAddr = "127.0.0.1:7070".parse().unwrap();
94        let addr = BindingAddress::new(socket, Protocol::Tcp);
95
96        assert_eq!(addr.socket(), &socket);
97        assert_eq!(addr.protocol(), Protocol::Tcp);
98    }
99
100    #[test]
101    fn it_should_display_binding_address_with_protocol() {
102        let addr = BindingAddress::new("0.0.0.0:6969".parse().unwrap(), Protocol::Udp);
103        assert_eq!(addr.to_string(), "0.0.0.0:6969 (UDP)");
104
105        let addr = BindingAddress::new("127.0.0.1:7070".parse().unwrap(), Protocol::Tcp);
106        assert_eq!(addr.to_string(), "127.0.0.1:7070 (TCP)");
107    }
108
109    #[test]
110    fn it_should_consider_same_socket_different_protocol_as_different() {
111        let udp_addr = BindingAddress::new("0.0.0.0:7070".parse().unwrap(), Protocol::Udp);
112        let tcp_addr = BindingAddress::new("0.0.0.0:7070".parse().unwrap(), Protocol::Tcp);
113
114        assert_ne!(udp_addr, tcp_addr);
115    }
116
117    #[test]
118    fn it_should_consider_same_socket_same_protocol_as_equal() {
119        let addr1 = BindingAddress::new("0.0.0.0:7070".parse().unwrap(), Protocol::Tcp);
120        let addr2 = BindingAddress::new("0.0.0.0:7070".parse().unwrap(), Protocol::Tcp);
121
122        assert_eq!(addr1, addr2);
123    }
124
125    #[test]
126    fn it_should_consider_different_ips_same_port_as_different() {
127        let addr1 = BindingAddress::new("192.168.1.10:7070".parse().unwrap(), Protocol::Tcp);
128        let addr2 = BindingAddress::new("192.168.1.20:7070".parse().unwrap(), Protocol::Tcp);
129
130        assert_ne!(addr1, addr2);
131    }
132
133    #[test]
134    fn it_should_be_usable_as_hash_map_key() {
135        use std::collections::HashMap;
136
137        let mut map = HashMap::new();
138        let addr = BindingAddress::new("0.0.0.0:6969".parse().unwrap(), Protocol::Udp);
139        map.insert(addr, "UDP Tracker");
140
141        assert_eq!(map.get(&addr), Some(&"UDP Tracker"));
142    }
143}