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#[repr(transparent)]
31#[derive(Eq, PartialEq, Hash, Debug)]
32pub struct ProtoPath {
33 path: str,
34}
35
36#[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}