use_docker_network/
lib.rs1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub enum DockerNetworkError {
10 Empty,
12 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#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
29pub enum DockerNetworkMode {
30 Bridge,
32 Host,
34 None,
36 Service(String),
38 Container(String),
40 Named(String),
42}
43
44impl DockerNetworkMode {
45 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 #[must_use]
54 pub const fn is_isolated(&self) -> bool {
55 matches!(self, Self::None)
56 }
57
58 #[must_use]
60 pub const fn is_service_reference(&self) -> bool {
61 matches!(self, Self::Service(_))
62 }
63
64 #[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}