#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::{fmt, str::FromStr};
use std::error::Error;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DockerNetworkError {
Empty,
InvalidName,
}
impl fmt::Display for DockerNetworkError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("Docker network value cannot be empty"),
Self::InvalidName => formatter.write_str("invalid Docker network name"),
}
}
}
impl Error for DockerNetworkError {}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum DockerNetworkMode {
Bridge,
Host,
None,
Service(String),
Container(String),
Named(String),
}
impl DockerNetworkMode {
pub fn named(value: impl AsRef<str>) -> Result<Self, DockerNetworkError> {
let value = value.as_ref().trim();
validate_name(value)?;
Ok(Self::Named(value.to_string()))
}
#[must_use]
pub const fn is_isolated(&self) -> bool {
matches!(self, Self::None)
}
#[must_use]
pub const fn is_service_reference(&self) -> bool {
matches!(self, Self::Service(_))
}
#[must_use]
pub const fn is_named(&self) -> bool {
matches!(self, Self::Named(_))
}
}
impl fmt::Display for DockerNetworkMode {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Bridge => formatter.write_str("bridge"),
Self::Host => formatter.write_str("host"),
Self::None => formatter.write_str("none"),
Self::Service(name) => write!(formatter, "service:{name}"),
Self::Container(name) => write!(formatter, "container:{name}"),
Self::Named(name) => formatter.write_str(name),
}
}
}
impl FromStr for DockerNetworkMode {
type Err = DockerNetworkError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(DockerNetworkError::Empty);
}
match trimmed {
"bridge" => Ok(Self::Bridge),
"host" => Ok(Self::Host),
"none" => Ok(Self::None),
_ => {
if let Some(service) = trimmed.strip_prefix("service:") {
validate_name(service)?;
Ok(Self::Service(service.to_string()))
} else if let Some(container) = trimmed.strip_prefix("container:") {
validate_name(container)?;
Ok(Self::Container(container.to_string()))
} else {
Self::named(trimmed)
}
},
}
}
}
impl TryFrom<&str> for DockerNetworkMode {
type Error = DockerNetworkError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
value.parse()
}
}
fn validate_name(value: &str) -> Result<(), DockerNetworkError> {
if value.is_empty() || value.chars().any(char::is_whitespace) {
Err(DockerNetworkError::InvalidName)
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::DockerNetworkMode;
#[test]
fn parses_network_modes() -> Result<(), Box<dyn std::error::Error>> {
let service: DockerNetworkMode = "service:web".parse()?;
let named: DockerNetworkMode = "frontend".parse()?;
assert!(service.is_service_reference());
assert!(named.is_named());
assert!(DockerNetworkMode::None.is_isolated());
assert_eq!(service.to_string(), "service:web");
Ok(())
}
}