1use std::borrow::Cow;
2use std::fmt::{self, Display};
3
4use crate::coding::{Decode, DecodeError, Encode};
5
6pub trait IntoPathRef<'a>: Into<PathRef<'a>> {}
9
10impl<'a, T: Into<PathRef<'a>>> IntoPathRef<'a> for T {}
11
12#[derive(Debug, PartialEq, Eq, Hash, Clone)]
19pub struct PathRef<'a>(Cow<'a, str>);
20
21impl<'a> PathRef<'a> {
22 pub fn new(s: &'a str) -> Self {
27 let trimmed = s.trim_start_matches('/').trim_end_matches('/');
28
29 if trimmed.contains("//") {
31 let normalized = trimmed
33 .split('/')
34 .filter(|s| !s.is_empty())
35 .collect::<Vec<_>>()
36 .join("/");
37 Self(Cow::Owned(normalized))
38 } else {
39 Self(Cow::Borrowed(trimmed))
41 }
42 }
43
44 pub fn has_prefix<'b>(&self, prefix: impl Into<PathRef<'b>>) -> bool {
66 let prefix = prefix.into();
67 if prefix.is_empty() {
68 return true;
69 }
70
71 if !self.0.starts_with(prefix.as_str()) {
72 return false;
73 }
74
75 if self.0.len() == prefix.len() {
77 return true;
78 }
79
80 self.0.chars().nth(prefix.len()) == Some('/')
82 }
83
84 pub fn as_str(&self) -> &str {
86 &self.0
87 }
88
89 pub fn is_empty(&self) -> bool {
91 self.0.is_empty()
92 }
93
94 pub fn len(&self) -> usize {
96 self.0.len()
97 }
98
99 pub fn to_owned(&self) -> Path {
101 Path(self.0.clone().into_owned())
102 }
103}
104
105impl<'a> From<&'a str> for PathRef<'a> {
106 fn from(s: &'a str) -> Self {
107 Self::new(s)
108 }
109}
110
111impl<'a> From<&'a String> for PathRef<'a> {
112 fn from(s: &'a String) -> Self {
113 Self::new(s.as_str())
114 }
115}
116
117impl From<String> for PathRef<'static> {
118 fn from(s: String) -> Self {
119 let trimmed = s.trim_start_matches('/').trim_end_matches('/');
121
122 if trimmed.contains("//") {
124 let normalized = trimmed
126 .split('/')
127 .filter(|s| !s.is_empty())
128 .collect::<Vec<_>>()
129 .join("/");
130 Self(Cow::Owned(normalized))
131 } else if trimmed == s {
132 Self(Cow::Owned(s))
134 } else {
135 Self(Cow::Owned(trimmed.to_string()))
137 }
138 }
139}
140
141impl<'a> From<&'a Path> for PathRef<'a> {
142 fn from(p: &'a Path) -> Self {
143 Self(Cow::Borrowed(p.0.as_str()))
145 }
146}
147
148impl<'a, 'b> From<&'a PathRef<'b>> for PathRef<'a>
149where
150 'b: 'a,
151{
152 fn from(p: &'a PathRef<'b>) -> Self {
153 Self(p.0.clone())
154 }
155}
156
157impl<'a> AsRef<str> for PathRef<'a> {
158 fn as_ref(&self) -> &str {
159 &self.0
160 }
161}
162
163impl<'a> Display for PathRef<'a> {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 write!(f, "{}", self.0)
166 }
167}
168
169#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
196#[cfg_attr(feature = "serde", derive(serde::Serialize))]
197pub struct Path(String);
198
199impl Path {
200 pub fn new<'a>(path: impl Into<PathRef<'a>>) -> Self {
206 Self(path.into().0.to_string())
208 }
209
210 pub fn has_prefix<'a>(&self, prefix: impl Into<PathRef<'a>>) -> bool {
232 let inner: PathRef = self.into();
233 inner.has_prefix(prefix)
234 }
235
236 pub fn strip_prefix<'a>(&self, prefix: impl Into<PathRef<'a>>) -> Option<PathRef<'_>> {
253 let prefix = prefix.into();
254 if !self.has_prefix(&prefix) {
255 return None;
256 }
257
258 let suffix = &self.0[prefix.len()..];
259 let suffix = suffix.trim_start_matches('/');
261 Some(PathRef(Cow::Borrowed(suffix)))
262 }
263
264 pub fn as_str(&self) -> &str {
266 &self.0
267 }
268
269 pub fn is_empty(&self) -> bool {
271 self.0.is_empty()
272 }
273
274 pub fn len(&self) -> usize {
276 self.0.len()
277 }
278
279 pub fn join<'a>(&self, other: impl Into<PathRef<'a>>) -> Path {
293 let other = other.into();
294 if self.0.is_empty() {
295 other.to_owned()
296 } else if other.is_empty() {
297 self.clone()
298 } else {
299 Path::new(format!("{}/{}", self.0, other.as_str()))
301 }
302 }
303}
304
305impl Display for Path {
306 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307 write!(f, "{}", self.0)
308 }
309}
310
311impl AsRef<str> for Path {
312 fn as_ref(&self) -> &str {
313 &self.0
314 }
315}
316
317impl From<String> for Path {
318 fn from(s: String) -> Self {
319 Self::new(&s)
320 }
321}
322
323impl From<&str> for Path {
324 fn from(s: &str) -> Self {
325 Self::new(s)
326 }
327}
328
329impl From<&String> for Path {
330 fn from(s: &String) -> Self {
331 Self::new(s)
332 }
333}
334
335impl From<&Path> for Path {
336 fn from(p: &Path) -> Self {
337 p.clone()
338 }
339}
340
341impl From<PathRef<'_>> for Path {
342 fn from(p: PathRef<'_>) -> Self {
343 Path(p.0.into_owned())
344 }
345}
346
347impl Decode for Path {
348 fn decode<R: bytes::Buf>(r: &mut R) -> Result<Self, DecodeError> {
349 let path = String::decode(r)?;
350 Ok(Self::new(&path))
351 }
352}
353
354impl Encode for Path {
355 fn encode<W: bytes::BufMut>(&self, w: &mut W) {
356 self.0.encode(w)
357 }
358}
359
360#[cfg(feature = "serde")]
361impl<'de> serde::Deserialize<'de> for Path {
362 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
363 where
364 D: serde::Deserializer<'de>,
365 {
366 let s = String::deserialize(deserializer)?;
367 Ok(Path::new(&s))
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn test_has_prefix() {
377 let path = Path::new("foo/bar/baz");
378
379 assert!(path.has_prefix(""));
381 assert!(path.has_prefix("foo"));
382 assert!(path.has_prefix(&Path::new("foo")));
383 assert!(path.has_prefix("foo/"));
384 assert!(path.has_prefix("foo/bar"));
385 assert!(path.has_prefix(&Path::new("foo/bar/")));
386 assert!(path.has_prefix("foo/bar/baz"));
387
388 assert!(!path.has_prefix("f"));
390 assert!(!path.has_prefix(&Path::new("fo")));
391 assert!(!path.has_prefix("foo/b"));
392 assert!(!path.has_prefix("foo/ba"));
393 assert!(!path.has_prefix(&Path::new("foo/bar/ba")));
394
395 let path = Path::new("foobar");
397 assert!(!path.has_prefix("foo"));
398 assert!(path.has_prefix(&Path::new("foobar")));
399 }
400
401 #[test]
402 fn test_strip_prefix() {
403 let path = Path::new("foo/bar/baz");
404
405 assert_eq!(path.strip_prefix("").unwrap().as_str(), "foo/bar/baz");
407 assert_eq!(path.strip_prefix("foo").unwrap().as_str(), "bar/baz");
408 assert_eq!(path.strip_prefix(&Path::new("foo/")).unwrap().as_str(), "bar/baz");
409 assert_eq!(path.strip_prefix("foo/bar").unwrap().as_str(), "baz");
410 assert_eq!(path.strip_prefix(&Path::new("foo/bar/")).unwrap().as_str(), "baz");
411 assert_eq!(path.strip_prefix("foo/bar/baz").unwrap().as_str(), "");
412
413 assert!(path.strip_prefix("fo").is_none());
415 assert!(path.strip_prefix(&Path::new("bar")).is_none());
416 }
417
418 #[test]
419 fn test_join() {
420 assert_eq!(Path::new("foo").join("bar").as_str(), "foo/bar");
422 assert_eq!(Path::new("foo/").join(&Path::new("bar")).as_str(), "foo/bar");
423 assert_eq!(Path::new("").join("bar").as_str(), "bar");
424 assert_eq!(Path::new("foo/bar").join(&Path::new("baz")).as_str(), "foo/bar/baz");
425 }
426
427 #[test]
428 fn test_empty() {
429 let empty = Path::new("");
430 assert!(empty.is_empty());
431 assert_eq!(empty.len(), 0);
432
433 let non_empty = Path::new("foo");
434 assert!(!non_empty.is_empty());
435 assert_eq!(non_empty.len(), 3);
436 }
437
438 #[test]
439 fn test_from_conversions() {
440 let path1 = Path::from("foo/bar");
441 let path2 = Path::from(String::from("foo/bar"));
442 let s = String::from("foo/bar");
443 let path3 = Path::from(&s);
444
445 assert_eq!(path1.as_str(), "foo/bar");
446 assert_eq!(path2.as_str(), "foo/bar");
447 assert_eq!(path3.as_str(), "foo/bar");
448 }
449
450 #[test]
451 fn test_path_prefix_join() {
452 let prefix = Path::new("foo");
453 let suffix = Path::new("bar/baz");
454 let path = prefix.join(&suffix);
455 assert_eq!(path.as_str(), "foo/bar/baz");
456
457 let prefix = Path::new("foo/");
458 let suffix = Path::new("bar/baz");
459 let path = prefix.join(&suffix);
460 assert_eq!(path.as_str(), "foo/bar/baz");
461
462 let prefix = Path::new("foo");
463 let suffix = Path::new("/bar/baz");
464 let path = prefix.join(&suffix);
465 assert_eq!(path.as_str(), "foo/bar/baz");
466
467 let prefix = Path::new("");
468 let suffix = Path::new("bar/baz");
469 let path = prefix.join(&suffix);
470 assert_eq!(path.as_str(), "bar/baz");
471 }
472
473 #[test]
474 fn test_path_prefix_conversions() {
475 let prefix1 = Path::from("foo/bar");
476 let prefix2 = Path::from(String::from("foo/bar"));
477 let s = String::from("foo/bar");
478 let prefix3 = Path::from(&s);
479
480 assert_eq!(prefix1.as_str(), "foo/bar");
481 assert_eq!(prefix2.as_str(), "foo/bar");
482 assert_eq!(prefix3.as_str(), "foo/bar");
483 }
484
485 #[test]
486 fn test_path_suffix_conversions() {
487 let suffix1 = Path::from("foo/bar");
488 let suffix2 = Path::from(String::from("foo/bar"));
489 let s = String::from("foo/bar");
490 let suffix3 = Path::from(&s);
491
492 assert_eq!(suffix1.as_str(), "foo/bar");
493 assert_eq!(suffix2.as_str(), "foo/bar");
494 assert_eq!(suffix3.as_str(), "foo/bar");
495 }
496
497 #[test]
498 fn test_path_types_basic_operations() {
499 let prefix = Path::new("foo/bar");
500 assert_eq!(prefix.as_str(), "foo/bar");
501 assert!(!prefix.is_empty());
502 assert_eq!(prefix.len(), 7);
503
504 let suffix = Path::new("baz/qux");
505 assert_eq!(suffix.as_str(), "baz/qux");
506 assert!(!suffix.is_empty());
507 assert_eq!(suffix.len(), 7);
508
509 let empty_prefix = Path::new("");
510 assert!(empty_prefix.is_empty());
511 assert_eq!(empty_prefix.len(), 0);
512
513 let empty_suffix = Path::new("");
514 assert!(empty_suffix.is_empty());
515 assert_eq!(empty_suffix.len(), 0);
516 }
517
518 #[test]
519 fn test_prefix_has_prefix() {
520 let prefix = Path::new("foo/bar");
522 assert!(prefix.has_prefix(&Path::new("")));
523
524 let prefix = Path::new("foo/bar");
526 assert!(prefix.has_prefix(&Path::new("foo/bar")));
527
528 assert!(prefix.has_prefix(&Path::new("foo")));
530 assert!(prefix.has_prefix(&Path::new("foo/")));
531
532 assert!(!prefix.has_prefix(&Path::new("f")));
534 assert!(!prefix.has_prefix(&Path::new("fo")));
535 assert!(!prefix.has_prefix(&Path::new("foo/b")));
536 assert!(!prefix.has_prefix(&Path::new("foo/ba")));
537
538 let prefix = Path::new("foobar");
540 assert!(!prefix.has_prefix(&Path::new("foo")));
541 assert!(prefix.has_prefix(&Path::new("foobar")));
542
543 let prefix = Path::new("foo/bar/");
545 assert!(prefix.has_prefix(&Path::new("foo")));
546 assert!(prefix.has_prefix(&Path::new("foo/")));
547 assert!(prefix.has_prefix(&Path::new("foo/bar")));
548 assert!(prefix.has_prefix(&Path::new("foo/bar/")));
549
550 let prefix = Path::new("foo");
552 assert!(prefix.has_prefix(&Path::new("")));
553 assert!(prefix.has_prefix(&Path::new("foo")));
554 assert!(prefix.has_prefix(&Path::new("foo/"))); assert!(!prefix.has_prefix(&Path::new("f")));
556
557 let prefix = Path::new("");
559 assert!(prefix.has_prefix(&Path::new("")));
560 assert!(!prefix.has_prefix(&Path::new("foo")));
561 }
562
563 #[test]
564 fn test_prefix_join() {
565 let prefix = Path::new("foo");
567 let suffix = Path::new("bar");
568 assert_eq!(prefix.join(&suffix).as_str(), "foo/bar");
569
570 let prefix = Path::new("foo/");
572 let suffix = Path::new("bar");
573 assert_eq!(prefix.join(&suffix).as_str(), "foo/bar");
574
575 let prefix = Path::new("foo");
577 let suffix = Path::new("/bar");
578 assert_eq!(prefix.join(&suffix).as_str(), "foo/bar");
579
580 let prefix = Path::new("foo");
582 let suffix = Path::new("bar/");
583 assert_eq!(prefix.join(&suffix).as_str(), "foo/bar"); let prefix = Path::new("foo/");
587 let suffix = Path::new("/bar");
588 assert_eq!(prefix.join(&suffix).as_str(), "foo/bar");
589
590 let prefix = Path::new("foo");
592 let suffix = Path::new("");
593 assert_eq!(prefix.join(&suffix).as_str(), "foo");
594
595 let prefix = Path::new("");
597 let suffix = Path::new("bar");
598 assert_eq!(prefix.join(&suffix).as_str(), "bar");
599
600 let prefix = Path::new("");
602 let suffix = Path::new("");
603 assert_eq!(prefix.join(&suffix).as_str(), "");
604
605 let prefix = Path::new("foo/bar");
607 let suffix = Path::new("baz/qux");
608 assert_eq!(prefix.join(&suffix).as_str(), "foo/bar/baz/qux");
609
610 let prefix = Path::new("foo/bar/");
612 let suffix = Path::new("/baz/qux/");
613 assert_eq!(prefix.join(&suffix).as_str(), "foo/bar/baz/qux"); }
615
616 #[test]
617 fn test_path_ref() {
618 let ref1 = PathRef::new("/foo/bar/");
620 assert_eq!(ref1.as_str(), "foo/bar");
621
622 let ref2 = PathRef::from("///foo///");
623 assert_eq!(ref2.as_str(), "foo");
624
625 let ref3 = PathRef::new("foo//bar///baz");
627 assert_eq!(ref3.as_str(), "foo/bar/baz");
628
629 let path = Path::new("foo/bar");
631 let path_ref = PathRef::from(&path);
632 assert_eq!(path_ref.as_str(), "foo/bar");
633
634 let path2 = Path::new("foo/bar/baz");
636 assert!(path2.has_prefix(&path_ref));
637 assert_eq!(path2.strip_prefix(&path_ref).unwrap().as_str(), "baz");
638
639 let empty = PathRef::new("");
641 assert!(empty.is_empty());
642 assert_eq!(empty.len(), 0);
643 }
644
645 #[test]
646 fn test_multiple_consecutive_slashes() {
647 let path = Path::new("foo//bar///baz");
648 assert_eq!(path.as_str(), "foo/bar/baz");
650
651 let path2 = Path::new("//foo//bar///baz//");
653 assert_eq!(path2.as_str(), "foo/bar/baz");
654
655 let path3 = Path::new("foo///bar");
657 assert_eq!(path3.as_str(), "foo/bar");
658 }
659
660 #[test]
661 fn test_removes_multiple_slashes_comprehensively() {
662 assert_eq!(Path::new("foo//bar").as_str(), "foo/bar");
664 assert_eq!(Path::new("foo///bar").as_str(), "foo/bar");
665 assert_eq!(Path::new("foo////bar").as_str(), "foo/bar");
666
667 assert_eq!(Path::new("foo//bar//baz").as_str(), "foo/bar/baz");
669 assert_eq!(Path::new("a//b//c//d").as_str(), "a/b/c/d");
670
671 assert_eq!(Path::new("foo//bar///baz////qux").as_str(), "foo/bar/baz/qux");
673
674 assert_eq!(Path::new("//foo//bar//").as_str(), "foo/bar");
676 assert_eq!(Path::new("///foo///bar///").as_str(), "foo/bar");
677
678 assert_eq!(Path::new("//").as_str(), "");
680 assert_eq!(Path::new("////").as_str(), "");
681
682 let path_with_slashes = Path::new("foo//bar///baz");
684 assert!(path_with_slashes.has_prefix("foo/bar"));
685 assert_eq!(path_with_slashes.strip_prefix("foo").unwrap().as_str(), "bar/baz");
686 assert_eq!(path_with_slashes.join("qux").as_str(), "foo/bar/baz/qux");
687
688 let path_ref = PathRef::new("foo//bar///baz");
690 assert_eq!(path_ref.as_str(), "foo/bar/baz"); let path_from_ref = path_ref.to_owned();
692 assert_eq!(path_from_ref.as_str(), "foo/bar/baz"); }
694
695 #[test]
696 fn test_path_ref_multiple_slashes() {
697 let path_ref = PathRef::new("//foo//bar///baz//");
699 assert_eq!(path_ref.as_str(), "foo/bar/baz"); assert_eq!(PathRef::new("foo//bar").as_str(), "foo/bar");
703 assert_eq!(PathRef::new("foo///bar").as_str(), "foo/bar");
704 assert_eq!(PathRef::new("a//b//c//d").as_str(), "a/b/c/d");
705
706 assert_eq!(PathRef::new("foo//bar").to_owned().as_str(), "foo/bar");
708 assert_eq!(PathRef::new("foo///bar").to_owned().as_str(), "foo/bar");
709 assert_eq!(PathRef::new("a//b//c//d").to_owned().as_str(), "a/b/c/d");
710
711 assert_eq!(PathRef::new("//").as_str(), "");
713 assert_eq!(PathRef::new("////").as_str(), "");
714 assert_eq!(PathRef::new("//").to_owned().as_str(), "");
715 assert_eq!(PathRef::new("////").to_owned().as_str(), "");
716
717 let normal_path = PathRef::new("foo/bar/baz");
719 assert_eq!(normal_path.as_str(), "foo/bar/baz");
720 let needs_norm = PathRef::new("foo//bar");
723 assert_eq!(needs_norm.as_str(), "foo/bar");
724 }
726
727 #[test]
728 fn test_ergonomic_conversions() {
729 fn takes_path_ref<'a>(p: impl Into<PathRef<'a>>) -> String {
731 p.into().as_str().to_string()
732 }
733
734 fn takes_path_ref_with_trait<'a>(p: impl IntoPathRef<'a>) -> String {
736 p.into().as_str().to_string()
737 }
738
739 assert_eq!(takes_path_ref("foo//bar"), "foo/bar");
741
742 let owned_string = String::from("foo//bar///baz");
744 assert_eq!(takes_path_ref(owned_string), "foo/bar/baz");
745
746 let string_ref = String::from("foo//bar");
748 assert_eq!(takes_path_ref(&string_ref), "foo/bar");
749
750 let path_ref = PathRef::new("foo//bar");
752 assert_eq!(takes_path_ref(&path_ref), "foo/bar");
753
754 let path = Path::new("foo//bar");
756 assert_eq!(takes_path_ref(&path), "foo/bar");
757
758 let _path1 = Path::new("foo/bar"); let _path2 = Path::new(String::from("foo/bar")); let _path3 = Path::new(String::from("foo/bar")); let _path4 = Path::new(PathRef::new("foo/bar")); assert_eq!(takes_path_ref_with_trait("foo//bar"), "foo/bar");
766 assert_eq!(takes_path_ref_with_trait(String::from("foo//bar")), "foo/bar");
767 }
768
769 #[test]
770 fn test_prefix_strip_prefix() {
771 let prefix = Path::new("foo/bar/baz");
773 assert_eq!(prefix.strip_prefix(&Path::new("")).unwrap().as_str(), "foo/bar/baz");
774 assert_eq!(prefix.strip_prefix(&Path::new("foo")).unwrap().as_str(), "bar/baz");
775 assert_eq!(prefix.strip_prefix(&Path::new("foo/")).unwrap().as_str(), "bar/baz");
776 assert_eq!(prefix.strip_prefix(&Path::new("foo/bar")).unwrap().as_str(), "baz");
777 assert_eq!(prefix.strip_prefix(&Path::new("foo/bar/")).unwrap().as_str(), "baz");
778 assert_eq!(prefix.strip_prefix(&Path::new("foo/bar/baz")).unwrap().as_str(), "");
779
780 assert!(prefix.strip_prefix(&Path::new("fo")).is_none());
782 assert!(prefix.strip_prefix(&Path::new("bar")).is_none());
783 assert!(prefix.strip_prefix(&Path::new("foo/ba")).is_none());
784
785 let prefix = Path::new("foobar");
787 assert!(prefix.strip_prefix(&Path::new("foo")).is_none());
788 assert_eq!(prefix.strip_prefix(&Path::new("foobar")).unwrap().as_str(), "");
789
790 let prefix = Path::new("");
792 assert_eq!(prefix.strip_prefix(&Path::new("")).unwrap().as_str(), "");
793 assert!(prefix.strip_prefix(&Path::new("foo")).is_none());
794
795 let prefix = Path::new("foo");
797 assert_eq!(prefix.strip_prefix(&Path::new("foo")).unwrap().as_str(), "");
798 assert_eq!(prefix.strip_prefix(&Path::new("foo/")).unwrap().as_str(), ""); let prefix = Path::new("foo/bar/");
802 assert_eq!(prefix.strip_prefix(&Path::new("foo")).unwrap().as_str(), "bar");
803 assert_eq!(prefix.strip_prefix(&Path::new("foo/")).unwrap().as_str(), "bar");
804 assert_eq!(prefix.strip_prefix(&Path::new("foo/bar")).unwrap().as_str(), "");
805 assert_eq!(prefix.strip_prefix(&Path::new("foo/bar/")).unwrap().as_str(), "");
806 }
807}