hydrate_data/
path_reference.rs

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    // Given the namespace, return the path associated with it
11    fn namespace_root(
12        &self,
13        namespace: &str,
14    ) -> Option<PathBuf>;
15
16    // Given the canonicalized absolute path, if it can be expressed as a namespace and path within the namepsace, return that
17    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// Given any path, parsed as a PathReference, the same CanonicalPathReference will be produced, and it is comparable,
60// hashable, etc.
61#[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// A hash of a potentially non-canonical path. Be very careful how these are used to check equality. It should really
132// only be done to look up a path identified during a scan/import.
133#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
134pub struct PathReferenceHash(pub Uuid);
135
136// This path reference is good for parsing from string and representing a path other than the canonical path reference
137// (i.e. an absolute path when it could be represented relative to a namespace.)
138#[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            // If it has a namespace it can't be simplified
218        } else if Path::new(&self.path).is_relative() {
219            // If it's a relative path it can't be simplified
220        } else {
221            // If it's an absolute path, see if it is in a namespace, if it is, we can return a PathReference relative
222            // to the namespace
223            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}