1use std::borrow::Cow;
2use std::fmt::{self, Display};
3
4use crate::coding::{Decode, DecodeError, Encode, EncodeError};
5
6pub type PathOwned = Path<'static>;
8
9pub trait AsPath {
14 fn as_path(&self) -> Path<'_>;
15}
16
17impl<'a> AsPath for &'a str {
18 fn as_path(&self) -> Path<'a> {
19 Path::new(self)
20 }
21}
22
23impl<'a> AsPath for &'a Path<'a> {
24 fn as_path(&self) -> Path<'a> {
25 Path(Cow::Borrowed(self.as_str()))
27 }
28}
29
30impl AsPath for Path<'_> {
31 fn as_path(&self) -> Path<'_> {
32 Path(Cow::Borrowed(self.0.as_ref()))
33 }
34}
35
36impl AsPath for String {
37 fn as_path(&self) -> Path<'_> {
38 Path::new(self)
39 }
40}
41
42impl<'a> AsPath for &'a String {
43 fn as_path(&self) -> Path<'a> {
44 Path::new(self)
45 }
46}
47
48#[derive(Debug, PartialEq, Eq, Hash, Clone)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize))]
76pub struct Path<'a>(Cow<'a, str>);
77
78impl<'a> Path<'a> {
79 pub fn new(s: &'a str) -> Self {
84 let trimmed = s.trim_start_matches('/').trim_end_matches('/');
85
86 if trimmed.contains("//") {
88 let normalized = trimmed
90 .split('/')
91 .filter(|s| !s.is_empty())
92 .collect::<Vec<_>>()
93 .join("/");
94 Self(Cow::Owned(normalized))
95 } else {
96 Self(Cow::Borrowed(trimmed))
98 }
99 }
100
101 pub fn has_prefix(&self, prefix: impl AsPath) -> bool {
123 let prefix = prefix.as_path();
124
125 if prefix.is_empty() {
126 return true;
127 }
128
129 if !self.0.starts_with(prefix.as_str()) {
130 return false;
131 }
132
133 if self.0.len() == prefix.len() {
135 return true;
136 }
137
138 self.0.as_bytes().get(prefix.len()) == Some(&b'/')
140 }
141
142 pub fn strip_prefix(&'a self, prefix: impl AsPath) -> Option<Path<'a>> {
143 let prefix = prefix.as_path();
144
145 if prefix.is_empty() {
146 return Some(self.borrow());
147 }
148
149 if !self.0.starts_with(prefix.as_str()) {
150 return None;
151 }
152
153 if self.0.len() == prefix.len() {
155 return Some(Path(Cow::Borrowed("")));
156 }
157
158 if self.0.as_bytes().get(prefix.len()) != Some(&b'/') {
160 return None;
161 }
162
163 Some(Path(Cow::Borrowed(&self.0[prefix.len() + 1..])))
164 }
165
166 pub fn next_part(&'a self) -> Option<(&'a str, Path<'a>)> {
168 if self.0.is_empty() {
169 return None;
170 }
171
172 if let Some(i) = self.0.find('/') {
173 let dir = &self.0[..i];
174 let rest = Path(Cow::Borrowed(&self.0[i + 1..]));
175 Some((dir, rest))
176 } else {
177 Some((&self.0, Path(Cow::Borrowed(""))))
178 }
179 }
180
181 pub fn as_str(&self) -> &str {
182 &self.0
183 }
184
185 pub fn empty() -> Path<'static> {
186 Path(Cow::Borrowed(""))
187 }
188
189 pub fn is_empty(&self) -> bool {
190 self.0.is_empty()
191 }
192
193 pub fn len(&self) -> usize {
194 self.0.len()
195 }
196
197 pub fn to_owned(&self) -> PathOwned {
198 Path(Cow::Owned(self.0.to_string()))
199 }
200
201 pub fn into_owned(self) -> PathOwned {
202 Path(Cow::Owned(self.0.to_string()))
203 }
204
205 pub fn borrow(&'a self) -> Path<'a> {
206 Path(Cow::Borrowed(&self.0))
207 }
208
209 pub fn join(&self, other: impl AsPath) -> PathOwned {
223 let other = other.as_path();
224
225 if self.0.is_empty() {
226 Path(Cow::Owned(other.0.to_string()))
227 } else if other.is_empty() {
228 self.to_owned()
230 } else {
231 Path(Cow::Owned(format!("{}/{}", self.0, other.as_str())))
233 }
234 }
235}
236
237impl<'a> From<&'a str> for Path<'a> {
238 fn from(s: &'a str) -> Self {
239 Self::new(s)
240 }
241}
242
243impl<'a> From<&'a String> for Path<'a> {
244 fn from(s: &'a String) -> Self {
245 Self::new(s)
247 }
248}
249
250impl Default for Path<'_> {
251 fn default() -> Self {
252 Self(Cow::Borrowed(""))
253 }
254}
255
256impl From<String> for Path<'_> {
257 fn from(s: String) -> Self {
258 let trimmed = s.trim_start_matches('/').trim_end_matches('/');
260
261 if trimmed.contains("//") {
263 let normalized = trimmed
265 .split('/')
266 .filter(|s| !s.is_empty())
267 .collect::<Vec<_>>()
268 .join("/");
269 Self(Cow::Owned(normalized))
270 } else if trimmed == s {
271 Self(Cow::Owned(s))
273 } else {
274 Self(Cow::Owned(trimmed.to_string()))
276 }
277 }
278}
279
280impl AsRef<str> for Path<'_> {
281 fn as_ref(&self) -> &str {
282 &self.0
283 }
284}
285
286impl Display for Path<'_> {
287 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288 write!(f, "{}", self.0)
289 }
290}
291
292impl<V: Copy> Decode<V> for Path<'_>
293where
294 String: Decode<V>,
295{
296 fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
297 Ok(String::decode(r, version)?.into())
298 }
299}
300
301impl<V: Copy> Encode<V> for Path<'_>
302where
303 for<'a> &'a str: Encode<V>,
304{
305 fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
306 self.as_str().encode(w, version)?;
307 Ok(())
308 }
309}
310
311#[cfg(feature = "serde")]
313impl<'de: 'a, 'a> serde::Deserialize<'de> for Path<'a> {
314 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
315 where
316 D: serde::Deserializer<'de>,
317 {
318 let s = <&'a str as serde::Deserialize<'de>>::deserialize(deserializer)?;
319 Ok(Path::new(s))
320 }
321}
322
323#[derive(Debug, Clone, Default, Eq)]
329pub struct PathPrefixes {
330 paths: Vec<PathOwned>,
331}
332
333impl PathPrefixes {
334 pub fn new(paths: impl IntoIterator<Item = impl AsPath>) -> Self {
346 let mut paths: Vec<PathOwned> = paths.into_iter().map(|p| p.as_path().to_owned()).collect();
347
348 if paths.len() <= 1 {
349 return Self { paths };
350 }
351
352 paths.sort_by(|a, b| a.len().cmp(&b.len()).then_with(|| a.as_str().cmp(b.as_str())));
355 paths.dedup();
356
357 let mut result: Vec<PathOwned> = Vec::new();
358 'outer: for path in paths {
359 for existing in &result {
360 if path.has_prefix(existing) {
361 continue 'outer;
362 }
363 }
364 result.push(path);
365 }
366
367 Self { paths: result }
368 }
369
370 pub fn is_empty(&self) -> bool {
371 self.paths.is_empty()
372 }
373
374 pub fn len(&self) -> usize {
375 self.paths.len()
376 }
377
378 pub fn iter(&self) -> std::slice::Iter<'_, PathOwned> {
379 self.paths.iter()
380 }
381}
382
383impl std::ops::Deref for PathPrefixes {
384 type Target = [PathOwned];
385
386 fn deref(&self) -> &[PathOwned] {
387 &self.paths
388 }
389}
390
391impl FromIterator<PathOwned> for PathPrefixes {
392 fn from_iter<I: IntoIterator<Item = PathOwned>>(iter: I) -> Self {
393 Self::new(iter)
394 }
395}
396
397impl From<Vec<PathOwned>> for PathPrefixes {
398 fn from(paths: Vec<PathOwned>) -> Self {
399 Self::new(paths)
400 }
401}
402
403impl<'a> PartialEq<Vec<Path<'a>>> for PathPrefixes {
404 fn eq(&self, other: &Vec<Path<'a>>) -> bool {
405 self.paths == *other
406 }
407}
408
409impl<'a> PartialEq<PathPrefixes> for Vec<Path<'a>> {
410 fn eq(&self, other: &PathPrefixes) -> bool {
411 *self == other.paths
412 }
413}
414
415impl PartialEq for PathPrefixes {
416 fn eq(&self, other: &Self) -> bool {
417 self.paths == other.paths
418 }
419}
420
421impl IntoIterator for PathPrefixes {
422 type Item = PathOwned;
423 type IntoIter = std::vec::IntoIter<PathOwned>;
424
425 fn into_iter(self) -> Self::IntoIter {
426 self.paths.into_iter()
427 }
428}
429
430impl<'a> IntoIterator for &'a PathPrefixes {
431 type Item = &'a PathOwned;
432 type IntoIter = std::slice::Iter<'a, PathOwned>;
433
434 fn into_iter(self) -> Self::IntoIter {
435 self.paths.iter()
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use super::*;
442
443 #[test]
444 fn test_has_prefix() {
445 let path = Path::new("foo/bar/baz");
446
447 assert!(path.has_prefix(""));
449 assert!(path.has_prefix("foo"));
450 assert!(path.has_prefix(Path::new("foo")));
451 assert!(path.has_prefix("foo/"));
452 assert!(path.has_prefix("foo/bar"));
453 assert!(path.has_prefix(Path::new("foo/bar/")));
454 assert!(path.has_prefix("foo/bar/baz"));
455
456 assert!(!path.has_prefix("f"));
458 assert!(!path.has_prefix(Path::new("fo")));
459 assert!(!path.has_prefix("foo/b"));
460 assert!(!path.has_prefix("foo/ba"));
461 assert!(!path.has_prefix(Path::new("foo/bar/ba")));
462
463 let path = Path::new("foobar");
465 assert!(!path.has_prefix("foo"));
466 assert!(path.has_prefix(Path::new("foobar")));
467 }
468
469 #[test]
470 fn test_strip_prefix() {
471 let path = Path::new("foo/bar/baz");
472
473 assert_eq!(path.strip_prefix("").unwrap().as_str(), "foo/bar/baz");
475 assert_eq!(path.strip_prefix("foo").unwrap().as_str(), "bar/baz");
476 assert_eq!(path.strip_prefix(Path::new("foo/")).unwrap().as_str(), "bar/baz");
477 assert_eq!(path.strip_prefix("foo/bar").unwrap().as_str(), "baz");
478 assert_eq!(path.strip_prefix(Path::new("foo/bar/")).unwrap().as_str(), "baz");
479 assert_eq!(path.strip_prefix("foo/bar/baz").unwrap().as_str(), "");
480
481 assert!(path.strip_prefix("fo").is_none());
483 assert!(path.strip_prefix(Path::new("bar")).is_none());
484 }
485
486 #[test]
487 fn test_join() {
488 assert_eq!(Path::new("foo").join("bar").as_str(), "foo/bar");
490 assert_eq!(Path::new("foo/").join(Path::new("bar")).as_str(), "foo/bar");
491 assert_eq!(Path::new("").join("bar").as_str(), "bar");
492 assert_eq!(Path::new("foo/bar").join(Path::new("baz")).as_str(), "foo/bar/baz");
493 }
494
495 #[test]
496 fn test_empty() {
497 let empty = Path::new("");
498 assert!(empty.is_empty());
499 assert_eq!(empty.len(), 0);
500
501 let non_empty = Path::new("foo");
502 assert!(!non_empty.is_empty());
503 assert_eq!(non_empty.len(), 3);
504 }
505
506 #[test]
507 fn test_from_conversions() {
508 let path1 = Path::from("foo/bar");
509 let path2 = Path::from("foo/bar");
510 let s = String::from("foo/bar");
511 let path3 = Path::from(&s);
512
513 assert_eq!(path1.as_str(), "foo/bar");
514 assert_eq!(path2.as_str(), "foo/bar");
515 assert_eq!(path3.as_str(), "foo/bar");
516 }
517
518 #[test]
519 fn test_path_prefix_join() {
520 let prefix = Path::new("foo");
521 let suffix = Path::new("bar/baz");
522 let path = prefix.join(&suffix);
523 assert_eq!(path.as_str(), "foo/bar/baz");
524
525 let prefix = Path::new("foo/");
526 let suffix = Path::new("bar/baz");
527 let path = prefix.join(&suffix);
528 assert_eq!(path.as_str(), "foo/bar/baz");
529
530 let prefix = Path::new("foo");
531 let suffix = Path::new("/bar/baz");
532 let path = prefix.join(&suffix);
533 assert_eq!(path.as_str(), "foo/bar/baz");
534
535 let prefix = Path::new("");
536 let suffix = Path::new("bar/baz");
537 let path = prefix.join(&suffix);
538 assert_eq!(path.as_str(), "bar/baz");
539 }
540
541 #[test]
542 fn test_path_prefix_conversions() {
543 let prefix1 = Path::from("foo/bar");
544 let prefix2 = Path::from(String::from("foo/bar"));
545 let s = String::from("foo/bar");
546 let prefix3 = Path::from(&s);
547
548 assert_eq!(prefix1.as_str(), "foo/bar");
549 assert_eq!(prefix2.as_str(), "foo/bar");
550 assert_eq!(prefix3.as_str(), "foo/bar");
551 }
552
553 #[test]
554 fn test_path_suffix_conversions() {
555 let suffix1 = Path::from("foo/bar");
556 let suffix2 = Path::from(String::from("foo/bar"));
557 let s = String::from("foo/bar");
558 let suffix3 = Path::from(&s);
559
560 assert_eq!(suffix1.as_str(), "foo/bar");
561 assert_eq!(suffix2.as_str(), "foo/bar");
562 assert_eq!(suffix3.as_str(), "foo/bar");
563 }
564
565 #[test]
566 fn test_path_types_basic_operations() {
567 let prefix = Path::new("foo/bar");
568 assert_eq!(prefix.as_str(), "foo/bar");
569 assert!(!prefix.is_empty());
570 assert_eq!(prefix.len(), 7);
571
572 let suffix = Path::new("baz/qux");
573 assert_eq!(suffix.as_str(), "baz/qux");
574 assert!(!suffix.is_empty());
575 assert_eq!(suffix.len(), 7);
576
577 let empty_prefix = Path::new("");
578 assert!(empty_prefix.is_empty());
579 assert_eq!(empty_prefix.len(), 0);
580
581 let empty_suffix = Path::new("");
582 assert!(empty_suffix.is_empty());
583 assert_eq!(empty_suffix.len(), 0);
584 }
585
586 #[test]
587 fn test_prefix_has_prefix() {
588 let prefix = Path::new("foo/bar");
590 assert!(prefix.has_prefix(""));
591
592 let prefix = Path::new("foo/bar");
594 assert!(prefix.has_prefix("foo/bar"));
595
596 assert!(prefix.has_prefix("foo"));
598 assert!(prefix.has_prefix("foo/"));
599
600 assert!(!prefix.has_prefix("f"));
602 assert!(!prefix.has_prefix("fo"));
603 assert!(!prefix.has_prefix("foo/b"));
604 assert!(!prefix.has_prefix("foo/ba"));
605
606 let prefix = Path::new("foobar");
608 assert!(!prefix.has_prefix("foo"));
609 assert!(prefix.has_prefix("foobar"));
610
611 let prefix = Path::new("foo/bar/");
613 assert!(prefix.has_prefix("foo"));
614 assert!(prefix.has_prefix("foo/"));
615 assert!(prefix.has_prefix("foo/bar"));
616 assert!(prefix.has_prefix("foo/bar/"));
617
618 let prefix = Path::new("foo");
620 assert!(prefix.has_prefix(""));
621 assert!(prefix.has_prefix("foo"));
622 assert!(prefix.has_prefix("foo/")); assert!(!prefix.has_prefix("f"));
624
625 let prefix = Path::new("");
627 assert!(prefix.has_prefix(""));
628 assert!(!prefix.has_prefix("foo"));
629 }
630
631 #[test]
632 fn test_prefix_join() {
633 let prefix = Path::new("foo");
635 let suffix = Path::new("bar");
636 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
637
638 let prefix = Path::new("foo/");
640 let suffix = Path::new("bar");
641 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
642
643 let prefix = Path::new("foo");
645 let suffix = Path::new("/bar");
646 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
647
648 let prefix = Path::new("foo");
650 let suffix = Path::new("bar/");
651 assert_eq!(prefix.join(suffix).as_str(), "foo/bar"); let prefix = Path::new("foo/");
655 let suffix = Path::new("/bar");
656 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
657
658 let prefix = Path::new("foo");
660 let suffix = Path::new("");
661 assert_eq!(prefix.join(suffix).as_str(), "foo");
662
663 let prefix = Path::new("");
665 let suffix = Path::new("bar");
666 assert_eq!(prefix.join(suffix).as_str(), "bar");
667
668 let prefix = Path::new("");
670 let suffix = Path::new("");
671 assert_eq!(prefix.join(suffix).as_str(), "");
672
673 let prefix = Path::new("foo/bar");
675 let suffix = Path::new("baz/qux");
676 assert_eq!(prefix.join(suffix).as_str(), "foo/bar/baz/qux");
677
678 let prefix = Path::new("foo/bar/");
680 let suffix = Path::new("/baz/qux/");
681 assert_eq!(prefix.join(suffix).as_str(), "foo/bar/baz/qux"); }
683
684 #[test]
685 fn test_path_ref() {
686 let ref1 = Path::new("/foo/bar/");
688 assert_eq!(ref1.as_str(), "foo/bar");
689
690 let ref2 = Path::from("///foo///");
691 assert_eq!(ref2.as_str(), "foo");
692
693 let ref3 = Path::new("foo//bar///baz");
695 assert_eq!(ref3.as_str(), "foo/bar/baz");
696
697 let path = Path::new("foo/bar");
699 let path_ref = path;
700 assert_eq!(path_ref.as_str(), "foo/bar");
701
702 let path2 = Path::new("foo/bar/baz");
704 assert!(path2.has_prefix(&path_ref));
705 assert_eq!(path2.strip_prefix(path_ref).unwrap().as_str(), "baz");
706
707 let empty = Path::new("");
709 assert!(empty.is_empty());
710 assert_eq!(empty.len(), 0);
711 }
712
713 #[test]
714 fn test_multiple_consecutive_slashes() {
715 let path = Path::new("foo//bar///baz");
716 assert_eq!(path.as_str(), "foo/bar/baz");
718
719 let path2 = Path::new("//foo//bar///baz//");
721 assert_eq!(path2.as_str(), "foo/bar/baz");
722
723 let path3 = Path::new("foo///bar");
725 assert_eq!(path3.as_str(), "foo/bar");
726 }
727
728 #[test]
729 fn test_removes_multiple_slashes_comprehensively() {
730 assert_eq!(Path::new("foo//bar").as_str(), "foo/bar");
732 assert_eq!(Path::new("foo///bar").as_str(), "foo/bar");
733 assert_eq!(Path::new("foo////bar").as_str(), "foo/bar");
734
735 assert_eq!(Path::new("foo//bar//baz").as_str(), "foo/bar/baz");
737 assert_eq!(Path::new("a//b//c//d").as_str(), "a/b/c/d");
738
739 assert_eq!(Path::new("foo//bar///baz////qux").as_str(), "foo/bar/baz/qux");
741
742 assert_eq!(Path::new("//foo//bar//").as_str(), "foo/bar");
744 assert_eq!(Path::new("///foo///bar///").as_str(), "foo/bar");
745
746 assert_eq!(Path::new("//").as_str(), "");
748 assert_eq!(Path::new("////").as_str(), "");
749
750 let path_with_slashes = Path::new("foo//bar///baz");
752 assert!(path_with_slashes.has_prefix("foo/bar"));
753 assert_eq!(path_with_slashes.strip_prefix("foo").unwrap().as_str(), "bar/baz");
754 assert_eq!(path_with_slashes.join("qux").as_str(), "foo/bar/baz/qux");
755
756 let path_ref = Path::new("foo//bar///baz");
758 assert_eq!(path_ref.as_str(), "foo/bar/baz"); let path_from_ref = path_ref.to_owned();
760 assert_eq!(path_from_ref.as_str(), "foo/bar/baz"); }
762
763 #[test]
764 fn test_path_ref_multiple_slashes() {
765 let path_ref = Path::new("//foo//bar///baz//");
767 assert_eq!(path_ref.as_str(), "foo/bar/baz"); assert_eq!(Path::new("foo//bar").as_str(), "foo/bar");
771 assert_eq!(Path::new("foo///bar").as_str(), "foo/bar");
772 assert_eq!(Path::new("a//b//c//d").as_str(), "a/b/c/d");
773
774 assert_eq!(Path::new("foo//bar").to_owned().as_str(), "foo/bar");
776 assert_eq!(Path::new("foo///bar").to_owned().as_str(), "foo/bar");
777 assert_eq!(Path::new("a//b//c//d").to_owned().as_str(), "a/b/c/d");
778
779 assert_eq!(Path::new("//").as_str(), "");
781 assert_eq!(Path::new("////").as_str(), "");
782 assert_eq!(Path::new("//").to_owned().as_str(), "");
783 assert_eq!(Path::new("////").to_owned().as_str(), "");
784
785 let normal_path = Path::new("foo/bar/baz");
787 assert_eq!(normal_path.as_str(), "foo/bar/baz");
788 let needs_norm = Path::new("foo//bar");
791 assert_eq!(needs_norm.as_str(), "foo/bar");
792 }
794
795 #[test]
796 fn test_ergonomic_conversions() {
797 fn takes_path_ref<'a>(p: impl Into<Path<'a>>) -> String {
799 p.into().as_str().to_string()
800 }
801
802 fn takes_path_ref_with_trait<'a>(p: impl Into<Path<'a>>) -> String {
804 p.into().as_str().to_string()
805 }
806
807 assert_eq!(takes_path_ref("foo//bar"), "foo/bar");
809
810 let owned_string = String::from("foo//bar///baz");
812 assert_eq!(takes_path_ref(owned_string), "foo/bar/baz");
813
814 let string_ref = String::from("foo//bar");
816 assert_eq!(takes_path_ref(string_ref), "foo/bar");
817
818 let path_ref = Path::new("foo//bar");
820 assert_eq!(takes_path_ref(path_ref), "foo/bar");
821
822 let path = Path::new("foo//bar");
824 assert_eq!(takes_path_ref(path), "foo/bar");
825
826 let _path1 = Path::new("foo/bar"); let _path2 = Path::new("foo/bar"); let _path3 = Path::new("foo/bar"); let _path4 = Path::new("foo/bar"); assert_eq!(takes_path_ref_with_trait("foo//bar"), "foo/bar");
834 assert_eq!(takes_path_ref_with_trait(String::from("foo//bar")), "foo/bar");
835 }
836
837 #[test]
838 fn test_prefix_strip_prefix() {
839 let prefix = Path::new("foo/bar/baz");
841 assert_eq!(prefix.strip_prefix("").unwrap().as_str(), "foo/bar/baz");
842 assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "bar/baz");
843 assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), "bar/baz");
844 assert_eq!(prefix.strip_prefix("foo/bar").unwrap().as_str(), "baz");
845 assert_eq!(prefix.strip_prefix("foo/bar/").unwrap().as_str(), "baz");
846 assert_eq!(prefix.strip_prefix("foo/bar/baz").unwrap().as_str(), "");
847
848 assert!(prefix.strip_prefix("fo").is_none());
850 assert!(prefix.strip_prefix("bar").is_none());
851 assert!(prefix.strip_prefix("foo/ba").is_none());
852
853 let prefix = Path::new("foobar");
855 assert!(prefix.strip_prefix("foo").is_none());
856 assert_eq!(prefix.strip_prefix("foobar").unwrap().as_str(), "");
857
858 let prefix = Path::new("");
860 assert_eq!(prefix.strip_prefix("").unwrap().as_str(), "");
861 assert!(prefix.strip_prefix("foo").is_none());
862
863 let prefix = Path::new("foo");
865 assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "");
866 assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), ""); let prefix = Path::new("foo/bar/");
870 assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "bar");
871 assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), "bar");
872 assert_eq!(prefix.strip_prefix("foo/bar").unwrap().as_str(), "");
873 assert_eq!(prefix.strip_prefix("foo/bar/").unwrap().as_str(), "");
874 }
875
876 #[test]
877 fn test_prefix_list_dedup() {
878 let list = PathPrefixes::new(["demo", "demo"]);
880 assert_eq!(list.len(), 1);
881 assert_eq!(list[0], Path::new("demo"));
882 }
883
884 #[test]
885 fn test_prefix_list_overlap() {
886 let list = PathPrefixes::new(["demo", "demo/foo", "anon"]);
888 assert_eq!(list.len(), 2);
889 assert!(list.iter().any(|p| p == &Path::new("demo")));
890 assert!(list.iter().any(|p| p == &Path::new("anon")));
891 }
892
893 #[test]
894 fn test_prefix_list_overlap_reverse_order() {
895 let list = PathPrefixes::new(["demo/foo", "demo"]);
897 assert_eq!(list.len(), 1);
898 assert_eq!(list[0], Path::new("demo"));
899 }
900
901 #[test]
902 fn test_prefix_list_empty_covers_all() {
903 let list = PathPrefixes::new(["", "demo", "anon"]);
905 assert_eq!(list.len(), 1);
906 assert_eq!(list[0], Path::new(""));
907 }
908
909 #[test]
910 fn test_prefix_list_no_overlap() {
911 let list = PathPrefixes::new(["demo", "anon", "secret"]);
913 assert_eq!(list.len(), 3);
914 }
915
916 #[test]
917 fn test_prefix_list_single() {
918 let list = PathPrefixes::new(["demo"]);
919 assert_eq!(list.len(), 1);
920 }
921
922 #[test]
923 fn test_prefix_list_empty() {
924 let list = PathPrefixes::new(std::iter::empty::<&str>());
925 assert!(list.is_empty());
926 assert_eq!(list.len(), 0);
927 }
928
929 #[test]
930 fn test_prefix_list_deep_overlap() {
931 let list = PathPrefixes::new(["a/b/c", "a/b", "a"]);
933 assert_eq!(list.len(), 1);
934 assert_eq!(list[0], Path::new("a"));
935 }
936
937 #[test]
938 fn test_prefix_list_partial_name_not_overlap() {
939 let list = PathPrefixes::new(["demo", "demonstration"]);
941 assert_eq!(list.len(), 2);
942 }
943
944 #[test]
945 fn test_prefix_list_collect() {
946 let paths: Vec<PathOwned> = vec!["demo".into(), "demo/foo".into()];
947 let list: PathPrefixes = paths.into_iter().collect();
948 assert_eq!(list.len(), 1);
949 assert_eq!(list[0], Path::new("demo"));
950 }
951
952 #[test]
953 fn test_prefix_list_eq_vec() {
954 let list = PathPrefixes::new(["demo", "anon"]);
955 assert_eq!(list, vec!["anon".as_path(), "demo".as_path()]);
957 }
958
959 #[test]
960 fn test_prefix_list_canonical_order() {
961 let a = PathPrefixes::new(["foo", "bar"]);
963 let b = PathPrefixes::new(["bar", "foo"]);
964 assert_eq!(a, b);
965 }
966}