brume/vfs/
virtual_path.rs

1//! Representation of path objects that are not necessarily linked to the local filesystem.
2
3use std::{borrow::Borrow, ops::Deref, path::Path};
4
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8use super::NodeKind;
9
10#[derive(Error, Debug)]
11pub enum InvalidPathError {
12    #[error("path {0:?} not found in the VFS")]
13    NotFound(VirtualPathBuf),
14    #[error("expected a directory at {0:?}")]
15    NotADir(VirtualPathBuf),
16    #[error("expected a file at {0:?}")]
17    NotAFile(VirtualPathBuf),
18}
19
20impl InvalidPathError {
21    pub fn for_kind(kind: NodeKind, path: &VirtualPath) -> Self {
22        match kind {
23            NodeKind::Dir => Self::NotADir(path.to_owned()),
24            NodeKind::File => Self::NotAFile(path.to_owned()),
25        }
26    }
27}
28
29/// A wrapper type that allows doing path operations on strings, without considerations for any
30/// concrete file system. These paths are supposed to be absolute and should start with a '/'.
31#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
32#[repr(transparent)]
33pub struct VirtualPath {
34    path: str,
35}
36
37#[derive(Error, Debug)]
38pub enum VirtualPathError {
39    #[error("this string cannot be converted into a path: {0}")]
40    InvalidString(String),
41    #[error("the path {subpath} is not a valid subpath for {base}")]
42    NotASubpath { base: String, subpath: String },
43}
44
45impl VirtualPath {
46    /// Return the root path: "/"
47    pub const fn root() -> &'static Self {
48        // safety: because of `#[repr(transparent)]`, &'a str can be safely converted to &'a Self
49        unsafe { &*("/" as *const str as *const Self) }
50    }
51
52    /// Check if this path represent a "root"
53    pub fn is_root(&self) -> bool {
54        &self.path == "/"
55    }
56
57    /// Return the number of elements in the path
58    pub fn len(&self) -> usize {
59        self.iter().count()
60    }
61
62    /// alias for [`Self::is_root`]
63    pub fn is_empty(&self) -> bool {
64        self.is_root()
65    }
66
67    /// Create a path from a &str, without any validation
68    fn new(path: &str) -> &Self {
69        // safety: because of `#[repr(transparent)]`, &'a str can be safely converted to &'a Self
70        unsafe { &*(path as *const str as *const Self) }
71    }
72
73    /// Return the path without any trailing '/'
74    fn trimmed_path(&self) -> &str {
75        if let Some(prefix) = self.path.strip_suffix('/') {
76            prefix
77        } else {
78            &self.path
79        }
80    }
81
82    /// Return a new [`VirtualPath`] with a new root
83    pub fn chroot(&self, new_root: &VirtualPath) -> Result<&Self, VirtualPathError> {
84        let new_root_path = new_root.trimmed_path();
85
86        if let Some(new_path) = self.path.strip_prefix(new_root_path) {
87            new_path.try_into()
88        } else {
89            Err(VirtualPathError::NotASubpath {
90                base: self.path.to_string(),
91                subpath: new_root.path.to_string(),
92            })
93        }
94    }
95
96    /// Return true if the node pointed by "self" is contained in "other", eventually recursively
97    /// Return also true if self == other
98    pub fn is_inside(&self, other: &VirtualPath) -> bool {
99        if let Some(suffix) = self.path.strip_prefix(other.trimmed_path()) {
100            // Check for false positives caused by files that are in the same dir and start with the
101            // same name. For example: /some/file and /some/file_long should return false
102            suffix.is_empty() || suffix.starts_with('/')
103        } else {
104            false
105        }
106    }
107
108    /// Return the name of the element pointed by this path
109    pub fn name(&self) -> &str {
110        let path = self.trimmed_path();
111
112        // Since we have the invariant that all paths should start with '/', if we don't find a '/'
113        // we have removed it in the previous step, so our path is actually the root
114        path.rsplit_once('/')
115            .map(|(_, suffix)| suffix)
116            .unwrap_or("")
117    }
118
119    /// Return the parent of this path, if any
120    pub fn parent(&self) -> Option<&Self> {
121        let path = self.trimmed_path();
122
123        // It's ok to unwrap here, if "path" is valid we know that "prefix will be too
124        path.rsplit_once('/').map(|(prefix, _)| {
125            if prefix.is_empty() {
126                Self::root()
127            } else {
128                prefix.try_into().unwrap()
129            }
130        })
131    }
132
133    /// Return the top level directory of this path, for example "a" in "/a/b/c". Return `None` is
134    /// the provided path is the root
135    pub fn top_level(&self) -> Option<&str> {
136        if self.is_root() {
137            return None;
138        }
139
140        let noroot = &self.path[1..];
141
142        if let Some(end_pos) = noroot.find('/') {
143            Some(&noroot[..end_pos])
144        } else {
145            Some(self.name())
146        }
147    }
148
149    /// Return the path split in two components, the top level and the rest. For example, the path
150    /// "a/b/c" will return Some("a", "/b/c"). Return None when called on a root path.
151    pub fn top_level_split(&self) -> Option<(&str, &Self)> {
152        if self.is_root() {
153            return None;
154        }
155
156        let noroot = &self.path[1..];
157
158        if let Some(end_pos) = noroot.find('/') {
159            // Ok to unwrap here because the path is known to be valid
160            let top_level = &noroot[..end_pos];
161            let remainder = self.path[(end_pos + 1)..].try_into().unwrap();
162            Some((top_level, remainder))
163        } else {
164            Some((self.name(), Self::root()))
165        }
166    }
167
168    /// Return an iterator over the components of the path
169    pub fn iter(&self) -> VirtualPathIterator {
170        VirtualPathIterator { path: self }
171    }
172}
173
174impl<'a> TryFrom<&'a str> for &'a VirtualPath {
175    type Error = VirtualPathError;
176
177    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
178        if !value.starts_with('/') {
179            return Err(VirtualPathError::InvalidString(value.to_string()));
180        }
181
182        if value.contains("//") {
183            return Err(VirtualPathError::InvalidString(value.to_string()));
184        }
185
186        Ok(VirtualPath::new(value))
187    }
188}
189
190impl<'a> From<&'a VirtualPath> for &'a str {
191    fn from(value: &'a VirtualPath) -> Self {
192        &value.path
193    }
194}
195
196impl Borrow<VirtualPath> for VirtualPathBuf {
197    fn borrow(&self) -> &VirtualPath {
198        self.deref()
199    }
200}
201
202impl Deref for VirtualPathBuf {
203    type Target = VirtualPath;
204
205    fn deref(&self) -> &VirtualPath {
206        VirtualPath::new(&self.path)
207    }
208}
209
210impl AsRef<VirtualPath> for VirtualPathBuf {
211    fn as_ref(&self) -> &VirtualPath {
212        self.deref()
213    }
214}
215
216impl ToOwned for VirtualPath {
217    type Owned = VirtualPathBuf;
218
219    fn to_owned(&self) -> Self::Owned {
220        VirtualPathBuf {
221            path: self.path.to_string(),
222        }
223    }
224}
225
226impl<'a> PartialEq<&'a VirtualPath> for VirtualPathBuf {
227    fn eq(&self, other: &&'a VirtualPath) -> bool {
228        self.path == other.path
229    }
230}
231
232impl PartialEq<VirtualPathBuf> for &VirtualPath {
233    fn eq(&self, other: &VirtualPathBuf) -> bool {
234        self.path == other.path
235    }
236}
237
238impl AsRef<Path> for VirtualPath {
239    fn as_ref(&self) -> &Path {
240        self.path.as_ref()
241    }
242}
243
244pub struct VirtualPathIterator<'a> {
245    path: &'a VirtualPath,
246}
247
248impl<'a> Iterator for VirtualPathIterator<'a> {
249    type Item = &'a str;
250
251    fn next(&mut self) -> Option<Self::Item> {
252        let (top_level, remainder) = self.path.top_level_split()?;
253
254        self.path = remainder;
255
256        Some(top_level)
257    }
258}
259
260/// Similar to the distinction with Path and PathBuf, this is a VirtualPath that owns the underlying
261/// data.
262#[derive(Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Clone, Serialize, Deserialize)]
263#[repr(transparent)]
264pub struct VirtualPathBuf {
265    path: String,
266}
267
268impl VirtualPathBuf {
269    pub fn root() -> Self {
270        Self {
271            path: String::from('/'),
272        }
273    }
274
275    pub fn new(path: &str) -> Result<Self, VirtualPathError> {
276        let ref_path: &VirtualPath = path.try_into()?;
277
278        Ok(ref_path.to_owned())
279    }
280
281    /// Extend a path with a new segment
282    pub fn push(&mut self, value: &str) {
283        if self.path.ends_with('/') {
284            self.path.push_str(value.trim_matches('/'));
285        } else {
286            self.path.push('/');
287            self.path.push_str(value.trim_matches('/'));
288        }
289    }
290}
291
292#[cfg(test)]
293mod test {
294    use super::*;
295
296    #[test]
297    fn new_path() {
298        let bad = VirtualPathBuf::new("a/bad/path");
299
300        assert!(bad.is_err());
301
302        let bad = VirtualPathBuf::new("/a//bad/path");
303
304        assert!(bad.is_err());
305
306        let bad = VirtualPathBuf::new("/a///bad/path");
307
308        assert!(bad.is_err());
309
310        VirtualPathBuf::new("/a/good/path").unwrap();
311
312        VirtualPathBuf::new("/a/good/path/").unwrap();
313    }
314
315    #[test]
316    fn test_chroot() {
317        let base = VirtualPathBuf::new("/a/random/path").unwrap();
318
319        let good_root = VirtualPathBuf::new("/a/random").unwrap();
320
321        assert_eq!(
322            base.chroot(&good_root).unwrap(),
323            VirtualPathBuf::new("/path").unwrap()
324        );
325
326        let other_good_root = VirtualPathBuf::new("/a/random/").unwrap();
327
328        assert_eq!(
329            base.chroot(&other_good_root).unwrap(),
330            VirtualPathBuf::new("/path").unwrap()
331        );
332
333        let bad_root = VirtualPathBuf::new("/b/random").unwrap();
334
335        assert!(base.chroot(&bad_root).is_err());
336    }
337
338    #[test]
339    fn test_name() {
340        let base = VirtualPathBuf::new("/a/random/path").unwrap();
341
342        assert_eq!(base.name(), "path");
343
344        let trailing = VirtualPathBuf::new("/a/random/path/").unwrap();
345
346        assert_eq!(trailing.name(), "path");
347
348        let root = VirtualPath::root();
349
350        assert_eq!(root.name(), "");
351    }
352
353    #[test]
354    fn test_parent() {
355        let base = VirtualPathBuf::new("/a/random/path").unwrap();
356
357        assert_eq!(
358            base.parent().unwrap(),
359            VirtualPathBuf::new("/a/random").unwrap()
360        );
361
362        let trailing = VirtualPathBuf::new("/a/random/path/").unwrap();
363
364        assert_eq!(
365            trailing.parent().unwrap(),
366            VirtualPathBuf::new("/a/random").unwrap()
367        );
368
369        let first_level = VirtualPathBuf::new("/a").unwrap();
370
371        assert_eq!(first_level.parent().unwrap(), VirtualPath::root());
372
373        let root = VirtualPathBuf::new("/").unwrap();
374
375        assert_eq!(root.parent(), None);
376    }
377
378    #[test]
379    fn test_push() {
380        let reference = VirtualPathBuf::new("/a/random/path").unwrap();
381        let mut base = VirtualPathBuf::new("/a/random").unwrap();
382        base.push("path");
383
384        assert_eq!(base, reference);
385
386        let mut base = VirtualPathBuf::new("/a/random/").unwrap();
387        base.push("path");
388
389        assert_eq!(base, reference);
390
391        let mut base = VirtualPathBuf::new("/a/random").unwrap();
392        base.push("/path");
393
394        assert_eq!(base, reference);
395
396        let mut base = VirtualPathBuf::new("/a/random/").unwrap();
397        base.push("/path");
398
399        assert_eq!(base, reference);
400    }
401
402    #[test]
403    fn test_top_level() {
404        let base = VirtualPathBuf::new("/a/random/path").unwrap();
405
406        assert_eq!(base.top_level().unwrap(), "a");
407
408        let root = VirtualPath::root();
409
410        assert!(root.top_level().is_none());
411    }
412
413    #[test]
414    fn test_top_level_split() {
415        let base = VirtualPathBuf::new("/a/random/path").unwrap();
416
417        assert_eq!(
418            base.top_level_split().unwrap(),
419            ("a", VirtualPathBuf::new("/random/path").unwrap().as_ref())
420        );
421
422        let base = VirtualPathBuf::new("/a/random/path/").unwrap();
423
424        assert_eq!(
425            base.top_level_split().unwrap(),
426            ("a", VirtualPathBuf::new("/random/path/").unwrap().as_ref())
427        );
428
429        let base = VirtualPathBuf::new("/a/").unwrap();
430
431        assert_eq!(base.top_level_split().unwrap(), ("a", VirtualPath::root()));
432
433        let base = VirtualPathBuf::new("/a").unwrap();
434
435        assert_eq!(base.top_level_split().unwrap(), ("a", VirtualPath::root()));
436
437        let root = VirtualPath::root();
438
439        assert!(root.top_level_split().is_none());
440    }
441
442    #[test]
443    fn test_is_inside() {
444        let base = VirtualPathBuf::new("/a/b").unwrap();
445        let elem = VirtualPathBuf::new("/a/b/c").unwrap();
446
447        assert!(elem.is_inside(&base));
448
449        let elem = VirtualPathBuf::new("/a/b/c/d/e").unwrap();
450
451        assert!(elem.is_inside(&base));
452
453        let elem = VirtualPathBuf::new("/a/f/g").unwrap();
454
455        assert!(!elem.is_inside(&base));
456
457        let elem = VirtualPathBuf::new("/a/baba").unwrap();
458
459        assert!(!elem.is_inside(&base));
460
461        let elem = VirtualPathBuf::new("/a/b").unwrap();
462
463        assert!(elem.is_inside(&base));
464    }
465
466    #[test]
467    fn test_iter() {
468        let elem = VirtualPathBuf::new("/a/b/c").unwrap();
469        let mut iter = elem.iter();
470
471        assert_eq!(iter.next(), Some("a"));
472        assert_eq!(iter.next(), Some("b"));
473        assert_eq!(iter.next(), Some("c"));
474        assert_eq!(iter.next(), None);
475    }
476
477    #[test]
478    fn test_len() {
479        let elem = VirtualPathBuf::new("/a/b/c").unwrap();
480
481        assert_eq!(elem.len(), 3);
482
483        let elem = VirtualPathBuf::new("/a").unwrap();
484
485        assert_eq!(elem.len(), 1);
486
487        let elem = VirtualPathBuf::new("/").unwrap();
488
489        assert_eq!(elem.len(), 0);
490        assert!(elem.is_empty());
491    }
492}