xref: /aosp_15_r20/development/tools/external_crates/rooted_path/src/lib.rs (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
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