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.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());
}
if !self.0.starts_with(prefix.as_str()) {
return None;
}
if self.0.len() == prefix.len() {
return Some(Path(Cow::Borrowed("")));
}
if self.0.as_bytes().get(prefix.len()) != Some(&b'/') {
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))
}
}
#[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_prefix_list_canonical_order() {
let a = PathPrefixes::new(["foo", "bar"]);
let b = PathPrefixes::new(["bar", "foo"]);
assert_eq!(a, b);
}
}