Skip to main content

use_docker_network/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7/// Error returned when a Docker network mode is invalid.
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub enum DockerNetworkError {
10    /// The network value was empty after trimming.
11    Empty,
12    /// A service, container, or named network label was invalid.
13    InvalidName,
14}
15
16impl fmt::Display for DockerNetworkError {
17    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            Self::Empty => formatter.write_str("Docker network value cannot be empty"),
20            Self::InvalidName => formatter.write_str("invalid Docker network name"),
21        }
22    }
23}
24
25impl Error for DockerNetworkError {}
26
27/// Docker network mode or named network value.
28#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
29pub enum DockerNetworkMode {
30    /// Docker bridge networking.
31    Bridge,
32    /// Host networking.
33    Host,
34    /// No networking.
35    None,
36    /// Share a service network namespace.
37    Service(String),
38    /// Share a container network namespace.
39    Container(String),
40    /// A named network.
41    Named(String),
42}
43
44impl DockerNetworkMode {
45    /// Creates a named network value.
46    pub fn named(value: impl AsRef<str>) -> Result<Self, DockerNetworkError> {
47        let value = value.as_ref().trim();
48        validate_name(value)?;
49        Ok(Self::Named(value.to_string()))
50    }
51
52    /// Returns true for `none`.
53    #[must_use]
54    pub const fn is_isolated(&self) -> bool {
55        matches!(self, Self::None)
56    }
57
58    /// Returns true for `service:<name>`.
59    #[must_use]
60    pub const fn is_service_reference(&self) -> bool {
61        matches!(self, Self::Service(_))
62    }
63
64    /// Returns true for named networks.
65    #[must_use]
66    pub const fn is_named(&self) -> bool {
67        matches!(self, Self::Named(_))
68    }
69}
70
71impl fmt::Display for DockerNetworkMode {
72    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match self {
74            Self::Bridge => formatter.write_str("bridge"),
75            Self::Host => formatter.write_str("host"),
76            Self::None => formatter.write_str("none"),
77            Self::Service(name) => write!(formatter, "service:{name}"),
78            Self::Container(name) => write!(formatter, "container:{name}"),
79            Self::Named(name) => formatter.write_str(name),
80        }
81    }
82}
83
84impl FromStr for DockerNetworkMode {
85    type Err = DockerNetworkError;
86
87    fn from_str(value: &str) -> Result<Self, Self::Err> {
88        let trimmed = value.trim();
89        if trimmed.is_empty() {
90            return Err(DockerNetworkError::Empty);
91        }
92        match trimmed {
93            "bridge" => Ok(Self::Bridge),
94            "host" => Ok(Self::Host),
95            "none" => Ok(Self::None),
96            _ => {
97                if let Some(service) = trimmed.strip_prefix("service:") {
98                    validate_name(service)?;
99                    Ok(Self::Service(service.to_string()))
100                } else if let Some(container) = trimmed.strip_prefix("container:") {
101                    validate_name(container)?;
102                    Ok(Self::Container(container.to_string()))
103                } else {
104                    Self::named(trimmed)
105                }
106            },
107        }
108    }
109}
110
111impl TryFrom<&str> for DockerNetworkMode {
112    type Error = DockerNetworkError;
113
114    fn try_from(value: &str) -> Result<Self, Self::Error> {
115        value.parse()
116    }
117}
118
119fn validate_name(value: &str) -> Result<(), DockerNetworkError> {
120    if value.is_empty() || value.chars().any(char::is_whitespace) {
121        Err(DockerNetworkError::InvalidName)
122    } else {
123        Ok(())
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::DockerNetworkMode;
130
131    #[test]
132    fn parses_network_modes() -> Result<(), Box<dyn std::error::Error>> {
133        let service: DockerNetworkMode = "service:web".parse()?;
134        let named: DockerNetworkMode = "frontend".parse()?;
135
136        assert!(service.is_service_reference());
137        assert!(named.is_named());
138        assert!(DockerNetworkMode::None.is_isolated());
139        assert_eq!(service.to_string(), "service:web");
140        Ok(())
141    }
142}