microsandbox_core/config/
path_segment.rs1use std::{
2 ffi::{OsStr, OsString},
3 fmt::{self, Display},
4 path::{Component, Path, PathBuf},
5 str::FromStr,
6};
7
8use crate::MicrosandboxError;
9
10#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
19pub struct PathSegment(OsString);
20
21impl PathSegment {
26 pub fn as_os_str(&self) -> &OsStr {
28 &self.0
29 }
30
31 pub fn as_bytes(&self) -> &[u8] {
33 self.as_os_str().as_encoded_bytes()
34 }
35
36 pub fn len(&self) -> usize {
38 self.as_bytes().len()
39 }
40
41 pub fn is_empty(&self) -> bool {
43 self.len() == 0
44 }
45}
46
47impl FromStr for PathSegment {
52 type Err = MicrosandboxError;
53
54 fn from_str(s: &str) -> Result<Self, Self::Err> {
55 PathSegment::try_from(s)
56 }
57}
58
59impl Display for PathSegment {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 write!(f, "{}", self.0.to_string_lossy())
62 }
63}
64
65impl TryFrom<&str> for PathSegment {
66 type Error = MicrosandboxError;
67
68 fn try_from(value: &str) -> Result<Self, Self::Error> {
69 if value.is_empty() {
70 return Err(MicrosandboxError::EmptyPathSegment);
71 }
72
73 #[cfg(unix)]
74 {
75 if value.contains('/') {
76 return Err(MicrosandboxError::InvalidPathComponent(value.to_string()));
77 }
78 }
79
80 #[cfg(windows)]
81 {
82 if value.contains('/') || value.contains('\\') {
83 return Err(MicrosandboxError::InvalidPathComponent(value.to_string()));
84 }
85 }
86
87 let mut components = Path::new(value).components();
89 let component = components
90 .next()
91 .ok_or_else(|| MicrosandboxError::InvalidPathComponent(value.to_string()))?;
92
93 if components.next().is_some() {
95 return Err(MicrosandboxError::InvalidPathComponent(value.to_string()));
96 }
97
98 match component {
99 Component::Normal(comp) => Ok(PathSegment(comp.to_os_string())),
100 _ => Err(MicrosandboxError::InvalidPathComponent(value.to_string())),
101 }
102 }
103}
104
105impl<'a> TryFrom<Component<'a>> for PathSegment {
106 type Error = MicrosandboxError;
107
108 fn try_from(component: Component<'a>) -> Result<Self, Self::Error> {
109 PathSegment::try_from(&component)
110 }
111}
112
113impl<'a> TryFrom<&Component<'a>> for PathSegment {
114 type Error = MicrosandboxError;
115
116 fn try_from(component: &Component<'a>) -> Result<Self, Self::Error> {
117 match component {
118 Component::Normal(component) => Ok(PathSegment(component.to_os_string())),
119 _ => Err(MicrosandboxError::InvalidPathComponent(
120 component.as_os_str().to_string_lossy().into_owned(),
121 )),
122 }
123 }
124}
125
126impl<'a> From<&'a PathSegment> for Component<'a> {
127 fn from(segment: &'a PathSegment) -> Self {
128 Component::Normal(segment.as_os_str())
129 }
130}
131
132impl From<PathSegment> for PathBuf {
133 #[inline]
134 fn from(segment: PathSegment) -> Self {
135 PathBuf::from(segment.0)
136 }
137}
138
139impl AsRef<[u8]> for PathSegment {
140 #[inline]
141 fn as_ref(&self) -> &[u8] {
142 self.as_bytes()
143 }
144}
145
146impl AsRef<OsStr> for PathSegment {
147 #[inline]
148 fn as_ref(&self) -> &OsStr {
149 self.as_os_str()
150 }
151}
152
153impl AsRef<Path> for PathSegment {
154 #[inline]
155 fn as_ref(&self) -> &Path {
156 Path::new(self.as_os_str())
157 }
158}
159
160#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn test_segment_as_os_str() {
170 let segment = PathSegment::from_str("example").unwrap();
171 assert_eq!(segment.as_os_str(), OsStr::new("example"));
172 }
173
174 #[test]
175 fn test_segment_as_bytes() {
176 let segment = PathSegment::from_str("example").unwrap();
177 assert_eq!(segment.as_bytes(), b"example");
178 }
179
180 #[test]
181 fn test_segment_len() {
182 let segment = PathSegment::from_str("example").unwrap();
183 assert_eq!(segment.len(), 7);
184 }
185
186 #[test]
187 fn test_segment_display() {
188 let segment = PathSegment::from_str("example").unwrap();
189 assert_eq!(format!("{}", segment), "example");
190 }
191
192 #[test]
193 fn test_segment_try_from_str() {
194 assert!(PathSegment::try_from("example").is_ok());
195 assert!(PathSegment::from_str("example").is_ok());
196 assert!("example".parse::<PathSegment>().is_ok());
197
198 assert!(PathSegment::from_str("").is_err());
200 assert!(PathSegment::from_str(".").is_err());
201 assert!(PathSegment::from_str("..").is_err());
202 assert!(".".parse::<PathSegment>().is_err());
203 assert!("..".parse::<PathSegment>().is_err());
204 assert!("".parse::<PathSegment>().is_err());
205 assert!(PathSegment::try_from(".").is_err());
206 assert!(PathSegment::try_from("..").is_err());
207 assert!(PathSegment::try_from("/").is_err());
208 assert!(PathSegment::try_from("").is_err());
209 }
210
211 #[test]
212 fn test_segment_from_path_segment_to_component() {
213 let segment = PathSegment::from_str("example").unwrap();
214 assert_eq!(
215 Component::from(&segment),
216 Component::Normal(OsStr::new("example"))
217 );
218 }
219
220 #[test]
221 fn test_segment_from_path_segment_to_path_buf() {
222 let segment = PathSegment::from_str("example").unwrap();
223 assert_eq!(PathBuf::from(segment), PathBuf::from("example"));
224 }
225
226 #[test]
227 fn test_segment_normal_with_special_characters() {
228 assert!(PathSegment::try_from("file.txt").is_ok());
229 assert!(PathSegment::try_from("file-name").is_ok());
230 assert!(PathSegment::try_from("file_name").is_ok());
231 assert!(PathSegment::try_from("file name").is_ok());
232 assert!(PathSegment::try_from("file:name").is_ok());
233 assert!(PathSegment::try_from("file*name").is_ok());
234 assert!(PathSegment::try_from("file?name").is_ok());
235 }
236
237 #[test]
238 #[cfg(unix)]
239 fn test_segment_with_unix_separator() {
240 assert!(PathSegment::try_from("file/name").is_err());
242 assert!(PathSegment::try_from("/").is_err());
243 assert!(PathSegment::try_from("///").is_err());
244 assert!(PathSegment::try_from("name/").is_err());
245 assert!(PathSegment::try_from("/name").is_err());
246 }
247
248 #[test]
249 #[cfg(windows)]
250 fn test_segment_with_windows_separators() {
251 assert!(PathSegment::try_from("file\\name").is_err());
253 assert!(PathSegment::try_from("file/name").is_err());
254 assert!(PathSegment::try_from("\\").is_err());
255 assert!(PathSegment::try_from("/").is_err());
256 assert!(PathSegment::try_from("\\\\\\").is_err());
257 assert!(PathSegment::try_from("///").is_err());
258 assert!(PathSegment::try_from("name\\").is_err());
259 assert!(PathSegment::try_from("name/").is_err());
260 assert!(PathSegment::try_from("\\name").is_err());
261 assert!(PathSegment::try_from("/name").is_err());
262 }
263}