about summary refs log tree commit diff
diff options
context:
space:
mode:
authoredef <edef@edef.eu>2024-05-01T12·02+0000
committeredef <edef@edef.eu>2024-05-01T13·40+0000
commit1bb023df91640268fad1e74571b5dc94822a79b3 (patch)
tree24fdfbe0bddafc6c7af4dea409d0c56ccf78e541
parentaa53338ddbcf6a7c0173789c78608df50839e15b (diff)
feat(tvix/castore/path): single-component paths are children of ROOT r/8058
The empty path (Path::ROOT) is explicitly a valid path, and "foo" is
simply a child of "". The root itself is the only path without a parent.

Change-Id: Iff00dc8aed89eaf98702b664c0df658bd5a1d88a
Reviewed-on: https://cl.tvl.fyi/c/depot/+/11569
Reviewed-by: flokli <flokli@flokli.de>
Tested-by: BuildkiteCI
-rw-r--r--tvix/castore/src/path.rs32
1 files changed, 21 insertions, 11 deletions
diff --git a/tvix/castore/src/path.rs b/tvix/castore/src/path.rs
index dfbf92232557..d2b011947a87 100644
--- a/tvix/castore/src/path.rs
+++ b/tvix/castore/src/path.rs
@@ -46,11 +46,25 @@ impl Path {
         Some(unsafe { Path::from_bytes_unchecked(bytes) })
     }
 
+    /// Returns the path without its final component, if there is one.
+    ///
+    /// Note that the parent of a bare file name is [Path::ROOT].
+    /// [Path::ROOT] is the only path without a parent.
     pub fn parent(&self) -> Option<&Path> {
-        let (parent, _file_name) = self.inner.rsplit_once_str(b"/")?;
+        // The root does not have a parent.
+        if self.inner.is_empty() {
+            return None;
+        }
 
-        // SAFETY: The parent of a valid Path is a valid Path.
-        Some(unsafe { Path::from_bytes_unchecked(parent) })
+        Some(
+            if let Some((parent, _file_name)) = self.inner.rsplit_once_str(b"/") {
+                // SAFETY: The parent of a valid Path is a valid Path.
+                unsafe { Path::from_bytes_unchecked(parent) }
+            } else {
+                // The parent of a bare file name is the root.
+                Path::ROOT
+            },
+        )
     }
 
     pub fn join(&self, name: &[u8]) -> Result<PathBuf, std::io::Error> {
@@ -166,7 +180,7 @@ impl Display for PathBuf {
 
 #[cfg(test)]
 mod test {
-    use super::PathBuf;
+    use super::{Path, PathBuf};
     use bstr::ByteSlice;
     use rstest::rstest;
 
@@ -214,6 +228,7 @@ mod test {
     }
 
     #[rstest]
+    #[case("foo", "")]
     #[case("foo/bar", "foo")]
     #[case("foo2/bar2", "foo2")]
     #[case("foo/bar/baz", "foo/bar")]
@@ -222,13 +237,8 @@ mod test {
     }
 
     #[rstest]
-    #[case::empty("")]
-    #[case::single("foo")]
-    pub fn no_parent(#[case] p: PathBuf) {
-        assert!(p.parent().is_none());
-
-        // same for Path
-        assert!(p.as_ref().parent().is_none());
+    pub fn no_parent() {
+        assert!(Path::ROOT.parent().is_none());
     }
 
     #[rstest]