1pub use moq_pattern::{InvalidPattern, Pattern, Patterns, Segment, Specificity};
12
13use std::borrow::Cow;
14use std::fmt::{self, Display};
15use std::sync::Arc;
16
17use crate::coding::{Decode, DecodeError, Encode, EncodeError};
18
19pub type PathOwned = Path<'static>;
21
22pub trait AsPath {
27 fn as_path(&self) -> Path<'_>;
29}
30
31impl<'a> AsPath for &'a str {
32 fn as_path(&self) -> Path<'a> {
33 Path::new(self)
34 }
35}
36
37impl<'a> AsPath for &'a Path<'a> {
38 fn as_path(&self) -> Path<'a> {
39 self.borrow()
41 }
42}
43
44impl AsPath for Path<'_> {
45 fn as_path(&self) -> Path<'_> {
46 self.borrow()
47 }
48}
49
50impl AsPath for String {
51 fn as_path(&self) -> Path<'_> {
52 Path::new(self)
53 }
54}
55
56impl<'a> AsPath for &'a String {
57 fn as_path(&self) -> Path<'a> {
58 Path::new(self)
59 }
60}
61
62#[derive(Clone)]
68enum Repr<'a> {
69 Borrowed(&'a str),
70 Shared { buf: Arc<str>, start: usize },
71}
72
73#[derive(Clone)]
106pub struct Path<'a>(Repr<'a>);
107
108impl<'a> Path<'a> {
109 pub const MAX_PARTS: usize = 32;
115
116 pub fn new(s: &'a str) -> Self {
121 let trimmed = s.trim_start_matches('/').trim_end_matches('/');
122
123 if trimmed.contains("//") {
125 let normalized = trimmed
127 .split('/')
128 .filter(|s| !s.is_empty())
129 .collect::<Vec<_>>()
130 .join("/");
131 Self(Repr::Shared {
132 buf: normalized.into(),
133 start: 0,
134 })
135 } else {
136 Self(Repr::Borrowed(trimmed))
138 }
139 }
140
141 pub(crate) fn from_escaped(s: String) -> PathOwned {
142 if s.is_empty() {
143 Path::empty()
144 } else {
145 Path(Repr::Shared {
146 buf: s.into(),
147 start: 0,
148 })
149 }
150 }
151
152 fn slice_from(&'a self, n: usize) -> Path<'a> {
154 match &self.0 {
155 Repr::Borrowed(s) => Path(Repr::Borrowed(&s[n..])),
156 Repr::Shared { buf, start } => Path(Repr::Shared {
157 buf: buf.clone(),
158 start: start + n,
159 }),
160 }
161 }
162
163 pub fn has_prefix(&self, prefix: impl AsPath) -> bool {
185 let prefix = prefix.as_path();
186
187 if prefix.is_empty() {
188 return true;
189 }
190
191 let s = self.as_str();
192 if !s.starts_with(prefix.as_str()) {
193 return false;
194 }
195
196 if s.len() == prefix.len() {
198 return true;
199 }
200
201 s.as_bytes().get(prefix.len()) == Some(&b'/')
203 }
204
205 pub fn strip_prefix(&'a self, prefix: impl AsPath) -> Option<Path<'a>> {
210 let prefix = prefix.as_path();
211
212 if prefix.is_empty() {
213 return Some(self.borrow());
214 }
215
216 let s = self.as_str();
217 if !s.starts_with(prefix.as_str()) {
218 return None;
219 }
220
221 if s.len() == prefix.len() {
223 return Some(Path::empty());
224 }
225
226 if s.as_bytes().get(prefix.len()) != Some(&b'/') {
228 return None;
229 }
230
231 Some(self.slice_from(prefix.len() + 1))
232 }
233
234 pub fn parts(&self) -> impl Iterator<Item = &str> {
247 self.as_str().split('/').filter(|part| !part.is_empty())
250 }
251
252 pub fn next_part(&'a self) -> Option<(&'a str, Path<'a>)> {
254 let s = self.as_str();
255 if s.is_empty() {
256 return None;
257 }
258
259 if let Some(i) = s.find('/') {
260 Some((&s[..i], self.slice_from(i + 1)))
261 } else {
262 Some((s, Path::empty()))
263 }
264 }
265
266 pub(crate) fn is_hidden(&self) -> bool {
269 self.parts().any(|part| part.starts_with('.'))
270 }
271
272 pub fn as_str(&self) -> &str {
274 match &self.0 {
275 Repr::Borrowed(s) => s,
276 Repr::Shared { buf, start } => &buf[*start..],
277 }
278 }
279
280 pub fn empty() -> Path<'static> {
282 Path(Repr::Borrowed(""))
283 }
284
285 pub fn is_empty(&self) -> bool {
287 self.as_str().is_empty()
288 }
289
290 pub fn len(&self) -> usize {
292 self.as_str().len()
293 }
294
295 pub fn to_owned(&self) -> PathOwned {
297 match &self.0 {
298 Repr::Borrowed("") => Path::empty(),
299 Repr::Borrowed(s) => Path(Repr::Shared {
300 buf: Arc::from(*s),
301 start: 0,
302 }),
303 Repr::Shared { buf, start } => Path(Repr::Shared {
304 buf: buf.clone(),
305 start: *start,
306 }),
307 }
308 }
309
310 pub fn into_owned(self) -> PathOwned {
312 match self.0 {
313 Repr::Borrowed("") => Path::empty(),
314 Repr::Borrowed(s) => Path(Repr::Shared {
315 buf: Arc::from(s),
316 start: 0,
317 }),
318 Repr::Shared { buf, start } => Path(Repr::Shared { buf, start }),
319 }
320 }
321
322 pub fn borrow(&'a self) -> Path<'a> {
324 self.slice_from(0)
325 }
326
327 pub fn join(&self, other: impl AsPath) -> PathOwned {
341 let other = other.as_path();
342
343 if self.is_empty() {
344 other.to_owned()
345 } else if other.is_empty() {
346 self.to_owned()
347 } else {
348 Path(Repr::Shared {
350 buf: format!("{}/{}", self.as_str(), other.as_str()).into(),
351 start: 0,
352 })
353 }
354 }
355
356 pub fn resolve(&self, rel: &Relative<'_>) -> PathOwned {
376 if rel.is_empty() {
377 return self.to_owned();
378 }
379
380 let mut segments: Vec<&str> = self.parts().collect();
381 segments.pop();
382
383 for seg in rel.as_str().split('/') {
384 if seg == "." {
385 continue;
386 } else if seg == ".." {
387 segments.pop();
388 } else {
389 segments.push(seg);
390 }
391 }
392
393 let path = segments.join("/");
394 if path.is_empty() {
395 Path::empty()
396 } else {
397 Path(Repr::Shared {
398 buf: path.into(),
399 start: 0,
400 })
401 }
402 }
403
404 pub fn try_resolve(&self, rel: &Relative<'_>) -> Option<PathOwned> {
410 if rel.is_empty() {
411 return Some(self.to_owned());
412 }
413
414 let mut segments: Vec<&str> = self.parts().collect();
415 segments.pop();
416
417 for seg in rel.as_str().split('/') {
418 if seg == "." {
419 continue;
420 } else if seg == ".." {
421 segments.pop()?;
422 } else {
423 segments.push(seg);
424 }
425 }
426
427 let path = segments.join("/");
428 if path.is_empty() {
429 Some(Path::empty())
430 } else {
431 Some(Path(Repr::Shared {
432 buf: path.into(),
433 start: 0,
434 }))
435 }
436 }
437
438 pub fn relative(&self, base: impl AsPath) -> Option<RelativeOwned> {
472 let base = base.as_path();
473
474 if *self == base {
477 return Some(Relative::empty());
478 }
479
480 let mut dir: Vec<&str> = base.parts().collect();
482 dir.pop();
483
484 let target: Vec<&str> = self.parts().collect();
485 let common = dir.iter().zip(&target).take_while(|(a, b)| a == b).count();
486
487 let down = &target[common..];
488 if down.iter().any(|part| *part == "." || *part == "..") {
489 return None;
491 }
492
493 let mut rel: Vec<&str> = vec![".."; dir.len() - common];
494 rel.extend(down);
495
496 if rel.is_empty() {
497 return Some(Relative::new("."));
499 }
500
501 Some(RelativeOwned::from(rel.join("/")))
502 }
503}
504
505impl<'b> PartialEq<Path<'b>> for Path<'_> {
508 fn eq(&self, other: &Path<'b>) -> bool {
509 self.as_str() == other.as_str()
510 }
511}
512
513impl Eq for Path<'_> {}
514
515impl PartialOrd for Path<'_> {
516 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
517 Some(self.cmp(other))
518 }
519}
520
521impl Ord for Path<'_> {
522 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
523 self.as_str().cmp(other.as_str())
524 }
525}
526
527impl std::hash::Hash for Path<'_> {
528 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
529 self.as_str().hash(state)
530 }
531}
532
533impl fmt::Debug for Path<'_> {
534 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
535 f.debug_tuple("Path").field(&self.as_str()).finish()
536 }
537}
538
539impl serde::Serialize for Path<'_> {
540 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
541 serializer.serialize_str(self.as_str())
542 }
543}
544
545impl<'a> From<&'a str> for Path<'a> {
546 fn from(s: &'a str) -> Self {
547 Self::new(s)
548 }
549}
550
551impl<'a> From<&'a String> for Path<'a> {
552 fn from(s: &'a String) -> Self {
553 Self::new(s)
555 }
556}
557
558impl Default for Path<'_> {
559 fn default() -> Self {
560 Path::empty()
561 }
562}
563
564impl From<String> for Path<'_> {
565 fn from(s: String) -> Self {
566 Path::new(&s).into_owned()
567 }
568}
569
570impl AsRef<str> for Path<'_> {
571 fn as_ref(&self) -> &str {
572 self.as_str()
573 }
574}
575
576impl Display for Path<'_> {
577 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
578 write!(f, "{}", self.as_str())
579 }
580}
581
582impl<V: Copy> Decode<V> for Path<'_>
583where
584 String: Decode<V>,
585{
586 fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
587 let path: Path = String::decode(r, version)?.into();
588 if path.parts().count() > Path::MAX_PARTS {
589 return Err(DecodeError::BoundsExceeded);
590 }
591 Ok(path)
592 }
593}
594
595impl<V: Copy> Encode<V> for Path<'_>
596where
597 for<'a> &'a str: Encode<V>,
598{
599 fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
600 if self.parts().count() > Path::MAX_PARTS {
601 return Err(EncodeError::BoundsExceeded);
602 }
603 self.as_str().encode(w, version)?;
604 Ok(())
605 }
606}
607
608pub type RelativeOwned = Relative<'static>;
610
611#[derive(Debug, PartialEq, Eq, Hash, Clone, serde::Serialize)]
639pub struct Relative<'a>(Cow<'a, str>);
640
641impl<'a> Relative<'a> {
642 pub fn new(s: &'a str) -> Self {
647 let trimmed = s.trim_start_matches('/').trim_end_matches('/');
648
649 if needs_normalize_relative(trimmed) {
650 Self(Cow::Owned(normalize_relative_segments(trimmed)))
651 } else {
652 Self(Cow::Borrowed(trimmed))
653 }
654 }
655
656 pub fn as_str(&self) -> &str {
658 &self.0
659 }
660
661 pub fn empty() -> Relative<'static> {
663 Relative(Cow::Borrowed(""))
664 }
665
666 pub fn is_empty(&self) -> bool {
668 self.0.is_empty()
669 }
670
671 pub fn len(&self) -> usize {
673 self.0.len()
674 }
675
676 pub fn to_owned(&self) -> RelativeOwned {
678 Relative(Cow::Owned(self.0.to_string()))
679 }
680
681 pub fn into_owned(self) -> RelativeOwned {
683 Relative(Cow::Owned(self.0.into_owned()))
684 }
685
686 pub fn borrow(&'a self) -> Relative<'a> {
688 Relative(Cow::Borrowed(&self.0))
689 }
690}
691
692impl<'a> From<&'a str> for Relative<'a> {
693 fn from(s: &'a str) -> Self {
694 Self::new(s)
695 }
696}
697
698impl<'a> From<&'a String> for Relative<'a> {
699 fn from(s: &'a String) -> Self {
700 Self::new(s)
701 }
702}
703
704impl From<String> for Relative<'_> {
705 fn from(s: String) -> Self {
706 let trimmed = s.trim_start_matches('/').trim_end_matches('/');
707
708 if needs_normalize_relative(trimmed) {
709 Self(Cow::Owned(normalize_relative_segments(trimmed)))
710 } else if trimmed == s {
711 Self(Cow::Owned(s))
712 } else {
713 Self(Cow::Owned(trimmed.to_string()))
714 }
715 }
716}
717
718fn needs_normalize_relative(trimmed: &str) -> bool {
719 trimmed.split('/').any(|seg| seg.is_empty() || seg == ".")
720}
721
722fn normalize_relative_segments(trimmed: &str) -> String {
723 let segments = trimmed
724 .split('/')
725 .filter(|seg| !seg.is_empty() && *seg != ".")
726 .collect::<Vec<_>>()
727 .join("/");
728
729 if segments.is_empty() && trimmed.split('/').any(|seg| seg == ".") {
730 ".".to_string()
731 } else {
732 segments
733 }
734}
735
736impl Default for Relative<'_> {
737 fn default() -> Self {
738 Self(Cow::Borrowed(""))
739 }
740}
741
742impl AsRef<str> for Relative<'_> {
743 fn as_ref(&self) -> &str {
744 &self.0
745 }
746}
747
748impl Display for Relative<'_> {
749 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
750 write!(f, "{}", self.0)
751 }
752}
753
754impl<'de> serde::Deserialize<'de> for Relative<'static> {
758 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
759 where
760 D: serde::Deserializer<'de>,
761 {
762 let s = String::deserialize(deserializer)?;
763 Ok(Relative::from(s))
764 }
765}
766
767#[cfg(test)]
768mod tests {
769 use super::*;
770
771 #[test]
772 fn test_has_prefix() {
773 let path = Path::new("foo/bar/baz");
774
775 assert!(path.has_prefix(""));
777 assert!(path.has_prefix("foo"));
778 assert!(path.has_prefix(Path::new("foo")));
779 assert!(path.has_prefix("foo/"));
780 assert!(path.has_prefix("foo/bar"));
781 assert!(path.has_prefix(Path::new("foo/bar/")));
782 assert!(path.has_prefix("foo/bar/baz"));
783
784 assert!(!path.has_prefix("f"));
786 assert!(!path.has_prefix(Path::new("fo")));
787 assert!(!path.has_prefix("foo/b"));
788 assert!(!path.has_prefix("foo/ba"));
789 assert!(!path.has_prefix(Path::new("foo/bar/ba")));
790
791 let path = Path::new("foobar");
793 assert!(!path.has_prefix("foo"));
794 assert!(path.has_prefix(Path::new("foobar")));
795 }
796
797 #[test]
798 fn test_strip_prefix() {
799 let path = Path::new("foo/bar/baz");
800
801 assert_eq!(path.strip_prefix("").unwrap().as_str(), "foo/bar/baz");
803 assert_eq!(path.strip_prefix("foo").unwrap().as_str(), "bar/baz");
804 assert_eq!(path.strip_prefix(Path::new("foo/")).unwrap().as_str(), "bar/baz");
805 assert_eq!(path.strip_prefix("foo/bar").unwrap().as_str(), "baz");
806 assert_eq!(path.strip_prefix(Path::new("foo/bar/")).unwrap().as_str(), "baz");
807 assert_eq!(path.strip_prefix("foo/bar/baz").unwrap().as_str(), "");
808
809 assert!(path.strip_prefix("fo").is_none());
811 assert!(path.strip_prefix(Path::new("bar")).is_none());
812 }
813
814 #[test]
815 fn test_join() {
816 assert_eq!(Path::new("foo").join("bar").as_str(), "foo/bar");
818 assert_eq!(Path::new("foo/").join(Path::new("bar")).as_str(), "foo/bar");
819 assert_eq!(Path::new("").join("bar").as_str(), "bar");
820 assert_eq!(Path::new("foo/bar").join(Path::new("baz")).as_str(), "foo/bar/baz");
821 }
822
823 #[test]
824 fn test_empty() {
825 let empty = Path::new("");
826 assert!(empty.is_empty());
827 assert_eq!(empty.len(), 0);
828
829 let non_empty = Path::new("foo");
830 assert!(!non_empty.is_empty());
831 assert_eq!(non_empty.len(), 3);
832 }
833
834 #[test]
835 fn test_from_conversions() {
836 let path1 = Path::from("foo/bar");
837 let path2 = Path::from("foo/bar");
838 let s = String::from("foo/bar");
839 let path3 = Path::from(&s);
840
841 assert_eq!(path1.as_str(), "foo/bar");
842 assert_eq!(path2.as_str(), "foo/bar");
843 assert_eq!(path3.as_str(), "foo/bar");
844 }
845
846 #[test]
847 fn test_path_prefix_join() {
848 let prefix = Path::new("foo");
849 let suffix = Path::new("bar/baz");
850 let path = prefix.join(&suffix);
851 assert_eq!(path.as_str(), "foo/bar/baz");
852
853 let prefix = Path::new("foo/");
854 let suffix = Path::new("bar/baz");
855 let path = prefix.join(&suffix);
856 assert_eq!(path.as_str(), "foo/bar/baz");
857
858 let prefix = Path::new("foo");
859 let suffix = Path::new("/bar/baz");
860 let path = prefix.join(&suffix);
861 assert_eq!(path.as_str(), "foo/bar/baz");
862
863 let prefix = Path::new("");
864 let suffix = Path::new("bar/baz");
865 let path = prefix.join(&suffix);
866 assert_eq!(path.as_str(), "bar/baz");
867 }
868
869 #[test]
870 fn test_path_prefix_conversions() {
871 let prefix1 = Path::from("foo/bar");
872 let prefix2 = Path::from(String::from("foo/bar"));
873 let s = String::from("foo/bar");
874 let prefix3 = Path::from(&s);
875
876 assert_eq!(prefix1.as_str(), "foo/bar");
877 assert_eq!(prefix2.as_str(), "foo/bar");
878 assert_eq!(prefix3.as_str(), "foo/bar");
879 }
880
881 #[test]
882 fn test_path_suffix_conversions() {
883 let suffix1 = Path::from("foo/bar");
884 let suffix2 = Path::from(String::from("foo/bar"));
885 let s = String::from("foo/bar");
886 let suffix3 = Path::from(&s);
887
888 assert_eq!(suffix1.as_str(), "foo/bar");
889 assert_eq!(suffix2.as_str(), "foo/bar");
890 assert_eq!(suffix3.as_str(), "foo/bar");
891 }
892
893 #[test]
894 fn test_path_types_basic_operations() {
895 let prefix = Path::new("foo/bar");
896 assert_eq!(prefix.as_str(), "foo/bar");
897 assert!(!prefix.is_empty());
898 assert_eq!(prefix.len(), 7);
899
900 let suffix = Path::new("baz/qux");
901 assert_eq!(suffix.as_str(), "baz/qux");
902 assert!(!suffix.is_empty());
903 assert_eq!(suffix.len(), 7);
904
905 let empty_prefix = Path::new("");
906 assert!(empty_prefix.is_empty());
907 assert_eq!(empty_prefix.len(), 0);
908
909 let empty_suffix = Path::new("");
910 assert!(empty_suffix.is_empty());
911 assert_eq!(empty_suffix.len(), 0);
912 }
913
914 #[test]
915 fn test_prefix_has_prefix() {
916 let prefix = Path::new("foo/bar");
918 assert!(prefix.has_prefix(""));
919
920 let prefix = Path::new("foo/bar");
922 assert!(prefix.has_prefix("foo/bar"));
923
924 assert!(prefix.has_prefix("foo"));
926 assert!(prefix.has_prefix("foo/"));
927
928 assert!(!prefix.has_prefix("f"));
930 assert!(!prefix.has_prefix("fo"));
931 assert!(!prefix.has_prefix("foo/b"));
932 assert!(!prefix.has_prefix("foo/ba"));
933
934 let prefix = Path::new("foobar");
936 assert!(!prefix.has_prefix("foo"));
937 assert!(prefix.has_prefix("foobar"));
938
939 let prefix = Path::new("foo/bar/");
941 assert!(prefix.has_prefix("foo"));
942 assert!(prefix.has_prefix("foo/"));
943 assert!(prefix.has_prefix("foo/bar"));
944 assert!(prefix.has_prefix("foo/bar/"));
945
946 let prefix = Path::new("foo");
948 assert!(prefix.has_prefix(""));
949 assert!(prefix.has_prefix("foo"));
950 assert!(prefix.has_prefix("foo/")); assert!(!prefix.has_prefix("f"));
952
953 let prefix = Path::new("");
955 assert!(prefix.has_prefix(""));
956 assert!(!prefix.has_prefix("foo"));
957 }
958
959 #[test]
960 fn test_prefix_join() {
961 let prefix = Path::new("foo");
963 let suffix = Path::new("bar");
964 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
965
966 let prefix = Path::new("foo/");
968 let suffix = Path::new("bar");
969 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
970
971 let prefix = Path::new("foo");
973 let suffix = Path::new("/bar");
974 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
975
976 let prefix = Path::new("foo");
978 let suffix = Path::new("bar/");
979 assert_eq!(prefix.join(suffix).as_str(), "foo/bar"); let prefix = Path::new("foo/");
983 let suffix = Path::new("/bar");
984 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
985
986 let prefix = Path::new("foo");
988 let suffix = Path::new("");
989 assert_eq!(prefix.join(suffix).as_str(), "foo");
990
991 let prefix = Path::new("");
993 let suffix = Path::new("bar");
994 assert_eq!(prefix.join(suffix).as_str(), "bar");
995
996 let prefix = Path::new("");
998 let suffix = Path::new("");
999 assert_eq!(prefix.join(suffix).as_str(), "");
1000
1001 let prefix = Path::new("foo/bar");
1003 let suffix = Path::new("baz/qux");
1004 assert_eq!(prefix.join(suffix).as_str(), "foo/bar/baz/qux");
1005
1006 let prefix = Path::new("foo/bar/");
1008 let suffix = Path::new("/baz/qux/");
1009 assert_eq!(prefix.join(suffix).as_str(), "foo/bar/baz/qux"); }
1011
1012 #[test]
1013 fn test_path_ref() {
1014 let ref1 = Path::new("/foo/bar/");
1016 assert_eq!(ref1.as_str(), "foo/bar");
1017
1018 let ref2 = Path::from("///foo///");
1019 assert_eq!(ref2.as_str(), "foo");
1020
1021 let ref3 = Path::new("foo//bar///baz");
1023 assert_eq!(ref3.as_str(), "foo/bar/baz");
1024
1025 let path = Path::new("foo/bar");
1027 let path_ref = path;
1028 assert_eq!(path_ref.as_str(), "foo/bar");
1029
1030 let path2 = Path::new("foo/bar/baz");
1032 assert!(path2.has_prefix(&path_ref));
1033 assert_eq!(path2.strip_prefix(path_ref).unwrap().as_str(), "baz");
1034
1035 let empty = Path::new("");
1037 assert!(empty.is_empty());
1038 assert_eq!(empty.len(), 0);
1039 }
1040
1041 #[test]
1042 fn test_multiple_consecutive_slashes() {
1043 let path = Path::new("foo//bar///baz");
1044 assert_eq!(path.as_str(), "foo/bar/baz");
1046
1047 let path2 = Path::new("//foo//bar///baz//");
1049 assert_eq!(path2.as_str(), "foo/bar/baz");
1050
1051 let path3 = Path::new("foo///bar");
1053 assert_eq!(path3.as_str(), "foo/bar");
1054 }
1055
1056 #[test]
1057 fn test_removes_multiple_slashes_comprehensively() {
1058 assert_eq!(Path::new("foo//bar").as_str(), "foo/bar");
1060 assert_eq!(Path::new("foo///bar").as_str(), "foo/bar");
1061 assert_eq!(Path::new("foo////bar").as_str(), "foo/bar");
1062
1063 assert_eq!(Path::new("foo//bar//baz").as_str(), "foo/bar/baz");
1065 assert_eq!(Path::new("a//b//c//d").as_str(), "a/b/c/d");
1066
1067 assert_eq!(Path::new("foo//bar///baz////qux").as_str(), "foo/bar/baz/qux");
1069
1070 assert_eq!(Path::new("//foo//bar//").as_str(), "foo/bar");
1072 assert_eq!(Path::new("///foo///bar///").as_str(), "foo/bar");
1073
1074 assert_eq!(Path::new("//").as_str(), "");
1076 assert_eq!(Path::new("////").as_str(), "");
1077
1078 let path_with_slashes = Path::new("foo//bar///baz");
1080 assert!(path_with_slashes.has_prefix("foo/bar"));
1081 assert_eq!(path_with_slashes.strip_prefix("foo").unwrap().as_str(), "bar/baz");
1082 assert_eq!(path_with_slashes.join("qux").as_str(), "foo/bar/baz/qux");
1083
1084 let path_ref = Path::new("foo//bar///baz");
1086 assert_eq!(path_ref.as_str(), "foo/bar/baz"); let path_from_ref = path_ref.to_owned();
1088 assert_eq!(path_from_ref.as_str(), "foo/bar/baz"); }
1090
1091 #[test]
1092 fn test_path_ref_multiple_slashes() {
1093 let path_ref = Path::new("//foo//bar///baz//");
1095 assert_eq!(path_ref.as_str(), "foo/bar/baz"); assert_eq!(Path::new("foo//bar").as_str(), "foo/bar");
1099 assert_eq!(Path::new("foo///bar").as_str(), "foo/bar");
1100 assert_eq!(Path::new("a//b//c//d").as_str(), "a/b/c/d");
1101
1102 assert_eq!(Path::new("foo//bar").to_owned().as_str(), "foo/bar");
1104 assert_eq!(Path::new("foo///bar").to_owned().as_str(), "foo/bar");
1105 assert_eq!(Path::new("a//b//c//d").to_owned().as_str(), "a/b/c/d");
1106
1107 assert_eq!(Path::new("//").as_str(), "");
1109 assert_eq!(Path::new("////").as_str(), "");
1110 assert_eq!(Path::new("//").to_owned().as_str(), "");
1111 assert_eq!(Path::new("////").to_owned().as_str(), "");
1112
1113 let normal_path = Path::new("foo/bar/baz");
1115 assert_eq!(normal_path.as_str(), "foo/bar/baz");
1116 let needs_norm = Path::new("foo//bar");
1119 assert_eq!(needs_norm.as_str(), "foo/bar");
1120 }
1122
1123 #[test]
1124 fn test_ergonomic_conversions() {
1125 fn takes_path_ref<'a>(p: impl Into<Path<'a>>) -> String {
1127 p.into().as_str().to_string()
1128 }
1129
1130 fn takes_path_ref_with_trait<'a>(p: impl Into<Path<'a>>) -> String {
1132 p.into().as_str().to_string()
1133 }
1134
1135 assert_eq!(takes_path_ref("foo//bar"), "foo/bar");
1137
1138 let owned_string = String::from("foo//bar///baz");
1140 assert_eq!(takes_path_ref(owned_string), "foo/bar/baz");
1141
1142 let string_ref = String::from("foo//bar");
1144 assert_eq!(takes_path_ref(string_ref), "foo/bar");
1145
1146 let path_ref = Path::new("foo//bar");
1148 assert_eq!(takes_path_ref(path_ref), "foo/bar");
1149
1150 let path = Path::new("foo//bar");
1152 assert_eq!(takes_path_ref(path), "foo/bar");
1153
1154 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");
1162 assert_eq!(takes_path_ref_with_trait(String::from("foo//bar")), "foo/bar");
1163 }
1164
1165 #[test]
1166 fn test_prefix_strip_prefix() {
1167 let prefix = Path::new("foo/bar/baz");
1169 assert_eq!(prefix.strip_prefix("").unwrap().as_str(), "foo/bar/baz");
1170 assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "bar/baz");
1171 assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), "bar/baz");
1172 assert_eq!(prefix.strip_prefix("foo/bar").unwrap().as_str(), "baz");
1173 assert_eq!(prefix.strip_prefix("foo/bar/").unwrap().as_str(), "baz");
1174 assert_eq!(prefix.strip_prefix("foo/bar/baz").unwrap().as_str(), "");
1175
1176 assert!(prefix.strip_prefix("fo").is_none());
1178 assert!(prefix.strip_prefix("bar").is_none());
1179 assert!(prefix.strip_prefix("foo/ba").is_none());
1180
1181 let prefix = Path::new("foobar");
1183 assert!(prefix.strip_prefix("foo").is_none());
1184 assert_eq!(prefix.strip_prefix("foobar").unwrap().as_str(), "");
1185
1186 let prefix = Path::new("");
1188 assert_eq!(prefix.strip_prefix("").unwrap().as_str(), "");
1189 assert!(prefix.strip_prefix("foo").is_none());
1190
1191 let prefix = Path::new("foo");
1193 assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "");
1194 assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), ""); let prefix = Path::new("foo/bar/");
1198 assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "bar");
1199 assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), "bar");
1200 assert_eq!(prefix.strip_prefix("foo/bar").unwrap().as_str(), "");
1201 assert_eq!(prefix.strip_prefix("foo/bar/").unwrap().as_str(), "");
1202 }
1203
1204 #[test]
1207 fn test_owned_paths_share_allocation() {
1208 let path = Path::new("customer/room/broadcast").to_owned();
1209
1210 let cloned = path.clone();
1212 assert_eq!(path.as_str().as_ptr(), cloned.as_str().as_ptr());
1213
1214 let requeued = path.as_path().to_owned();
1216 assert_eq!(path.as_str().as_ptr(), requeued.as_str().as_ptr());
1217
1218 let stripped = path.strip_prefix("customer").unwrap().to_owned();
1220 assert_eq!(stripped.as_str(), "room/broadcast");
1221 assert_eq!(stripped.as_str().as_ptr(), path.as_str()["customer/".len()..].as_ptr());
1222
1223 let (dir, rest) = path.next_part().unwrap();
1225 assert_eq!(dir, "customer");
1226 let rest = rest.to_owned();
1227 assert_eq!(rest.as_str().as_ptr(), stripped.as_str().as_ptr());
1228
1229 let joined = path.join("alice");
1231 let joined2 = joined.clone();
1232 assert_eq!(joined.as_str(), "customer/room/broadcast/alice");
1233 assert_eq!(joined.as_str().as_ptr(), joined2.as_str().as_ptr());
1234 }
1235
1236 #[test]
1237 fn test_parts() {
1238 assert_eq!(Path::empty().parts().count(), 0);
1239 assert_eq!(Path::new("foo").parts().collect::<Vec<_>>(), ["foo"]);
1240 assert_eq!(Path::new("/foo//bar/").parts().collect::<Vec<_>>(), ["foo", "bar"]);
1241 }
1242
1243 #[test]
1244 fn test_wire_max_parts() {
1245 use crate::lite::Version;
1246
1247 let ok = (0..Path::MAX_PARTS)
1248 .map(|i| i.to_string())
1249 .collect::<Vec<_>>()
1250 .join("/");
1251 let too_deep = format!("{ok}/extra");
1252
1253 let mut buf = bytes::BytesMut::new();
1255 Path::new(&ok).encode(&mut buf, Version::Lite04).unwrap();
1256 assert!(matches!(
1257 Path::new(&too_deep).encode(&mut bytes::BytesMut::new(), Version::Lite04),
1258 Err(EncodeError::BoundsExceeded)
1259 ));
1260
1261 let decoded = Path::decode(&mut buf.freeze(), Version::Lite04).unwrap();
1263 assert_eq!(decoded.as_str(), ok);
1264
1265 let mut buf = bytes::BytesMut::new();
1267 too_deep.as_str().encode(&mut buf, Version::Lite04).unwrap();
1268 assert!(matches!(
1269 Path::decode(&mut buf.freeze(), Version::Lite04),
1270 Err(DecodeError::BoundsExceeded)
1271 ));
1272 }
1273
1274 #[test]
1275 fn test_owned_empty_paths() {
1276 let empty = Path::new("").to_owned();
1278 assert!(empty.is_empty());
1279 assert_eq!(empty, Path::empty());
1280
1281 let path = Path::new("foo").to_owned();
1282 let rest = path.strip_prefix("foo").unwrap().to_owned();
1283 assert!(rest.is_empty());
1284 }
1285
1286 #[test]
1287 fn test_path_relative_normalize() {
1288 assert_eq!(Relative::new("foo").as_str(), "foo");
1289 assert_eq!(Relative::new("/foo/").as_str(), "foo");
1290 assert_eq!(Relative::new("foo//bar").as_str(), "foo/bar");
1291 assert_eq!(Relative::new("../foo").as_str(), "../foo");
1292 assert_eq!(Relative::new("../../a/b").as_str(), "../../a/b");
1293 assert!(Relative::new("").is_empty());
1294 }
1295
1296 #[test]
1297 fn test_path_relative_normalizes_dot_segments() {
1298 assert_eq!(Relative::new(".").as_str(), ".");
1299 assert_eq!(Relative::new("././").as_str(), ".");
1300 assert_eq!(Relative::new("./foo").as_str(), "foo");
1301 assert_eq!(Relative::new("foo/./bar").as_str(), "foo/bar");
1302 assert_eq!(Relative::new("./../foo").as_str(), "../foo");
1303 assert_eq!(Relative::from("./foo".to_string()).as_str(), "foo");
1305 assert_eq!(Relative::from(".".to_string()).as_str(), ".");
1306 }
1307
1308 #[test]
1309 fn test_resolve_replaces_base_name() {
1310 let base = Path::new("a/b");
1311 assert_eq!(base.resolve(&Relative::new("c")).as_str(), "a/c");
1312 assert_eq!(base.resolve(&Relative::new("c/d")).as_str(), "a/c/d");
1313 assert_eq!(
1314 Path::new("foo.hang/catalog.pro")
1315 .resolve(&Relative::new("./transcode.pro"))
1316 .as_str(),
1317 "foo.hang/transcode.pro"
1318 );
1319 }
1320
1321 #[test]
1322 fn test_resolve_empty_rel_returns_base() {
1323 let base = Path::new("a/b");
1324 assert_eq!(base.resolve(&Relative::new("")).as_str(), "a/b");
1325 }
1326
1327 #[test]
1328 fn test_resolve_single_dotdot() {
1329 let base = Path::new("a/b/c");
1330 assert_eq!(base.resolve(&Relative::new("../d")).as_str(), "a/d");
1331 assert_eq!(base.resolve(&Relative::new("..")).as_str(), "a");
1332 }
1333
1334 #[test]
1335 fn test_resolve_multiple_dotdot() {
1336 let base = Path::new("a/b/c");
1337 assert_eq!(base.resolve(&Relative::new("../../x")).as_str(), "x");
1338 assert_eq!(base.resolve(&Relative::new("../../../x")).as_str(), "x");
1339 }
1340
1341 #[test]
1342 fn test_resolve_dotdot_clamps_at_root() {
1343 let base = Path::new("a");
1344 assert_eq!(base.resolve(&Relative::new("../../../foo")).as_str(), "foo");
1346 assert_eq!(base.resolve(&Relative::new("..")).as_str(), "");
1347 }
1348
1349 #[test]
1350 fn test_resolve_empty_base() {
1351 let base = Path::empty();
1352 assert_eq!(base.resolve(&Relative::new("foo")).as_str(), "foo");
1353 assert_eq!(base.resolve(&Relative::new("..")).as_str(), "");
1354 }
1355
1356 #[test]
1357 fn test_resolve_dot_names_parent() {
1358 let base = Path::new("a/b");
1359 assert_eq!(base.resolve(&Relative::new(".")).as_str(), "a");
1360 assert_eq!(base.resolve(&Relative::new("./c")).as_str(), "a/c");
1361 assert_eq!(base.resolve(&Relative::new("./../c")).as_str(), "c");
1362 }
1363
1364 #[test]
1365 fn test_resolve_self_reference_via_sibling_name() {
1366 let base = Path::new("a/b");
1369 assert_eq!(base.resolve(&Relative::new("./b")).as_str(), "a/b");
1370 }
1371
1372 #[test]
1373 fn test_try_resolve_distinguishes_root_from_escape() {
1374 let base = Path::new("top");
1375 assert_eq!(base.try_resolve(&Relative::new(".")).unwrap().as_str(), "");
1376 assert!(base.try_resolve(&Relative::new("..")).is_none());
1377
1378 let nested = Path::new("a/b");
1379 assert_eq!(nested.try_resolve(&Relative::new("..")).unwrap().as_str(), "");
1380 assert!(nested.try_resolve(&Relative::new("../..")).is_none());
1381 }
1382
1383 #[test]
1384 fn test_relative() {
1385 let rel = |target: &str, base: &str| Path::new(target).relative(base).unwrap();
1386
1387 assert_eq!(rel("foo/bar/baz", "foo/bar").as_str(), "bar/baz");
1389 assert_eq!(rel("foo/baz", "foo/bar").as_str(), "baz");
1391 assert_eq!(rel("foo/baz/bar", "foo/bar/baz").as_str(), "../baz/bar");
1393 assert_eq!(rel("a/b", "a/b/transcode.hang").as_str(), ".");
1395 assert_eq!(rel("a/b", "a/b/one/two/transcode.hang").as_str(), "../..");
1396 assert_eq!(rel("foo/bar", "").as_str(), "foo/bar");
1398 assert_eq!(rel("", "foo").as_str(), ".");
1399 assert_eq!(rel("a/b", "a/b").as_str(), "");
1401 assert_eq!(rel("", "").as_str(), "");
1402 assert_eq!(rel("/a//b/", "//a/b/dir//").as_str(), ".");
1404 }
1405
1406 #[test]
1407 fn test_relative_rejects_unnameable_targets() {
1408 assert!(Path::new("a/../b").relative("").is_none());
1411 assert!(Path::new("x/./y").relative("x/z").is_none());
1412 assert!(Path::new("a/..").relative("a/b").is_none());
1413
1414 assert_eq!(Path::new("a/..").relative("a/..").unwrap().as_str(), "");
1416
1417 let rel = Path::new("a/../b/x").relative("a/../b/c").unwrap();
1419 assert_eq!(rel.as_str(), "x");
1420 assert_eq!(Path::new("a/../b/c").resolve(&rel).as_str(), "a/../b/x");
1421 }
1422
1423 #[test]
1424 fn test_relative_round_trips() {
1425 let paths = [
1426 "", "a", "b", "a/b", "a/c", "a/b/c", "a/b/c/d", "x/y/z", "a/../b", "a/./b", "a/..", "a/.",
1427 ];
1428
1429 for base in paths {
1430 for target in paths {
1431 let base = Path::new(base);
1432 let target = Path::new(target);
1433 let Some(rel) = target.relative(&base) else {
1434 assert!(
1436 target != base && target.parts().any(|part| part == "." || part == ".."),
1437 "{base} -> {target} refused a nameable target"
1438 );
1439 continue;
1440 };
1441
1442 assert_eq!(base.resolve(&rel), target, "{base} -> {target} via {rel}");
1443 assert!(
1445 base.try_resolve(&rel).is_some(),
1446 "{base} -> {target} via {rel} escaped the root"
1447 );
1448 }
1449 }
1450 }
1451}