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