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() { "." } else { &self.inner[self.next..] }
183    }
184}
185
186impl PartialEq for Path {
187    fn eq(&self, other: &Self) -> bool {
188        self.remainder() == other.remainder()
189    }
190}
191impl Eq for Path {}
192
193impl AsRef<str> for Path {
194    /// Returns a reference to the remaining portion of this path that will be used in future
195    /// `next` calls.
196    fn as_ref(&self) -> &str {
197        self.remainder()
198    }
199}
200
201impl TryInto<Path> for String {
202    type Error = Status;
203
204    fn try_into(self) -> Result<Path, Self::Error> {
205        Path::validate_and_split(self)
206    }
207}
208
209impl TryInto<Path> for &str {
210    type Error = Status;
211
212    fn try_into(self) -> Result<Path, Self::Error> {
213        Path::validate_and_split(self)
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220    use fidl_fuchsia_io as fio;
221
222    macro_rules! simple_construction_test {
223        (path: $str:expr, $path:ident => $body:block) => {
224            match Path::validate_and_split($str) {
225                Ok($path) => $body,
226                Err(status) => panic!("'{}' construction failed: {}", stringify!(path), status),
227            }
228        };
229        (path: $str:expr, mut $path:ident => $body:block) => {
230            match Path::validate_and_split($str) {
231                Ok(mut $path) => $body,
232                Err(status) => panic!("'{}' construction failed: {}", stringify!(path), status),
233            }
234        };
235    }
236
237    macro_rules! negative_construction_test {
238        (path: $path:expr, $details:expr, $status:expr) => {
239            match Path::validate_and_split($path) {
240                Ok(path) => {
241                    panic!("Constructed '{}' with {}: {:?}", stringify!($path), $details, path)
242                }
243                Err(status) => assert_eq!(status, $status),
244            }
245        };
246    }
247
248    fn path(s: &str) -> Path {
249        match Path::validate_and_split(s) {
250            Ok(path) => path,
251            Err(e) => panic!("'{}' construction failed: {}", s, e),
252        }
253    }
254
255    #[test]
256    fn empty() {
257        negative_construction_test! {
258            path: "",
259            "empty path",
260            Status::INVALID_ARGS
261        };
262    }
263
264    #[test]
265    fn forward_slash_only() {
266        simple_construction_test! {
267            path: "/",
268            mut path => {
269                assert!(path.is_empty());
270                assert!(!path.is_dir());  // It is converted into ".".
271                assert!(!path.is_single_component());
272                assert_eq!(path.as_ref(), ".");
273                assert_eq!(path.peek(), None);
274                assert_eq!(path.next(), None);
275                assert_eq!(path.as_ref(), ".");
276                assert_eq!(path.into_string(), String::new());
277            }
278        };
279    }
280
281    #[test]
282    fn one_component_short() {
283        simple_construction_test! {
284            path: "a",
285            mut path => {
286                assert!(!path.is_empty());
287                assert!(!path.is_dir());
288                assert!(path.is_single_component());
289                assert_eq!(path.peek(), Some("a"));
290                assert_eq!(path.peek(), Some("a"));
291                assert_eq!(path.next(), Some("a"));
292                assert_eq!(path.peek(), None);
293                assert_eq!(path.next(), None);
294                assert_eq!(path.as_ref(), ".");
295                assert_eq!(path.into_string(), String::new());
296            }
297        };
298    }
299
300    #[test]
301    fn one_component() {
302        simple_construction_test! {
303            path: "some",
304            mut path => {
305                assert!(!path.is_empty());
306                assert!(!path.is_dir());
307                assert!(path.is_single_component());
308                assert_eq!(path.peek(), Some("some"));
309                assert_eq!(path.peek(), Some("some"));
310                assert_eq!(path.next(), Some("some"));
311                assert_eq!(path.peek(), None);
312                assert_eq!(path.next(), None);
313                assert_eq!(path.as_ref(), ".");
314                assert_eq!(path.into_string(), String::new());
315            }
316        };
317    }
318
319    #[test]
320    fn one_component_dir() {
321        simple_construction_test! {
322            path: "some/",
323            mut path => {
324                assert!(!path.is_empty());
325                assert!(path.is_dir());
326                assert!(path.is_single_component());
327                assert_eq!(path.peek(), Some("some"));
328                assert_eq!(path.peek(), Some("some"));
329                assert_eq!(path.next(), Some("some"));
330                assert_eq!(path.peek(), None);
331                assert_eq!(path.next(), None);
332                assert_eq!(path.as_ref(), ".");
333                assert_eq!(path.into_string(), String::new());
334            }
335        };
336    }
337
338    #[test]
339    fn two_component_short() {
340        simple_construction_test! {
341            path: "a/b",
342            mut path => {
343                assert!(!path.is_empty());
344                assert!(!path.is_dir());
345                assert!(!path.is_single_component());
346                assert_eq!(path.peek(), Some("a"));
347                assert_eq!(path.peek(), Some("a"));
348                assert_eq!(path.next(), Some("a"));
349                assert_eq!(path.peek(), Some("b"));
350                assert_eq!(path.peek(), Some("b"));
351                assert_eq!(path.next(), Some("b"));
352                assert_eq!(path.peek(), None);
353                assert_eq!(path.next(), None);
354                assert_eq!(path.as_ref(), ".");
355                assert_eq!(path.into_string(), String::new());
356            }
357        };
358    }
359
360    #[test]
361    fn two_component() {
362        simple_construction_test! {
363            path: "some/path",
364            mut path => {
365                assert!(!path.is_empty());
366                assert!(!path.is_dir());
367                assert!(!path.is_single_component());
368                assert_eq!(path.peek(), Some("some"));
369                assert_eq!(path.peek(), Some("some"));
370                assert_eq!(path.next(), Some("some"));
371                assert_eq!(path.peek(), Some("path"));
372                assert_eq!(path.peek(), Some("path"));
373                assert_eq!(path.next(), Some("path"));
374                assert_eq!(path.peek(), None);
375                assert_eq!(path.next(), None);
376                assert_eq!(path.as_ref(), ".");
377                assert_eq!(path.into_string(), String::new());
378            }
379        };
380    }
381
382    #[test]
383    fn two_component_dir() {
384        simple_construction_test! {
385            path: "some/path/",
386            mut path => {
387                assert!(!path.is_empty());
388                assert!(path.is_dir());
389                assert!(!path.is_single_component());
390                assert_eq!(path.peek(), Some("some"));
391                assert_eq!(path.peek(), Some("some"));
392                assert_eq!(path.next(), Some("some"));
393                assert_eq!(path.peek(), Some("path"));
394                assert_eq!(path.peek(), Some("path"));
395                assert_eq!(path.next(), Some("path"));
396                assert_eq!(path.peek(), None);
397                assert_eq!(path.next(), None);
398                assert_eq!(path.as_ref(), ".");
399                assert_eq!(path.into_string(), String::new());
400            }
401        };
402    }
403
404    #[test]
405    fn into_string_half_way() {
406        simple_construction_test! {
407            path: "into/string/half/way",
408            mut path => {
409                assert!(!path.is_empty());
410                assert!(!path.is_dir());
411                assert!(!path.is_single_component());
412                assert_eq!(path.peek(), Some("into"));
413                assert_eq!(path.peek(), Some("into"));
414                assert_eq!(path.next(), Some("into"));
415                assert_eq!(path.peek(), Some("string"));
416                assert_eq!(path.peek(), Some("string"));
417                assert_eq!(path.next(), Some("string"));
418                assert_eq!(path.peek(), Some("half"));
419                assert_eq!(path.peek(), Some("half"));
420                assert_eq!(path.as_ref(), "half/way");
421                assert_eq!(path.into_string(), "half/way".to_string());
422            }
423        };
424    }
425
426    #[test]
427    fn into_string_half_way_dir() {
428        simple_construction_test! {
429            path: "into/string/half/way/",
430            mut path => {
431                assert!(!path.is_empty());
432                assert!(path.is_dir());
433                assert!(!path.is_single_component());
434                assert_eq!(path.peek(), Some("into"));
435                assert_eq!(path.peek(), Some("into"));
436                assert_eq!(path.next(), Some("into"));
437                assert_eq!(path.peek(), Some("string"));
438                assert_eq!(path.peek(), Some("string"));
439                assert_eq!(path.next(), Some("string"));
440                assert_eq!(path.peek(), Some("half"));
441                assert_eq!(path.peek(), Some("half"));
442                assert_eq!(path.as_ref(), "half/way/");
443                assert_eq!(path.into_string(), "half/way/".to_string());
444            }
445        };
446    }
447
448    #[test]
449    fn into_string_dir_last_component() {
450        simple_construction_test! {
451            path: "into/string/",
452            mut path => {
453                assert!(!path.is_empty());
454                assert!(path.is_dir());
455                assert!(!path.is_single_component());
456                assert_eq!(path.peek(), Some("into"));
457                assert_eq!(path.peek(), Some("into"));
458                assert_eq!(path.next(), Some("into"));
459                assert_eq!(path.peek(), Some("string"));
460                assert_eq!(path.peek(), Some("string"));
461                assert_eq!(path.next(), Some("string"));
462                assert_eq!(path.peek(), None);
463                assert_eq!(path.as_ref(), ".");
464                assert_eq!(path.into_string(), "".to_string());
465            }
466        };
467    }
468
469    #[test]
470    fn no_empty_components() {
471        negative_construction_test! {
472            path: "//",
473            "empty components",
474            Status::INVALID_ARGS
475        };
476    }
477
478    #[test]
479    fn absolute_paths() {
480        simple_construction_test! {
481            path: "/a/b/c",
482            mut path => {
483                assert!(!path.is_empty());
484                assert!(!path.is_dir());
485                assert!(!path.is_single_component());
486                assert_eq!(path.as_ref(), "a/b/c");
487                assert_eq!(path.clone().into_string(), "a/b/c");
488                assert_eq!(path.peek(), Some("a"));
489                assert_eq!(path.peek(), Some("a"));
490                assert_eq!(path.next(), Some("a"));
491                assert_eq!(path.next(), Some("b"));
492                assert_eq!(path.next(), Some("c"));
493                assert_eq!(path.peek(), None);
494                assert_eq!(path.next(), None);
495                assert_eq!(path.as_ref(), ".");
496                assert_eq!(path.into_string(), "".to_string());
497            }
498        }
499    }
500
501    #[test]
502    fn as_str() {
503        simple_construction_test! {
504            path: "a/b/c",
505            mut path => {
506                assert!(!path.is_empty());
507                assert!(!path.is_dir());
508                assert!(!path.is_single_component());
509                assert_eq!(path.as_ref(), "a/b/c");
510                assert_eq!(path.as_str(), "a/b/c");
511                assert_eq!(path.next(), Some("a"));
512                assert_eq!(path.as_str(), "a/b/c");
513                assert_eq!(path.next(), Some("b"));
514                assert_eq!(path.as_str(), "a/b/c");
515                assert_eq!(path.next(), Some("c"));
516                assert_eq!(path.as_str(), "a/b/c");
517                assert_eq!(path.next(), None);
518                assert_eq!(path.as_str(), "a/b/c");
519            }
520        };
521    }
522
523    #[test]
524    fn dot_components() {
525        negative_construction_test! {
526            path: "a/./b",
527            "'.' components",
528            Status::INVALID_ARGS
529        };
530    }
531
532    #[test]
533    fn dot_dot_components() {
534        negative_construction_test! {
535            path: "a/../b",
536            "'..' components",
537            Status::INVALID_ARGS
538        };
539    }
540
541    #[test]
542    fn too_long_filename() {
543        let string = "a".repeat(fio::MAX_NAME_LENGTH as usize + 1);
544        negative_construction_test! {
545            path: &string,
546            "filename too long",
547            Status::BAD_PATH
548        };
549    }
550
551    #[test]
552    fn too_long_path() {
553        let filename = "a".repeat(fio::MAX_NAME_LENGTH as usize);
554        const OVER_LIMIT_LENGTH: usize = fio::MAX_PATH_LENGTH as usize + 1;
555        let mut path = String::new();
556        while path.len() < OVER_LIMIT_LENGTH as usize {
557            path.push('/');
558            path.push_str(&filename);
559        }
560        assert_eq!(path.len(), OVER_LIMIT_LENGTH as usize);
561        negative_construction_test! {
562            path: &path,
563            "path too long",
564            Status::BAD_PATH
565        };
566    }
567
568    #[test]
569    fn long_path() {
570        #[cfg(not(target_os = "macos"))]
571        let max_path_len = fio::MAX_PATH_LENGTH as usize;
572        #[cfg(target_os = "macos")]
573        let max_path_len = libc::PATH_MAX as usize - 1;
574
575        let mut path = "a/".repeat(max_path_len / 2);
576        if path.len() < max_path_len {
577            path.push('a');
578        }
579        assert_eq!(path.len(), max_path_len);
580        simple_construction_test! {
581            path: &path,
582            mut path => {
583                assert!(!path.is_empty());
584                assert_eq!(path.next(), Some("a"));
585            }
586        };
587    }
588
589    #[test]
590    fn long_filename() {
591        let string = "a".repeat(fio::MAX_NAME_LENGTH as usize);
592        simple_construction_test! {
593            path: &string,
594            mut path => {
595                assert!(!path.is_empty());
596                assert!(path.is_single_component());
597                assert_eq!(path.next(), Some(string.as_str()));
598            }
599        };
600    }
601
602    #[test]
603    fn dot() {
604        for mut path in
605            [Path::dot(), Path::validate_and_split(".").expect("validate_and_split failed")]
606        {
607            assert!(path.is_dot());
608            assert!(path.is_empty());
609            assert!(!path.is_dir());
610            assert!(!path.is_single_component());
611            assert_eq!(path.next(), None);
612            assert_eq!(path.peek(), None);
613            assert_eq!(path.as_ref(), ".");
614            assert_eq!(path.as_ref(), ".");
615            assert_eq!(path.into_string(), "");
616        }
617    }
618
619    #[test]
620    fn eq_compares_remainder() {
621        let mut pos = path("a/b/c");
622
623        assert_eq!(pos, path("a/b/c"));
624        assert_ne!(pos, path("b/c"));
625        assert_ne!(pos, path("c"));
626        assert_ne!(pos, path("."));
627
628        assert_eq!(pos.next(), Some("a"));
629
630        assert_ne!(pos, path("a/b/c"));
631        assert_eq!(pos, path("b/c"));
632        assert_ne!(pos, path("c"));
633        assert_ne!(pos, path("."));
634
635        assert_eq!(pos.next(), Some("b"));
636
637        assert_ne!(pos, path("a/b/c"));
638        assert_ne!(pos, path("b/c"));
639        assert_eq!(pos, path("c"));
640        assert_ne!(pos, path("."));
641
642        assert_eq!(pos.next(), Some("c"));
643
644        assert_ne!(pos, path("a/b/c"));
645        assert_ne!(pos, path("b/c"));
646        assert_ne!(pos, path("c"));
647        assert_eq!(pos, path("."));
648    }
649
650    #[test]
651    fn eq_considers_is_dir() {
652        let mut pos_not = path("a/b");
653        let mut pos_dir = path("a/b/");
654
655        assert_ne!(pos_not, pos_dir);
656        assert_eq!(pos_not, path("a/b"));
657        assert_eq!(pos_dir, path("a/b/"));
658
659        pos_not.next();
660        pos_dir.next();
661
662        assert_ne!(pos_not, pos_dir);
663        assert_eq!(pos_not, path("b"));
664        assert_eq!(pos_dir, path("b/"));
665
666        pos_not.next();
667        pos_dir.next();
668
669        // once all that is left is ".", now they are equivalent
670        assert_eq!(pos_not, pos_dir);
671        assert_eq!(pos_not, path("."));
672        assert_eq!(pos_dir, path("."));
673    }
674
675    #[test]
676    fn eq_does_not_consider_absolute_different_from_relative() {
677        let abs = path("/a/b");
678        let rel = path("a/b");
679
680        assert_eq!(abs, rel);
681        assert_ne!(abs, path("different/path"));
682        assert_ne!(rel, path("different/path"));
683    }
684
685    #[test]
686    fn as_ref_is_remainder() {
687        let mut path = Path::validate_and_split(".").unwrap();
688        assert_eq!(path.as_ref(), ".");
689        path.next();
690        assert_eq!(path.as_ref(), ".");
691
692        let mut path = Path::validate_and_split("a/b/c").unwrap();
693        assert_eq!(path.as_ref(), "a/b/c");
694        path.next();
695        assert_eq!(path.as_ref(), "b/c");
696        path.next();
697        assert_eq!(path.as_ref(), "c");
698        path.next();
699        assert_eq!(path.as_ref(), ".");
700
701        let mut path = Path::validate_and_split("/a/b/c").unwrap();
702        assert_eq!(path.as_ref(), "a/b/c");
703        path.next();
704        assert_eq!(path.as_ref(), "b/c");
705        path.next();
706        assert_eq!(path.as_ref(), "c");
707        path.next();
708        assert_eq!(path.as_ref(), ".");
709
710        let mut path = Path::validate_and_split("/a/b/c/").unwrap();
711        assert_eq!(path.as_ref(), "a/b/c/");
712        path.next();
713        assert_eq!(path.as_ref(), "b/c/");
714        path.next();
715        assert_eq!(path.as_ref(), "c/");
716        path.next();
717        assert_eq!(path.as_ref(), ".");
718    }
719
720    #[test]
721    fn str_try_into() {
722        let valid = "abc";
723        let path: Path = valid.try_into().unwrap();
724        assert_eq!(path.as_str(), "abc");
725
726        let invalid = "..";
727        let path: Result<Path, _> = invalid.try_into();
728        assert!(path.is_err());
729    }
730
731    #[test]
732    fn string_try_into() {
733        let valid = "abc".to_string();
734        let path: Path = valid.try_into().unwrap();
735        assert_eq!(path.as_str(), "abc");
736
737        let invalid = "..".to_string();
738        let path: Result<Path, _> = invalid.try_into();
739        assert!(path.is_err());
740    }
741
742    #[test]
743    fn with_prefix() {
744        let mut foo: Path = "apple/ball".try_into().unwrap();
745        let mut bar: Path = "cat/dog/".try_into().unwrap();
746
747        let combined = bar.with_prefix(&foo);
748        assert_eq!(combined.as_str(), "apple/ball/cat/dog/");
749        assert!(combined.is_dir());
750
751        let combined = foo.with_prefix(&bar);
752        assert_eq!(combined.as_str(), "cat/dog/apple/ball");
753        assert!(!combined.is_dir());
754
755        let combined = bar.with_prefix(&bar);
756        assert_eq!(combined.as_str(), "cat/dog/cat/dog/");
757        assert!(combined.is_dir());
758
759        bar.next();
760        let combined = foo.with_prefix(&bar);
761        assert_eq!(combined.as_str(), "dog/apple/ball");
762        assert!(!combined.is_dir());
763
764        foo.next();
765        foo.next();
766        let combined = foo.with_prefix(&bar);
767        assert_eq!(combined.as_str(), "dog");
768        assert!(!combined.is_dir());
769
770        let combined = Path::dot().with_prefix(&Path::dot());
771        assert_eq!(combined.as_str(), "");
772        assert!(!combined.is_dir());
773
774        let combined = bar.with_prefix(&Path::dot());
775        assert_eq!(combined.as_str(), "dog/");
776        assert!(combined.is_dir());
777    }
778}