protobuf_parse/
proto_path.rs

1#![doc(hidden)]
2
3use std::borrow::Borrow;
4use std::fmt;
5use std::hash::Hash;
6use std::ops::Deref;
7use std::path::Component;
8use std::path::Path;
9use std::path::PathBuf;
10
11#[derive(Debug, thiserror::Error)]
12enum 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)]
32pub struct ProtoPath {
33    path: str,
34}
35
36/// Protobuf file relative normalized file path.
37#[derive(Debug, Clone, PartialEq, Eq, Default)]
38pub struct ProtoPathBuf {
39    path: String,
40}
41
42impl Hash for ProtoPathBuf {
43    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
44        self.as_path().hash(state);
45    }
46}
47
48impl Borrow<ProtoPath> for ProtoPathBuf {
49    fn borrow(&self) -> &ProtoPath {
50        self.as_path()
51    }
52}
53
54impl Deref for ProtoPathBuf {
55    type Target = ProtoPath;
56
57    fn deref(&self) -> &ProtoPath {
58        self.as_path()
59    }
60}
61
62impl fmt::Display for ProtoPath {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        write!(f, "{}", &self.path)
65    }
66}
67
68impl fmt::Display for ProtoPathBuf {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        write!(f, "{}", &self.path)
71    }
72}
73
74impl PartialEq<str> for ProtoPath {
75    fn eq(&self, other: &str) -> bool {
76        &self.path == other
77    }
78}
79
80impl PartialEq<str> for ProtoPathBuf {
81    fn eq(&self, other: &str) -> bool {
82        &self.path == other
83    }
84}
85
86impl ProtoPath {
87    fn unchecked_new(path: &str) -> &ProtoPath {
88        unsafe { &*(path as *const str as *const ProtoPath) }
89    }
90
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
112    pub fn to_str(&self) -> &str {
113        &self.path
114    }
115
116    pub fn to_path(&self) -> &Path {
117        Path::new(&self.path)
118    }
119
120    pub fn to_proto_path_buf(&self) -> ProtoPathBuf {
121        ProtoPathBuf {
122            path: self.path.to_owned(),
123        }
124    }
125}
126
127impl ProtoPathBuf {
128    pub fn as_path(&self) -> &ProtoPath {
129        ProtoPath::unchecked_new(&self.path)
130    }
131
132    pub fn new(path: String) -> anyhow::Result<ProtoPathBuf> {
133        ProtoPath::new(&path)?;
134        Ok(ProtoPathBuf { path })
135    }
136
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}