use std::fmt::{self, Display};
use std::sync::Arc;
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> {
self.borrow()
}
}
impl AsPath for Path<'_> {
fn as_path(&self) -> Path<'_> {
self.borrow()
}
}
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(Clone)]
enum Repr<'a> {
Borrowed(&'a str),
Shared { buf: Arc<str>, start: usize },
}
#[derive(Clone)]
pub struct Path<'a>(Repr<'a>);
impl<'a> Path<'a> {
pub const MAX_PARTS: usize = 32;
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(Repr::Shared {
buf: normalized.into(),
start: 0,
})
} else {
Self(Repr::Borrowed(trimmed))
}
}
fn slice_from(&'a self, n: usize) -> Path<'a> {
match &self.0 {
Repr::Borrowed(s) => Path(Repr::Borrowed(&s[n..])),
Repr::Shared { buf, start } => Path(Repr::Shared {
buf: buf.clone(),
start: start + n,
}),
}
}
pub fn has_prefix(&self, prefix: impl AsPath) -> bool {
let prefix = prefix.as_path();
if prefix.is_empty() {
return true;
}
let s = self.as_str();
if !s.starts_with(prefix.as_str()) {
return false;
}
if s.len() == prefix.len() {
return true;
}
s.as_bytes().get(prefix.len()) == Some(&b'/')
}
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());
}
let s = self.as_str();
if !s.starts_with(prefix.as_str()) {
return None;
}
if s.len() == prefix.len() {
return Some(Path::empty());
}
if s.as_bytes().get(prefix.len()) != Some(&b'/') {
return None;
}
Some(self.slice_from(prefix.len() + 1))
}
pub fn parts(&self) -> impl Iterator<Item = &str> {
self.as_str().split('/').filter(|part| !part.is_empty())
}
pub fn next_part(&'a self) -> Option<(&'a str, Path<'a>)> {
let s = self.as_str();
if s.is_empty() {
return None;
}
if let Some(i) = s.find('/') {
Some((&s[..i], self.slice_from(i + 1)))
} else {
Some((s, Path::empty()))
}
}
pub fn as_str(&self) -> &str {
match &self.0 {
Repr::Borrowed(s) => s,
Repr::Shared { buf, start } => &buf[*start..],
}
}
pub fn empty() -> Path<'static> {
Path(Repr::Borrowed(""))
}
pub fn is_empty(&self) -> bool {
self.as_str().is_empty()
}
pub fn len(&self) -> usize {
self.as_str().len()
}
pub fn to_owned(&self) -> PathOwned {
match &self.0 {
Repr::Borrowed("") => Path::empty(),
Repr::Borrowed(s) => Path(Repr::Shared {
buf: Arc::from(*s),
start: 0,
}),
Repr::Shared { buf, start } => Path(Repr::Shared {
buf: buf.clone(),
start: *start,
}),
}
}
pub fn into_owned(self) -> PathOwned {
match self.0 {
Repr::Borrowed("") => Path::empty(),
Repr::Borrowed(s) => Path(Repr::Shared {
buf: Arc::from(s),
start: 0,
}),
Repr::Shared { buf, start } => Path(Repr::Shared { buf, start }),
}
}
pub fn borrow(&'a self) -> Path<'a> {
self.slice_from(0)
}
pub fn join(&self, other: impl AsPath) -> PathOwned {
let other = other.as_path();
if self.is_empty() {
other.to_owned()
} else if other.is_empty() {
self.to_owned()
} else {
Path(Repr::Shared {
buf: format!("{}/{}", self.as_str(), other.as_str()).into(),
start: 0,
})
}
}
}
impl<'b> PartialEq<Path<'b>> for Path<'_> {
fn eq(&self, other: &Path<'b>) -> bool {
self.as_str() == other.as_str()
}
}
impl Eq for Path<'_> {}
impl PartialOrd for Path<'_> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Path<'_> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.as_str().cmp(other.as_str())
}
}
impl std::hash::Hash for Path<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state)
}
}
impl fmt::Debug for Path<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Path").field(&self.as_str()).finish()
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Path<'_> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.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 {
Path::empty()
}
}
impl From<String> for Path<'_> {
fn from(s: String) -> Self {
Path::new(&s).into_owned()
}
}
impl AsRef<str> for Path<'_> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Display for Path<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl<V: Copy> Decode<V> for Path<'_>
where
String: Decode<V>,
{
fn decode<R: bytes::Buf>(r: &mut R, version: V) -> Result<Self, DecodeError> {
let path: Path = String::decode(r, version)?.into();
if path.parts().count() > Path::MAX_PARTS {
return Err(DecodeError::BoundsExceeded);
}
Ok(path)
}
}
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> {
if self.parts().count() > Path::MAX_PARTS {
return Err(EncodeError::BoundsExceeded);
}
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))
}
}
#[derive(Debug, Clone, Default, Eq)]
pub struct PathPrefixes {
paths: Vec<PathOwned>,
}
impl PathPrefixes {
pub fn new(paths: impl IntoIterator<Item = impl AsPath>) -> Self {
let mut paths: Vec<PathOwned> = paths.into_iter().map(|p| p.as_path().to_owned()).collect();
if paths.len() <= 1 {
return Self { paths };
}
paths.sort_by(|a, b| a.len().cmp(&b.len()).then_with(|| a.as_str().cmp(b.as_str())));
paths.dedup();
let mut result: Vec<PathOwned> = Vec::new();
'outer: for path in paths {
for existing in &result {
if path.has_prefix(existing) {
continue 'outer;
}
}
result.push(path);
}
Self { paths: result }
}
pub fn is_empty(&self) -> bool {
self.paths.is_empty()
}
pub fn len(&self) -> usize {
self.paths.len()
}
pub fn iter(&self) -> std::slice::Iter<'_, PathOwned> {
self.paths.iter()
}
}
impl std::ops::Deref for PathPrefixes {
type Target = [PathOwned];
fn deref(&self) -> &[PathOwned] {
&self.paths
}
}
impl FromIterator<PathOwned> for PathPrefixes {
fn from_iter<I: IntoIterator<Item = PathOwned>>(iter: I) -> Self {
Self::new(iter)
}
}
impl From<Vec<PathOwned>> for PathPrefixes {
fn from(paths: Vec<PathOwned>) -> Self {
Self::new(paths)
}
}
impl<'a> PartialEq<Vec<Path<'a>>> for PathPrefixes {
fn eq(&self, other: &Vec<Path<'a>>) -> bool {
self.paths == *other
}
}
impl<'a> PartialEq<PathPrefixes> for Vec<Path<'a>> {
fn eq(&self, other: &PathPrefixes) -> bool {
*self == other.paths
}
}
impl PartialEq for PathPrefixes {
fn eq(&self, other: &Self) -> bool {
self.paths == other.paths
}
}
impl IntoIterator for PathPrefixes {
type Item = PathOwned;
type IntoIter = std::vec::IntoIter<PathOwned>;
fn into_iter(self) -> Self::IntoIter {
self.paths.into_iter()
}
}
impl<'a> IntoIterator for &'a PathPrefixes {
type Item = &'a PathOwned;
type IntoIter = std::slice::Iter<'a, PathOwned>;
fn into_iter(self) -> Self::IntoIter {
self.paths.iter()
}
}
#[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(), "");
}
#[test]
fn test_prefix_list_dedup() {
let list = PathPrefixes::new(["demo", "demo"]);
assert_eq!(list.len(), 1);
assert_eq!(list[0], Path::new("demo"));
}
#[test]
fn test_prefix_list_overlap() {
let list = PathPrefixes::new(["demo", "demo/foo", "anon"]);
assert_eq!(list.len(), 2);
assert!(list.iter().any(|p| p == &Path::new("demo")));
assert!(list.iter().any(|p| p == &Path::new("anon")));
}
#[test]
fn test_prefix_list_overlap_reverse_order() {
let list = PathPrefixes::new(["demo/foo", "demo"]);
assert_eq!(list.len(), 1);
assert_eq!(list[0], Path::new("demo"));
}
#[test]
fn test_prefix_list_empty_covers_all() {
let list = PathPrefixes::new(["", "demo", "anon"]);
assert_eq!(list.len(), 1);
assert_eq!(list[0], Path::new(""));
}
#[test]
fn test_prefix_list_no_overlap() {
let list = PathPrefixes::new(["demo", "anon", "secret"]);
assert_eq!(list.len(), 3);
}
#[test]
fn test_prefix_list_single() {
let list = PathPrefixes::new(["demo"]);
assert_eq!(list.len(), 1);
}
#[test]
fn test_prefix_list_empty() {
let list = PathPrefixes::new(std::iter::empty::<&str>());
assert!(list.is_empty());
assert_eq!(list.len(), 0);
}
#[test]
fn test_prefix_list_deep_overlap() {
let list = PathPrefixes::new(["a/b/c", "a/b", "a"]);
assert_eq!(list.len(), 1);
assert_eq!(list[0], Path::new("a"));
}
#[test]
fn test_prefix_list_partial_name_not_overlap() {
let list = PathPrefixes::new(["demo", "demonstration"]);
assert_eq!(list.len(), 2);
}
#[test]
fn test_prefix_list_collect() {
let paths: Vec<PathOwned> = vec!["demo".into(), "demo/foo".into()];
let list: PathPrefixes = paths.into_iter().collect();
assert_eq!(list.len(), 1);
assert_eq!(list[0], Path::new("demo"));
}
#[test]
fn test_prefix_list_eq_vec() {
let list = PathPrefixes::new(["demo", "anon"]);
assert_eq!(list, vec!["anon".as_path(), "demo".as_path()]);
}
#[test]
fn test_owned_paths_share_allocation() {
let path = Path::new("customer/room/broadcast").to_owned();
let cloned = path.clone();
assert_eq!(path.as_str().as_ptr(), cloned.as_str().as_ptr());
let requeued = path.as_path().to_owned();
assert_eq!(path.as_str().as_ptr(), requeued.as_str().as_ptr());
let stripped = path.strip_prefix("customer").unwrap().to_owned();
assert_eq!(stripped.as_str(), "room/broadcast");
assert_eq!(stripped.as_str().as_ptr(), path.as_str()["customer/".len()..].as_ptr());
let (dir, rest) = path.next_part().unwrap();
assert_eq!(dir, "customer");
let rest = rest.to_owned();
assert_eq!(rest.as_str().as_ptr(), stripped.as_str().as_ptr());
let joined = path.join("alice");
let joined2 = joined.clone();
assert_eq!(joined.as_str(), "customer/room/broadcast/alice");
assert_eq!(joined.as_str().as_ptr(), joined2.as_str().as_ptr());
}
#[test]
fn test_parts() {
assert_eq!(Path::empty().parts().count(), 0);
assert_eq!(Path::new("foo").parts().collect::<Vec<_>>(), ["foo"]);
assert_eq!(Path::new("/foo//bar/").parts().collect::<Vec<_>>(), ["foo", "bar"]);
}
#[test]
fn test_wire_max_parts() {
use crate::lite::Version;
let ok = (0..Path::MAX_PARTS)
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join("/");
let too_deep = format!("{ok}/extra");
let mut buf = bytes::BytesMut::new();
Path::new(&ok).encode(&mut buf, Version::Lite04).unwrap();
assert!(matches!(
Path::new(&too_deep).encode(&mut bytes::BytesMut::new(), Version::Lite04),
Err(EncodeError::BoundsExceeded)
));
let decoded = Path::decode(&mut buf.freeze(), Version::Lite04).unwrap();
assert_eq!(decoded.as_str(), ok);
let mut buf = bytes::BytesMut::new();
too_deep.as_str().encode(&mut buf, Version::Lite04).unwrap();
assert!(matches!(
Path::decode(&mut buf.freeze(), Version::Lite04),
Err(DecodeError::BoundsExceeded)
));
}
#[test]
fn test_owned_empty_paths() {
let empty = Path::new("").to_owned();
assert!(empty.is_empty());
assert_eq!(empty, Path::empty());
let path = Path::new("foo").to_owned();
let rest = path.strip_prefix("foo").unwrap().to_owned();
assert!(rest.is_empty());
}
#[test]
fn test_prefix_list_canonical_order() {
let a = PathPrefixes::new(["foo", "bar"]);
let b = PathPrefixes::new(["bar", "foo"]);
assert_eq!(a, b);
}
}