vfs/
path.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Type that represents a path in a file system.  Objects of this type own a String that holds the
6//! "full" path and provide an iterator that goes over individual components of the path.  This
7//! approach is used to allow passing the path string, from one `open()` method to the next,
8//! without the need to copy the path itself.
9
10use zx_status::Status;
11
12#[derive(Clone, Debug)]
13pub struct Path {
14    is_dir: bool,
15    inner: String,
16    next: usize,
17}
18
19impl Path {
20    /// Returns a path for dot, i.e. the current directory.  The `next` will return None for this
21    /// path.
22    pub fn dot() -> Path {
23        // We use an empty string to avoid allocations.  as_ref() handles this correctly below and
24        // will return ".".
25        Path { is_dir: false, inner: String::new(), next: 0 }
26    }
27
28    pub fn is_dot(&self) -> bool {
29        self.inner.is_empty()
30    }
31
32    /// Splits a `path` string into components, also checking if it is in a canonical form,
33    /// disallowing any ".." components, as well as empty component names.  A lone "/" is translated
34    /// to "." (the canonical form).
35    pub fn validate_and_split<Source>(path: Source) -> Result<Path, Status>
36    where
37        Source: Into<String>,
38    {
39        let path = path.into();
40
41        // Make sure that we don't accept paths longer than POSIX's PATH_MAX, plus one character
42        // which accounts for the null terminator.
43        if (path.len() as u64) > libc::PATH_MAX as u64 - 1 {
44            return Err(Status::BAD_PATH);
45        }
46
47        match path.as_str() {
48            "." | "/" => Ok(Self::dot()),
49            _ => {
50                let is_dir = path.ends_with('/');
51
52                // Allow a leading slash.
53                let next = if path.len() > 1 && path.starts_with('/') { 1 } else { 0 };
54
55                let mut check = path[next..].split('/');
56
57                // Allow trailing slash to indicate a directory.
58                if is_dir {
59                    let _ = check.next_back();
60                }
61
62                // Disallow empty components, ".", and ".."s.  Path is expected to be
63                // canonicalized.  See https://fxbug.dev/42103076 for discussion of empty components.
64                for c in check {
65                    crate::name::validate_name(c)?;
66                }
67
68                Ok(Path { is_dir, inner: path, next })
69            }
70        }
71    }
72
73    /// Returns `true` when there are no more components left in this `Path`.
74    pub fn is_empty(&self) -> bool {
75        self.next >= self.inner.len()
76    }
77
78    /// Returns `true` if the canonical path contains '/' as the last symbol.  Note that is_dir is
79    /// `false` for ".", even though a directory is implied.  The canonical form for "/" is ".".
80    pub fn is_dir(&self) -> bool {
81        self.is_dir
82    }
83
84    /// Returns `true` when the path contains only one component - that is, it is not empty and
85    /// contains no `/` characters.
86    pub fn is_single_component(&self) -> bool {
87        let end = if self.is_dir { self.inner.len() - 1 } else { self.inner.len() };
88        self.next < self.inner.len() && self.inner[self.next..end].find('/').is_none()
89    }
90
91    /// Returns a reference to a portion of the string that names the next component, and move the
92    /// internal pointer to point to the next component.  See also [`Path::peek()`].
93    ///
94    /// Also see [`Path::next_with_ref()`] if you want to use `self` while holding a reference to
95    /// the returned name.
96    pub fn next(&mut self) -> Option<&str> {
97        self.next_with_ref().1
98    }
99
100    /// Rust does not allow usage of `self` while the returned reference is alive, even when the
101    /// reference is actually shared.  See, for example,
102    ///
103    ///     https://internals.rust-lang.org/t/relaxing-the-borrow-checker-for-fn-mut-self-t/3256
104    ///
105    /// for additional details.  So if the caller wants to call any other methods on the `path`
106    /// after calling `next()` while still holding a reference to the returned name they can use
107    /// this method as a workaround.  When Rust is extended to cover this use case, `next_with_ref`
108    /// should be merged into [`Self::next()`].
109    pub fn next_with_ref(&mut self) -> (&Self, Option<&str>) {
110        match self.inner[self.next..].find('/') {
111            Some(i) => {
112                let from = self.next;
113                self.next = self.next + i + 1;
114                (self, Some(&self.inner[from..from + i]))
115            }
116            None => {
117                if self.next >= self.inner.len() {
118                    (self, None)
119                } else {
120                    let from = self.next;
121                    self.next = self.inner.len();
122                    (self, Some(&self.inner[from..]))
123                }
124            }
125        }
126    }
127
128    /// Returns a reference to a position of the string that names the next component, without
129    /// moving the internal pointer.  So calling `peek()` multiple times in a row would return the
130    /// same result.  See also [`Self::next()`].
131    pub fn peek(&self) -> Option<&str> {
132        match self.inner[self.next..].find('/') {
133            Some(i) => Some(&self.inner[self.next..self.next + i]),
134            None => {
135                if self.next >= self.inner.len() {
136                    None
137                } else {
138                    Some(&self.inner[self.next..])
139                }
140            }
141        }
142    }
143
144    /// Converts this `Path` into a `String` holding the rest of the path.  Note that if there are
145    /// no more components, this will return an empty string, which is *not* a valid path for
146    /// fuchsia.io.
147    pub fn into_string(mut self) -> String {
148        self.inner.drain(0..self.next);
149        self.inner
150    }
151
152    /// Returns a reference to the full string that represents this path.
153    pub fn as_str(&self) -> &str {
154        self.inner.as_str()
155    }
156
157    /// Returns a path with `prefix` as a prefix.  This will preserve the result of is_dir().
158    pub fn with_prefix(&self, prefix: &Self) -> Self {
159        if prefix.is_empty() {
160            Self { is_dir: self.is_dir, inner: self.inner[self.next..].to_string(), next: 0 }
161        } else {
162            let end = if prefix.is_dir { prefix.inner.len() - 1 } else { prefix.inner.len() };
163            if self.is_empty() && !self.is_dir {
164                Self { is_dir: false, inner: prefix.inner[prefix.next..end].to_string(), next: 0 }
165            } else {
166                Self {
167                    is_dir: self.is_dir,
168                    inner: format!(
169                        "{}/{}",
170                        &prefix.inner[prefix.next..end],
171                        &self.inner[self.next..]
172                    ),
173                    next: 0,
174                }
175            }
176        }
177    }
178
179    /// Like `into_string` but returns a reference and the path returned is valid for fuchsia.io
180    /// i.e. if there are no remaining components, "." is returned.
181    fn remainder(&self) -> &str {
182        if self.is_empty() {
183            "."
184        } else {
185            &self.inner[self.next..]
186        }
187    }
188}
189
190impl PartialEq for Path {
191    fn eq(&self, other: &Self) -> bool {
192        self.remainder() == other.remainder()
193    }
194}
195impl Eq for Path {}
196
197impl AsRef<str> for Path {
198    /// Returns a reference to the remaining portion of this path that will be used in future
199    /// `next` calls.
200    fn as_ref(&self) -> &str {
201        self.remainder()
202    }
203}
204
205impl TryInto<Path> for String {
206    type Error = Status;
207
208    fn try_into(self) -> Result<Path, Self::Error> {
209        Path::validate_and_split(self)
210    }
211}
212
213impl TryInto<Path> for &str {
214    type Error = Status;
215
216    fn try_into(self) -> Result<Path, Self::Error> {
217        Path::validate_and_split(self)
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224    use fidl_fuchsia_io as fio;
225
226    macro_rules! simple_construction_test {
227        (path: $str:expr, $path:ident => $body:block) => {
228            match Path::validate_and_split($str) {
229                Ok($path) => $body,
230                Err(status) => panic!("'{}' construction failed: {}", stringify!(path), status),
231            }
232        };
233        (path: $str:expr, mut $path:ident => $body:block) => {
234            match Path::validate_and_split($str) {
235                Ok(mut $path) => $body,
236                Err(status) => panic!("'{}' construction failed: {}", stringify!(path), status),
237            }
238        };
239    }
240
241    macro_rules! negative_construction_test {
242        (path: $path:expr, $details:expr, $status:expr) => {
243            match Path::validate_and_split($path) {
244                Ok(path) => {
245                    panic!("Constructed '{}' with {}: {:?}", stringify!($path), $details, path)
246                }
247                Err(status) => assert_eq!(status, $status),
248            }
249        };
250    }
251
252    fn path(s: &str) -> Path {
253        match Path::validate_and_split(s) {
254            Ok(path) => path,
255            Err(e) => panic!("'{}' construction failed: {}", s, e),
256        }
257    }
258
259    #[test]
260    fn empty() {
261        negative_construction_test! {
262            path: "",
263            "empty path",
264            Status::INVALID_ARGS
265        };
266    }
267
268    #[test]
269    fn forward_slash_only() {
270        simple_construction_test! {
271            path: "/",
272            mut path => {
273                assert!(path.is_empty());
274                assert!(!path.is_dir());  // It is converted into ".".
275                assert!(!path.is_single_component());
276                assert_eq!(path.as_ref(), ".");
277                assert_eq!(path.peek(), None);
278                assert_eq!(path.next(), None);
279                assert_eq!(path.as_ref(), ".");
280                assert_eq!(path.into_string(), String::new());
281            }
282        };
283    }
284
285    #[test]
286    fn one_component_short() {
287        simple_construction_test! {
288            path: "a",
289            mut path => {
290                assert!(!path.is_empty());
291                assert!(!path.is_dir());
292                assert!(path.is_single_component());
293                assert_eq!(path.peek(), Some("a"));
294                assert_eq!(path.peek(), Some("a"));
295                assert_eq!(path.next(), Some("a"));
296                assert_eq!(path.peek(), None);
297                assert_eq!(path.next(), None);
298                assert_eq!(path.as_ref(), ".");
299                assert_eq!(path.into_string(), String::new());
300            }
301        };
302    }
303
304    #[test]
305    fn one_component() {
306        simple_construction_test! {
307            path: "some",
308            mut path => {
309                assert!(!path.is_empty());
310                assert!(!path.is_dir());
311                assert!(path.is_single_component());
312                assert_eq!(path.peek(), Some("some"));
313                assert_eq!(path.peek(), Some("some"));
314                assert_eq!(path.next(), Some("some"));
315                assert_eq!(path.peek(), None);
316                assert_eq!(path.next(), None);
317                assert_eq!(path.as_ref(), ".");
318                assert_eq!(path.into_string(), String::new());
319            }
320        };
321    }
322
323    #[test]
324    fn one_component_dir() {
325        simple_construction_test! {
326            path: "some/",
327            mut path => {
328                assert!(!path.is_empty());
329                assert!(path.is_dir());
330                assert!(path.is_single_component());
331                assert_eq!(path.peek(), Some("some"));
332                assert_eq!(path.peek(), Some("some"));
333                assert_eq!(path.next(), Some("some"));
334                assert_eq!(path.peek(), None);
335                assert_eq!(path.next(), None);
336                assert_eq!(path.as_ref(), ".");
337                assert_eq!(path.into_string(), String::new());
338            }
339        };
340    }
341
342    #[test]
343    fn two_component_short() {
344        simple_construction_test! {
345            path: "a/b",
346            mut path => {
347                assert!(!path.is_empty());
348                assert!(!path.is_dir());
349                assert!(!path.is_single_component());
350                assert_eq!(path.peek(), Some("a"));
351                assert_eq!(path.peek(), Some("a"));
352                assert_eq!(path.next(), Some("a"));
353                assert_eq!(path.peek(), Some("b"));
354                assert_eq!(path.peek(), Some("b"));
355                assert_eq!(path.next(), Some("b"));
356                assert_eq!(path.peek(), None);
357                assert_eq!(path.next(), None);
358                assert_eq!(path.as_ref(), ".");
359                assert_eq!(path.into_string(), String::new());
360            }
361        };
362    }
363
364    #[test]
365    fn two_component() {
366        simple_construction_test! {
367            path: "some/path",
368            mut path => {
369                assert!(!path.is_empty());
370                assert!(!path.is_dir());
371                assert!(!path.is_single_component());
372                assert_eq!(path.peek(), Some("some"));
373                assert_eq!(path.peek(), Some("some"));
374                assert_eq!(path.next(), Some("some"));
375                assert_eq!(path.peek(), Some("path"));
376                assert_eq!(path.peek(), Some("path"));
377                assert_eq!(path.next(), Some("path"));
378                assert_eq!(path.peek(), None);
379                assert_eq!(path.next(), None);
380                assert_eq!(path.as_ref(), ".");
381                assert_eq!(path.into_string(), String::new());
382            }
383        };
384    }
385
386    #[test]
387    fn two_component_dir() {
388        simple_construction_test! {
389            path: "some/path/",
390            mut path => {
391                assert!(!path.is_empty());
392                assert!(path.is_dir());
393                assert!(!path.is_single_component());
394                assert_eq!(path.peek(), Some("some"));
395                assert_eq!(path.peek(), Some("some"));
396                assert_eq!(path.next(), Some("some"));
397                assert_eq!(path.peek(), Some("path"));
398                assert_eq!(path.peek(), Some("path"));
399                assert_eq!(path.next(), Some("path"));
400                assert_eq!(path.peek(), None);
401                assert_eq!(path.next(), None);
402                assert_eq!(path.as_ref(), ".");
403                assert_eq!(path.into_string(), String::new());
404            }
405        };
406    }
407
408    #[test]
409    fn into_string_half_way() {
410        simple_construction_test! {
411            path: "into/string/half/way",
412            mut path => {
413                assert!(!path.is_empty());
414                assert!(!path.is_dir());
415                assert!(!path.is_single_component());
416                assert_eq!(path.peek(), Some("into"));
417                assert_eq!(path.peek(), Some("into"));
418                assert_eq!(path.next(), Some("into"));
419                assert_eq!(path.peek(), Some("string"));
420                assert_eq!(path.peek(), Some("string"));
421                assert_eq!(path.next(), Some("string"));
422                assert_eq!(path.peek(), Some("half"));
423                assert_eq!(path.peek(), Some("half"));
424                assert_eq!(path.as_ref(), "half/way");
425                assert_eq!(path.into_string(), "half/way".to_string());
426            }
427        };
428    }
429
430    #[test]
431    fn into_string_half_way_dir() {
432        simple_construction_test! {
433            path: "into/string/half/way/",
434            mut path => {
435                assert!(!path.is_empty());
436                assert!(path.is_dir());
437                assert!(!path.is_single_component());
438                assert_eq!(path.peek(), Some("into"));
439                assert_eq!(path.peek(), Some("into"));
440                assert_eq!(path.next(), Some("into"));
441                assert_eq!(path.peek(), Some("string"));
442                assert_eq!(path.peek(), Some("string"));
443                assert_eq!(path.next(), Some("string"));
444                assert_eq!(path.peek(), Some("half"));
445                assert_eq!(path.peek(), Some("half"));
446                assert_eq!(path.as_ref(), "half/way/");
447                assert_eq!(path.into_string(), "half/way/".to_string());
448            }
449        };
450    }
451
452    #[test]
453    fn into_string_dir_last_component() {
454        simple_construction_test! {
455            path: "into/string/",
456            mut path => {
457                assert!(!path.is_empty());
458                assert!(path.is_dir());
459                assert!(!path.is_single_component());
460                assert_eq!(path.peek(), Some("into"));
461                assert_eq!(path.peek(), Some("into"));
462                assert_eq!(path.next(), Some("into"));
463                assert_eq!(path.peek(), Some("string"));
464                assert_eq!(path.peek(), Some("string"));
465                assert_eq!(path.next(), Some("string"));
466                assert_eq!(path.peek(), None);
467                assert_eq!(path.as_ref(), ".");
468                assert_eq!(path.into_string(), "".to_string());
469            }
470        };
471    }
472
473    #[test]
474    fn no_empty_components() {
475        negative_construction_test! {
476            path: "//",
477            "empty components",
478            Status::INVALID_ARGS
479        };
480    }
481
482    #[test]
483    fn absolute_paths() {
484        simple_construction_test! {
485            path: "/a/b/c",
486            mut path => {
487                assert!(!path.is_empty());
488                assert!(!path.is_dir());
489                assert!(!path.is_single_component());
490                assert_eq!(path.as_ref(), "a/b/c");
491                assert_eq!(path.clone().into_string(), "a/b/c");
492                assert_eq!(path.peek(), Some("a"));
493                assert_eq!(path.peek(), Some("a"));
494                assert_eq!(path.next(), Some("a"));
495                assert_eq!(path.next(), Some("b"));
496                assert_eq!(path.next(), Some("c"));
497                assert_eq!(path.peek(), None);
498                assert_eq!(path.next(), None);
499                assert_eq!(path.as_ref(), ".");
500                assert_eq!(path.into_string(), "".to_string());
501            }
502        }
503    }
504
505    #[test]
506    fn as_str() {
507        simple_construction_test! {
508            path: "a/b/c",
509            mut path => {
510                assert!(!path.is_empty());
511                assert!(!path.is_dir());
512                assert!(!path.is_single_component());
513                assert_eq!(path.as_ref(), "a/b/c");
514                assert_eq!(path.as_str(), "a/b/c");
515                assert_eq!(path.next(), Some("a"));
516                assert_eq!(path.as_str(), "a/b/c");
517                assert_eq!(path.next(), Some("b"));
518                assert_eq!(path.as_str(), "a/b/c");
519                assert_eq!(path.next(), Some("c"));
520                assert_eq!(path.as_str(), "a/b/c");
521                assert_eq!(path.next(), None);
522                assert_eq!(path.as_str(), "a/b/c");
523            }
524        };
525    }
526
527    #[test]
528    fn dot_components() {
529        negative_construction_test! {
530            path: "a/./b",
531            "'.' components",
532            Status::INVALID_ARGS
533        };
534    }
535
536    #[test]
537    fn dot_dot_components() {
538        negative_construction_test! {
539            path: "a/../b",
540            "'..' components",
541            Status::INVALID_ARGS
542        };
543    }
544
545    #[test]
546    fn too_long_filename() {
547        let string = "a".repeat(fio::MAX_NAME_LENGTH as usize + 1);
548        negative_construction_test! {
549            path: &string,
550            "filename too long",
551            Status::BAD_PATH
552        };
553    }
554
555    #[test]
556    fn too_long_path() {
557        let filename = "a".repeat(fio::MAX_NAME_LENGTH as usize);
558        const OVER_LIMIT_LENGTH: usize = fio::MAX_PATH_LENGTH as usize + 1;
559        let mut path = String::new();
560        while path.len() < OVER_LIMIT_LENGTH as usize {
561            path.push('/');
562            path.push_str(&filename);
563        }
564        assert_eq!(path.len(), OVER_LIMIT_LENGTH as usize);
565        negative_construction_test! {
566            path: &path,
567            "path too long",
568            Status::BAD_PATH
569        };
570    }
571
572    #[test]
573    fn long_path() {
574        #[cfg(not(target_os = "macos"))]
575        let max_path_len = fio::MAX_PATH_LENGTH as usize;
576        #[cfg(target_os = "macos")]
577        let max_path_len = libc::PATH_MAX as usize - 1;
578
579        let mut path = "a/".repeat(max_path_len / 2);
580        if path.len() < max_path_len {
581            path.push('a');
582        }
583        assert_eq!(path.len(), max_path_len);
584        simple_construction_test! {
585            path: &path,
586            mut path => {
587                assert!(!path.is_empty());
588                assert_eq!(path.next(), Some("a"));
589            }
590        };
591    }
592
593    #[test]
594    fn long_filename() {
595        let string = "a".repeat(fio::MAX_NAME_LENGTH as usize);
596        simple_construction_test! {
597            path: &string,
598            mut path => {
599                assert!(!path.is_empty());
600                assert!(path.is_single_component());
601                assert_eq!(path.next(), Some(string.as_str()));
602            }
603        };
604    }
605
606    #[test]
607    fn dot() {
608        for mut path in
609            [Path::dot(), Path::validate_and_split(".").expect("validate_and_split failed")]
610        {
611            assert!(path.is_dot());
612            assert!(path.is_empty());
613            assert!(!path.is_dir());
614            assert!(!path.is_single_component());
615            assert_eq!(path.next(), None);
616            assert_eq!(path.peek(), None);
617            assert_eq!(path.as_ref(), ".");
618            assert_eq!(path.as_ref(), ".");
619            assert_eq!(path.into_string(), "");
620        }
621    }
622
623    #[test]
624    fn eq_compares_remainder() {
625        let mut pos = path("a/b/c");
626
627        assert_eq!(pos, path("a/b/c"));
628        assert_ne!(pos, path("b/c"));
629        assert_ne!(pos, path("c"));
630        assert_ne!(pos, path("."));
631
632        assert_eq!(pos.next(), Some("a"));
633
634        assert_ne!(pos, path("a/b/c"));
635        assert_eq!(pos, path("b/c"));
636        assert_ne!(pos, path("c"));
637        assert_ne!(pos, path("."));
638
639        assert_eq!(pos.next(), Some("b"));
640
641        assert_ne!(pos, path("a/b/c"));
642        assert_ne!(pos, path("b/c"));
643        assert_eq!(pos, path("c"));
644        assert_ne!(pos, path("."));
645
646        assert_eq!(pos.next(), Some("c"));
647
648        assert_ne!(pos, path("a/b/c"));
649        assert_ne!(pos, path("b/c"));
650        assert_ne!(pos, path("c"));
651        assert_eq!(pos, path("."));
652    }
653
654    #[test]
655    fn eq_considers_is_dir() {
656        let mut pos_not = path("a/b");
657        let mut pos_dir = path("a/b/");
658
659        assert_ne!(pos_not, pos_dir);
660        assert_eq!(pos_not, path("a/b"));
661        assert_eq!(pos_dir, path("a/b/"));
662
663        pos_not.next();
664        pos_dir.next();
665
666        assert_ne!(pos_not, pos_dir);
667        assert_eq!(pos_not, path("b"));
668        assert_eq!(pos_dir, path("b/"));
669
670        pos_not.next();
671        pos_dir.next();
672
673        // once all that is left is ".", now they are equivalent
674        assert_eq!(pos_not, pos_dir);
675        assert_eq!(pos_not, path("."));
676        assert_eq!(pos_dir, path("."));
677    }
678
679    #[test]
680    fn eq_does_not_consider_absolute_different_from_relative() {
681        let abs = path("/a/b");
682        let rel = path("a/b");
683
684        assert_eq!(abs, rel);
685        assert_ne!(abs, path("different/path"));
686        assert_ne!(rel, path("different/path"));
687    }
688
689    #[test]
690    fn as_ref_is_remainder() {
691        let mut path = Path::validate_and_split(".").unwrap();
692        assert_eq!(path.as_ref(), ".");
693        path.next();
694        assert_eq!(path.as_ref(), ".");
695
696        let mut path = Path::validate_and_split("a/b/c").unwrap();
697        assert_eq!(path.as_ref(), "a/b/c");
698        path.next();
699        assert_eq!(path.as_ref(), "b/c");
700        path.next();
701        assert_eq!(path.as_ref(), "c");
702        path.next();
703        assert_eq!(path.as_ref(), ".");
704
705        let mut path = Path::validate_and_split("/a/b/c").unwrap();
706        assert_eq!(path.as_ref(), "a/b/c");
707        path.next();
708        assert_eq!(path.as_ref(), "b/c");
709        path.next();
710        assert_eq!(path.as_ref(), "c");
711        path.next();
712        assert_eq!(path.as_ref(), ".");
713
714        let mut path = Path::validate_and_split("/a/b/c/").unwrap();
715        assert_eq!(path.as_ref(), "a/b/c/");
716        path.next();
717        assert_eq!(path.as_ref(), "b/c/");
718        path.next();
719        assert_eq!(path.as_ref(), "c/");
720        path.next();
721        assert_eq!(path.as_ref(), ".");
722    }
723
724    #[test]
725    fn str_try_into() {
726        let valid = "abc";
727        let path: Path = valid.try_into().unwrap();
728        assert_eq!(path.as_str(), "abc");
729
730        let invalid = "..";
731        let path: Result<Path, _> = invalid.try_into();
732        assert!(path.is_err());
733    }
734
735    #[test]
736    fn string_try_into() {
737        let valid = "abc".to_string();
738        let path: Path = valid.try_into().unwrap();
739        assert_eq!(path.as_str(), "abc");
740
741        let invalid = "..".to_string();
742        let path: Result<Path, _> = invalid.try_into();
743        assert!(path.is_err());
744    }
745
746    #[test]
747    fn with_prefix() {
748        let mut foo: Path = "apple/ball".try_into().unwrap();
749        let mut bar: Path = "cat/dog/".try_into().unwrap();
750
751        let combined = bar.with_prefix(&foo);
752        assert_eq!(combined.as_str(), "apple/ball/cat/dog/");
753        assert!(combined.is_dir());
754
755        let combined = foo.with_prefix(&bar);
756        assert_eq!(combined.as_str(), "cat/dog/apple/ball");
757        assert!(!combined.is_dir());
758
759        let combined = bar.with_prefix(&bar);
760        assert_eq!(combined.as_str(), "cat/dog/cat/dog/");
761        assert!(combined.is_dir());
762
763        bar.next();
764        let combined = foo.with_prefix(&bar);
765        assert_eq!(combined.as_str(), "dog/apple/ball");
766        assert!(!combined.is_dir());
767
768        foo.next();
769        foo.next();
770        let combined = foo.with_prefix(&bar);
771        assert_eq!(combined.as_str(), "dog");
772        assert!(!combined.is_dir());
773
774        let combined = Path::dot().with_prefix(&Path::dot());
775        assert_eq!(combined.as_str(), "");
776        assert!(!combined.is_dir());
777
778        let combined = bar.with_prefix(&Path::dot());
779        assert_eq!(combined.as_str(), "dog/");
780        assert!(combined.is_dir());
781    }
782}