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 fn as_str(&self) -> &str {
268 match &self.0 {
269 Repr::Borrowed(s) => s,
270 Repr::Shared { buf, start } => &buf[*start..],
271 }
272 }
273
274 pub fn empty() -> Path<'static> {
276 Path(Repr::Borrowed(""))
277 }
278
279 pub fn is_empty(&self) -> bool {
281 self.as_str().is_empty()
282 }
283
284 pub fn len(&self) -> usize {
286 self.as_str().len()
287 }
288
289 pub fn to_owned(&self) -> PathOwned {
291 match &self.0 {
292 Repr::Borrowed("") => Path::empty(),
293 Repr::Borrowed(s) => Path(Repr::Shared {
294 buf: Arc::from(*s),
295 start: 0,
296 }),
297 Repr::Shared { buf, start } => Path(Repr::Shared {
298 buf: buf.clone(),
299 start: *start,
300 }),
301 }
302 }
303
304 pub fn into_owned(self) -> PathOwned {
306 match self.0 {
307 Repr::Borrowed("") => Path::empty(),
308 Repr::Borrowed(s) => Path(Repr::Shared {
309 buf: Arc::from(s),
310 start: 0,
311 }),
312 Repr::Shared { buf, start } => Path(Repr::Shared { buf, start }),
313 }
314 }
315
316 pub fn borrow(&'a self) -> Path<'a> {
318 self.slice_from(0)
319 }
320
321 pub fn join(&self, other: impl AsPath) -> PathOwned {
335 let other = other.as_path();
336
337 if self.is_empty() {
338 other.to_owned()
339 } else if other.is_empty() {
340 self.to_owned()
341 } else {
342 Path(Repr::Shared {
344 buf: format!("{}/{}", self.as_str(), other.as_str()).into(),
345 start: 0,
346 })
347 }
348 }
349
350 pub fn resolve(&self, rel: &Relative<'_>) -> PathOwned {
370 if rel.is_empty() {
371 return self.to_owned();
372 }
373
374 let mut segments: Vec<&str> = self.parts().collect();
375 segments.pop();
376
377 for seg in rel.as_str().split('/') {
378 if seg == "." {
379 continue;
380 } else if seg == ".." {
381 segments.pop();
382 } else {
383 segments.push(seg);
384 }
385 }
386
387 let path = segments.join("/");
388 if path.is_empty() {
389 Path::empty()
390 } else {
391 Path(Repr::Shared {
392 buf: path.into(),
393 start: 0,
394 })
395 }
396 }
397
398 pub fn try_resolve(&self, rel: &Relative<'_>) -> Option<PathOwned> {
404 if rel.is_empty() {
405 return Some(self.to_owned());
406 }
407
408 let mut segments: Vec<&str> = self.parts().collect();
409 segments.pop();
410
411 for seg in rel.as_str().split('/') {
412 if seg == "." {
413 continue;
414 } else if seg == ".." {
415 segments.pop()?;
416 } else {
417 segments.push(seg);
418 }
419 }
420
421 let path = segments.join("/");
422 if path.is_empty() {
423 Some(Path::empty())
424 } else {
425 Some(Path(Repr::Shared {
426 buf: path.into(),
427 start: 0,
428 }))
429 }
430 }
431
432 pub fn relative(&self, base: impl AsPath) -> Option<RelativeOwned> {
466 let base = base.as_path();
467
468 if *self == base {
471 return Some(Relative::empty());
472 }
473
474 let mut dir: Vec<&str> = base.parts().collect();
476 dir.pop();
477
478 let target: Vec<&str> = self.parts().collect();
479 let common = dir.iter().zip(&target).take_while(|(a, b)| a == b).count();
480
481 let down = &target[common..];
482 if down.iter().any(|part| *part == "." || *part == "..") {
483 return None;
485 }
486
487 let mut rel: Vec<&str> = vec![".."; dir.len() - common];
488 rel.extend(down);
489
490 if rel.is_empty() {
491 return Some(Relative::new("."));
493 }
494
495 Some(RelativeOwned::from(rel.join("/")))
496 }
497}
498
499impl<'b> PartialEq<Path<'b>> for Path<'_> {
502 fn eq(&self, other: &Path<'b>) -> bool {
503 self.as_str() == other.as_str()
504 }
505}
506
507impl Eq for Path<'_> {}
508
509impl PartialOrd for Path<'_> {
510 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
511 Some(self.cmp(other))
512 }
513}
514
515impl Ord for Path<'_> {
516 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
517 self.as_str().cmp(other.as_str())
518 }
519}
520
521impl std::hash::Hash for Path<'_> {
522 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
523 self.as_str().hash(state)
524 }
525}
526
527impl fmt::Debug for Path<'_> {
528 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
529 f.debug_tuple("Path").field(&self.as_str()).finish()
530 }
531}
532
533impl serde::Serialize for Path<'_> {
534 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
535 serializer.serialize_str(self.as_str())
536 }
537}
538
539impl<'a> From<&'a str> for Path<'a> {
540 fn from(s: &'a str) -> Self {
541 Self::new(s)
542 }
543}
544
545impl<'a> From<&'a String> for Path<'a> {
546 fn from(s: &'a String) -> Self {
547 Self::new(s)
549 }
550}
551
552impl Default for Path<'_> {
553 fn default() -> Self {
554 Path::empty()
555 }
556}
557
558impl From<String> for Path<'_> {
559 fn from(s: String) -> Self {
560 Path::new(&s).into_owned()
561 }
562}
563
564impl AsRef<str> for Path<'_> {
565 fn as_ref(&self) -> &str {
566 self.as_str()
567 }
568}
569
570impl Display for Path<'_> {
571 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
572 write!(f, "{}", self.as_str())
573 }
574}
575
576impl<V: Copy> Decode<V> for Path<'_>
577where
578 String: Decode<V>,
579{
580 fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
581 let path: Path = String::decode(r, version)?.into();
582 if path.parts().count() > Path::MAX_PARTS {
583 return Err(DecodeError::BoundsExceeded);
584 }
585 Ok(path)
586 }
587}
588
589impl<V: Copy> Encode<V> for Path<'_>
590where
591 for<'a> &'a str: Encode<V>,
592{
593 fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
594 if self.parts().count() > Path::MAX_PARTS {
595 return Err(EncodeError::BoundsExceeded);
596 }
597 self.as_str().encode(w, version)?;
598 Ok(())
599 }
600}
601
602pub type RelativeOwned = Relative<'static>;
604
605#[derive(Debug, PartialEq, Eq, Hash, Clone, serde::Serialize)]
633pub struct Relative<'a>(Cow<'a, str>);
634
635impl<'a> Relative<'a> {
636 pub fn new(s: &'a str) -> Self {
641 let trimmed = s.trim_start_matches('/').trim_end_matches('/');
642
643 if needs_normalize_relative(trimmed) {
644 Self(Cow::Owned(normalize_relative_segments(trimmed)))
645 } else {
646 Self(Cow::Borrowed(trimmed))
647 }
648 }
649
650 pub fn as_str(&self) -> &str {
652 &self.0
653 }
654
655 pub fn empty() -> Relative<'static> {
657 Relative(Cow::Borrowed(""))
658 }
659
660 pub fn is_empty(&self) -> bool {
662 self.0.is_empty()
663 }
664
665 pub fn len(&self) -> usize {
667 self.0.len()
668 }
669
670 pub fn to_owned(&self) -> RelativeOwned {
672 Relative(Cow::Owned(self.0.to_string()))
673 }
674
675 pub fn into_owned(self) -> RelativeOwned {
677 Relative(Cow::Owned(self.0.into_owned()))
678 }
679
680 pub fn borrow(&'a self) -> Relative<'a> {
682 Relative(Cow::Borrowed(&self.0))
683 }
684}
685
686impl<'a> From<&'a str> for Relative<'a> {
687 fn from(s: &'a str) -> Self {
688 Self::new(s)
689 }
690}
691
692impl<'a> From<&'a String> for Relative<'a> {
693 fn from(s: &'a String) -> Self {
694 Self::new(s)
695 }
696}
697
698impl From<String> for Relative<'_> {
699 fn from(s: String) -> Self {
700 let trimmed = s.trim_start_matches('/').trim_end_matches('/');
701
702 if needs_normalize_relative(trimmed) {
703 Self(Cow::Owned(normalize_relative_segments(trimmed)))
704 } else if trimmed == s {
705 Self(Cow::Owned(s))
706 } else {
707 Self(Cow::Owned(trimmed.to_string()))
708 }
709 }
710}
711
712fn needs_normalize_relative(trimmed: &str) -> bool {
713 trimmed.split('/').any(|seg| seg.is_empty() || seg == ".")
714}
715
716fn normalize_relative_segments(trimmed: &str) -> String {
717 let segments = trimmed
718 .split('/')
719 .filter(|seg| !seg.is_empty() && *seg != ".")
720 .collect::<Vec<_>>()
721 .join("/");
722
723 if segments.is_empty() && trimmed.split('/').any(|seg| seg == ".") {
724 ".".to_string()
725 } else {
726 segments
727 }
728}
729
730impl Default for Relative<'_> {
731 fn default() -> Self {
732 Self(Cow::Borrowed(""))
733 }
734}
735
736impl AsRef<str> for Relative<'_> {
737 fn as_ref(&self) -> &str {
738 &self.0
739 }
740}
741
742impl Display for Relative<'_> {
743 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
744 write!(f, "{}", self.0)
745 }
746}
747
748impl<'de> serde::Deserialize<'de> for Relative<'static> {
752 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
753 where
754 D: serde::Deserializer<'de>,
755 {
756 let s = String::deserialize(deserializer)?;
757 Ok(Relative::from(s))
758 }
759}
760
761#[cfg(test)]
762mod tests {
763 use super::*;
764
765 #[test]
766 fn test_has_prefix() {
767 let path = Path::new("foo/bar/baz");
768
769 assert!(path.has_prefix(""));
771 assert!(path.has_prefix("foo"));
772 assert!(path.has_prefix(Path::new("foo")));
773 assert!(path.has_prefix("foo/"));
774 assert!(path.has_prefix("foo/bar"));
775 assert!(path.has_prefix(Path::new("foo/bar/")));
776 assert!(path.has_prefix("foo/bar/baz"));
777
778 assert!(!path.has_prefix("f"));
780 assert!(!path.has_prefix(Path::new("fo")));
781 assert!(!path.has_prefix("foo/b"));
782 assert!(!path.has_prefix("foo/ba"));
783 assert!(!path.has_prefix(Path::new("foo/bar/ba")));
784
785 let path = Path::new("foobar");
787 assert!(!path.has_prefix("foo"));
788 assert!(path.has_prefix(Path::new("foobar")));
789 }
790
791 #[test]
792 fn test_strip_prefix() {
793 let path = Path::new("foo/bar/baz");
794
795 assert_eq!(path.strip_prefix("").unwrap().as_str(), "foo/bar/baz");
797 assert_eq!(path.strip_prefix("foo").unwrap().as_str(), "bar/baz");
798 assert_eq!(path.strip_prefix(Path::new("foo/")).unwrap().as_str(), "bar/baz");
799 assert_eq!(path.strip_prefix("foo/bar").unwrap().as_str(), "baz");
800 assert_eq!(path.strip_prefix(Path::new("foo/bar/")).unwrap().as_str(), "baz");
801 assert_eq!(path.strip_prefix("foo/bar/baz").unwrap().as_str(), "");
802
803 assert!(path.strip_prefix("fo").is_none());
805 assert!(path.strip_prefix(Path::new("bar")).is_none());
806 }
807
808 #[test]
809 fn test_join() {
810 assert_eq!(Path::new("foo").join("bar").as_str(), "foo/bar");
812 assert_eq!(Path::new("foo/").join(Path::new("bar")).as_str(), "foo/bar");
813 assert_eq!(Path::new("").join("bar").as_str(), "bar");
814 assert_eq!(Path::new("foo/bar").join(Path::new("baz")).as_str(), "foo/bar/baz");
815 }
816
817 #[test]
818 fn test_empty() {
819 let empty = Path::new("");
820 assert!(empty.is_empty());
821 assert_eq!(empty.len(), 0);
822
823 let non_empty = Path::new("foo");
824 assert!(!non_empty.is_empty());
825 assert_eq!(non_empty.len(), 3);
826 }
827
828 #[test]
829 fn test_from_conversions() {
830 let path1 = Path::from("foo/bar");
831 let path2 = Path::from("foo/bar");
832 let s = String::from("foo/bar");
833 let path3 = Path::from(&s);
834
835 assert_eq!(path1.as_str(), "foo/bar");
836 assert_eq!(path2.as_str(), "foo/bar");
837 assert_eq!(path3.as_str(), "foo/bar");
838 }
839
840 #[test]
841 fn test_path_prefix_join() {
842 let prefix = Path::new("foo");
843 let suffix = Path::new("bar/baz");
844 let path = prefix.join(&suffix);
845 assert_eq!(path.as_str(), "foo/bar/baz");
846
847 let prefix = Path::new("foo/");
848 let suffix = Path::new("bar/baz");
849 let path = prefix.join(&suffix);
850 assert_eq!(path.as_str(), "foo/bar/baz");
851
852 let prefix = Path::new("foo");
853 let suffix = Path::new("/bar/baz");
854 let path = prefix.join(&suffix);
855 assert_eq!(path.as_str(), "foo/bar/baz");
856
857 let prefix = Path::new("");
858 let suffix = Path::new("bar/baz");
859 let path = prefix.join(&suffix);
860 assert_eq!(path.as_str(), "bar/baz");
861 }
862
863 #[test]
864 fn test_path_prefix_conversions() {
865 let prefix1 = Path::from("foo/bar");
866 let prefix2 = Path::from(String::from("foo/bar"));
867 let s = String::from("foo/bar");
868 let prefix3 = Path::from(&s);
869
870 assert_eq!(prefix1.as_str(), "foo/bar");
871 assert_eq!(prefix2.as_str(), "foo/bar");
872 assert_eq!(prefix3.as_str(), "foo/bar");
873 }
874
875 #[test]
876 fn test_path_suffix_conversions() {
877 let suffix1 = Path::from("foo/bar");
878 let suffix2 = Path::from(String::from("foo/bar"));
879 let s = String::from("foo/bar");
880 let suffix3 = Path::from(&s);
881
882 assert_eq!(suffix1.as_str(), "foo/bar");
883 assert_eq!(suffix2.as_str(), "foo/bar");
884 assert_eq!(suffix3.as_str(), "foo/bar");
885 }
886
887 #[test]
888 fn test_path_types_basic_operations() {
889 let prefix = Path::new("foo/bar");
890 assert_eq!(prefix.as_str(), "foo/bar");
891 assert!(!prefix.is_empty());
892 assert_eq!(prefix.len(), 7);
893
894 let suffix = Path::new("baz/qux");
895 assert_eq!(suffix.as_str(), "baz/qux");
896 assert!(!suffix.is_empty());
897 assert_eq!(suffix.len(), 7);
898
899 let empty_prefix = Path::new("");
900 assert!(empty_prefix.is_empty());
901 assert_eq!(empty_prefix.len(), 0);
902
903 let empty_suffix = Path::new("");
904 assert!(empty_suffix.is_empty());
905 assert_eq!(empty_suffix.len(), 0);
906 }
907
908 #[test]
909 fn test_prefix_has_prefix() {
910 let prefix = Path::new("foo/bar");
912 assert!(prefix.has_prefix(""));
913
914 let prefix = Path::new("foo/bar");
916 assert!(prefix.has_prefix("foo/bar"));
917
918 assert!(prefix.has_prefix("foo"));
920 assert!(prefix.has_prefix("foo/"));
921
922 assert!(!prefix.has_prefix("f"));
924 assert!(!prefix.has_prefix("fo"));
925 assert!(!prefix.has_prefix("foo/b"));
926 assert!(!prefix.has_prefix("foo/ba"));
927
928 let prefix = Path::new("foobar");
930 assert!(!prefix.has_prefix("foo"));
931 assert!(prefix.has_prefix("foobar"));
932
933 let prefix = Path::new("foo/bar/");
935 assert!(prefix.has_prefix("foo"));
936 assert!(prefix.has_prefix("foo/"));
937 assert!(prefix.has_prefix("foo/bar"));
938 assert!(prefix.has_prefix("foo/bar/"));
939
940 let prefix = Path::new("foo");
942 assert!(prefix.has_prefix(""));
943 assert!(prefix.has_prefix("foo"));
944 assert!(prefix.has_prefix("foo/")); assert!(!prefix.has_prefix("f"));
946
947 let prefix = Path::new("");
949 assert!(prefix.has_prefix(""));
950 assert!(!prefix.has_prefix("foo"));
951 }
952
953 #[test]
954 fn test_prefix_join() {
955 let prefix = Path::new("foo");
957 let suffix = Path::new("bar");
958 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
959
960 let prefix = Path::new("foo/");
962 let suffix = Path::new("bar");
963 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
964
965 let prefix = Path::new("foo");
967 let suffix = Path::new("/bar");
968 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
969
970 let prefix = Path::new("foo");
972 let suffix = Path::new("bar/");
973 assert_eq!(prefix.join(suffix).as_str(), "foo/bar"); let prefix = Path::new("foo/");
977 let suffix = Path::new("/bar");
978 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
979
980 let prefix = Path::new("foo");
982 let suffix = Path::new("");
983 assert_eq!(prefix.join(suffix).as_str(), "foo");
984
985 let prefix = Path::new("");
987 let suffix = Path::new("bar");
988 assert_eq!(prefix.join(suffix).as_str(), "bar");
989
990 let prefix = Path::new("");
992 let suffix = Path::new("");
993 assert_eq!(prefix.join(suffix).as_str(), "");
994
995 let prefix = Path::new("foo/bar");
997 let suffix = Path::new("baz/qux");
998 assert_eq!(prefix.join(suffix).as_str(), "foo/bar/baz/qux");
999
1000 let prefix = Path::new("foo/bar/");
1002 let suffix = Path::new("/baz/qux/");
1003 assert_eq!(prefix.join(suffix).as_str(), "foo/bar/baz/qux"); }
1005
1006 #[test]
1007 fn test_path_ref() {
1008 let ref1 = Path::new("/foo/bar/");
1010 assert_eq!(ref1.as_str(), "foo/bar");
1011
1012 let ref2 = Path::from("///foo///");
1013 assert_eq!(ref2.as_str(), "foo");
1014
1015 let ref3 = Path::new("foo//bar///baz");
1017 assert_eq!(ref3.as_str(), "foo/bar/baz");
1018
1019 let path = Path::new("foo/bar");
1021 let path_ref = path;
1022 assert_eq!(path_ref.as_str(), "foo/bar");
1023
1024 let path2 = Path::new("foo/bar/baz");
1026 assert!(path2.has_prefix(&path_ref));
1027 assert_eq!(path2.strip_prefix(path_ref).unwrap().as_str(), "baz");
1028
1029 let empty = Path::new("");
1031 assert!(empty.is_empty());
1032 assert_eq!(empty.len(), 0);
1033 }
1034
1035 #[test]
1036 fn test_multiple_consecutive_slashes() {
1037 let path = Path::new("foo//bar///baz");
1038 assert_eq!(path.as_str(), "foo/bar/baz");
1040
1041 let path2 = Path::new("//foo//bar///baz//");
1043 assert_eq!(path2.as_str(), "foo/bar/baz");
1044
1045 let path3 = Path::new("foo///bar");
1047 assert_eq!(path3.as_str(), "foo/bar");
1048 }
1049
1050 #[test]
1051 fn test_removes_multiple_slashes_comprehensively() {
1052 assert_eq!(Path::new("foo//bar").as_str(), "foo/bar");
1054 assert_eq!(Path::new("foo///bar").as_str(), "foo/bar");
1055 assert_eq!(Path::new("foo////bar").as_str(), "foo/bar");
1056
1057 assert_eq!(Path::new("foo//bar//baz").as_str(), "foo/bar/baz");
1059 assert_eq!(Path::new("a//b//c//d").as_str(), "a/b/c/d");
1060
1061 assert_eq!(Path::new("foo//bar///baz////qux").as_str(), "foo/bar/baz/qux");
1063
1064 assert_eq!(Path::new("//foo//bar//").as_str(), "foo/bar");
1066 assert_eq!(Path::new("///foo///bar///").as_str(), "foo/bar");
1067
1068 assert_eq!(Path::new("//").as_str(), "");
1070 assert_eq!(Path::new("////").as_str(), "");
1071
1072 let path_with_slashes = Path::new("foo//bar///baz");
1074 assert!(path_with_slashes.has_prefix("foo/bar"));
1075 assert_eq!(path_with_slashes.strip_prefix("foo").unwrap().as_str(), "bar/baz");
1076 assert_eq!(path_with_slashes.join("qux").as_str(), "foo/bar/baz/qux");
1077
1078 let path_ref = Path::new("foo//bar///baz");
1080 assert_eq!(path_ref.as_str(), "foo/bar/baz"); let path_from_ref = path_ref.to_owned();
1082 assert_eq!(path_from_ref.as_str(), "foo/bar/baz"); }
1084
1085 #[test]
1086 fn test_path_ref_multiple_slashes() {
1087 let path_ref = Path::new("//foo//bar///baz//");
1089 assert_eq!(path_ref.as_str(), "foo/bar/baz"); assert_eq!(Path::new("foo//bar").as_str(), "foo/bar");
1093 assert_eq!(Path::new("foo///bar").as_str(), "foo/bar");
1094 assert_eq!(Path::new("a//b//c//d").as_str(), "a/b/c/d");
1095
1096 assert_eq!(Path::new("foo//bar").to_owned().as_str(), "foo/bar");
1098 assert_eq!(Path::new("foo///bar").to_owned().as_str(), "foo/bar");
1099 assert_eq!(Path::new("a//b//c//d").to_owned().as_str(), "a/b/c/d");
1100
1101 assert_eq!(Path::new("//").as_str(), "");
1103 assert_eq!(Path::new("////").as_str(), "");
1104 assert_eq!(Path::new("//").to_owned().as_str(), "");
1105 assert_eq!(Path::new("////").to_owned().as_str(), "");
1106
1107 let normal_path = Path::new("foo/bar/baz");
1109 assert_eq!(normal_path.as_str(), "foo/bar/baz");
1110 let needs_norm = Path::new("foo//bar");
1113 assert_eq!(needs_norm.as_str(), "foo/bar");
1114 }
1116
1117 #[test]
1118 fn test_ergonomic_conversions() {
1119 fn takes_path_ref<'a>(p: impl Into<Path<'a>>) -> String {
1121 p.into().as_str().to_string()
1122 }
1123
1124 fn takes_path_ref_with_trait<'a>(p: impl Into<Path<'a>>) -> String {
1126 p.into().as_str().to_string()
1127 }
1128
1129 assert_eq!(takes_path_ref("foo//bar"), "foo/bar");
1131
1132 let owned_string = String::from("foo//bar///baz");
1134 assert_eq!(takes_path_ref(owned_string), "foo/bar/baz");
1135
1136 let string_ref = String::from("foo//bar");
1138 assert_eq!(takes_path_ref(string_ref), "foo/bar");
1139
1140 let path_ref = Path::new("foo//bar");
1142 assert_eq!(takes_path_ref(path_ref), "foo/bar");
1143
1144 let path = Path::new("foo//bar");
1146 assert_eq!(takes_path_ref(path), "foo/bar");
1147
1148 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");
1156 assert_eq!(takes_path_ref_with_trait(String::from("foo//bar")), "foo/bar");
1157 }
1158
1159 #[test]
1160 fn test_prefix_strip_prefix() {
1161 let prefix = Path::new("foo/bar/baz");
1163 assert_eq!(prefix.strip_prefix("").unwrap().as_str(), "foo/bar/baz");
1164 assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "bar/baz");
1165 assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), "bar/baz");
1166 assert_eq!(prefix.strip_prefix("foo/bar").unwrap().as_str(), "baz");
1167 assert_eq!(prefix.strip_prefix("foo/bar/").unwrap().as_str(), "baz");
1168 assert_eq!(prefix.strip_prefix("foo/bar/baz").unwrap().as_str(), "");
1169
1170 assert!(prefix.strip_prefix("fo").is_none());
1172 assert!(prefix.strip_prefix("bar").is_none());
1173 assert!(prefix.strip_prefix("foo/ba").is_none());
1174
1175 let prefix = Path::new("foobar");
1177 assert!(prefix.strip_prefix("foo").is_none());
1178 assert_eq!(prefix.strip_prefix("foobar").unwrap().as_str(), "");
1179
1180 let prefix = Path::new("");
1182 assert_eq!(prefix.strip_prefix("").unwrap().as_str(), "");
1183 assert!(prefix.strip_prefix("foo").is_none());
1184
1185 let prefix = Path::new("foo");
1187 assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "");
1188 assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), ""); let prefix = Path::new("foo/bar/");
1192 assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "bar");
1193 assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), "bar");
1194 assert_eq!(prefix.strip_prefix("foo/bar").unwrap().as_str(), "");
1195 assert_eq!(prefix.strip_prefix("foo/bar/").unwrap().as_str(), "");
1196 }
1197
1198 #[test]
1201 fn test_owned_paths_share_allocation() {
1202 let path = Path::new("customer/room/broadcast").to_owned();
1203
1204 let cloned = path.clone();
1206 assert_eq!(path.as_str().as_ptr(), cloned.as_str().as_ptr());
1207
1208 let requeued = path.as_path().to_owned();
1210 assert_eq!(path.as_str().as_ptr(), requeued.as_str().as_ptr());
1211
1212 let stripped = path.strip_prefix("customer").unwrap().to_owned();
1214 assert_eq!(stripped.as_str(), "room/broadcast");
1215 assert_eq!(stripped.as_str().as_ptr(), path.as_str()["customer/".len()..].as_ptr());
1216
1217 let (dir, rest) = path.next_part().unwrap();
1219 assert_eq!(dir, "customer");
1220 let rest = rest.to_owned();
1221 assert_eq!(rest.as_str().as_ptr(), stripped.as_str().as_ptr());
1222
1223 let joined = path.join("alice");
1225 let joined2 = joined.clone();
1226 assert_eq!(joined.as_str(), "customer/room/broadcast/alice");
1227 assert_eq!(joined.as_str().as_ptr(), joined2.as_str().as_ptr());
1228 }
1229
1230 #[test]
1231 fn test_parts() {
1232 assert_eq!(Path::empty().parts().count(), 0);
1233 assert_eq!(Path::new("foo").parts().collect::<Vec<_>>(), ["foo"]);
1234 assert_eq!(Path::new("/foo//bar/").parts().collect::<Vec<_>>(), ["foo", "bar"]);
1235 }
1236
1237 #[test]
1238 fn test_wire_max_parts() {
1239 use crate::lite::Version;
1240
1241 let ok = (0..Path::MAX_PARTS)
1242 .map(|i| i.to_string())
1243 .collect::<Vec<_>>()
1244 .join("/");
1245 let too_deep = format!("{ok}/extra");
1246
1247 let mut buf = bytes::BytesMut::new();
1249 Path::new(&ok).encode(&mut buf, Version::Lite04).unwrap();
1250 assert!(matches!(
1251 Path::new(&too_deep).encode(&mut bytes::BytesMut::new(), Version::Lite04),
1252 Err(EncodeError::BoundsExceeded)
1253 ));
1254
1255 let decoded = Path::decode(&mut buf.freeze(), Version::Lite04).unwrap();
1257 assert_eq!(decoded.as_str(), ok);
1258
1259 let mut buf = bytes::BytesMut::new();
1261 too_deep.as_str().encode(&mut buf, Version::Lite04).unwrap();
1262 assert!(matches!(
1263 Path::decode(&mut buf.freeze(), Version::Lite04),
1264 Err(DecodeError::BoundsExceeded)
1265 ));
1266 }
1267
1268 #[test]
1269 fn test_owned_empty_paths() {
1270 let empty = Path::new("").to_owned();
1272 assert!(empty.is_empty());
1273 assert_eq!(empty, Path::empty());
1274
1275 let path = Path::new("foo").to_owned();
1276 let rest = path.strip_prefix("foo").unwrap().to_owned();
1277 assert!(rest.is_empty());
1278 }
1279
1280 #[test]
1281 fn test_path_relative_normalize() {
1282 assert_eq!(Relative::new("foo").as_str(), "foo");
1283 assert_eq!(Relative::new("/foo/").as_str(), "foo");
1284 assert_eq!(Relative::new("foo//bar").as_str(), "foo/bar");
1285 assert_eq!(Relative::new("../foo").as_str(), "../foo");
1286 assert_eq!(Relative::new("../../a/b").as_str(), "../../a/b");
1287 assert!(Relative::new("").is_empty());
1288 }
1289
1290 #[test]
1291 fn test_path_relative_normalizes_dot_segments() {
1292 assert_eq!(Relative::new(".").as_str(), ".");
1293 assert_eq!(Relative::new("././").as_str(), ".");
1294 assert_eq!(Relative::new("./foo").as_str(), "foo");
1295 assert_eq!(Relative::new("foo/./bar").as_str(), "foo/bar");
1296 assert_eq!(Relative::new("./../foo").as_str(), "../foo");
1297 assert_eq!(Relative::from("./foo".to_string()).as_str(), "foo");
1299 assert_eq!(Relative::from(".".to_string()).as_str(), ".");
1300 }
1301
1302 #[test]
1303 fn test_resolve_replaces_base_name() {
1304 let base = Path::new("a/b");
1305 assert_eq!(base.resolve(&Relative::new("c")).as_str(), "a/c");
1306 assert_eq!(base.resolve(&Relative::new("c/d")).as_str(), "a/c/d");
1307 assert_eq!(
1308 Path::new("foo.hang/catalog.pro")
1309 .resolve(&Relative::new("./transcode.pro"))
1310 .as_str(),
1311 "foo.hang/transcode.pro"
1312 );
1313 }
1314
1315 #[test]
1316 fn test_resolve_empty_rel_returns_base() {
1317 let base = Path::new("a/b");
1318 assert_eq!(base.resolve(&Relative::new("")).as_str(), "a/b");
1319 }
1320
1321 #[test]
1322 fn test_resolve_single_dotdot() {
1323 let base = Path::new("a/b/c");
1324 assert_eq!(base.resolve(&Relative::new("../d")).as_str(), "a/d");
1325 assert_eq!(base.resolve(&Relative::new("..")).as_str(), "a");
1326 }
1327
1328 #[test]
1329 fn test_resolve_multiple_dotdot() {
1330 let base = Path::new("a/b/c");
1331 assert_eq!(base.resolve(&Relative::new("../../x")).as_str(), "x");
1332 assert_eq!(base.resolve(&Relative::new("../../../x")).as_str(), "x");
1333 }
1334
1335 #[test]
1336 fn test_resolve_dotdot_clamps_at_root() {
1337 let base = Path::new("a");
1338 assert_eq!(base.resolve(&Relative::new("../../../foo")).as_str(), "foo");
1340 assert_eq!(base.resolve(&Relative::new("..")).as_str(), "");
1341 }
1342
1343 #[test]
1344 fn test_resolve_empty_base() {
1345 let base = Path::empty();
1346 assert_eq!(base.resolve(&Relative::new("foo")).as_str(), "foo");
1347 assert_eq!(base.resolve(&Relative::new("..")).as_str(), "");
1348 }
1349
1350 #[test]
1351 fn test_resolve_dot_names_parent() {
1352 let base = Path::new("a/b");
1353 assert_eq!(base.resolve(&Relative::new(".")).as_str(), "a");
1354 assert_eq!(base.resolve(&Relative::new("./c")).as_str(), "a/c");
1355 assert_eq!(base.resolve(&Relative::new("./../c")).as_str(), "c");
1356 }
1357
1358 #[test]
1359 fn test_resolve_self_reference_via_sibling_name() {
1360 let base = Path::new("a/b");
1363 assert_eq!(base.resolve(&Relative::new("./b")).as_str(), "a/b");
1364 }
1365
1366 #[test]
1367 fn test_try_resolve_distinguishes_root_from_escape() {
1368 let base = Path::new("top");
1369 assert_eq!(base.try_resolve(&Relative::new(".")).unwrap().as_str(), "");
1370 assert!(base.try_resolve(&Relative::new("..")).is_none());
1371
1372 let nested = Path::new("a/b");
1373 assert_eq!(nested.try_resolve(&Relative::new("..")).unwrap().as_str(), "");
1374 assert!(nested.try_resolve(&Relative::new("../..")).is_none());
1375 }
1376
1377 #[test]
1378 fn test_relative() {
1379 let rel = |target: &str, base: &str| Path::new(target).relative(base).unwrap();
1380
1381 assert_eq!(rel("foo/bar/baz", "foo/bar").as_str(), "bar/baz");
1383 assert_eq!(rel("foo/baz", "foo/bar").as_str(), "baz");
1385 assert_eq!(rel("foo/baz/bar", "foo/bar/baz").as_str(), "../baz/bar");
1387 assert_eq!(rel("a/b", "a/b/transcode.hang").as_str(), ".");
1389 assert_eq!(rel("a/b", "a/b/one/two/transcode.hang").as_str(), "../..");
1390 assert_eq!(rel("foo/bar", "").as_str(), "foo/bar");
1392 assert_eq!(rel("", "foo").as_str(), ".");
1393 assert_eq!(rel("a/b", "a/b").as_str(), "");
1395 assert_eq!(rel("", "").as_str(), "");
1396 assert_eq!(rel("/a//b/", "//a/b/dir//").as_str(), ".");
1398 }
1399
1400 #[test]
1401 fn test_relative_rejects_unnameable_targets() {
1402 assert!(Path::new("a/../b").relative("").is_none());
1405 assert!(Path::new("x/./y").relative("x/z").is_none());
1406 assert!(Path::new("a/..").relative("a/b").is_none());
1407
1408 assert_eq!(Path::new("a/..").relative("a/..").unwrap().as_str(), "");
1410
1411 let rel = Path::new("a/../b/x").relative("a/../b/c").unwrap();
1413 assert_eq!(rel.as_str(), "x");
1414 assert_eq!(Path::new("a/../b/c").resolve(&rel).as_str(), "a/../b/x");
1415 }
1416
1417 #[test]
1418 fn test_relative_round_trips() {
1419 let paths = [
1420 "", "a", "b", "a/b", "a/c", "a/b/c", "a/b/c/d", "x/y/z", "a/../b", "a/./b", "a/..", "a/.",
1421 ];
1422
1423 for base in paths {
1424 for target in paths {
1425 let base = Path::new(base);
1426 let target = Path::new(target);
1427 let Some(rel) = target.relative(&base) else {
1428 assert!(
1430 target != base && target.parts().any(|part| part == "." || part == ".."),
1431 "{base} -> {target} refused a nameable target"
1432 );
1433 continue;
1434 };
1435
1436 assert_eq!(base.resolve(&rel), target, "{base} -> {target} via {rel}");
1437 assert!(
1439 base.try_resolve(&rel).is_some(),
1440 "{base} -> {target} via {rel} escaped the root"
1441 );
1442 }
1443 }
1444 }
1445}