1#![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 NamespaceError {
10 Empty,
11 UnknownKind,
12 InvalidPath,
13}
14
15impl fmt::Display for NamespaceError {
16 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
17 match self {
18 Self::Empty => formatter.write_str("OCI namespace value cannot be empty"),
19 Self::UnknownKind => formatter.write_str("unknown OCI namespace kind"),
20 Self::InvalidPath => formatter.write_str("invalid OCI namespace path"),
21 }
22 }
23}
24
25impl Error for NamespaceError {}
26
27#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
29pub enum NamespaceKind {
30 Pid,
31 Network,
32 Mount,
33 Ipc,
34 Uts,
35 User,
36 Cgroup,
37}
38
39impl NamespaceKind {
40 #[must_use]
42 pub const fn as_str(self) -> &'static str {
43 match self {
44 Self::Pid => "pid",
45 Self::Network => "network",
46 Self::Mount => "mount",
47 Self::Ipc => "ipc",
48 Self::Uts => "uts",
49 Self::User => "user",
50 Self::Cgroup => "cgroup",
51 }
52 }
53}
54
55impl fmt::Display for NamespaceKind {
56 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
57 formatter.write_str(self.as_str())
58 }
59}
60
61impl FromStr for NamespaceKind {
62 type Err = NamespaceError;
63
64 fn from_str(value: &str) -> Result<Self, Self::Err> {
65 match value.trim().to_ascii_lowercase().as_str() {
66 "pid" => Ok(Self::Pid),
67 "network" | "net" => Ok(Self::Network),
68 "mount" | "mnt" => Ok(Self::Mount),
69 "ipc" => Ok(Self::Ipc),
70 "uts" => Ok(Self::Uts),
71 "user" => Ok(Self::User),
72 "cgroup" => Ok(Self::Cgroup),
73 "" => Err(NamespaceError::Empty),
74 _ => Err(NamespaceError::UnknownKind),
75 }
76 }
77}
78
79#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
81pub struct NamespacePath(String);
82
83impl NamespacePath {
84 pub fn new(value: impl AsRef<str>) -> Result<Self, NamespaceError> {
86 let trimmed = value.as_ref().trim();
87 if trimmed.is_empty() {
88 return Err(NamespaceError::Empty);
89 }
90 if trimmed
91 .bytes()
92 .any(|byte| byte.is_ascii_control() || byte == b'\0')
93 {
94 return Err(NamespaceError::InvalidPath);
95 }
96 Ok(Self(trimmed.to_string()))
97 }
98
99 #[must_use]
101 pub fn as_str(&self) -> &str {
102 &self.0
103 }
104}
105
106impl AsRef<str> for NamespacePath {
107 fn as_ref(&self) -> &str {
108 self.as_str()
109 }
110}
111
112impl fmt::Display for NamespacePath {
113 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
114 formatter.write_str(self.as_str())
115 }
116}
117
118#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
120pub struct Namespace {
121 kind: NamespaceKind,
122 path: Option<NamespacePath>,
123}
124
125impl Namespace {
126 #[must_use]
128 pub const fn new(kind: NamespaceKind) -> Self {
129 Self { kind, path: None }
130 }
131
132 #[must_use]
134 pub fn with_path(mut self, path: NamespacePath) -> Self {
135 self.path = Some(path);
136 self
137 }
138
139 #[must_use]
141 pub const fn kind(&self) -> NamespaceKind {
142 self.kind
143 }
144
145 #[must_use]
147 pub const fn path(&self) -> Option<&NamespacePath> {
148 self.path.as_ref()
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::{Namespace, NamespaceError, NamespaceKind, NamespacePath};
155
156 #[test]
157 fn parses_namespace_kinds() -> Result<(), NamespaceError> {
158 assert_eq!("pid".parse::<NamespaceKind>()?, NamespaceKind::Pid);
159 assert_eq!("net".parse::<NamespaceKind>()?, NamespaceKind::Network);
160 assert_eq!("mnt".parse::<NamespaceKind>()?, NamespaceKind::Mount);
161 assert_eq!(
162 "bad".parse::<NamespaceKind>(),
163 Err(NamespaceError::UnknownKind)
164 );
165 Ok(())
166 }
167
168 #[test]
169 fn models_namespace_paths_lexically() -> Result<(), NamespaceError> {
170 let namespace =
171 Namespace::new(NamespaceKind::Pid).with_path(NamespacePath::new("/proc/self/ns/pid")?);
172
173 assert_eq!(namespace.kind(), NamespaceKind::Pid);
174 assert_eq!(
175 namespace.path().map(NamespacePath::as_str),
176 Some("/proc/self/ns/pid")
177 );
178 Ok(())
179 }
180}