Skip to main content

ddapi_rs/util/
tw_addr.rs

1use serde::{Deserialize, Serialize};
2use std::net::IpAddr;
3
4/// Teeworlds/DDNet server address, including protocol.
5///
6/// # Examples
7/// ```rust
8/// use ddapi_rs::prelude::Addr;
9///
10/// let a = Addr::try_from("tw-0.7+udp://127.0.0.1:8303").unwrap();
11/// assert_eq!(a.port, 8303);
12/// ```
13#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
14pub struct Addr {
15    pub ip: IpAddr,
16    pub port: u16,
17    pub protocol: Protocol,
18}
19
20/// Supported protocol identifiers used by `DDNet` master responses.
21///
22/// # Examples
23/// ```rust
24/// use ddapi_rs::prelude::Protocol;
25///
26/// assert_eq!(Protocol::V7.as_str(), "tw-0.7+udp");
27/// ```
28#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
29pub enum Protocol {
30    V5,
31    V6,
32    V7,
33    VPg,
34}
35
36impl Protocol {
37    #[must_use]
38    pub fn as_str(&self) -> &'static str {
39        match self {
40            Protocol::V5 => "tw-0.5+udp",
41            Protocol::V6 => "tw-0.6+udp",
42            Protocol::V7 => "tw-0.7+udp",
43            Protocol::VPg => "ddrs-0.1+quic",
44        }
45    }
46}
47
48impl From<&str> for Protocol {
49    fn from(s: &str) -> Self {
50        Protocol::try_from_str(s).unwrap_or_else(|e| panic!("{e}"))
51    }
52}
53
54impl std::fmt::Display for Protocol {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        write!(f, "{}", self.as_str())
57    }
58}
59
60impl Protocol {
61    /// Parses a protocol identifier into a [`Protocol`].
62    ///
63    /// # Errors
64    ///
65    /// Returns `Err` with a description if `value` is not a known protocol
66    /// identifier.
67    pub fn try_from_str(value: &str) -> Result<Self, String> {
68        match value {
69            "tw-0.5+udp" => Ok(Protocol::V5),
70            "tw-0.6+udp" => Ok(Protocol::V6),
71            "tw-0.7+udp" => Ok(Protocol::V7),
72            "ddrs-0.1+quic" => Ok(Protocol::VPg),
73            _ => Err(format!("Unknown protocol: {value}")),
74        }
75    }
76}
77
78impl TryFrom<&str> for Addr {
79    type Error = String;
80
81    fn try_from(url: &str) -> Result<Self, Self::Error> {
82        let (protocol_str, host_port) = url.split_once("://").ok_or_else(|| {
83            format!("Invalid URL format, expected 'protocol://host:port', got: {url}")
84        })?;
85
86        let protocol = Protocol::try_from_str(protocol_str)?;
87
88        let (host, port_str) = if let Some(rest) = host_port.strip_prefix('[') {
89            let (host, rest) = rest.split_once(']').ok_or_else(|| {
90                format!("Invalid IPv6 format, expected '[host]:port', got: {host_port}")
91            })?;
92            let port_str = rest.strip_prefix(':').ok_or_else(|| {
93                format!("Invalid IPv6 port format, expected ']:port', got: {host_port}")
94            })?;
95            (host, port_str)
96        } else {
97            host_port.split_once(':').ok_or_else(|| {
98                format!("Invalid host:port format, expected 'host:port', got: {host_port}")
99            })?
100        };
101
102        let ip: IpAddr = host
103            .parse()
104            .map_err(|e| format!("Failed to parse IP '{host}': {e}"))?;
105
106        let port: u16 = port_str
107            .parse()
108            .map_err(|e| format!("Failed to parse port '{port_str}': {e}"))?;
109
110        Ok(Addr { ip, port, protocol })
111    }
112}
113
114pub mod addr_serialization {
115    use super::Addr;
116    use serde::{Deserialize, Deserializer, Serializer};
117
118    /// Serializes a slice of [`Addr`] as an array of `protocol://host:port` URLs.
119    ///
120    /// # Errors
121    ///
122    /// Returns an error if the underlying serializer fails.
123    pub fn serialize<S>(addrs: &[Addr], serializer: S) -> Result<S::Ok, S::Error>
124    where
125        S: Serializer,
126    {
127        use serde::ser::SerializeSeq;
128
129        let mut seq = serializer.serialize_seq(Some(addrs.len()))?;
130        for addr in addrs {
131            let url = format!("{}://{}:{}", addr.protocol.as_str(), addr.ip, addr.port);
132            seq.serialize_element(&url)?;
133        }
134        seq.end()
135    }
136
137    /// Deserializes an array of `protocol://host:port` URLs into [`Addr`]s.
138    ///
139    /// # Errors
140    ///
141    /// Returns an error if an element is not a valid address or if the
142    /// underlying deserializer fails.
143    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Addr>, D::Error>
144    where
145        D: Deserializer<'de>,
146    {
147        use serde::de::Error;
148
149        let string_vec: Vec<String> = Vec::deserialize(deserializer)?;
150        let mut addrs = Vec::with_capacity(string_vec.len());
151
152        for s in string_vec {
153            match Addr::try_from(s.as_str()) {
154                Ok(addr) => addrs.push(addr),
155                Err(e) => {
156                    return Err(D::Error::custom(format!(
157                        "Failed to parse address '{s}': {e}"
158                    )))
159                }
160            }
161        }
162
163        Ok(addrs)
164    }
165}