1 #![doc(hidden)]
2 
3 use std::borrow::Borrow;
4 use std::fmt;
5 use std::hash::Hash;
6 use std::ops::Deref;
7 use std::path::Component;
8 use std::path::Path;
9 use std::path::PathBuf;
10 
11 #[derive(Debug, thiserror::Error)]
12 enum Error {
13     #[error("path is empty")]
14     Empty,
15     #[error("backslashes in path: {0:?}")]
16     Backslashes(String),
17     #[error("path contains empty components: {0:?}")]
18     EmptyComponent(String),
19     #[error("dot in path: {0:?}")]
20     Dot(String),
21     #[error("dot-dot in path: {0:?}")]
22     DotDot(String),
23     #[error("path is absolute: `{}`", _0.display())]
24     Absolute(PathBuf),
25     #[error("non-UTF-8 component in path: `{}`", _0.display())]
26     NotUtf8(PathBuf),
27 }
28 
29 /// Protobuf file relative normalized file path.
30 #[repr(transparent)]
31 #[derive(Eq, PartialEq, Hash, Debug)]
32 pub struct ProtoPath {
33     path: str,
34 }
35 
36 /// Protobuf file relative normalized file path.
37 #[derive(Debug, Clone, PartialEq, Eq, Default)]
38 pub struct ProtoPathBuf {
39     path: String,
40 }
41 
42 impl Hash for ProtoPathBuf {
hash<H: std::hash::Hasher>(&self, state: &mut H)43     fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
44         self.as_path().hash(state);
45     }
46 }
47 
48 impl Borrow<ProtoPath> for ProtoPathBuf {
borrow(&self) -> &ProtoPath49     fn borrow(&self) -> &ProtoPath {
50         self.as_path()
51     }
52 }
53 
54 impl Deref for ProtoPathBuf {
55     type Target = ProtoPath;
56 
deref(&self) -> &ProtoPath57     fn deref(&self) -> &ProtoPath {
58         self.as_path()
59     }
60 }
61 
62 impl fmt::Display for ProtoPath {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result63     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64         write!(f, "{}", &self.path)
65     }
66 }
67 
68 impl fmt::Display for ProtoPathBuf {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result69     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70         write!(f, "{}", &self.path)
71     }
72 }
73 
74 impl PartialEq<str> for ProtoPath {
eq(&self, other: &str) -> bool75     fn eq(&self, other: &str) -> bool {
76         &self.path == other
77     }
78 }
79 
80 impl PartialEq<str> for ProtoPathBuf {
eq(&self, other: &str) -> bool81     fn eq(&self, other: &str) -> bool {
82         &self.path == other
83     }
84 }
85 
86 impl ProtoPath {
unchecked_new(path: &str) -> &ProtoPath87     fn unchecked_new(path: &str) -> &ProtoPath {
88         unsafe { &*(path as *const str as *const ProtoPath) }
89     }
90 
new(path: &str) -> anyhow::Result<&ProtoPath>91     pub fn new(path: &str) -> anyhow::Result<&ProtoPath> {
92         if path.is_empty() {
93             return Err(Error::Empty.into());
94         }
95         if path.contains('\\') {
96             return Err(Error::Backslashes(path.to_owned()).into());
97         }
98         for component in path.split('/') {
99             if component.is_empty() {
100                 return Err(Error::EmptyComponent(path.to_owned()).into());
101             }
102             if component == "." {
103                 return Err(Error::Dot(path.to_owned()).into());
104             }
105             if component == ".." {
106                 return Err(Error::DotDot(path.to_owned()).into());
107             }
108         }
109         Ok(Self::unchecked_new(path))
110     }
111 
to_str(&self) -> &str112     pub fn to_str(&self) -> &str {
113         &self.path
114     }
115 
to_path(&self) -> &Path116     pub fn to_path(&self) -> &Path {
117         Path::new(&self.path)
118     }
119 
to_proto_path_buf(&self) -> ProtoPathBuf120     pub fn to_proto_path_buf(&self) -> ProtoPathBuf {
121         ProtoPathBuf {
122             path: self.path.to_owned(),
123         }
124     }
125 }
126 
127 impl ProtoPathBuf {
as_path(&self) -> &ProtoPath128     pub fn as_path(&self) -> &ProtoPath {
129         ProtoPath::unchecked_new(&self.path)
130     }
131 
new(path: String) -> anyhow::Result<ProtoPathBuf>132     pub fn new(path: String) -> anyhow::Result<ProtoPathBuf> {
133         ProtoPath::new(&path)?;
134         Ok(ProtoPathBuf { path })
135     }
136 
from_path(path: &Path) -> anyhow::Result<ProtoPathBuf>137     pub fn from_path(path: &Path) -> anyhow::Result<ProtoPathBuf> {
138         let mut path_str = String::new();
139         for component in path.components() {
140             match component {
141                 Component::Prefix(..) => return Err(Error::Absolute(path.to_owned()).into()),
142                 Component::RootDir => return Err(Error::Absolute(path.to_owned()).into()),
143                 Component::CurDir if path_str.is_empty() => {}
144                 Component::CurDir => return Err(Error::Dot(path.display().to_string()).into()),
145                 Component::ParentDir => {
146                     return Err(Error::DotDot(path.display().to_string()).into())
147                 }
148                 Component::Normal(c) => {
149                     if !path_str.is_empty() {
150                         path_str.push('/');
151                     }
152                     path_str.push_str(c.to_str().ok_or_else(|| Error::NotUtf8(path.to_owned()))?);
153                 }
154             }
155         }
156         Ok(ProtoPathBuf { path: path_str })
157     }
158 }
159