s3_path/
lib.rs

1pub mod error;
2mod validation;
3
4use crate::error::InvalidS3PathComponent;
5use std::borrow::Cow;
6use std::fmt::Formatter;
7use std::ops::Deref;
8use std::path::PathBuf;
9
10/// Var-arg macro to create an `S3Path`, borrowing from the given string literals.
11///
12/// ```
13/// use s3_path::s3_path;
14///
15/// let path = s3_path!("foo", "bar").unwrap();
16/// ```
17#[macro_export]
18macro_rules! s3_path {
19    ($($component:expr),* $(,)?) => {{
20        let components = &[$(::std::borrow::Cow::Borrowed($component)),*];
21        $crate::S3Path::new(components)
22    }}
23}
24
25/// Var-arg macro to create an `S3Path` from individual components.
26///
27/// ```
28/// use std::borrow::Cow;
29/// use s3_path::s3_path_buf;
30///
31/// let path = s3_path_buf!("foo", String::from("bar"), Cow::Borrowed("baz")).unwrap();
32/// ```
33///
34/// Every component passed into this macro must either
35/// - be a `&'static str`
36/// - an owned `String`
37/// - or a `Cow<'static, str>`
38///
39/// str-slices of arbitrary lifetime cannot be used, as an `S3PathBuf` requires ownership,
40/// and this API enforces that any allocation, if needed, is performed at call-site.
41#[macro_export]
42macro_rules! s3_path_buf {
43    ($($component:expr),* $(,)?) => {{
44        #[allow(unused_mut)] // In case zero components are passed in.
45        let mut path = $crate::S3PathBuf::new();
46        #[allow(unused_mut)] // In case zero components are passed in.
47        let mut error = None;
48        $(
49            if error.is_none() {
50                match path.push($component) {
51                    Ok(_) => {},
52                    Err(e) => error = Some(e),
53                }
54            }
55        )*
56        match error {
57            Some(err) => Result::<$crate::S3PathBuf, $crate::error::InvalidS3PathComponent>::Err(err),
58            None => Result::<$crate::S3PathBuf, $crate::error::InvalidS3PathComponent>::Ok(path),
59        }
60    }}
61}
62
63/// A borrowed, unsized S3 storage path.
64///
65// Must be repr(transparent) to safely convert from the slice.
66#[repr(transparent)]
67#[derive(PartialEq, Eq)]
68pub struct S3Path<'i>([Cow<'i, str>]);
69
70/// An owned S3 storage path.
71#[derive(Clone, PartialEq, Eq, Default)]
72pub struct S3PathBuf {
73    components: Vec<Cow<'static, str>>,
74}
75
76/// Allow comparisons between `S3Path` and `S3PathBuf`.
77impl PartialEq<S3Path<'_>> for S3PathBuf {
78    fn eq(&self, other: &S3Path<'_>) -> bool {
79        self.as_path() == other
80    }
81}
82
83/// Allow comparisons between `S3Path` and `S3PathBuf`.
84impl<'i> PartialEq<&S3Path<'i>> for S3PathBuf {
85    fn eq(&self, other: &&S3Path<'i>) -> bool {
86        self.as_path() == *other
87    }
88}
89
90/// Allow comparisons between `S3Path` and `S3PathBuf`.
91impl<'i> PartialEq<&&S3Path<'i>> for S3PathBuf {
92    fn eq(&self, other: &&&S3Path<'i>) -> bool {
93        self.as_path() == **other
94    }
95}
96
97/// Allow comparisons between `S3Path` and `S3PathBuf`.
98impl PartialEq<S3PathBuf> for S3Path<'_> {
99    fn eq(&self, other: &S3PathBuf) -> bool {
100        self == other.as_path()
101    }
102}
103
104/// Allow comparisons between `S3Path` and `S3PathBuf`.
105impl PartialEq<S3PathBuf> for &S3Path<'_> {
106    fn eq(&self, other: &S3PathBuf) -> bool {
107        *self == other.as_path()
108    }
109}
110
111/// Allow comparisons between `S3Path` and `S3PathBuf`.
112impl PartialEq<S3PathBuf> for &&S3Path<'_> {
113    fn eq(&self, other: &S3PathBuf) -> bool {
114        **self == other.as_path()
115    }
116}
117
118impl<'i> AsRef<S3Path<'i>> for S3Path<'i> {
119    fn as_ref(&self) -> &S3Path<'i> {
120        self
121    }
122}
123
124impl<'i> AsRef<S3Path<'i>> for S3PathBuf {
125    fn as_ref(&self) -> &S3Path<'i> {
126        self
127    }
128}
129
130impl AsRef<S3PathBuf> for S3PathBuf {
131    fn as_ref(&self) -> &S3PathBuf {
132        self
133    }
134}
135
136fn write_components<C: AsRef<str>>(
137    components: impl Iterator<Item = C>,
138    f: &mut Formatter,
139) -> Result<(), std::fmt::Error> {
140    for (i, c) in components.enumerate() {
141        if i > 0 {
142            f.write_str("/")?;
143        }
144        f.write_str(c.as_ref())?;
145    }
146    Ok(())
147}
148
149impl std::fmt::Display for S3Path<'_> {
150    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
151        write_components(self.0.iter(), f)?;
152        Ok(())
153    }
154}
155
156impl std::fmt::Debug for S3Path<'_> {
157    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
158        write_components(self.0.iter(), f)?;
159        Ok(())
160    }
161}
162
163impl std::fmt::Display for S3PathBuf {
164    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
165        write_components(self.components.iter(), f)?;
166        Ok(())
167    }
168}
169
170impl std::fmt::Debug for S3PathBuf {
171    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
172        write_components(self.components.iter(), f)?;
173        Ok(())
174    }
175}
176
177impl<'i> S3Path<'i> {
178    /// Create a new `S3Path` from a slice of static `components`.
179    ///
180    /// # Errors
181    ///
182    /// Returns `Err` when any given `component`
183    /// - is empty
184    /// - contains characters other than: ascii alphanumeric characters, '-', '_' and '.'
185    /// - is equal to `.` or `..`
186    pub fn new(components: &'i [Cow<'i, str>]) -> Result<&'i S3Path<'i>, InvalidS3PathComponent> {
187        for component in components {
188            validation::validate_component(component)?;
189        }
190        // Safety: S3Path is repr(transparent) over [Cow<'i, str>].
191        Ok(unsafe { &*(std::ptr::from_ref::<[Cow<'i, str>]>(components) as *const S3Path<'i>) })
192    }
193
194    /// Converts to an owned `S3PathBuf`.
195    #[must_use]
196    pub fn to_owned(&'i self) -> S3PathBuf {
197        S3PathBuf {
198            components: self.0.iter().map(|it| Cow::Owned(it.to_string())).collect(),
199        }
200    }
201
202    /// Converts to an owned `S3PathBuf` and appends `component` to it after validating it.
203    ///
204    /// # Errors
205    ///
206    /// Returns `Err` when the given `component`
207    /// - is empty
208    /// - contains characters other than: ascii alphanumeric characters, '-', '_' and '.'
209    /// - is equal to `.` or `..`
210    pub fn join<C: Into<Cow<'static, str>>>(
211        &self,
212        component: C,
213    ) -> Result<S3PathBuf, InvalidS3PathComponent> {
214        let mut path = self.to_owned();
215        path.push(component)?;
216        Ok(path)
217    }
218
219    /// Returns true if this path has no components.
220    #[must_use]
221    pub fn is_empty(&'i self) -> bool {
222        self.0.is_empty()
223    }
224
225    /// Returns the number of components in this path.
226    #[must_use]
227    pub fn len(&'i self) -> usize {
228        self.0.len()
229    }
230
231    /// Returns an iterator over the components of this path.
232    pub fn components(&'i self) -> impl Iterator<Item = &'i str> {
233        self.0.iter().map(std::convert::AsRef::as_ref)
234    }
235
236    /// Returns the component at the given index, or None if the index is out of bounds.
237    pub fn get(&'i self, index: usize) -> Option<&'i str> {
238        self.0.get(index).map(std::convert::AsRef::as_ref)
239    }
240
241    /// Returns the last component of this path, or None if the path is empty.
242    pub fn last(&'i self) -> Option<&'i str> {
243        self.0.last().map(std::convert::AsRef::as_ref)
244    }
245
246    /// Returns all but the last component of this path, or None if the path is empty.
247    #[must_use]
248    pub fn parent(&'i self) -> Option<&'i S3Path<'i>> {
249        if self.0.is_empty() {
250            None
251        } else {
252            let parent_slice = &self.0[..self.0.len() - 1];
253            Some(
254                // Safety: S3Path is repr(transparent) over [Cow<'i, str>]
255                unsafe {
256                    &*(std::ptr::from_ref::<[Cow<'i, str>]>(parent_slice) as *const S3Path<'i>)
257                },
258            )
259        }
260    }
261
262    /// Convert this S3 path to a `std::path::PathBuf`, allowing you to use this S3 path as a
263    /// system file path.
264    ///
265    /// Our strong guarantee that path components only consist of ascii alphanumeric characters,
266    /// '-', '_' and '.' and that no path traversal components ('.' and '..') are allowed, makes
267    /// this a safe operation.
268    #[must_use]
269    pub fn to_std_path_buf(&self) -> PathBuf {
270        let mut path = PathBuf::new();
271        for c in &self.0 {
272            path.push(c.as_ref());
273        }
274        path
275    }
276}
277
278// Deref - NameBuf can be automatically converted to &Name<'static>
279impl Deref for S3PathBuf {
280    type Target = S3Path<'static>;
281
282    fn deref(&self) -> &Self::Target {
283        // Safety: This is safe because Name is repr(transparent) over [Cow<'i, str>] and we
284        // store [Cow<'static>, str>], with 'static outliving any lifetime 'i.
285        unsafe {
286            &*(std::ptr::from_ref::<[Cow<'static, str>]>(self.components.as_slice())
287                as *const S3Path<'static>)
288        }
289    }
290}
291
292impl S3PathBuf {
293    /// Creates an empty S3 path. Call `join` or `push` to extend it with additional path segments.
294    #[must_use]
295    pub fn new() -> Self {
296        Self::default()
297    }
298
299    /// Validates and adds all `components` to the returned `S3PathBuf`.
300    ///
301    /// NO component is parsed for slashes ('/') to be split up further!
302    ///
303    /// # Errors
304    ///
305    /// Returns `Err` when any given component
306    /// - is empty
307    /// - contains characters other than: ascii alphanumeric characters, '-', '_' and '.'
308    /// - is equal to `.` or `..`
309    pub fn try_from<C: Into<Cow<'static, str>>, I: IntoIterator<Item = C>>(
310        components: I,
311    ) -> Result<Self, InvalidS3PathComponent> {
312        let mut path = S3PathBuf::new();
313        for component in components {
314            path.push(component)?;
315        }
316        Ok(path)
317    }
318
319    /// Splits `string` at each occurrence of a `/`, then validates and add all components to the
320    /// returned `S3PathBuf`.
321    ///
322    /// Multiple consecutive slashes, as in "foo//bar", are treated as one.
323    ///
324    /// # Errors
325    ///
326    /// Returns `Err` when any component read
327    /// - contains characters other than: ascii alphanumeric characters, '-', '_' and '.'
328    /// - is equal to `.` or `..`
329    pub fn try_from_str(string: impl AsRef<str>) -> Result<Self, InvalidS3PathComponent> {
330        let mut path = S3PathBuf::new();
331        for c in string.as_ref().split('/') {
332            // Skip empty components from consecutive slashes
333            if !c.is_empty() {
334                path.push(Cow::Owned(c.to_string()))?;
335            }
336        }
337        Ok(path)
338    }
339
340    /// Adds `component` to the path after validating it.
341    ///
342    /// # Errors
343    ///
344    /// Returns `Err` when the given component
345    /// - is empty
346    /// - contains characters other than: ascii alphanumeric characters, '-', '_' and '.'
347    /// - is equal to `.` or `..`
348    pub fn push(
349        &mut self,
350        component: impl Into<Cow<'static, str>>,
351    ) -> Result<&mut Self, InvalidS3PathComponent> {
352        let comp = component.into();
353        validation::validate_component(&comp)?;
354        self.components.push(comp);
355        Ok(self)
356    }
357
358    /// Extend this path with a new segment in a new object.
359    pub fn join(
360        &self,
361        component: impl Into<Cow<'static, str>>,
362    ) -> Result<Self, InvalidS3PathComponent> {
363        let mut clone = self.clone();
364        clone.push(component)?;
365        Ok(clone)
366    }
367
368    #[must_use]
369    #[inline]
370    pub fn as_path(&self) -> &S3Path<'_> {
371        self
372    }
373
374    /// Pop the last component from this path, returning true if a component was removed
375    pub fn pop(&mut self) -> Option<Cow<'static, str>> {
376        self.components.pop()
377    }
378}
379
380#[cfg(test)]
381impl assertr::assertions::HasLength for S3PathBuf {
382    fn length(&self) -> usize {
383        self.len()
384    }
385}
386
387#[cfg(test)]
388mod test {
389    use crate::S3PathBuf;
390    use assertr::prelude::*;
391
392    mod s3_path_buf {
393        use crate::S3PathBuf;
394        use assertr::prelude::*;
395
396        #[test]
397        fn new_is_initially_empty() {
398            let path = S3PathBuf::new();
399            assert_that(path).has_display_value("");
400        }
401
402        #[test]
403        fn construct_using_new_and_push_components() {
404            let mut path = S3PathBuf::new();
405            path.push("foo").unwrap();
406            path.push("bar").unwrap();
407            assert_that(path).has_display_value("foo/bar");
408        }
409
410        #[test]
411        fn try_from_str_parses_empty_string_as_empty_path() {
412            let path = S3PathBuf::try_from_str("").unwrap();
413            assert_that(path).has_display_value("");
414        }
415
416        #[test]
417        fn try_from_str_parses_single_slash_as_empty_path() {
418            let path = S3PathBuf::try_from_str("/").unwrap();
419            assert_that(path).has_display_value("");
420        }
421
422        #[test]
423        fn try_from_str_removes_leading_and_trailing_slashes() {
424            let path = S3PathBuf::try_from_str("/foo/bar/").unwrap();
425            assert_that(path).has_display_value("foo/bar");
426        }
427
428        #[test]
429        fn try_from_str_ignores_repeated_slashes() {
430            let path = S3PathBuf::try_from_str("foo/////bar").unwrap();
431            assert_that(path).has_display_value("foo/bar");
432        }
433
434        #[test]
435        fn construct_using_try_from_given_str() {
436            let path = S3PathBuf::try_from_str("foo/bar").unwrap();
437            assert_that(path).has_display_value("foo/bar");
438        }
439
440        #[test]
441        fn construct_using_try_from_given_string() {
442            let path = S3PathBuf::try_from_str(String::from("foo/bar")).unwrap();
443            assert_that(path).has_display_value("foo/bar");
444        }
445
446        #[test]
447        fn try_from_static_str_array() {
448            let path = S3PathBuf::try_from(["foo", "bar"]).unwrap();
449            assert_that(path).has_display_value("foo/bar");
450        }
451
452        #[test]
453        fn try_from_string_array() {
454            let path = S3PathBuf::try_from(["foo".to_string(), "bar".to_string()]).unwrap();
455            assert_that(path).has_display_value("foo/bar");
456        }
457
458        #[test]
459        fn reject_invalid_characters() {
460            let mut path = S3PathBuf::new();
461            let result = path.push("invalid/path");
462            assert_that(result).is_err();
463
464            let result = S3PathBuf::try_from_str("foo/bar$baz");
465            assert_that(result).is_err();
466        }
467
468        #[test]
469        fn push_mutates_original() {
470            let mut foo = S3PathBuf::try_from_str("foo").unwrap();
471            let foo_bar = foo.push("bar").unwrap();
472
473            assert_that(foo_bar).has_display_value("foo/bar");
474            assert_that(foo).has_display_value("foo/bar");
475        }
476
477        #[test]
478        fn join_creates_clone() {
479            let foo = S3PathBuf::try_from_str("foo").unwrap();
480            let foo_bar = foo.join("bar").unwrap();
481
482            assert_that(foo).has_display_value("foo");
483            assert_that(foo_bar).has_display_value("foo/bar");
484        }
485
486        #[test]
487        fn as_path() {
488            let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
489            let path = path_buf.as_path();
490            assert_that(path).has_display_value("foo/bar");
491            let cloned = path.to_owned();
492            drop(path_buf);
493            assert_that(cloned).has_display_value("foo/bar");
494        }
495
496        #[test] // Function `is_empty` inherited through deref to S3Path!
497        fn is_empty_returns_true_when_path_has_no_components() {
498            let path_buf = S3PathBuf::new();
499            assert_that(path_buf.is_empty()).is_true();
500        }
501
502        #[test] // Function `is_empty` inherited through deref to S3Path!
503        fn is_empty_returns_false_when_path_has_components() {
504            let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
505            assert_that(path_buf.is_empty()).is_false();
506        }
507
508        #[test] // Function `len` inherited through deref to S3Path!
509        fn len_returns_number_of_components() {
510            let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
511            assert_that(path_buf.len()).is_equal_to(2);
512        }
513
514        #[test] // Function `components` inherited through deref to S3Path!
515        fn components_iterates_over_all_components() {
516            let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
517            assert_that(path_buf.components()).contains_exactly(["foo", "bar"]);
518        }
519
520        #[test] // Function `get` inherited through deref to S3Path!
521        fn get_returns_component_at_index() {
522            let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
523            assert_that(path_buf.get(1)).is_some().is_equal_to("bar");
524        }
525
526        #[test] // Function `last` inherited through deref to S3Path!
527        fn last_returns_last_component() {
528            let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
529            assert_that(path_buf.last()).is_some().is_equal_to("bar");
530        }
531
532        #[test] // Function `parent` inherited through deref to S3Path!
533        fn parent_returns_none_when_path_has_no_components() {
534            let path_buf = S3PathBuf::new();
535            assert_that(path_buf.parent()).is_none();
536        }
537
538        #[test] // Function `parent` inherited through deref to S3Path!
539        fn parent_returns_empty_component_when_path_has_only_one_component() {
540            let path_buf = S3PathBuf::try_from(["foo"]).unwrap();
541            assert_that(path_buf.parent())
542                .is_some()
543                .has_display_value("");
544        }
545
546        #[test] // Function `parent` inherited through deref to S3Path!
547        fn parent_returns_view_of_parent_path_when_path_has_multiple_components() {
548            let path_buf = S3PathBuf::try_from(["foo", "bar", "baz"]).unwrap();
549            assert_that(path_buf.parent())
550                .is_some()
551                .has_display_value("foo/bar");
552        }
553
554        #[test] // Function `to_std_path_buf` inherited through deref to S3Path!
555        fn to_std_path_buf_returns_empty_path_buf_when_s3_path_has_zero_components() {
556            let path_buf = S3PathBuf::new();
557            assert_that(path_buf.to_std_path_buf().display()).has_display_value("");
558        }
559
560        #[test] // Function `to_std_path_buf` inherited through deref to S3Path!
561        fn to_std_path_buf_joins_components_with_path_separator_does_not_add_slashes() {
562            let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
563            assert_that(path_buf.to_std_path_buf().display()).has_display_value("foo/bar");
564        }
565
566        mod s3_path_buf_macro {
567            use assertr::prelude::*;
568            use std::borrow::Cow;
569
570            #[test]
571            fn taking_nothing() {
572                let path = s3_path_buf!().unwrap();
573                assert_that(path).has_display_value("");
574            }
575
576            #[test]
577            fn taking_single_static_str() {
578                let path = s3_path_buf!("foo").unwrap();
579                assert_that(path).has_display_value("foo");
580            }
581
582            #[test]
583            fn taking_owned_string_and_static_str() {
584                let foo = String::from("foo");
585                let path = s3_path_buf!(foo, "bar").unwrap();
586                assert_that(path).has_display_value("foo/bar");
587            }
588
589            #[test]
590            fn taking_owned_string_and_static_str_and_cow() {
591                let foo = String::from("foo");
592                let path = s3_path_buf!(foo, "bar", Cow::Borrowed("baz")).unwrap();
593                assert_that(path).has_display_value("foo/bar/baz");
594            }
595
596            #[test]
597            fn allows_a_trailing_comma() {
598                let path = s3_path_buf!("foo",).unwrap();
599                assert_that(path).has_display_value("foo");
600            }
601        }
602    }
603
604    mod s3_path {
605        use crate::S3Path;
606        use assertr::prelude::*;
607        use std::borrow::Cow;
608
609        #[test]
610        fn new_is_initially_empty() {
611            let path = S3Path::new(&[]).unwrap();
612            assert_that(path).has_display_value("");
613        }
614
615        #[test]
616        fn construct_using_new() {
617            let path = S3Path::new(&[Cow::Borrowed("foo"), Cow::Borrowed("bar")]).unwrap();
618            assert_that(path).has_display_value("foo/bar");
619        }
620
621        #[test]
622        fn to_owned_converts_to_owned_path() {
623            let path = s3_path!("foo", "bar").unwrap();
624            let path_owned = path.to_owned();
625            assert_that(path_owned).has_display_value("foo/bar");
626        }
627
628        #[test]
629        fn join_converts_to_owned_path_and_appends_component() {
630            let path = s3_path!("foo", "bar").unwrap();
631            let path_owned = path.join("baz").unwrap();
632            assert_that(path_owned).has_display_value("foo/bar/baz");
633        }
634
635        mod s3_path_macro {
636            use assertr::prelude::*;
637
638            #[test]
639            fn handles_zero_components() {
640                let path = s3_path!().unwrap();
641                assert_that(path).has_display_value("");
642            }
643
644            #[test]
645            fn handles_one_component() {
646                let path = s3_path!("foo").unwrap();
647                assert_that(path).has_display_value("foo");
648            }
649
650            #[test]
651            fn handles_multiple_components() {
652                let path = s3_path!("foo", "bar").unwrap();
653                assert_that(path).has_display_value("foo/bar");
654            }
655
656            #[test]
657            fn allows_a_trailing_comma() {
658                let path = s3_path!("foo",).unwrap();
659                assert_that(path).has_display_value("foo");
660            }
661        }
662    }
663
664    mod validation {
665        use crate::S3PathBuf;
666        use assertr::prelude::*;
667
668        #[test]
669        fn reject_invalid_characters() {
670            assert_that(S3PathBuf::try_from(["foo-bar"]))
671                .is_ok()
672                .has_display_value("foo-bar");
673            assert_that(S3PathBuf::try_from(["foo_bar"]))
674                .is_ok()
675                .has_display_value("foo_bar");
676            assert_that(S3PathBuf::try_from(["foo.bar"]))
677                .is_ok()
678                .has_display_value("foo.bar");
679            assert_that(S3PathBuf::try_from([".test"]))
680                .is_ok()
681                .has_display_value(".test");
682            assert_that(S3PathBuf::try_from(["..test"]))
683                .is_ok()
684                .has_display_value("..test");
685
686            assert_that(S3PathBuf::try_from(["foo:bar"])).is_err();
687            assert_that(S3PathBuf::try_from(["foo;bar"])).is_err();
688            assert_that(S3PathBuf::try_from(["foo$bar"])).is_err();
689            assert_that(S3PathBuf::try_from(["foo&bar"])).is_err();
690            assert_that(S3PathBuf::try_from(["foo#bar"])).is_err();
691            assert_that(S3PathBuf::try_from(["foo/bar"])).is_err();
692            assert_that(S3PathBuf::try_from(["foo|bar"])).is_err();
693            assert_that(S3PathBuf::try_from(["foo\\bar"])).is_err();
694            assert_that(S3PathBuf::try_from(["."])).is_err();
695            assert_that(S3PathBuf::try_from([".."])).is_err();
696        }
697    }
698
699    mod take_any_path {
700        use crate::{S3Path, S3PathBuf};
701
702        fn take_any_path<'p>(path: impl AsRef<S3Path<'p>>) {
703            let _path: &S3Path<'p> = path.as_ref();
704        }
705
706        #[test]
707        fn takes_owned_s3_path_buf() {
708            take_any_path(S3PathBuf::new());
709        }
710
711        #[test]
712        fn takes_borrowed_s3_path_buf() {
713            take_any_path(&S3PathBuf::new());
714        }
715
716        #[test]
717        fn takes_s3_path() {
718            take_any_path(S3Path::new(&[]).unwrap());
719        }
720    }
721
722    #[test]
723    #[allow(clippy::op_ref)]
724    fn comparison_between_types() {
725        let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
726        let path = path_buf.as_path();
727
728        assert_that(path == path).is_true();
729        assert_that(path_buf == path_buf).is_true();
730
731        assert_that(path == path_buf).is_true();
732        assert_that(path == &path_buf).is_true();
733        assert_that(&path == path_buf).is_true();
734        assert_that(&path == &path_buf).is_true();
735
736        assert_that(path_buf == path).is_true();
737        assert_that(path_buf == &path).is_true();
738        assert_that(&path_buf == path).is_true();
739        assert_that(&path_buf == &path).is_true();
740
741        // Also works with assertr.
742        assert_that(&path_buf).is_equal_to(path);
743        assert_that(s3_path_buf!("foo", "bar"))
744            .is_ok()
745            .is_equal_to(path);
746    }
747}