1use std::borrow::Cow;
2use std::fmt::{self, Display};
3use std::sync::Arc;
4
5use crate::coding::{Decode, DecodeError, Encode, EncodeError};
6
7pub type PathOwned = Path<'static>;
9
10pub trait AsPath {
15 fn as_path(&self) -> Path<'_>;
17}
18
19impl<'a> AsPath for &'a str {
20 fn as_path(&self) -> Path<'a> {
21 Path::new(self)
22 }
23}
24
25impl<'a> AsPath for &'a Path<'a> {
26 fn as_path(&self) -> Path<'a> {
27 self.borrow()
29 }
30}
31
32impl AsPath for Path<'_> {
33 fn as_path(&self) -> Path<'_> {
34 self.borrow()
35 }
36}
37
38impl AsPath for String {
39 fn as_path(&self) -> Path<'_> {
40 Path::new(self)
41 }
42}
43
44impl<'a> AsPath for &'a String {
45 fn as_path(&self) -> Path<'a> {
46 Path::new(self)
47 }
48}
49
50#[derive(Clone)]
56enum Repr<'a> {
57 Borrowed(&'a str),
58 Shared { buf: Arc<str>, start: usize },
59}
60
61#[derive(Clone)]
92pub struct Path<'a>(Repr<'a>);
93
94impl<'a> Path<'a> {
95 pub const MAX_PARTS: usize = 32;
101
102 pub fn new(s: &'a str) -> Self {
107 let trimmed = s.trim_start_matches('/').trim_end_matches('/');
108
109 if trimmed.contains("//") {
111 let normalized = trimmed
113 .split('/')
114 .filter(|s| !s.is_empty())
115 .collect::<Vec<_>>()
116 .join("/");
117 Self(Repr::Shared {
118 buf: normalized.into(),
119 start: 0,
120 })
121 } else {
122 Self(Repr::Borrowed(trimmed))
124 }
125 }
126
127 fn slice_from(&'a self, n: usize) -> Path<'a> {
129 match &self.0 {
130 Repr::Borrowed(s) => Path(Repr::Borrowed(&s[n..])),
131 Repr::Shared { buf, start } => Path(Repr::Shared {
132 buf: buf.clone(),
133 start: start + n,
134 }),
135 }
136 }
137
138 pub fn has_prefix(&self, prefix: impl AsPath) -> bool {
160 let prefix = prefix.as_path();
161
162 if prefix.is_empty() {
163 return true;
164 }
165
166 let s = self.as_str();
167 if !s.starts_with(prefix.as_str()) {
168 return false;
169 }
170
171 if s.len() == prefix.len() {
173 return true;
174 }
175
176 s.as_bytes().get(prefix.len()) == Some(&b'/')
178 }
179
180 pub fn strip_prefix(&'a self, prefix: impl AsPath) -> Option<Path<'a>> {
185 let prefix = prefix.as_path();
186
187 if prefix.is_empty() {
188 return Some(self.borrow());
189 }
190
191 let s = self.as_str();
192 if !s.starts_with(prefix.as_str()) {
193 return None;
194 }
195
196 if s.len() == prefix.len() {
198 return Some(Path::empty());
199 }
200
201 if s.as_bytes().get(prefix.len()) != Some(&b'/') {
203 return None;
204 }
205
206 Some(self.slice_from(prefix.len() + 1))
207 }
208
209 pub fn parts(&self) -> impl Iterator<Item = &str> {
222 self.as_str().split('/').filter(|part| !part.is_empty())
225 }
226
227 pub fn next_part(&'a self) -> Option<(&'a str, Path<'a>)> {
229 let s = self.as_str();
230 if s.is_empty() {
231 return None;
232 }
233
234 if let Some(i) = s.find('/') {
235 Some((&s[..i], self.slice_from(i + 1)))
236 } else {
237 Some((s, Path::empty()))
238 }
239 }
240
241 pub fn as_str(&self) -> &str {
243 match &self.0 {
244 Repr::Borrowed(s) => s,
245 Repr::Shared { buf, start } => &buf[*start..],
246 }
247 }
248
249 pub fn empty() -> Path<'static> {
251 Path(Repr::Borrowed(""))
252 }
253
254 pub fn is_empty(&self) -> bool {
256 self.as_str().is_empty()
257 }
258
259 pub fn len(&self) -> usize {
261 self.as_str().len()
262 }
263
264 pub fn to_owned(&self) -> PathOwned {
266 match &self.0 {
267 Repr::Borrowed("") => Path::empty(),
268 Repr::Borrowed(s) => Path(Repr::Shared {
269 buf: Arc::from(*s),
270 start: 0,
271 }),
272 Repr::Shared { buf, start } => Path(Repr::Shared {
273 buf: buf.clone(),
274 start: *start,
275 }),
276 }
277 }
278
279 pub fn into_owned(self) -> PathOwned {
281 match self.0 {
282 Repr::Borrowed("") => Path::empty(),
283 Repr::Borrowed(s) => Path(Repr::Shared {
284 buf: Arc::from(s),
285 start: 0,
286 }),
287 Repr::Shared { buf, start } => Path(Repr::Shared { buf, start }),
288 }
289 }
290
291 pub fn borrow(&'a self) -> Path<'a> {
293 self.slice_from(0)
294 }
295
296 pub fn join(&self, other: impl AsPath) -> PathOwned {
310 let other = other.as_path();
311
312 if self.is_empty() {
313 other.to_owned()
314 } else if other.is_empty() {
315 self.to_owned()
316 } else {
317 Path(Repr::Shared {
319 buf: format!("{}/{}", self.as_str(), other.as_str()).into(),
320 start: 0,
321 })
322 }
323 }
324
325 pub fn resolve(&self, rel: &PathRelative<'_>) -> PathOwned {
345 if rel.is_empty() {
346 return self.to_owned();
347 }
348
349 let mut segments: Vec<&str> = self.parts().collect();
350 segments.pop();
351
352 for seg in rel.as_str().split('/') {
353 if seg == "." {
354 continue;
355 } else if seg == ".." {
356 segments.pop();
357 } else {
358 segments.push(seg);
359 }
360 }
361
362 let path = segments.join("/");
363 if path.is_empty() {
364 Path::empty()
365 } else {
366 Path(Repr::Shared {
367 buf: path.into(),
368 start: 0,
369 })
370 }
371 }
372
373 pub fn try_resolve(&self, rel: &PathRelative<'_>) -> Option<PathOwned> {
379 if rel.is_empty() {
380 return Some(self.to_owned());
381 }
382
383 let mut segments: Vec<&str> = self.parts().collect();
384 segments.pop();
385
386 for seg in rel.as_str().split('/') {
387 if seg == "." {
388 continue;
389 } else if seg == ".." {
390 segments.pop()?;
391 } else {
392 segments.push(seg);
393 }
394 }
395
396 let path = segments.join("/");
397 if path.is_empty() {
398 Some(Path::empty())
399 } else {
400 Some(Path(Repr::Shared {
401 buf: path.into(),
402 start: 0,
403 }))
404 }
405 }
406
407 pub fn relative(&self, base: impl AsPath) -> Option<PathRelativeOwned> {
441 let base = base.as_path();
442
443 if *self == base {
446 return Some(PathRelative::empty());
447 }
448
449 let mut dir: Vec<&str> = base.parts().collect();
451 dir.pop();
452
453 let target: Vec<&str> = self.parts().collect();
454 let common = dir.iter().zip(&target).take_while(|(a, b)| a == b).count();
455
456 let down = &target[common..];
457 if down.iter().any(|part| *part == "." || *part == "..") {
458 return None;
460 }
461
462 let mut rel: Vec<&str> = vec![".."; dir.len() - common];
463 rel.extend(down);
464
465 if rel.is_empty() {
466 return Some(PathRelative::new("."));
468 }
469
470 Some(PathRelativeOwned::from(rel.join("/")))
471 }
472}
473
474impl<'b> PartialEq<Path<'b>> for Path<'_> {
477 fn eq(&self, other: &Path<'b>) -> bool {
478 self.as_str() == other.as_str()
479 }
480}
481
482impl Eq for Path<'_> {}
483
484impl PartialOrd for Path<'_> {
485 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
486 Some(self.cmp(other))
487 }
488}
489
490impl Ord for Path<'_> {
491 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
492 self.as_str().cmp(other.as_str())
493 }
494}
495
496impl std::hash::Hash for Path<'_> {
497 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
498 self.as_str().hash(state)
499 }
500}
501
502impl fmt::Debug for Path<'_> {
503 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
504 f.debug_tuple("Path").field(&self.as_str()).finish()
505 }
506}
507
508impl serde::Serialize for Path<'_> {
509 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
510 serializer.serialize_str(self.as_str())
511 }
512}
513
514impl<'a> From<&'a str> for Path<'a> {
515 fn from(s: &'a str) -> Self {
516 Self::new(s)
517 }
518}
519
520impl<'a> From<&'a String> for Path<'a> {
521 fn from(s: &'a String) -> Self {
522 Self::new(s)
524 }
525}
526
527impl Default for Path<'_> {
528 fn default() -> Self {
529 Path::empty()
530 }
531}
532
533impl From<String> for Path<'_> {
534 fn from(s: String) -> Self {
535 Path::new(&s).into_owned()
536 }
537}
538
539impl AsRef<str> for Path<'_> {
540 fn as_ref(&self) -> &str {
541 self.as_str()
542 }
543}
544
545impl Display for Path<'_> {
546 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
547 write!(f, "{}", self.as_str())
548 }
549}
550
551impl<V: Copy> Decode<V> for Path<'_>
552where
553 String: Decode<V>,
554{
555 fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
556 let path: Path = String::decode(r, version)?.into();
557 if path.parts().count() > Path::MAX_PARTS {
558 return Err(DecodeError::BoundsExceeded);
559 }
560 Ok(path)
561 }
562}
563
564impl<V: Copy> Encode<V> for Path<'_>
565where
566 for<'a> &'a str: Encode<V>,
567{
568 fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
569 if self.parts().count() > Path::MAX_PARTS {
570 return Err(EncodeError::BoundsExceeded);
571 }
572 self.as_str().encode(w, version)?;
573 Ok(())
574 }
575}
576
577pub type PathRelativeOwned = PathRelative<'static>;
579
580#[derive(Debug, PartialEq, Eq, Hash, Clone, serde::Serialize)]
608pub struct PathRelative<'a>(Cow<'a, str>);
609
610impl<'a> PathRelative<'a> {
611 pub fn new(s: &'a str) -> Self {
616 let trimmed = s.trim_start_matches('/').trim_end_matches('/');
617
618 if needs_normalize_relative(trimmed) {
619 Self(Cow::Owned(normalize_relative_segments(trimmed)))
620 } else {
621 Self(Cow::Borrowed(trimmed))
622 }
623 }
624
625 pub fn as_str(&self) -> &str {
627 &self.0
628 }
629
630 pub fn empty() -> PathRelative<'static> {
632 PathRelative(Cow::Borrowed(""))
633 }
634
635 pub fn is_empty(&self) -> bool {
637 self.0.is_empty()
638 }
639
640 pub fn len(&self) -> usize {
642 self.0.len()
643 }
644
645 pub fn to_owned(&self) -> PathRelativeOwned {
647 PathRelative(Cow::Owned(self.0.to_string()))
648 }
649
650 pub fn into_owned(self) -> PathRelativeOwned {
652 PathRelative(Cow::Owned(self.0.into_owned()))
653 }
654
655 pub fn borrow(&'a self) -> PathRelative<'a> {
657 PathRelative(Cow::Borrowed(&self.0))
658 }
659}
660
661impl<'a> From<&'a str> for PathRelative<'a> {
662 fn from(s: &'a str) -> Self {
663 Self::new(s)
664 }
665}
666
667impl<'a> From<&'a String> for PathRelative<'a> {
668 fn from(s: &'a String) -> Self {
669 Self::new(s)
670 }
671}
672
673impl From<String> for PathRelative<'_> {
674 fn from(s: String) -> Self {
675 let trimmed = s.trim_start_matches('/').trim_end_matches('/');
676
677 if needs_normalize_relative(trimmed) {
678 Self(Cow::Owned(normalize_relative_segments(trimmed)))
679 } else if trimmed == s {
680 Self(Cow::Owned(s))
681 } else {
682 Self(Cow::Owned(trimmed.to_string()))
683 }
684 }
685}
686
687fn needs_normalize_relative(trimmed: &str) -> bool {
688 trimmed.split('/').any(|seg| seg.is_empty() || seg == ".")
689}
690
691fn normalize_relative_segments(trimmed: &str) -> String {
692 let segments = trimmed
693 .split('/')
694 .filter(|seg| !seg.is_empty() && *seg != ".")
695 .collect::<Vec<_>>()
696 .join("/");
697
698 if segments.is_empty() && trimmed.split('/').any(|seg| seg == ".") {
699 ".".to_string()
700 } else {
701 segments
702 }
703}
704
705impl Default for PathRelative<'_> {
706 fn default() -> Self {
707 Self(Cow::Borrowed(""))
708 }
709}
710
711impl AsRef<str> for PathRelative<'_> {
712 fn as_ref(&self) -> &str {
713 &self.0
714 }
715}
716
717impl Display for PathRelative<'_> {
718 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
719 write!(f, "{}", self.0)
720 }
721}
722
723impl<'de> serde::Deserialize<'de> for PathRelative<'static> {
727 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
728 where
729 D: serde::Deserializer<'de>,
730 {
731 let s = String::deserialize(deserializer)?;
732 Ok(PathRelative::from(s))
733 }
734}
735
736#[derive(Debug, Clone, Default, Eq)]
742pub struct PathPrefixes {
743 paths: Vec<PathOwned>,
744}
745
746impl PathPrefixes {
747 pub fn new(paths: impl IntoIterator<Item = impl AsPath>) -> Self {
759 let mut paths: Vec<PathOwned> = paths.into_iter().map(|p| p.as_path().to_owned()).collect();
760
761 if paths.len() <= 1 {
762 return Self { paths };
763 }
764
765 paths.sort_by(|a, b| a.len().cmp(&b.len()).then_with(|| a.as_str().cmp(b.as_str())));
768 paths.dedup();
769
770 let mut result: Vec<PathOwned> = Vec::new();
771 'outer: for path in paths {
772 for existing in &result {
773 if path.has_prefix(existing) {
774 continue 'outer;
775 }
776 }
777 result.push(path);
778 }
779
780 Self { paths: result }
781 }
782
783 pub fn is_empty(&self) -> bool {
785 self.paths.is_empty()
786 }
787
788 pub fn len(&self) -> usize {
790 self.paths.len()
791 }
792
793 pub fn iter(&self) -> std::slice::Iter<'_, PathOwned> {
795 self.paths.iter()
796 }
797}
798
799impl std::ops::Deref for PathPrefixes {
800 type Target = [PathOwned];
801
802 fn deref(&self) -> &[PathOwned] {
803 &self.paths
804 }
805}
806
807impl FromIterator<PathOwned> for PathPrefixes {
808 fn from_iter<I: IntoIterator<Item = PathOwned>>(iter: I) -> Self {
809 Self::new(iter)
810 }
811}
812
813impl From<Vec<PathOwned>> for PathPrefixes {
814 fn from(paths: Vec<PathOwned>) -> Self {
815 Self::new(paths)
816 }
817}
818
819impl<'a> PartialEq<Vec<Path<'a>>> for PathPrefixes {
820 fn eq(&self, other: &Vec<Path<'a>>) -> bool {
821 self.paths == *other
822 }
823}
824
825impl<'a> PartialEq<PathPrefixes> for Vec<Path<'a>> {
826 fn eq(&self, other: &PathPrefixes) -> bool {
827 *self == other.paths
828 }
829}
830
831impl PartialEq for PathPrefixes {
832 fn eq(&self, other: &Self) -> bool {
833 self.paths == other.paths
834 }
835}
836
837impl IntoIterator for PathPrefixes {
838 type Item = PathOwned;
839 type IntoIter = std::vec::IntoIter<PathOwned>;
840
841 fn into_iter(self) -> Self::IntoIter {
842 self.paths.into_iter()
843 }
844}
845
846impl<'a> IntoIterator for &'a PathPrefixes {
847 type Item = &'a PathOwned;
848 type IntoIter = std::slice::Iter<'a, PathOwned>;
849
850 fn into_iter(self) -> Self::IntoIter {
851 self.paths.iter()
852 }
853}
854
855#[cfg(test)]
856mod tests {
857 use super::*;
858
859 #[test]
860 fn test_has_prefix() {
861 let path = Path::new("foo/bar/baz");
862
863 assert!(path.has_prefix(""));
865 assert!(path.has_prefix("foo"));
866 assert!(path.has_prefix(Path::new("foo")));
867 assert!(path.has_prefix("foo/"));
868 assert!(path.has_prefix("foo/bar"));
869 assert!(path.has_prefix(Path::new("foo/bar/")));
870 assert!(path.has_prefix("foo/bar/baz"));
871
872 assert!(!path.has_prefix("f"));
874 assert!(!path.has_prefix(Path::new("fo")));
875 assert!(!path.has_prefix("foo/b"));
876 assert!(!path.has_prefix("foo/ba"));
877 assert!(!path.has_prefix(Path::new("foo/bar/ba")));
878
879 let path = Path::new("foobar");
881 assert!(!path.has_prefix("foo"));
882 assert!(path.has_prefix(Path::new("foobar")));
883 }
884
885 #[test]
886 fn test_strip_prefix() {
887 let path = Path::new("foo/bar/baz");
888
889 assert_eq!(path.strip_prefix("").unwrap().as_str(), "foo/bar/baz");
891 assert_eq!(path.strip_prefix("foo").unwrap().as_str(), "bar/baz");
892 assert_eq!(path.strip_prefix(Path::new("foo/")).unwrap().as_str(), "bar/baz");
893 assert_eq!(path.strip_prefix("foo/bar").unwrap().as_str(), "baz");
894 assert_eq!(path.strip_prefix(Path::new("foo/bar/")).unwrap().as_str(), "baz");
895 assert_eq!(path.strip_prefix("foo/bar/baz").unwrap().as_str(), "");
896
897 assert!(path.strip_prefix("fo").is_none());
899 assert!(path.strip_prefix(Path::new("bar")).is_none());
900 }
901
902 #[test]
903 fn test_join() {
904 assert_eq!(Path::new("foo").join("bar").as_str(), "foo/bar");
906 assert_eq!(Path::new("foo/").join(Path::new("bar")).as_str(), "foo/bar");
907 assert_eq!(Path::new("").join("bar").as_str(), "bar");
908 assert_eq!(Path::new("foo/bar").join(Path::new("baz")).as_str(), "foo/bar/baz");
909 }
910
911 #[test]
912 fn test_empty() {
913 let empty = Path::new("");
914 assert!(empty.is_empty());
915 assert_eq!(empty.len(), 0);
916
917 let non_empty = Path::new("foo");
918 assert!(!non_empty.is_empty());
919 assert_eq!(non_empty.len(), 3);
920 }
921
922 #[test]
923 fn test_from_conversions() {
924 let path1 = Path::from("foo/bar");
925 let path2 = Path::from("foo/bar");
926 let s = String::from("foo/bar");
927 let path3 = Path::from(&s);
928
929 assert_eq!(path1.as_str(), "foo/bar");
930 assert_eq!(path2.as_str(), "foo/bar");
931 assert_eq!(path3.as_str(), "foo/bar");
932 }
933
934 #[test]
935 fn test_path_prefix_join() {
936 let prefix = Path::new("foo");
937 let suffix = Path::new("bar/baz");
938 let path = prefix.join(&suffix);
939 assert_eq!(path.as_str(), "foo/bar/baz");
940
941 let prefix = Path::new("foo/");
942 let suffix = Path::new("bar/baz");
943 let path = prefix.join(&suffix);
944 assert_eq!(path.as_str(), "foo/bar/baz");
945
946 let prefix = Path::new("foo");
947 let suffix = Path::new("/bar/baz");
948 let path = prefix.join(&suffix);
949 assert_eq!(path.as_str(), "foo/bar/baz");
950
951 let prefix = Path::new("");
952 let suffix = Path::new("bar/baz");
953 let path = prefix.join(&suffix);
954 assert_eq!(path.as_str(), "bar/baz");
955 }
956
957 #[test]
958 fn test_path_prefix_conversions() {
959 let prefix1 = Path::from("foo/bar");
960 let prefix2 = Path::from(String::from("foo/bar"));
961 let s = String::from("foo/bar");
962 let prefix3 = Path::from(&s);
963
964 assert_eq!(prefix1.as_str(), "foo/bar");
965 assert_eq!(prefix2.as_str(), "foo/bar");
966 assert_eq!(prefix3.as_str(), "foo/bar");
967 }
968
969 #[test]
970 fn test_path_suffix_conversions() {
971 let suffix1 = Path::from("foo/bar");
972 let suffix2 = Path::from(String::from("foo/bar"));
973 let s = String::from("foo/bar");
974 let suffix3 = Path::from(&s);
975
976 assert_eq!(suffix1.as_str(), "foo/bar");
977 assert_eq!(suffix2.as_str(), "foo/bar");
978 assert_eq!(suffix3.as_str(), "foo/bar");
979 }
980
981 #[test]
982 fn test_path_types_basic_operations() {
983 let prefix = Path::new("foo/bar");
984 assert_eq!(prefix.as_str(), "foo/bar");
985 assert!(!prefix.is_empty());
986 assert_eq!(prefix.len(), 7);
987
988 let suffix = Path::new("baz/qux");
989 assert_eq!(suffix.as_str(), "baz/qux");
990 assert!(!suffix.is_empty());
991 assert_eq!(suffix.len(), 7);
992
993 let empty_prefix = Path::new("");
994 assert!(empty_prefix.is_empty());
995 assert_eq!(empty_prefix.len(), 0);
996
997 let empty_suffix = Path::new("");
998 assert!(empty_suffix.is_empty());
999 assert_eq!(empty_suffix.len(), 0);
1000 }
1001
1002 #[test]
1003 fn test_prefix_has_prefix() {
1004 let prefix = Path::new("foo/bar");
1006 assert!(prefix.has_prefix(""));
1007
1008 let prefix = Path::new("foo/bar");
1010 assert!(prefix.has_prefix("foo/bar"));
1011
1012 assert!(prefix.has_prefix("foo"));
1014 assert!(prefix.has_prefix("foo/"));
1015
1016 assert!(!prefix.has_prefix("f"));
1018 assert!(!prefix.has_prefix("fo"));
1019 assert!(!prefix.has_prefix("foo/b"));
1020 assert!(!prefix.has_prefix("foo/ba"));
1021
1022 let prefix = Path::new("foobar");
1024 assert!(!prefix.has_prefix("foo"));
1025 assert!(prefix.has_prefix("foobar"));
1026
1027 let prefix = Path::new("foo/bar/");
1029 assert!(prefix.has_prefix("foo"));
1030 assert!(prefix.has_prefix("foo/"));
1031 assert!(prefix.has_prefix("foo/bar"));
1032 assert!(prefix.has_prefix("foo/bar/"));
1033
1034 let prefix = Path::new("foo");
1036 assert!(prefix.has_prefix(""));
1037 assert!(prefix.has_prefix("foo"));
1038 assert!(prefix.has_prefix("foo/")); assert!(!prefix.has_prefix("f"));
1040
1041 let prefix = Path::new("");
1043 assert!(prefix.has_prefix(""));
1044 assert!(!prefix.has_prefix("foo"));
1045 }
1046
1047 #[test]
1048 fn test_prefix_join() {
1049 let prefix = Path::new("foo");
1051 let suffix = Path::new("bar");
1052 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
1053
1054 let prefix = Path::new("foo/");
1056 let suffix = Path::new("bar");
1057 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
1058
1059 let prefix = Path::new("foo");
1061 let suffix = Path::new("/bar");
1062 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
1063
1064 let prefix = Path::new("foo");
1066 let suffix = Path::new("bar/");
1067 assert_eq!(prefix.join(suffix).as_str(), "foo/bar"); let prefix = Path::new("foo/");
1071 let suffix = Path::new("/bar");
1072 assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
1073
1074 let prefix = Path::new("foo");
1076 let suffix = Path::new("");
1077 assert_eq!(prefix.join(suffix).as_str(), "foo");
1078
1079 let prefix = Path::new("");
1081 let suffix = Path::new("bar");
1082 assert_eq!(prefix.join(suffix).as_str(), "bar");
1083
1084 let prefix = Path::new("");
1086 let suffix = Path::new("");
1087 assert_eq!(prefix.join(suffix).as_str(), "");
1088
1089 let prefix = Path::new("foo/bar");
1091 let suffix = Path::new("baz/qux");
1092 assert_eq!(prefix.join(suffix).as_str(), "foo/bar/baz/qux");
1093
1094 let prefix = Path::new("foo/bar/");
1096 let suffix = Path::new("/baz/qux/");
1097 assert_eq!(prefix.join(suffix).as_str(), "foo/bar/baz/qux"); }
1099
1100 #[test]
1101 fn test_path_ref() {
1102 let ref1 = Path::new("/foo/bar/");
1104 assert_eq!(ref1.as_str(), "foo/bar");
1105
1106 let ref2 = Path::from("///foo///");
1107 assert_eq!(ref2.as_str(), "foo");
1108
1109 let ref3 = Path::new("foo//bar///baz");
1111 assert_eq!(ref3.as_str(), "foo/bar/baz");
1112
1113 let path = Path::new("foo/bar");
1115 let path_ref = path;
1116 assert_eq!(path_ref.as_str(), "foo/bar");
1117
1118 let path2 = Path::new("foo/bar/baz");
1120 assert!(path2.has_prefix(&path_ref));
1121 assert_eq!(path2.strip_prefix(path_ref).unwrap().as_str(), "baz");
1122
1123 let empty = Path::new("");
1125 assert!(empty.is_empty());
1126 assert_eq!(empty.len(), 0);
1127 }
1128
1129 #[test]
1130 fn test_multiple_consecutive_slashes() {
1131 let path = Path::new("foo//bar///baz");
1132 assert_eq!(path.as_str(), "foo/bar/baz");
1134
1135 let path2 = Path::new("//foo//bar///baz//");
1137 assert_eq!(path2.as_str(), "foo/bar/baz");
1138
1139 let path3 = Path::new("foo///bar");
1141 assert_eq!(path3.as_str(), "foo/bar");
1142 }
1143
1144 #[test]
1145 fn test_removes_multiple_slashes_comprehensively() {
1146 assert_eq!(Path::new("foo//bar").as_str(), "foo/bar");
1148 assert_eq!(Path::new("foo///bar").as_str(), "foo/bar");
1149 assert_eq!(Path::new("foo////bar").as_str(), "foo/bar");
1150
1151 assert_eq!(Path::new("foo//bar//baz").as_str(), "foo/bar/baz");
1153 assert_eq!(Path::new("a//b//c//d").as_str(), "a/b/c/d");
1154
1155 assert_eq!(Path::new("foo//bar///baz////qux").as_str(), "foo/bar/baz/qux");
1157
1158 assert_eq!(Path::new("//foo//bar//").as_str(), "foo/bar");
1160 assert_eq!(Path::new("///foo///bar///").as_str(), "foo/bar");
1161
1162 assert_eq!(Path::new("//").as_str(), "");
1164 assert_eq!(Path::new("////").as_str(), "");
1165
1166 let path_with_slashes = Path::new("foo//bar///baz");
1168 assert!(path_with_slashes.has_prefix("foo/bar"));
1169 assert_eq!(path_with_slashes.strip_prefix("foo").unwrap().as_str(), "bar/baz");
1170 assert_eq!(path_with_slashes.join("qux").as_str(), "foo/bar/baz/qux");
1171
1172 let path_ref = Path::new("foo//bar///baz");
1174 assert_eq!(path_ref.as_str(), "foo/bar/baz"); let path_from_ref = path_ref.to_owned();
1176 assert_eq!(path_from_ref.as_str(), "foo/bar/baz"); }
1178
1179 #[test]
1180 fn test_path_ref_multiple_slashes() {
1181 let path_ref = Path::new("//foo//bar///baz//");
1183 assert_eq!(path_ref.as_str(), "foo/bar/baz"); assert_eq!(Path::new("foo//bar").as_str(), "foo/bar");
1187 assert_eq!(Path::new("foo///bar").as_str(), "foo/bar");
1188 assert_eq!(Path::new("a//b//c//d").as_str(), "a/b/c/d");
1189
1190 assert_eq!(Path::new("foo//bar").to_owned().as_str(), "foo/bar");
1192 assert_eq!(Path::new("foo///bar").to_owned().as_str(), "foo/bar");
1193 assert_eq!(Path::new("a//b//c//d").to_owned().as_str(), "a/b/c/d");
1194
1195 assert_eq!(Path::new("//").as_str(), "");
1197 assert_eq!(Path::new("////").as_str(), "");
1198 assert_eq!(Path::new("//").to_owned().as_str(), "");
1199 assert_eq!(Path::new("////").to_owned().as_str(), "");
1200
1201 let normal_path = Path::new("foo/bar/baz");
1203 assert_eq!(normal_path.as_str(), "foo/bar/baz");
1204 let needs_norm = Path::new("foo//bar");
1207 assert_eq!(needs_norm.as_str(), "foo/bar");
1208 }
1210
1211 #[test]
1212 fn test_ergonomic_conversions() {
1213 fn takes_path_ref<'a>(p: impl Into<Path<'a>>) -> String {
1215 p.into().as_str().to_string()
1216 }
1217
1218 fn takes_path_ref_with_trait<'a>(p: impl Into<Path<'a>>) -> String {
1220 p.into().as_str().to_string()
1221 }
1222
1223 assert_eq!(takes_path_ref("foo//bar"), "foo/bar");
1225
1226 let owned_string = String::from("foo//bar///baz");
1228 assert_eq!(takes_path_ref(owned_string), "foo/bar/baz");
1229
1230 let string_ref = String::from("foo//bar");
1232 assert_eq!(takes_path_ref(string_ref), "foo/bar");
1233
1234 let path_ref = Path::new("foo//bar");
1236 assert_eq!(takes_path_ref(path_ref), "foo/bar");
1237
1238 let path = Path::new("foo//bar");
1240 assert_eq!(takes_path_ref(path), "foo/bar");
1241
1242 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");
1250 assert_eq!(takes_path_ref_with_trait(String::from("foo//bar")), "foo/bar");
1251 }
1252
1253 #[test]
1254 fn test_prefix_strip_prefix() {
1255 let prefix = Path::new("foo/bar/baz");
1257 assert_eq!(prefix.strip_prefix("").unwrap().as_str(), "foo/bar/baz");
1258 assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "bar/baz");
1259 assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), "bar/baz");
1260 assert_eq!(prefix.strip_prefix("foo/bar").unwrap().as_str(), "baz");
1261 assert_eq!(prefix.strip_prefix("foo/bar/").unwrap().as_str(), "baz");
1262 assert_eq!(prefix.strip_prefix("foo/bar/baz").unwrap().as_str(), "");
1263
1264 assert!(prefix.strip_prefix("fo").is_none());
1266 assert!(prefix.strip_prefix("bar").is_none());
1267 assert!(prefix.strip_prefix("foo/ba").is_none());
1268
1269 let prefix = Path::new("foobar");
1271 assert!(prefix.strip_prefix("foo").is_none());
1272 assert_eq!(prefix.strip_prefix("foobar").unwrap().as_str(), "");
1273
1274 let prefix = Path::new("");
1276 assert_eq!(prefix.strip_prefix("").unwrap().as_str(), "");
1277 assert!(prefix.strip_prefix("foo").is_none());
1278
1279 let prefix = Path::new("foo");
1281 assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "");
1282 assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), ""); let prefix = Path::new("foo/bar/");
1286 assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "bar");
1287 assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), "bar");
1288 assert_eq!(prefix.strip_prefix("foo/bar").unwrap().as_str(), "");
1289 assert_eq!(prefix.strip_prefix("foo/bar/").unwrap().as_str(), "");
1290 }
1291
1292 #[test]
1293 fn test_prefix_list_dedup() {
1294 let list = PathPrefixes::new(["demo", "demo"]);
1296 assert_eq!(list.len(), 1);
1297 assert_eq!(list[0], Path::new("demo"));
1298 }
1299
1300 #[test]
1301 fn test_prefix_list_overlap() {
1302 let list = PathPrefixes::new(["demo", "demo/foo", "anon"]);
1304 assert_eq!(list.len(), 2);
1305 assert!(list.iter().any(|p| p == &Path::new("demo")));
1306 assert!(list.iter().any(|p| p == &Path::new("anon")));
1307 }
1308
1309 #[test]
1310 fn test_prefix_list_overlap_reverse_order() {
1311 let list = PathPrefixes::new(["demo/foo", "demo"]);
1313 assert_eq!(list.len(), 1);
1314 assert_eq!(list[0], Path::new("demo"));
1315 }
1316
1317 #[test]
1318 fn test_prefix_list_empty_covers_all() {
1319 let list = PathPrefixes::new(["", "demo", "anon"]);
1321 assert_eq!(list.len(), 1);
1322 assert_eq!(list[0], Path::new(""));
1323 }
1324
1325 #[test]
1326 fn test_prefix_list_no_overlap() {
1327 let list = PathPrefixes::new(["demo", "anon", "secret"]);
1329 assert_eq!(list.len(), 3);
1330 }
1331
1332 #[test]
1333 fn test_prefix_list_single() {
1334 let list = PathPrefixes::new(["demo"]);
1335 assert_eq!(list.len(), 1);
1336 }
1337
1338 #[test]
1339 fn test_prefix_list_empty() {
1340 let list = PathPrefixes::new(std::iter::empty::<&str>());
1341 assert!(list.is_empty());
1342 assert_eq!(list.len(), 0);
1343 }
1344
1345 #[test]
1346 fn test_prefix_list_deep_overlap() {
1347 let list = PathPrefixes::new(["a/b/c", "a/b", "a"]);
1349 assert_eq!(list.len(), 1);
1350 assert_eq!(list[0], Path::new("a"));
1351 }
1352
1353 #[test]
1354 fn test_prefix_list_partial_name_not_overlap() {
1355 let list = PathPrefixes::new(["demo", "demonstration"]);
1357 assert_eq!(list.len(), 2);
1358 }
1359
1360 #[test]
1361 fn test_prefix_list_collect() {
1362 let paths: Vec<PathOwned> = vec!["demo".into(), "demo/foo".into()];
1363 let list: PathPrefixes = paths.into_iter().collect();
1364 assert_eq!(list.len(), 1);
1365 assert_eq!(list[0], Path::new("demo"));
1366 }
1367
1368 #[test]
1369 fn test_prefix_list_eq_vec() {
1370 let list = PathPrefixes::new(["demo", "anon"]);
1371 assert_eq!(list, vec!["anon".as_path(), "demo".as_path()]);
1373 }
1374
1375 #[test]
1378 fn test_owned_paths_share_allocation() {
1379 let path = Path::new("customer/room/broadcast").to_owned();
1380
1381 let cloned = path.clone();
1383 assert_eq!(path.as_str().as_ptr(), cloned.as_str().as_ptr());
1384
1385 let requeued = path.as_path().to_owned();
1387 assert_eq!(path.as_str().as_ptr(), requeued.as_str().as_ptr());
1388
1389 let stripped = path.strip_prefix("customer").unwrap().to_owned();
1391 assert_eq!(stripped.as_str(), "room/broadcast");
1392 assert_eq!(stripped.as_str().as_ptr(), path.as_str()["customer/".len()..].as_ptr());
1393
1394 let (dir, rest) = path.next_part().unwrap();
1396 assert_eq!(dir, "customer");
1397 let rest = rest.to_owned();
1398 assert_eq!(rest.as_str().as_ptr(), stripped.as_str().as_ptr());
1399
1400 let joined = path.join("alice");
1402 let joined2 = joined.clone();
1403 assert_eq!(joined.as_str(), "customer/room/broadcast/alice");
1404 assert_eq!(joined.as_str().as_ptr(), joined2.as_str().as_ptr());
1405 }
1406
1407 #[test]
1408 fn test_parts() {
1409 assert_eq!(Path::empty().parts().count(), 0);
1410 assert_eq!(Path::new("foo").parts().collect::<Vec<_>>(), ["foo"]);
1411 assert_eq!(Path::new("/foo//bar/").parts().collect::<Vec<_>>(), ["foo", "bar"]);
1412 }
1413
1414 #[test]
1415 fn test_wire_max_parts() {
1416 use crate::lite::Version;
1417
1418 let ok = (0..Path::MAX_PARTS)
1419 .map(|i| i.to_string())
1420 .collect::<Vec<_>>()
1421 .join("/");
1422 let too_deep = format!("{ok}/extra");
1423
1424 let mut buf = bytes::BytesMut::new();
1426 Path::new(&ok).encode(&mut buf, Version::Lite04).unwrap();
1427 assert!(matches!(
1428 Path::new(&too_deep).encode(&mut bytes::BytesMut::new(), Version::Lite04),
1429 Err(EncodeError::BoundsExceeded)
1430 ));
1431
1432 let decoded = Path::decode(&mut buf.freeze(), Version::Lite04).unwrap();
1434 assert_eq!(decoded.as_str(), ok);
1435
1436 let mut buf = bytes::BytesMut::new();
1438 too_deep.as_str().encode(&mut buf, Version::Lite04).unwrap();
1439 assert!(matches!(
1440 Path::decode(&mut buf.freeze(), Version::Lite04),
1441 Err(DecodeError::BoundsExceeded)
1442 ));
1443 }
1444
1445 #[test]
1446 fn test_owned_empty_paths() {
1447 let empty = Path::new("").to_owned();
1449 assert!(empty.is_empty());
1450 assert_eq!(empty, Path::empty());
1451
1452 let path = Path::new("foo").to_owned();
1453 let rest = path.strip_prefix("foo").unwrap().to_owned();
1454 assert!(rest.is_empty());
1455 }
1456
1457 #[test]
1458 fn test_prefix_list_canonical_order() {
1459 let a = PathPrefixes::new(["foo", "bar"]);
1461 let b = PathPrefixes::new(["bar", "foo"]);
1462 assert_eq!(a, b);
1463 }
1464
1465 #[test]
1466 fn test_path_relative_normalize() {
1467 assert_eq!(PathRelative::new("foo").as_str(), "foo");
1468 assert_eq!(PathRelative::new("/foo/").as_str(), "foo");
1469 assert_eq!(PathRelative::new("foo//bar").as_str(), "foo/bar");
1470 assert_eq!(PathRelative::new("../foo").as_str(), "../foo");
1471 assert_eq!(PathRelative::new("../../a/b").as_str(), "../../a/b");
1472 assert!(PathRelative::new("").is_empty());
1473 }
1474
1475 #[test]
1476 fn test_path_relative_normalizes_dot_segments() {
1477 assert_eq!(PathRelative::new(".").as_str(), ".");
1478 assert_eq!(PathRelative::new("././").as_str(), ".");
1479 assert_eq!(PathRelative::new("./foo").as_str(), "foo");
1480 assert_eq!(PathRelative::new("foo/./bar").as_str(), "foo/bar");
1481 assert_eq!(PathRelative::new("./../foo").as_str(), "../foo");
1482 assert_eq!(PathRelative::from("./foo".to_string()).as_str(), "foo");
1484 assert_eq!(PathRelative::from(".".to_string()).as_str(), ".");
1485 }
1486
1487 #[test]
1488 fn test_resolve_replaces_base_name() {
1489 let base = Path::new("a/b");
1490 assert_eq!(base.resolve(&PathRelative::new("c")).as_str(), "a/c");
1491 assert_eq!(base.resolve(&PathRelative::new("c/d")).as_str(), "a/c/d");
1492 assert_eq!(
1493 Path::new("foo.hang/catalog.pro")
1494 .resolve(&PathRelative::new("./transcode.pro"))
1495 .as_str(),
1496 "foo.hang/transcode.pro"
1497 );
1498 }
1499
1500 #[test]
1501 fn test_resolve_empty_rel_returns_base() {
1502 let base = Path::new("a/b");
1503 assert_eq!(base.resolve(&PathRelative::new("")).as_str(), "a/b");
1504 }
1505
1506 #[test]
1507 fn test_resolve_single_dotdot() {
1508 let base = Path::new("a/b/c");
1509 assert_eq!(base.resolve(&PathRelative::new("../d")).as_str(), "a/d");
1510 assert_eq!(base.resolve(&PathRelative::new("..")).as_str(), "a");
1511 }
1512
1513 #[test]
1514 fn test_resolve_multiple_dotdot() {
1515 let base = Path::new("a/b/c");
1516 assert_eq!(base.resolve(&PathRelative::new("../../x")).as_str(), "x");
1517 assert_eq!(base.resolve(&PathRelative::new("../../../x")).as_str(), "x");
1518 }
1519
1520 #[test]
1521 fn test_resolve_dotdot_clamps_at_root() {
1522 let base = Path::new("a");
1523 assert_eq!(base.resolve(&PathRelative::new("../../../foo")).as_str(), "foo");
1525 assert_eq!(base.resolve(&PathRelative::new("..")).as_str(), "");
1526 }
1527
1528 #[test]
1529 fn test_resolve_empty_base() {
1530 let base = Path::empty();
1531 assert_eq!(base.resolve(&PathRelative::new("foo")).as_str(), "foo");
1532 assert_eq!(base.resolve(&PathRelative::new("..")).as_str(), "");
1533 }
1534
1535 #[test]
1536 fn test_resolve_dot_names_parent() {
1537 let base = Path::new("a/b");
1538 assert_eq!(base.resolve(&PathRelative::new(".")).as_str(), "a");
1539 assert_eq!(base.resolve(&PathRelative::new("./c")).as_str(), "a/c");
1540 assert_eq!(base.resolve(&PathRelative::new("./../c")).as_str(), "c");
1541 }
1542
1543 #[test]
1544 fn test_resolve_self_reference_via_sibling_name() {
1545 let base = Path::new("a/b");
1548 assert_eq!(base.resolve(&PathRelative::new("./b")).as_str(), "a/b");
1549 }
1550
1551 #[test]
1552 fn test_try_resolve_distinguishes_root_from_escape() {
1553 let base = Path::new("top");
1554 assert_eq!(base.try_resolve(&PathRelative::new(".")).unwrap().as_str(), "");
1555 assert!(base.try_resolve(&PathRelative::new("..")).is_none());
1556
1557 let nested = Path::new("a/b");
1558 assert_eq!(nested.try_resolve(&PathRelative::new("..")).unwrap().as_str(), "");
1559 assert!(nested.try_resolve(&PathRelative::new("../..")).is_none());
1560 }
1561
1562 #[test]
1563 fn test_relative() {
1564 let rel = |target: &str, base: &str| Path::new(target).relative(base).unwrap();
1565
1566 assert_eq!(rel("foo/bar/baz", "foo/bar").as_str(), "bar/baz");
1568 assert_eq!(rel("foo/baz", "foo/bar").as_str(), "baz");
1570 assert_eq!(rel("foo/baz/bar", "foo/bar/baz").as_str(), "../baz/bar");
1572 assert_eq!(rel("a/b", "a/b/transcode.hang").as_str(), ".");
1574 assert_eq!(rel("a/b", "a/b/one/two/transcode.hang").as_str(), "../..");
1575 assert_eq!(rel("foo/bar", "").as_str(), "foo/bar");
1577 assert_eq!(rel("", "foo").as_str(), ".");
1578 assert_eq!(rel("a/b", "a/b").as_str(), "");
1580 assert_eq!(rel("", "").as_str(), "");
1581 assert_eq!(rel("/a//b/", "//a/b/dir//").as_str(), ".");
1583 }
1584
1585 #[test]
1586 fn test_relative_rejects_unnameable_targets() {
1587 assert!(Path::new("a/../b").relative("").is_none());
1590 assert!(Path::new("x/./y").relative("x/z").is_none());
1591 assert!(Path::new("a/..").relative("a/b").is_none());
1592
1593 assert_eq!(Path::new("a/..").relative("a/..").unwrap().as_str(), "");
1595
1596 let rel = Path::new("a/../b/x").relative("a/../b/c").unwrap();
1598 assert_eq!(rel.as_str(), "x");
1599 assert_eq!(Path::new("a/../b/c").resolve(&rel).as_str(), "a/../b/x");
1600 }
1601
1602 #[test]
1603 fn test_relative_round_trips() {
1604 let paths = [
1605 "", "a", "b", "a/b", "a/c", "a/b/c", "a/b/c/d", "x/y/z", "a/../b", "a/./b", "a/..", "a/.",
1606 ];
1607
1608 for base in paths {
1609 for target in paths {
1610 let base = Path::new(base);
1611 let target = Path::new(target);
1612 let Some(rel) = target.relative(&base) else {
1613 assert!(
1615 target != base && target.parts().any(|part| part == "." || part == ".."),
1616 "{base} -> {target} refused a nameable target"
1617 );
1618 continue;
1619 };
1620
1621 assert_eq!(base.resolve(&rel), target, "{base} -> {target} via {rel}");
1622 assert!(
1624 base.try_resolve(&rel).is_some(),
1625 "{base} -> {target} via {rel} escaped the root"
1626 );
1627 }
1628 }
1629 }
1630}