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.join($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.join(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 = path.join(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` 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.join(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.join(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 join(
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    #[must_use]
359    #[inline]
360    pub fn as_path(&self) -> &S3Path<'_> {
361        self
362    }
363
364    /// Pop the last component from this path, returning true if a component was removed
365    pub fn pop(&mut self) -> Option<Cow<'static, str>> {
366        self.components.pop()
367    }
368}
369
370#[cfg(test)]
371impl assertr::assertions::HasLength for S3PathBuf {
372    fn length(&self) -> usize {
373        self.len()
374    }
375}
376
377#[cfg(test)]
378mod test {
379    use crate::S3PathBuf;
380    use assertr::prelude::*;
381
382    mod s3_path_buf {
383        use crate::S3PathBuf;
384        use assertr::prelude::*;
385
386        #[test]
387        fn new_is_initially_empty() {
388            let path = S3PathBuf::new();
389            assert_that(path).has_display_value("");
390        }
391
392        #[test]
393        fn construct_using_new_and_join_components() {
394            let mut path = S3PathBuf::new();
395            path.join("foo").unwrap();
396            path.join("bar").unwrap();
397            assert_that(path).has_display_value("foo/bar");
398        }
399
400        #[test]
401        fn try_from_str_parses_empty_string_as_empty_path() {
402            let path = S3PathBuf::try_from_str("").unwrap();
403            assert_that(path).has_display_value("");
404        }
405
406        #[test]
407        fn try_from_str_parses_single_slash_as_empty_path() {
408            let path = S3PathBuf::try_from_str("/").unwrap();
409            assert_that(path).has_display_value("");
410        }
411
412        #[test]
413        fn try_from_str_removes_leading_and_trailing_slashes() {
414            let path = S3PathBuf::try_from_str("/foo/bar/").unwrap();
415            assert_that(path).has_display_value("foo/bar");
416        }
417
418        #[test]
419        fn try_from_str_ignores_repeated_slashes() {
420            let path = S3PathBuf::try_from_str("foo/////bar").unwrap();
421            assert_that(path).has_display_value("foo/bar");
422        }
423
424        #[test]
425        fn construct_using_try_from_given_str() {
426            let path = S3PathBuf::try_from_str("foo/bar").unwrap();
427            assert_that(path).has_display_value("foo/bar");
428        }
429
430        #[test]
431        fn construct_using_try_from_given_string() {
432            let path = S3PathBuf::try_from_str(String::from("foo/bar")).unwrap();
433            assert_that(path).has_display_value("foo/bar");
434        }
435
436        #[test]
437        fn try_from_static_str_array() {
438            let path = S3PathBuf::try_from(["foo", "bar"]).unwrap();
439            assert_that(path).has_display_value("foo/bar");
440        }
441
442        #[test]
443        fn try_from_string_array() {
444            let path = S3PathBuf::try_from(["foo".to_string(), "bar".to_string()]).unwrap();
445            assert_that(path).has_display_value("foo/bar");
446        }
447
448        #[test]
449        fn reject_invalid_characters() {
450            let mut path = S3PathBuf::new();
451            let result = path.join("invalid/path");
452            assert_that(result.is_err()).is_true();
453
454            let result = S3PathBuf::try_from_str("foo/bar$baz");
455            assert_that(result.is_err()).is_true();
456        }
457
458        #[test]
459        fn as_path() {
460            let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
461            let path = path_buf.as_path();
462            assert_that(path).has_display_value("foo/bar");
463            let cloned = path.to_owned();
464            drop(path_buf);
465            assert_that(cloned).has_display_value("foo/bar");
466        }
467
468        #[test] // Function `is_empty` inherited through deref to S3Path!
469        fn is_empty_returns_true_when_path_has_no_components() {
470            let path_buf = S3PathBuf::new();
471            assert_that(path_buf.is_empty()).is_true();
472        }
473
474        #[test] // Function `is_empty` inherited through deref to S3Path!
475        fn is_empty_returns_false_when_path_has_components() {
476            let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
477            assert_that(path_buf.is_empty()).is_false();
478        }
479
480        #[test] // Function `len` inherited through deref to S3Path!
481        fn len_returns_number_of_components() {
482            let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
483            assert_that(path_buf.len()).is_equal_to(2);
484        }
485
486        #[test] // Function `components` inherited through deref to S3Path!
487        fn components_iterates_over_all_components() {
488            let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
489            assert_that(path_buf.components()).contains_exactly(["foo", "bar"]);
490        }
491
492        #[test] // Function `get` inherited through deref to S3Path!
493        fn get_returns_component_at_index() {
494            let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
495            assert_that(path_buf.get(1)).is_some().is_equal_to("bar");
496        }
497
498        #[test] // Function `last` inherited through deref to S3Path!
499        fn last_returns_last_component() {
500            let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
501            assert_that(path_buf.last()).is_some().is_equal_to("bar");
502        }
503
504        #[test] // Function `parent` inherited through deref to S3Path!
505        fn parent_returns_none_when_path_has_no_components() {
506            let path_buf = S3PathBuf::new();
507            assert_that(path_buf.parent()).is_none();
508        }
509
510        #[test] // Function `parent` inherited through deref to S3Path!
511        fn parent_returns_empty_component_when_path_has_only_one_component() {
512            let path_buf = S3PathBuf::try_from(["foo"]).unwrap();
513            assert_that(path_buf.parent())
514                .is_some()
515                .has_display_value("");
516        }
517
518        #[test] // Function `parent` inherited through deref to S3Path!
519        fn parent_returns_view_of_parent_path_when_path_has_multiple_components() {
520            let path_buf = S3PathBuf::try_from(["foo", "bar", "baz"]).unwrap();
521            assert_that(path_buf.parent())
522                .is_some()
523                .has_display_value("foo/bar");
524        }
525
526        #[test] // Function `to_std_path_buf` inherited through deref to S3Path!
527        fn to_std_path_buf_returns_empty_path_buf_when_s3_path_has_zero_components() {
528            let path_buf = S3PathBuf::new();
529            assert_that(path_buf.to_std_path_buf().display()).has_display_value("");
530        }
531
532        #[test] // Function `to_std_path_buf` inherited through deref to S3Path!
533        fn to_std_path_buf_joins_components_with_path_separator_does_not_add_slashes() {
534            let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
535            assert_that(path_buf.to_std_path_buf().display()).has_display_value("foo/bar");
536        }
537
538        mod s3_path_buf_macro {
539            use assertr::prelude::*;
540            use std::borrow::Cow;
541
542            #[test]
543            fn taking_nothing() {
544                let path = s3_path_buf!().unwrap();
545                assert_that(path).has_display_value("");
546            }
547
548            #[test]
549            fn taking_single_static_str() {
550                let path = s3_path_buf!("foo").unwrap();
551                assert_that(path).has_display_value("foo");
552            }
553
554            #[test]
555            fn taking_owned_string_and_static_str() {
556                let foo = String::from("foo");
557                let path = s3_path_buf!(foo, "bar").unwrap();
558                assert_that(path).has_display_value("foo/bar");
559            }
560
561            #[test]
562            fn taking_owned_string_and_static_str_and_cow() {
563                let foo = String::from("foo");
564                let path = s3_path_buf!(foo, "bar", Cow::Borrowed("baz")).unwrap();
565                assert_that(path).has_display_value("foo/bar/baz");
566            }
567
568            #[test]
569            fn allows_a_trailing_comma() {
570                let path = s3_path_buf!("foo",).unwrap();
571                assert_that(path).has_display_value("foo");
572            }
573        }
574    }
575
576    mod s3_path {
577        use crate::S3Path;
578        use assertr::prelude::*;
579        use std::borrow::Cow;
580
581        #[test]
582        fn new_is_initially_empty() {
583            let path = S3Path::new(&[]).unwrap();
584            assert_that(path).has_display_value("");
585        }
586
587        #[test]
588        fn construct_using_new_and_join_components() {
589            let path = S3Path::new(&[Cow::Borrowed("foo"), Cow::Borrowed("bar")]).unwrap();
590            assert_that(path).has_display_value("foo/bar");
591        }
592
593        #[test]
594        fn to_owned_converts_to_owned_path() {
595            let path = s3_path!("foo", "bar").unwrap();
596            let path_owned = path.to_owned();
597            assert_that(path_owned).has_display_value("foo/bar");
598        }
599
600        #[test]
601        fn join_converts_to_owned_path_and_appends_component() {
602            let path = s3_path!("foo", "bar").unwrap();
603            let path_owned = path.join("baz").unwrap();
604            assert_that(path_owned).has_display_value("foo/bar/baz");
605        }
606
607        mod s3_path_macro {
608            use assertr::prelude::*;
609
610            #[test]
611            fn handles_zero_components() {
612                let path = s3_path!().unwrap();
613                assert_that(path).has_display_value("");
614            }
615
616            #[test]
617            fn handles_one_component() {
618                let path = s3_path!("foo").unwrap();
619                assert_that(path).has_display_value("foo");
620            }
621
622            #[test]
623            fn handles_multiple_components() {
624                let path = s3_path!("foo", "bar").unwrap();
625                assert_that(path).has_display_value("foo/bar");
626            }
627
628            #[test]
629            fn allows_a_trailing_comma() {
630                let path = s3_path!("foo",).unwrap();
631                assert_that(path).has_display_value("foo");
632            }
633        }
634    }
635
636    mod validation {
637        use crate::S3PathBuf;
638        use assertr::prelude::*;
639
640        #[test]
641        fn reject_invalid_characters() {
642            assert_that(S3PathBuf::try_from(["foo-bar"]))
643                .is_ok()
644                .has_display_value("foo-bar");
645            assert_that(S3PathBuf::try_from(["foo_bar"]))
646                .is_ok()
647                .has_display_value("foo_bar");
648            assert_that(S3PathBuf::try_from(["foo.bar"]))
649                .is_ok()
650                .has_display_value("foo.bar");
651            assert_that(S3PathBuf::try_from([".test"]))
652                .is_ok()
653                .has_display_value(".test");
654            assert_that(S3PathBuf::try_from(["..test"]))
655                .is_ok()
656                .has_display_value("..test");
657
658            assert_that(S3PathBuf::try_from(["foo:bar"])).is_err();
659            assert_that(S3PathBuf::try_from(["foo;bar"])).is_err();
660            assert_that(S3PathBuf::try_from(["foo$bar"])).is_err();
661            assert_that(S3PathBuf::try_from(["foo&bar"])).is_err();
662            assert_that(S3PathBuf::try_from(["foo#bar"])).is_err();
663            assert_that(S3PathBuf::try_from(["foo/bar"])).is_err();
664            assert_that(S3PathBuf::try_from(["foo|bar"])).is_err();
665            assert_that(S3PathBuf::try_from(["foo\\bar"])).is_err();
666            assert_that(S3PathBuf::try_from(["."])).is_err();
667            assert_that(S3PathBuf::try_from([".."])).is_err();
668        }
669    }
670
671    mod take_any_path {
672        use crate::{S3Path, S3PathBuf};
673
674        fn take_any_path<'p>(path: impl AsRef<S3Path<'p>>) {
675            let _path: &S3Path<'p> = path.as_ref();
676        }
677
678        #[test]
679        fn takes_owned_s3_path_buf() {
680            take_any_path(S3PathBuf::new());
681        }
682
683        #[test]
684        fn takes_borrowed_s3_path_buf() {
685            take_any_path(&S3PathBuf::new());
686        }
687
688        #[test]
689        fn takes_s3_path() {
690            take_any_path(S3Path::new(&[]).unwrap());
691        }
692    }
693
694    #[test]
695    #[allow(clippy::op_ref)]
696    fn comparison_between_types() {
697        let path_buf = S3PathBuf::try_from(["foo", "bar"]).unwrap();
698        let path = path_buf.as_path();
699
700        assert_that(path == path).is_true();
701        assert_that(path_buf == path_buf).is_true();
702
703        assert_that(path == path_buf).is_true();
704        assert_that(path == &path_buf).is_true();
705        assert_that(&path == path_buf).is_true();
706        assert_that(&path == &path_buf).is_true();
707
708        assert_that(path_buf == path).is_true();
709        assert_that(path_buf == &path).is_true();
710        assert_that(&path_buf == path).is_true();
711        assert_that(&path_buf == &path).is_true();
712
713        // Also works with assertr.
714        assert_that(&path_buf).is_equal_to(path);
715        assert_that(s3_path_buf!("foo", "bar"))
716            .is_ok()
717            .is_equal_to(path);
718    }
719}