1 // Copyright (C) 2024 The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //! A path relative to a root. Useful for paths relative to an Android repo, for example. 16 17 use core::fmt::Display; 18 use std::path::{Path, PathBuf}; 19 20 use thiserror::Error; 21 22 #[allow(missing_docs)] 23 #[derive(Error, Debug)] 24 pub enum RootedPathError { 25 #[error("Root path is not absolute: {}", .0.display())] 26 RootNotAbsolute(PathBuf), 27 #[error("Path is not relative: {}", .0.display())] 28 PathNotRelative(PathBuf), 29 } 30 31 /// A path relative to a root. 32 #[derive(Debug, PartialEq, Eq, Clone)] 33 pub struct RootedPath { 34 root: PathBuf, 35 path: PathBuf, 36 } 37 38 impl RootedPath { 39 /// Creates a new RootedPath from an absolute root and a path relative to the root. new<P: Into<PathBuf>>( root: P, path: impl AsRef<Path>, ) -> Result<RootedPath, RootedPathError>40 pub fn new<P: Into<PathBuf>>( 41 root: P, 42 path: impl AsRef<Path>, 43 ) -> Result<RootedPath, RootedPathError> { 44 let root: PathBuf = root.into(); 45 if !root.is_absolute() { 46 return Err(RootedPathError::RootNotAbsolute(root)); 47 } 48 let path = path.as_ref(); 49 if !path.is_relative() { 50 return Err(RootedPathError::PathNotRelative(path.to_path_buf())); 51 } 52 let path = root.join(path); 53 Ok(RootedPath { root, path }) 54 } 55 /// Returns the root. root(&self) -> &Path56 pub fn root(&self) -> &Path { 57 self.root.as_path() 58 } 59 /// Returns the path relative to the root. rel(&self) -> &Path60 pub fn rel(&self) -> &Path { 61 self.path.strip_prefix(&self.root).unwrap() 62 } 63 /// Returns the absolute path. abs(&self) -> &Path64 pub fn abs(&self) -> &Path { 65 self.path.as_path() 66 } 67 /// Creates a new RootedPath with path adjoined to self. join(&self, path: impl AsRef<Path>) -> Result<RootedPath, RootedPathError>68 pub fn join(&self, path: impl AsRef<Path>) -> Result<RootedPath, RootedPathError> { 69 let path = path.as_ref(); 70 if !path.is_relative() { 71 return Err(RootedPathError::PathNotRelative(path.to_path_buf())); 72 } 73 Ok(RootedPath { root: self.root.clone(), path: self.path.join(path) }) 74 } 75 /// Creates a new RootedPath with the same root but a new relative directory. with_same_root(&self, path: impl AsRef<Path>) -> Result<RootedPath, RootedPathError>76 pub fn with_same_root(&self, path: impl AsRef<Path>) -> Result<RootedPath, RootedPathError> { 77 RootedPath::new(self.root.clone(), path) 78 } 79 } 80 81 impl Display for RootedPath { fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 83 write!(f, "{}", self.rel().display()) 84 } 85 } 86 87 impl AsRef<Path> for RootedPath { as_ref(&self) -> &Path88 fn as_ref(&self) -> &Path { 89 self.abs() 90 } 91 } 92 93 impl From<RootedPath> for PathBuf { from(val: RootedPath) -> Self94 fn from(val: RootedPath) -> Self { 95 val.path 96 } 97 } 98 99 #[cfg(test)] 100 mod tests { 101 use super::*; 102 103 #[test] test_basic() -> Result<(), RootedPathError>104 fn test_basic() -> Result<(), RootedPathError> { 105 let p = RootedPath::new("/foo", "bar")?; 106 assert_eq!(p.root(), Path::new("/foo")); 107 assert_eq!(p.rel(), Path::new("bar")); 108 assert_eq!(p.abs(), PathBuf::from("/foo/bar")); 109 assert_eq!(p.join("baz")?, RootedPath::new("/foo", "bar/baz")?); 110 assert_eq!(p.with_same_root("baz")?, RootedPath::new("/foo", "baz")?); 111 Ok(()) 112 } 113 114 #[test] test_errors() -> Result<(), RootedPathError>115 fn test_errors() -> Result<(), RootedPathError> { 116 assert!(RootedPath::new("foo", "bar").is_err()); 117 assert!(RootedPath::new("/foo", "/bar").is_err()); 118 let p = RootedPath::new("/foo", "bar")?; 119 assert!(p.join("/baz").is_err()); 120 assert!(p.with_same_root("/baz").is_err()); 121 Ok(()) 122 } 123 124 #[test] test_conversion() -> Result<(), RootedPathError>125 fn test_conversion() -> Result<(), RootedPathError> { 126 let p = RootedPath::new("/foo", "bar")?; 127 128 let path = p.as_ref(); 129 assert_eq!(path, Path::new("/foo/bar")); 130 131 let pathbuf: PathBuf = p.into(); 132 assert_eq!(pathbuf, Path::new("/foo/bar")); 133 134 Ok(()) 135 } 136 } 137