microsandbox_core/config/
port_pair.rs1use std::{fmt, str::FromStr};
2
3use serde::{Deserialize, Serialize};
4
5use crate::MicrosandboxError;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum PortPair {
36 Distinct {
38 host: u16,
40
41 guest: u16,
43 },
44
45 Same(u16),
47}
48
49impl PortPair {
54 pub fn with_same(port: u16) -> Self {
56 Self::Same(port)
57 }
58
59 pub fn with_distinct(host: u16, guest: u16) -> Self {
61 Self::Distinct { host, guest }
62 }
63
64 pub fn get_host(&self) -> u16 {
66 match self {
67 Self::Distinct { host, .. } | Self::Same(host) => *host,
68 }
69 }
70
71 pub fn get_guest(&self) -> u16 {
73 match self {
74 Self::Distinct { guest, .. } | Self::Same(guest) => *guest,
75 }
76 }
77}
78
79impl FromStr for PortPair {
84 type Err = MicrosandboxError;
85
86 fn from_str(s: &str) -> Result<Self, Self::Err> {
87 if s.is_empty() {
88 return Err(MicrosandboxError::InvalidPortPair(s.to_string()));
89 }
90
91 if s.contains(':') {
92 let (host, guest) = s.split_once(':').unwrap();
93 if guest.is_empty() || host.is_empty() {
94 return Err(MicrosandboxError::InvalidPortPair(s.to_string()));
95 }
96
97 if guest == host {
98 return Ok(Self::Same(
99 host.parse()
100 .map_err(|_| MicrosandboxError::InvalidPortPair(s.to_string()))?,
101 ));
102 } else {
103 return Ok(Self::Distinct {
104 host: host
105 .parse()
106 .map_err(|_| MicrosandboxError::InvalidPortPair(s.to_string()))?,
107 guest: guest
108 .parse()
109 .map_err(|_| MicrosandboxError::InvalidPortPair(s.to_string()))?,
110 });
111 }
112 }
113
114 Ok(Self::Same(s.parse().map_err(|_| {
115 MicrosandboxError::InvalidPortPair(s.to_string())
116 })?))
117 }
118}
119
120impl fmt::Display for PortPair {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 match self {
124 Self::Distinct { host, guest } => {
125 write!(f, "{}:{}", host, guest)
126 }
127 Self::Same(port) => write!(f, "{}:{}", port, port),
128 }
129 }
130}
131
132impl Serialize for PortPair {
133 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
134 where
135 S: serde::Serializer,
136 {
137 serializer.serialize_str(&self.to_string())
138 }
139}
140
141impl<'de> Deserialize<'de> for PortPair {
142 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
143 where
144 D: serde::Deserializer<'de>,
145 {
146 let s = String::deserialize(deserializer)?;
147 Self::from_str(&s).map_err(serde::de::Error::custom)
148 }
149}
150
151#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn test_port_pair_from_str() {
161 assert_eq!("8080".parse::<PortPair>().unwrap(), PortPair::Same(8080));
163 assert_eq!(
164 "8080:8080".parse::<PortPair>().unwrap(),
165 PortPair::Same(8080)
166 );
167
168 assert_eq!(
170 "8080:80".parse::<PortPair>().unwrap(),
171 PortPair::Distinct {
172 host: 8080,
173 guest: 80
174 }
175 );
176
177 assert!("".parse::<PortPair>().is_err());
179 assert!(":80".parse::<PortPair>().is_err());
180 assert!("80:".parse::<PortPair>().is_err());
181 assert!("invalid".parse::<PortPair>().is_err());
182 assert!("invalid:80".parse::<PortPair>().is_err());
183 assert!("80:invalid".parse::<PortPair>().is_err());
184 }
185
186 #[test]
187 fn test_port_pair_display() {
188 assert_eq!(PortPair::Same(8080).to_string(), "8080:8080");
190
191 assert_eq!(
193 PortPair::Distinct {
194 host: 8080,
195 guest: 80
196 }
197 .to_string(),
198 "8080:80"
199 );
200 }
201
202 #[test]
203 fn test_port_pair_getters() {
204 let same = PortPair::Same(8080);
206 assert_eq!(same.get_host(), 8080);
207 assert_eq!(same.get_guest(), 8080);
208
209 let distinct = PortPair::Distinct {
211 host: 8080,
212 guest: 80,
213 };
214 assert_eq!(distinct.get_host(), 8080);
215 assert_eq!(distinct.get_guest(), 80);
216 }
217
218 #[test]
219 fn test_port_pair_constructors() {
220 assert_eq!(PortPair::with_same(8080), PortPair::Same(8080));
221 assert_eq!(
222 PortPair::with_distinct(8080, 80),
223 PortPair::Distinct {
224 host: 8080,
225 guest: 80
226 }
227 );
228 }
229}