#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum Ports {
Port(u16),
Range(u16, u16),
}
impl fmt::Display for Ports {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
&Ports::Port(port) => write!(f, "{}", port),
&Ports::Range(first, last) => write!(f, "{}-{}", first, last),
}
}
}
impl FromStr for Ports {
type Err = InvalidValueError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
lazy_static! {
static ref PORTS: Regex =
Regex::new("^([0-9]+)(?:-([0-9]+))?$").unwrap();
}
let caps = try!(PORTS.captures(s).ok_or_else(|| {
InvalidValueError::new("ports", s)
}));
let port_from_str = |i: usize| -> Result<u16, InvalidValueError> {
FromStr::from_str(caps.at(i).unwrap()).map_err(|_| {
InvalidValueError::new("port", s)
})
};
if caps.at(2).is_none() {
Ok(Ports::Port(try!(port_from_str(1))))
} else {
Ok(Ports::Range(try!(port_from_str(1)), try!(port_from_str(2))))
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PortMapping {
host_address: Option<IpAddr>,
host_ports: Option<Ports>,
container_ports: Ports,
}
impl_interpolatable_value!(PortMapping);
impl fmt::Display for PortMapping {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
if self.host_address.is_some() && self.host_ports.is_none() {
return Err(fmt::Error);
}
if let Some(ref addr) = self.host_address {
try!(write!(f, "{}:", addr));
}
if let Some(ports) = self.host_ports {
try!(write!(f, "{}:", ports));
}
write!(f, "{}", self.container_ports)
}
}
impl FromStr for PortMapping {
type Err = InvalidValueError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let fields: Vec<_> = s.rsplitn(3, ":").collect();
match fields.len() {
1 => {
Ok(PortMapping {
host_address: None,
host_ports: None,
container_ports: try!(FromStr::from_str(fields[0])),
})
}
2 => {
Ok(PortMapping {
host_address: None,
host_ports: Some(try!(FromStr::from_str(fields[1]))),
container_ports: try!(FromStr::from_str(fields[0])),
})
}
3 => {
let addr: IpAddr =
try!(FromStr::from_str(fields[2]).map_err(|_| {
InvalidValueError::new("IP address", s)
}));
Ok(PortMapping {
host_address: Some(addr),
host_ports: Some(try!(FromStr::from_str(fields[1]))),
container_ports: try!(FromStr::from_str(fields[0])),
})
}
_ => {
Err(InvalidValueError::new("port mapping", s))
}
}
}
}
#[test]
fn port_mapping_should_have_a_string_representation() {
let localhost: IpAddr = FromStr::from_str("127.0.0.1").unwrap();
let map1 = PortMapping {
host_address: None,
host_ports: None,
container_ports: Ports::Port(80),
};
let map2 = PortMapping {
host_address: None,
host_ports: Some(Ports::Range(8080, 8089)),
container_ports: Ports::Range(3000, 3009),
};
let map3 = PortMapping {
host_address: Some(localhost),
host_ports: Some(Ports::Port(80)),
container_ports: Ports::Port(80),
};
let pairs = vec!(
(map1, "80"),
(map2, "8080-8089:3000-3009"),
(map3, "127.0.0.1:80:80"),
);
for (map, s) in pairs {
assert_eq!(map.to_string(), s);
assert_eq!(map, PortMapping::from_str(s).unwrap());
}
}