use std::borrow::Cow;
use std::fmt::{self, Display};
use crate::coding::{Decode, DecodeError, Encode, EncodeError};
pub type PathOwned = Path<'static>;
pub trait AsPath {
fn as_path(&self) -> Path<'_>;
}
impl<'a> AsPath for &'a str {
fn as_path(&self) -> Path<'a> {
Path::new(self)
}
}
impl<'a> AsPath for &'a Path<'a> {
fn as_path(&self) -> Path<'a> {
Path(Cow::Borrowed(self.as_str()))
}
}
impl AsPath for Path<'_> {
fn as_path(&self) -> Path<'_> {
Path(Cow::Borrowed(self.0.as_ref()))
}
}
impl AsPath for String {
fn as_path(&self) -> Path<'_> {
Path::new(self)
}
}
impl<'a> AsPath for &'a String {
fn as_path(&self) -> Path<'a> {
Path::new(self)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Path<'a>(Cow<'a, str>);
impl<'a> Path<'a> {
pub fn new(s: &'a str) -> Self {
let trimmed = s.trim_start_matches('/').trim_end_matches('/');
if trimmed.contains("//") {
let normalized = trimmed
.split('/')
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join("/");
Self(Cow::Owned(normalized))
} else {
Self(Cow::Borrowed(trimmed))
}
}
pub fn has_prefix(&self, prefix: impl AsPath) -> bool {
let prefix = prefix.as_path();
if prefix.is_empty() {
return true;
}
if !self.0.starts_with(prefix.as_str()) {
return false;
}
if self.0.len() == prefix.len() {
return true;
}
self.0.chars().nth(prefix.len()) == Some('/')
}
pub fn strip_prefix(&'a self, prefix: impl AsPath) -> Option<Path<'a>> {
let prefix = prefix.as_path();
if prefix.is_empty() {
return Some(self.borrow());
}
if !self.0.starts_with(prefix.as_str()) {
return None;
}
if self.0.len() == prefix.len() {
return Some(Path(Cow::Borrowed("")));
}
if self.0.chars().nth(prefix.len()) != Some('/') {
return None;
}
Some(Path(Cow::Borrowed(&self.0[prefix.len() + 1..])))
}
pub fn next_part(&'a self) -> Option<(&'a str, Path<'a>)> {
if self.0.is_empty() {
return None;
}
if let Some(i) = self.0.find('/') {
let dir = &self.0[..i];
let rest = Path(Cow::Borrowed(&self.0[i + 1..]));
Some((dir, rest))
} else {
Some((&self.0, Path(Cow::Borrowed(""))))
}
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn empty() -> Path<'static> {
Path(Cow::Borrowed(""))
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn to_owned(&self) -> PathOwned {
Path(Cow::Owned(self.0.to_string()))
}
pub fn into_owned(self) -> PathOwned {
Path(Cow::Owned(self.0.to_string()))
}
pub fn borrow(&'a self) -> Path<'a> {
Path(Cow::Borrowed(&self.0))
}
pub fn join(&self, other: impl AsPath) -> PathOwned {
let other = other.as_path();
if self.0.is_empty() {
Path(Cow::Owned(other.0.to_string()))
} else if other.is_empty() {
self.to_owned()
} else {
Path(Cow::Owned(format!("{}/{}", self.0, other.as_str())))
}
}
}
impl<'a> From<&'a str> for Path<'a> {
fn from(s: &'a str) -> Self {
Self::new(s)
}
}
impl<'a> From<&'a String> for Path<'a> {
fn from(s: &'a String) -> Self {
Self::new(s)
}
}
impl Default for Path<'_> {
fn default() -> Self {
Self(Cow::Borrowed(""))
}
}
impl From<String> for Path<'_> {
fn from(s: String) -> Self {
let trimmed = s.trim_start_matches('/').trim_end_matches('/');
if trimmed.contains("//") {
let normalized = trimmed
.split('/')
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join("/");
Self(Cow::Owned(normalized))
} else if trimmed == s {
Self(Cow::Owned(s))
} else {
Self(Cow::Owned(trimmed.to_string()))
}
}
}
impl AsRef<str> for Path<'_> {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Display for Path<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<V: Copy> Decode<V> for Path<'_>
where
String: Decode<V>,
{
fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
Ok(String::decode(r, version)?.into())
}
}
impl<V: Copy> Encode<V> for Path<'_>
where
for<'a> &'a str: Encode<V>,
{
fn encode<W: bytes::BufMut>(&self, w: &mut W, version: V) -> Result<(), EncodeError> {
self.as_str().encode(w, version)?;
Ok(())
}
}
#[cfg(feature = "serde")]
impl<'de: 'a, 'a> serde::Deserialize<'de> for Path<'a> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = <&'a str as serde::Deserialize<'de>>::deserialize(deserializer)?;
Ok(Path::new(s))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_has_prefix() {
let path = Path::new("foo/bar/baz");
assert!(path.has_prefix(""));
assert!(path.has_prefix("foo"));
assert!(path.has_prefix(Path::new("foo")));
assert!(path.has_prefix("foo/"));
assert!(path.has_prefix("foo/bar"));
assert!(path.has_prefix(Path::new("foo/bar/")));
assert!(path.has_prefix("foo/bar/baz"));
assert!(!path.has_prefix("f"));
assert!(!path.has_prefix(Path::new("fo")));
assert!(!path.has_prefix("foo/b"));
assert!(!path.has_prefix("foo/ba"));
assert!(!path.has_prefix(Path::new("foo/bar/ba")));
let path = Path::new("foobar");
assert!(!path.has_prefix("foo"));
assert!(path.has_prefix(Path::new("foobar")));
}
#[test]
fn test_strip_prefix() {
let path = Path::new("foo/bar/baz");
assert_eq!(path.strip_prefix("").unwrap().as_str(), "foo/bar/baz");
assert_eq!(path.strip_prefix("foo").unwrap().as_str(), "bar/baz");
assert_eq!(path.strip_prefix(Path::new("foo/")).unwrap().as_str(), "bar/baz");
assert_eq!(path.strip_prefix("foo/bar").unwrap().as_str(), "baz");
assert_eq!(path.strip_prefix(Path::new("foo/bar/")).unwrap().as_str(), "baz");
assert_eq!(path.strip_prefix("foo/bar/baz").unwrap().as_str(), "");
assert!(path.strip_prefix("fo").is_none());
assert!(path.strip_prefix(Path::new("bar")).is_none());
}
#[test]
fn test_join() {
assert_eq!(Path::new("foo").join("bar").as_str(), "foo/bar");
assert_eq!(Path::new("foo/").join(Path::new("bar")).as_str(), "foo/bar");
assert_eq!(Path::new("").join("bar").as_str(), "bar");
assert_eq!(Path::new("foo/bar").join(Path::new("baz")).as_str(), "foo/bar/baz");
}
#[test]
fn test_empty() {
let empty = Path::new("");
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
let non_empty = Path::new("foo");
assert!(!non_empty.is_empty());
assert_eq!(non_empty.len(), 3);
}
#[test]
fn test_from_conversions() {
let path1 = Path::from("foo/bar");
let path2 = Path::from("foo/bar");
let s = String::from("foo/bar");
let path3 = Path::from(&s);
assert_eq!(path1.as_str(), "foo/bar");
assert_eq!(path2.as_str(), "foo/bar");
assert_eq!(path3.as_str(), "foo/bar");
}
#[test]
fn test_path_prefix_join() {
let prefix = Path::new("foo");
let suffix = Path::new("bar/baz");
let path = prefix.join(&suffix);
assert_eq!(path.as_str(), "foo/bar/baz");
let prefix = Path::new("foo/");
let suffix = Path::new("bar/baz");
let path = prefix.join(&suffix);
assert_eq!(path.as_str(), "foo/bar/baz");
let prefix = Path::new("foo");
let suffix = Path::new("/bar/baz");
let path = prefix.join(&suffix);
assert_eq!(path.as_str(), "foo/bar/baz");
let prefix = Path::new("");
let suffix = Path::new("bar/baz");
let path = prefix.join(&suffix);
assert_eq!(path.as_str(), "bar/baz");
}
#[test]
fn test_path_prefix_conversions() {
let prefix1 = Path::from("foo/bar");
let prefix2 = Path::from(String::from("foo/bar"));
let s = String::from("foo/bar");
let prefix3 = Path::from(&s);
assert_eq!(prefix1.as_str(), "foo/bar");
assert_eq!(prefix2.as_str(), "foo/bar");
assert_eq!(prefix3.as_str(), "foo/bar");
}
#[test]
fn test_path_suffix_conversions() {
let suffix1 = Path::from("foo/bar");
let suffix2 = Path::from(String::from("foo/bar"));
let s = String::from("foo/bar");
let suffix3 = Path::from(&s);
assert_eq!(suffix1.as_str(), "foo/bar");
assert_eq!(suffix2.as_str(), "foo/bar");
assert_eq!(suffix3.as_str(), "foo/bar");
}
#[test]
fn test_path_types_basic_operations() {
let prefix = Path::new("foo/bar");
assert_eq!(prefix.as_str(), "foo/bar");
assert!(!prefix.is_empty());
assert_eq!(prefix.len(), 7);
let suffix = Path::new("baz/qux");
assert_eq!(suffix.as_str(), "baz/qux");
assert!(!suffix.is_empty());
assert_eq!(suffix.len(), 7);
let empty_prefix = Path::new("");
assert!(empty_prefix.is_empty());
assert_eq!(empty_prefix.len(), 0);
let empty_suffix = Path::new("");
assert!(empty_suffix.is_empty());
assert_eq!(empty_suffix.len(), 0);
}
#[test]
fn test_prefix_has_prefix() {
let prefix = Path::new("foo/bar");
assert!(prefix.has_prefix(""));
let prefix = Path::new("foo/bar");
assert!(prefix.has_prefix("foo/bar"));
assert!(prefix.has_prefix("foo"));
assert!(prefix.has_prefix("foo/"));
assert!(!prefix.has_prefix("f"));
assert!(!prefix.has_prefix("fo"));
assert!(!prefix.has_prefix("foo/b"));
assert!(!prefix.has_prefix("foo/ba"));
let prefix = Path::new("foobar");
assert!(!prefix.has_prefix("foo"));
assert!(prefix.has_prefix("foobar"));
let prefix = Path::new("foo/bar/");
assert!(prefix.has_prefix("foo"));
assert!(prefix.has_prefix("foo/"));
assert!(prefix.has_prefix("foo/bar"));
assert!(prefix.has_prefix("foo/bar/"));
let prefix = Path::new("foo");
assert!(prefix.has_prefix(""));
assert!(prefix.has_prefix("foo"));
assert!(prefix.has_prefix("foo/")); assert!(!prefix.has_prefix("f"));
let prefix = Path::new("");
assert!(prefix.has_prefix(""));
assert!(!prefix.has_prefix("foo"));
}
#[test]
fn test_prefix_join() {
let prefix = Path::new("foo");
let suffix = Path::new("bar");
assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
let prefix = Path::new("foo/");
let suffix = Path::new("bar");
assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
let prefix = Path::new("foo");
let suffix = Path::new("/bar");
assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
let prefix = Path::new("foo");
let suffix = Path::new("bar/");
assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
let prefix = Path::new("foo/");
let suffix = Path::new("/bar");
assert_eq!(prefix.join(suffix).as_str(), "foo/bar");
let prefix = Path::new("foo");
let suffix = Path::new("");
assert_eq!(prefix.join(suffix).as_str(), "foo");
let prefix = Path::new("");
let suffix = Path::new("bar");
assert_eq!(prefix.join(suffix).as_str(), "bar");
let prefix = Path::new("");
let suffix = Path::new("");
assert_eq!(prefix.join(suffix).as_str(), "");
let prefix = Path::new("foo/bar");
let suffix = Path::new("baz/qux");
assert_eq!(prefix.join(suffix).as_str(), "foo/bar/baz/qux");
let prefix = Path::new("foo/bar/");
let suffix = Path::new("/baz/qux/");
assert_eq!(prefix.join(suffix).as_str(), "foo/bar/baz/qux"); }
#[test]
fn test_path_ref() {
let ref1 = Path::new("/foo/bar/");
assert_eq!(ref1.as_str(), "foo/bar");
let ref2 = Path::from("///foo///");
assert_eq!(ref2.as_str(), "foo");
let ref3 = Path::new("foo//bar///baz");
assert_eq!(ref3.as_str(), "foo/bar/baz");
let path = Path::new("foo/bar");
let path_ref = path;
assert_eq!(path_ref.as_str(), "foo/bar");
let path2 = Path::new("foo/bar/baz");
assert!(path2.has_prefix(&path_ref));
assert_eq!(path2.strip_prefix(path_ref).unwrap().as_str(), "baz");
let empty = Path::new("");
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
}
#[test]
fn test_multiple_consecutive_slashes() {
let path = Path::new("foo//bar///baz");
assert_eq!(path.as_str(), "foo/bar/baz");
let path2 = Path::new("//foo//bar///baz//");
assert_eq!(path2.as_str(), "foo/bar/baz");
let path3 = Path::new("foo///bar");
assert_eq!(path3.as_str(), "foo/bar");
}
#[test]
fn test_removes_multiple_slashes_comprehensively() {
assert_eq!(Path::new("foo//bar").as_str(), "foo/bar");
assert_eq!(Path::new("foo///bar").as_str(), "foo/bar");
assert_eq!(Path::new("foo////bar").as_str(), "foo/bar");
assert_eq!(Path::new("foo//bar//baz").as_str(), "foo/bar/baz");
assert_eq!(Path::new("a//b//c//d").as_str(), "a/b/c/d");
assert_eq!(Path::new("foo//bar///baz////qux").as_str(), "foo/bar/baz/qux");
assert_eq!(Path::new("//foo//bar//").as_str(), "foo/bar");
assert_eq!(Path::new("///foo///bar///").as_str(), "foo/bar");
assert_eq!(Path::new("//").as_str(), "");
assert_eq!(Path::new("////").as_str(), "");
let path_with_slashes = Path::new("foo//bar///baz");
assert!(path_with_slashes.has_prefix("foo/bar"));
assert_eq!(path_with_slashes.strip_prefix("foo").unwrap().as_str(), "bar/baz");
assert_eq!(path_with_slashes.join("qux").as_str(), "foo/bar/baz/qux");
let path_ref = Path::new("foo//bar///baz");
assert_eq!(path_ref.as_str(), "foo/bar/baz"); let path_from_ref = path_ref.to_owned();
assert_eq!(path_from_ref.as_str(), "foo/bar/baz"); }
#[test]
fn test_path_ref_multiple_slashes() {
let path_ref = Path::new("//foo//bar///baz//");
assert_eq!(path_ref.as_str(), "foo/bar/baz");
assert_eq!(Path::new("foo//bar").as_str(), "foo/bar");
assert_eq!(Path::new("foo///bar").as_str(), "foo/bar");
assert_eq!(Path::new("a//b//c//d").as_str(), "a/b/c/d");
assert_eq!(Path::new("foo//bar").to_owned().as_str(), "foo/bar");
assert_eq!(Path::new("foo///bar").to_owned().as_str(), "foo/bar");
assert_eq!(Path::new("a//b//c//d").to_owned().as_str(), "a/b/c/d");
assert_eq!(Path::new("//").as_str(), "");
assert_eq!(Path::new("////").as_str(), "");
assert_eq!(Path::new("//").to_owned().as_str(), "");
assert_eq!(Path::new("////").to_owned().as_str(), "");
let normal_path = Path::new("foo/bar/baz");
assert_eq!(normal_path.as_str(), "foo/bar/baz");
let needs_norm = Path::new("foo//bar");
assert_eq!(needs_norm.as_str(), "foo/bar");
}
#[test]
fn test_ergonomic_conversions() {
fn takes_path_ref<'a>(p: impl Into<Path<'a>>) -> String {
p.into().as_str().to_string()
}
fn takes_path_ref_with_trait<'a>(p: impl Into<Path<'a>>) -> String {
p.into().as_str().to_string()
}
assert_eq!(takes_path_ref("foo//bar"), "foo/bar");
let owned_string = String::from("foo//bar///baz");
assert_eq!(takes_path_ref(owned_string), "foo/bar/baz");
let string_ref = String::from("foo//bar");
assert_eq!(takes_path_ref(string_ref), "foo/bar");
let path_ref = Path::new("foo//bar");
assert_eq!(takes_path_ref(path_ref), "foo/bar");
let path = Path::new("foo//bar");
assert_eq!(takes_path_ref(path), "foo/bar");
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");
assert_eq!(takes_path_ref_with_trait(String::from("foo//bar")), "foo/bar");
}
#[test]
fn test_prefix_strip_prefix() {
let prefix = Path::new("foo/bar/baz");
assert_eq!(prefix.strip_prefix("").unwrap().as_str(), "foo/bar/baz");
assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "bar/baz");
assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), "bar/baz");
assert_eq!(prefix.strip_prefix("foo/bar").unwrap().as_str(), "baz");
assert_eq!(prefix.strip_prefix("foo/bar/").unwrap().as_str(), "baz");
assert_eq!(prefix.strip_prefix("foo/bar/baz").unwrap().as_str(), "");
assert!(prefix.strip_prefix("fo").is_none());
assert!(prefix.strip_prefix("bar").is_none());
assert!(prefix.strip_prefix("foo/ba").is_none());
let prefix = Path::new("foobar");
assert!(prefix.strip_prefix("foo").is_none());
assert_eq!(prefix.strip_prefix("foobar").unwrap().as_str(), "");
let prefix = Path::new("");
assert_eq!(prefix.strip_prefix("").unwrap().as_str(), "");
assert!(prefix.strip_prefix("foo").is_none());
let prefix = Path::new("foo");
assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "");
assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), "");
let prefix = Path::new("foo/bar/");
assert_eq!(prefix.strip_prefix("foo").unwrap().as_str(), "bar");
assert_eq!(prefix.strip_prefix("foo/").unwrap().as_str(), "bar");
assert_eq!(prefix.strip_prefix("foo/bar").unwrap().as_str(), "");
assert_eq!(prefix.strip_prefix("foo/bar/").unwrap().as_str(), "");
}
}