1use crate::ImportableName;
2use hydrate_schema::{DataSetError, DataSetResult};
3use siphasher::sip128::Hasher128;
4use std::fmt::{Display, Formatter};
5use std::hash::Hash;
6use std::path::{Path, PathBuf};
7use uuid::Uuid;
8
9pub trait PathReferenceNamespaceResolver {
10 fn namespace_root(
12 &self,
13 namespace: &str,
14 ) -> Option<PathBuf>;
15
16 fn simplify_path(
18 &self,
19 path: &Path,
20 ) -> Option<(String, PathBuf)>;
21}
22
23pub fn canonicalized_absolute_path(
24 namespace: &String,
25 referenced_path: &String,
26 importable_name: &ImportableName,
27 namespace_resolver: &dyn PathReferenceNamespaceResolver,
28 source_file_path: &Path,
29) -> DataSetResult<PathReference> {
30 let canonical_absolute_path = if namespace.is_empty() {
31 if Path::new(referenced_path).is_relative() {
32 dunce::canonicalize(
33 source_file_path
34 .parent()
35 .unwrap()
36 .join(Path::new(referenced_path))
37 .as_path(),
38 )
39 .map_err(|_| DataSetError::InvalidPath)?
40 } else {
41 dunce::canonicalize(PathBuf::from(referenced_path))
42 .map_err(|_| DataSetError::InvalidPath)?
43 }
44 } else {
45 let namespace_root = namespace_resolver
46 .namespace_root(namespace)
47 .ok_or(DataSetError::UnknownPathNamespace)?;
48 dunce::canonicalize(namespace_root.join(referenced_path))
49 .map_err(|_| DataSetError::InvalidPath)?
50 };
51
52 Ok(PathReference {
53 namespace: "".to_string(),
54 path: canonical_absolute_path.to_string_lossy().to_string(),
55 importable_name: importable_name.clone(),
56 })
57}
58
59#[derive(Clone, Debug, PartialEq, Eq, Hash)]
62pub struct CanonicalPathReference {
63 namespace: String,
64 path: String,
65 importable_name: ImportableName,
66}
67
68impl Display for CanonicalPathReference {
69 fn fmt(
70 &self,
71 f: &mut Formatter<'_>,
72 ) -> std::fmt::Result {
73 if self.namespace.is_empty() {
74 if let Some(importable_name) = self.importable_name.name() {
75 write!(f, "{}#{}", self.path, importable_name)
76 } else {
77 write!(f, "{}", self.path)
78 }
79 } else {
80 if let Some(importable_name) = self.importable_name.name() {
81 write!(f, "{}://{}#{}", self.namespace, self.path, importable_name)
82 } else {
83 write!(f, "{}://{}", self.namespace, self.path)
84 }
85 }
86 }
87}
88
89impl CanonicalPathReference {
90 pub fn new(
91 namespace_resolver: &dyn PathReferenceNamespaceResolver,
92 namespace: String,
93 path: String,
94 importable_name: ImportableName,
95 ) -> Self {
96 PathReference {
97 namespace,
98 path,
99 importable_name,
100 }
101 .simplify(namespace_resolver)
102 }
103
104 pub fn namespace(&self) -> &str {
105 &self.namespace
106 }
107
108 pub fn path(&self) -> &str {
109 &self.path
110 }
111
112 pub fn importable_name(&self) -> &ImportableName {
113 &self.importable_name
114 }
115
116 pub fn canonicalized_absolute_path(
117 &self,
118 namespace_resolver: &dyn PathReferenceNamespaceResolver,
119 source_file_path: &Path,
120 ) -> DataSetResult<PathReference> {
121 canonicalized_absolute_path(
122 &self.namespace,
123 &self.path,
124 &self.importable_name,
125 namespace_resolver,
126 source_file_path,
127 )
128 }
129}
130
131#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
134pub struct PathReferenceHash(pub Uuid);
135
136#[derive(Debug, Clone)]
139pub struct PathReference {
140 namespace: String,
141 path: String,
142 importable_name: ImportableName,
143}
144
145impl Display for PathReference {
146 fn fmt(
147 &self,
148 f: &mut Formatter<'_>,
149 ) -> std::fmt::Result {
150 if self.namespace.is_empty() {
151 if let Some(importable_name) = self.importable_name.name() {
152 write!(f, "{}#{}", self.path, importable_name)
153 } else {
154 write!(f, "{}", self.path)
155 }
156 } else {
157 if let Some(importable_name) = self.importable_name.name() {
158 write!(f, "{}://{}#{}", self.namespace, self.path, importable_name)
159 } else {
160 write!(f, "{}://{}", self.namespace, self.path)
161 }
162 }
163 }
164}
165
166impl PathReference {
167 pub fn new(
168 namespace: String,
169 path: String,
170 importable_name: ImportableName,
171 ) -> Self {
172 PathReference {
173 namespace,
174 path,
175 importable_name,
176 }
177 }
178
179 pub fn path_reference_hash(&self) -> PathReferenceHash {
180 let mut hasher = siphasher::sip128::SipHasher::default();
181 self.to_string().hash(&mut hasher);
182 let path_reference_hash = Uuid::from_u128(hasher.finish128().as_u128());
183 PathReferenceHash(path_reference_hash)
184 }
185
186 pub fn namespace(&self) -> &str {
187 &self.namespace
188 }
189
190 pub fn path(&self) -> &str {
191 &self.path
192 }
193
194 pub fn importable_name(&self) -> &ImportableName {
195 &self.importable_name
196 }
197
198 pub fn canonicalized_absolute_path(
199 &self,
200 namespace_resolver: &dyn PathReferenceNamespaceResolver,
201 source_file_path: &Path,
202 ) -> DataSetResult<PathReference> {
203 canonicalized_absolute_path(
204 &self.namespace,
205 &self.path,
206 &self.importable_name,
207 namespace_resolver,
208 source_file_path,
209 )
210 }
211
212 pub fn simplify(
213 self,
214 namespace_resolver: &dyn PathReferenceNamespaceResolver,
215 ) -> CanonicalPathReference {
216 if !self.namespace.is_empty() {
217 } else if Path::new(&self.path).is_relative() {
219 } else {
221 let canonicalized_path = dunce::canonicalize(PathBuf::from(&self.path)).unwrap();
224
225 if let Some((namespace, prefix)) = namespace_resolver.simplify_path(&canonicalized_path)
226 {
227 return CanonicalPathReference {
228 namespace,
229 path: prefix.to_string_lossy().to_string(),
230 importable_name: self.importable_name,
231 };
232 }
233 }
234
235 CanonicalPathReference {
236 namespace: self.namespace,
237 path: self.path,
238 importable_name: self.importable_name,
239 }
240 }
241}
242
243impl From<&str> for PathReference {
244 fn from(s: &str) -> PathReference {
245 let namespace_delimeter_position = s.rfind("://");
246 let importable_name_delimeter_position = s.rfind('#');
247
248 let (path_start_position, namespace) =
249 if let Some(namespace_delimeter_position) = namespace_delimeter_position {
250 (
251 namespace_delimeter_position + 3,
252 s[0..namespace_delimeter_position].to_string(),
253 )
254 } else {
255 (0, String::default())
256 };
257
258 let (path, importable_name) =
259 if let Some(importable_name_delimeter_position) = importable_name_delimeter_position {
260 let path = s[path_start_position..importable_name_delimeter_position].to_string();
261 let importable_name = &s[importable_name_delimeter_position + 1..];
262 let importable_name = if !importable_name.is_empty() {
263 ImportableName::new(importable_name.to_string())
264 } else {
265 ImportableName::default()
266 };
267 (path, importable_name)
268 } else {
269 (
270 s[path_start_position..].to_string(),
271 ImportableName::default(),
272 )
273 };
274
275 PathReference {
276 namespace,
277 path,
278 importable_name,
279 }
280 }
281}
282
283impl From<String> for PathReference {
284 fn from(path: String) -> PathReference {
285 let str: &str = &path;
286 PathReference::from(str)
287 }
288}
289
290impl From<&String> for PathReference {
291 fn from(path: &String) -> PathReference {
292 let str: &str = &path;
293 PathReference::from(str)
294 }
295}
296
297impl From<&Path> for PathReference {
298 fn from(path: &Path) -> PathReference {
299 let str: &str = path.to_str().unwrap();
300 PathReference::from(str)
301 }
302}
303
304impl From<&PathBuf> for PathReference {
305 fn from(path: &PathBuf) -> PathReference {
306 let str: &str = path.to_str().unwrap();
307 PathReference::from(str)
308 }
309}
310
311impl From<PathBuf> for PathReference {
312 fn from(path: PathBuf) -> PathReference {
313 let str: &str = path.to_str().unwrap();
314 PathReference::from(str)
315 }
316}