pathlink/
path.rs

1//! A segmented [`Path`] safe to use as a filesystem [`std::path::Path`] or in a [`super::Link`].
2
3use std::ops::{Deref, DerefMut};
4use std::str::FromStr;
5use std::{fmt, iter};
6
7use get_size::GetSize;
8use smallvec::*;
9
10use super::{label, Id, Label, ParseError, Segments};
11
12/// A segment of a [`Path`]
13pub type PathSegment = Id;
14
15/// A constant representing a [`PathBuf`].
16pub struct PathLabel {
17    segments: &'static [&'static str],
18}
19
20impl PathLabel {
21    /// Get the number of segments in this path label.
22    pub fn len(&self) -> usize {
23        self.segments.len()
24    }
25}
26
27impl<Idx: std::slice::SliceIndex<[&'static str]>> std::ops::Index<Idx> for PathLabel {
28    type Output = Idx::Output;
29
30    fn index(&self, index: Idx) -> &Self::Output {
31        &self.segments[index]
32    }
33}
34
35/// Return a [`PathLabel`] with the given segments.
36pub const fn path_label(segments: &'static [&'static str]) -> PathLabel {
37    PathLabel { segments }
38}
39
40impl fmt::Display for PathLabel {
41    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42        f.write_str("/")?;
43
44        let mut segments = self.segments.iter();
45        let last = segments.next_back();
46
47        for id in segments {
48            f.write_str(id)?;
49            f.write_str("/")?;
50        }
51
52        if let Some(last) = last {
53            f.write_str(last)?;
54        }
55
56        Ok(())
57    }
58}
59
60impl From<PathLabel> for Id {
61    fn from(path: PathLabel) -> Self {
62        Label::from(path).into()
63    }
64}
65
66impl From<PathLabel> for Label {
67    fn from(path: PathLabel) -> Self {
68        match path.segments {
69            [id] => label(id),
70            _ => panic!("not an Id: {}", PathBuf::from(path)),
71        }
72    }
73}
74
75impl From<PathLabel> for PathBuf {
76    fn from(path: PathLabel) -> Self {
77        let segments = path
78            .segments
79            .into_iter()
80            .map(|segment| label(*segment))
81            .map(PathSegment::from)
82            .collect();
83
84        Self { segments }
85    }
86}
87
88impl<const N: usize> From<[PathSegment; N]> for PathBuf {
89    fn from(segments: [PathSegment; N]) -> Self {
90        Self {
91            segments: segments.into_iter().collect(),
92        }
93    }
94}
95
96/// A segmented link safe to use with a filesystem or via HTTP.
97pub struct Path<'a> {
98    inner: &'a [PathSegment],
99}
100
101impl Default for Path<'static> {
102    fn default() -> Self {
103        Self { inner: &[] }
104    }
105}
106
107impl<'a> Deref for Path<'a> {
108    type Target = [PathSegment];
109
110    fn deref(&self) -> &Self::Target {
111        &self.inner
112    }
113}
114
115impl<'a> From<&'a [PathSegment]> for Path<'a> {
116    fn from(inner: &'a [PathSegment]) -> Path<'a> {
117        Path { inner }
118    }
119}
120
121impl<'a, Idx: std::slice::SliceIndex<[PathSegment]>> std::ops::Index<Idx> for Path<'a> {
122    type Output = Idx::Output;
123
124    fn index(&self, index: Idx) -> &Self::Output {
125        &self.inner[index]
126    }
127}
128
129impl<'a> fmt::Debug for Path<'a> {
130    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131        fmt::Display::fmt(self, f)
132    }
133}
134
135impl<'a> fmt::Display for Path<'a> {
136    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
137        f.write_str("/")?;
138
139        for i in 0..self.len() {
140            write!(f, "{}", self[i])?;
141
142            if i < self.len() - 1 {
143                f.write_str("/")?;
144            }
145        }
146
147        Ok(())
148    }
149}
150
151/// A segmented link buffer safe to use with a filesystem or via HTTP.
152#[derive(Clone, Default, Hash, Eq, PartialEq)]
153pub struct PathBuf {
154    segments: Segments<PathSegment>,
155}
156
157impl PathBuf {
158    /// Construct a new, empty [`PathBuf`].
159    pub fn new() -> Self {
160        Self {
161            segments: Segments::new(),
162        }
163    }
164
165    /// Construct a new [`PathBuf`] by cloning the path segments in the given `slice`.
166    pub fn from_slice(segments: &[PathSegment]) -> Self {
167        Self {
168            segments: segments.into_iter().cloned().collect(),
169        }
170    }
171
172    /// Construct a new, empty [`PathBuf`] with the given `capacity`.
173    pub fn with_capacity(capacity: usize) -> Self {
174        Self {
175            segments: Segments::with_capacity(capacity),
176        }
177    }
178
179    /// Destructures this [`PathBuf`] into its underlying [`SmallVec`].
180    pub fn into_inner(self) -> Segments<PathSegment> {
181        self.segments
182    }
183
184    /// Appends `suffix` to this `PathBuf`.
185    pub fn append<S: Into<PathSegment>>(mut self, suffix: S) -> Self {
186        self.segments.push(suffix.into());
187        self
188    }
189
190    /// Remove and return the last segment in this path, if any.
191    pub fn pop(&mut self) -> Option<PathSegment> {
192        self.segments.pop()
193    }
194
195    /// If this path begins with the specified prefix, returns the suffix following the prefix.
196    pub fn suffix<'a>(&self, path: &'a [PathSegment]) -> Option<&'a [PathSegment]> {
197        if path.starts_with(&self.segments) {
198            Some(&path[self.segments.len()..])
199        } else {
200            None
201        }
202    }
203}
204
205impl GetSize for PathBuf {
206    fn get_size(&self) -> usize {
207        self.segments.iter().map(|segment| segment.get_size()).sum()
208    }
209}
210
211impl Extend<PathSegment> for PathBuf {
212    fn extend<T: IntoIterator<Item = PathSegment>>(&mut self, iter: T) {
213        self.segments.extend(iter)
214    }
215}
216
217impl<Idx: std::slice::SliceIndex<[PathSegment]>> std::ops::Index<Idx> for PathBuf {
218    type Output = Idx::Output;
219
220    fn index(&self, index: Idx) -> &Self::Output {
221        &self.segments[index]
222    }
223}
224
225#[cfg(feature = "hash")]
226impl<D: async_hash::Digest> async_hash::Hash<D> for PathBuf {
227    fn hash(self) -> async_hash::Output<D> {
228        async_hash::Hash::<D>::hash(&self)
229    }
230}
231
232#[cfg(feature = "hash")]
233impl<'a, D: async_hash::Digest> async_hash::Hash<D> for &'a PathBuf {
234    fn hash(self) -> async_hash::Output<D> {
235        if self == &PathBuf::default() {
236            return async_hash::default_hash::<D>();
237        } else {
238            async_hash::Hash::<D>::hash(self.to_string())
239        }
240    }
241}
242
243impl PartialEq<String> for PathBuf {
244    fn eq(&self, other: &String) -> bool {
245        self == other.as_str()
246    }
247}
248
249impl PartialEq<str> for PathBuf {
250    fn eq(&self, other: &str) -> bool {
251        if other.is_empty() {
252            return false;
253        } else if self.segments.is_empty() {
254            return other == "/";
255        }
256
257        let mut i = 0;
258        for segment in other.split('/') {
259            if i >= self.segments.len() {
260                return false;
261            } else if segment == self.segments[i] {
262                i += 1;
263            } else {
264                return false;
265            }
266        }
267
268        self.segments.len() == i
269    }
270}
271
272impl IntoIterator for PathBuf {
273    type Item = PathSegment;
274    type IntoIter = <Segments<PathSegment> as IntoIterator>::IntoIter;
275
276    fn into_iter(self) -> Self::IntoIter {
277        self.segments.into_iter()
278    }
279}
280
281impl std::borrow::Borrow<[PathSegment]> for PathBuf {
282    fn borrow(&self) -> &[PathSegment] {
283        &self.segments[..]
284    }
285}
286
287impl Deref for PathBuf {
288    type Target = [PathSegment];
289
290    fn deref(&self) -> &Self::Target {
291        &self.segments
292    }
293}
294
295impl DerefMut for PathBuf {
296    fn deref_mut(&mut self) -> &mut Self::Target {
297        &mut self.segments
298    }
299}
300
301impl PartialEq<[PathSegment]> for PathBuf {
302    fn eq(&self, other: &[PathSegment]) -> bool {
303        self.segments.as_slice() == other
304    }
305}
306
307impl From<PathSegment> for PathBuf {
308    fn from(segment: PathSegment) -> PathBuf {
309        PathBuf {
310            segments: iter::once(segment).collect(),
311        }
312    }
313}
314
315impl From<Label> for PathBuf {
316    fn from(segment: Label) -> PathBuf {
317        PathBuf {
318            segments: iter::once(segment.into()).collect(),
319        }
320    }
321}
322
323impl FromStr for PathBuf {
324    type Err = ParseError;
325
326    #[inline]
327    fn from_str(to: &str) -> Result<Self, Self::Err> {
328        if to == "/" {
329            Ok(PathBuf {
330                segments: smallvec![],
331            })
332        } else if to.ends_with('/') {
333            Err(format!("Path {} cannot end with a slash", to).into())
334        } else if to.starts_with('/') {
335            let segments = to
336                .split('/')
337                .skip(1)
338                .map(PathSegment::from_str)
339                .collect::<Result<Segments<PathSegment>, ParseError>>()?;
340
341            Ok(PathBuf { segments })
342        } else {
343            to.parse()
344                .map(|id| PathBuf {
345                    segments: iter::once(id).collect(),
346                })
347                .map_err(|cause| format!("invalid path: {}", cause).into())
348        }
349    }
350}
351
352impl From<Segments<PathSegment>> for PathBuf {
353    fn from(segments: Segments<PathSegment>) -> Self {
354        Self { segments }
355    }
356}
357
358impl iter::FromIterator<PathSegment> for PathBuf {
359    fn from_iter<T: IntoIterator<Item = PathSegment>>(iter: T) -> Self {
360        PathBuf {
361            segments: iter.into_iter().collect(),
362        }
363    }
364}
365
366impl fmt::Debug for PathBuf {
367    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
368        write!(f, "{}", Path::from(&self[..]))
369    }
370}
371
372impl fmt::Display for PathBuf {
373    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
374        write!(f, "{}", Path::from(&self[..]))
375    }
376}
377
378#[cfg(test)]
379mod test {
380    use super::*;
381
382    #[test]
383    fn test_path_label_to_string() {
384        let path = path_label(&[]);
385        assert_eq!(path.to_string(), "/".to_string());
386
387        let path = path_label(&["one"]);
388        assert_eq!(path.to_string(), "/one".to_string());
389
390        let path = path_label(&["one", "two"]);
391        assert_eq!(path.to_string(), "/one/two".to_string());
392    }
393}