use std::{
borrow::Cow,
collections::TryReserveError,
ffi::{OsStr, OsString},
path::{Components, Path, PathBuf, PrefixComponent},
str::FromStr,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::{
imp,
trivial::{cast_box_unchecked, cast_ref_unchecked, ConvertError, Error, Normpath, NormpathBuf},
};
macro_rules! delegate {
($platform:ident => $expr:expr) => {{
#[cfg($platform)]
{
Some($expr)
}
#[cfg(not($platform))]
{
None
}
}};
}
impl Normpath {
#[cfg(any(unix, docsrs))]
#[cfg_attr(docsrs, doc(cfg(unix)))]
#[must_use]
#[inline]
pub fn unix_root() -> &'static Self {
unsafe { cast_ref_unchecked(Path::new("/")) }
}
#[must_use]
#[inline]
pub fn root() -> Option<&'static Self> {
delegate!(unix => Self::unix_root())
}
#[inline]
pub fn validate<S: AsRef<OsStr> + ?Sized>(path: &S) -> Result<&Self, Error> {
imp::validate(Path::new(path))
}
#[inline]
pub fn validate_canonical<S: AsRef<OsStr> + ?Sized>(path: &S) -> Result<&Self, Error> {
imp::validate_canonical(Path::new(path))
}
#[inline]
pub fn validate_parentless<S: AsRef<OsStr> + ?Sized>(path: &S) -> Result<&Self, Error> {
imp::validate_parentless(Path::new(path))
}
#[inline]
pub fn normalize<S: AsRef<OsStr> + ?Sized>(path: &S) -> Result<Cow<'_, Self>, Error> {
imp::normalize_new_cow(Path::new(path))
}
#[must_use]
#[inline]
pub unsafe fn new_unchecked<S: AsRef<OsStr> + ?Sized>(path: &S) -> &Self {
let path = Path::new(path.as_ref());
unsafe { cast_ref_unchecked(path) }
}
#[must_use]
#[inline]
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.0.as_os_str().len()
}
#[must_use]
#[inline]
pub fn as_path(&self) -> &Path {
&self.0
}
#[must_use]
#[inline]
pub fn parent(&self) -> Option<&Self> {
let parent = self.0.parent()?;
Some(unsafe { cast_ref_unchecked(parent) })
}
#[cfg(any(windows, docsrs))]
#[cfg_attr(docsrs, doc(cfg(windows)))]
pub fn windows_split_components(&self) -> (PrefixComponent<'_>, Components<'_>) {
use std::path::Component::Prefix;
let mut components = self.0.components();
let Some(Prefix(prefix)) = components.next() else {
unreachable!()
};
(prefix, components)
}
#[cfg(any(windows, docsrs))]
#[cfg_attr(docsrs, doc(cfg(windows)))]
#[must_use]
#[inline]
pub fn windows_prefix(&self) -> PrefixComponent<'_> {
self.windows_split_components().0
}
#[must_use]
#[inline]
pub fn split_components(&self) -> Option<(PrefixComponent<'_>, Components<'_>)> {
delegate!(windows => self.windows_split_components())
}
#[must_use]
#[inline]
pub fn prefix(&self) -> Option<PrefixComponent<'_>> {
delegate!(windows => self.windows_prefix())
}
#[inline]
pub fn checked_join<P: AsRef<Path>>(&self, path: P) -> Result<NormpathBuf, Error> {
let mut buf = self.0.to_path_buf();
imp::push(&mut buf, path.as_ref())?;
Ok(NormpathBuf(buf))
}
#[must_use]
#[inline]
pub fn quick_strip_prefix<P: AsRef<Path>>(&self, base: P) -> Option<&Path> {
imp::strip(&self.0, base.as_ref())
}
#[must_use]
#[inline]
pub fn quick_starts_with<P: AsRef<Path>>(&self, base: P) -> bool {
imp::strip(&self.0, base.as_ref()).is_some()
}
}
impl NormpathBuf {
#[cfg(unix)]
#[cfg_attr(docsrs, doc(cfg(unix)))]
#[must_use]
#[inline]
pub fn root() -> Self {
Self(PathBuf::from("/"))
}
#[inline]
pub fn validate<P>(path: P) -> Result<Self, ConvertError<P>>
where
P: AsRef<OsStr> + Into<OsString>,
{
match imp::validate(Path::new(&path)) {
Ok(_) => Ok(Self(PathBuf::from(path.into()))),
Err(e) => Err(ConvertError::new(e, path)),
}
}
#[inline]
pub fn normalize(mut path: PathBuf) -> Result<Self, ConvertError<PathBuf>> {
match imp::normalize(&mut path) {
Ok(_) => Ok(Self(path)),
Err(e) => Err(ConvertError::new(e, path)),
}
}
#[must_use]
#[inline]
pub unsafe fn new_unchecked<S: Into<OsString>>(path: S) -> Self {
Self(PathBuf::from(path.into()))
}
#[inline]
pub fn push<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> {
imp::push(&mut self.0, path.as_ref())
}
#[inline]
pub fn pop(&mut self) -> bool {
self.0.pop()
}
#[must_use]
#[inline]
pub fn as_normpath(&self) -> &Normpath {
self.as_ref()
}
#[must_use]
#[inline]
pub fn into_boxed_path(self) -> Box<Normpath> {
let value = self.0.into_boxed_path();
unsafe { cast_box_unchecked(value) }
}
#[must_use]
#[inline]
pub fn into_path_buf(self) -> PathBuf {
self.0
}
#[must_use]
#[inline]
pub fn into_os_string(self) -> OsString {
self.0.into_os_string()
}
#[inline]
pub fn capacity(&self) -> usize {
self.0.capacity()
}
#[inline]
pub fn reserve(&mut self, additional: usize) {
self.0.reserve(additional)
}
#[inline]
pub fn reserve_exact(&mut self, additional: usize) {
self.0.reserve_exact(additional)
}
#[inline]
pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
self.0.try_reserve(additional)
}
#[inline]
pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> {
self.0.try_reserve_exact(additional)
}
#[inline]
pub fn shrink_to_fit(&mut self) {
self.0.shrink_to_fit()
}
#[inline]
pub fn shrink_to(&mut self, min_capacity: usize) {
self.0.shrink_to(min_capacity)
}
}
macro_rules! impl_try_from {
(owned over <$($life:lifetime)?> $t:ty) => {
impl<$($life)?> TryFrom<$t> for NormpathBuf {
type Error = ConvertError<$t>;
fn try_from(value: $t) -> Result<Self, Self::Error> {
imp::normalize_new_buf(value)
}
}
};
(&$life:lifetime $t:ty) => {
impl<$life> TryFrom<&$life $t> for NormpathBuf {
type Error = ConvertError<&$life $t>;
fn try_from(value: &$life $t) -> Result<Self, Self::Error> {
imp::normalize_new_buf(PathBuf::from(value))
.map_err(|e| ConvertError::new(e.error, value))
}
}
};
}
impl_try_from!(owned over <> String);
impl_try_from!(owned over <> OsString);
impl_try_from!(owned over <> PathBuf);
impl_try_from!(owned over <> Box<Path>);
impl_try_from!(owned over <'a> Cow<'a, Path>);
impl_try_from!(&'a str);
impl_try_from!(&'a String);
impl_try_from!(&'a OsStr);
impl_try_from!(&'a OsString);
impl_try_from!(&'a Path);
impl_try_from!(&'a PathBuf);
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl Serialize for Normpath {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.as_path().serialize(serializer)
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl Serialize for NormpathBuf {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.as_path().serialize(serializer)
}
}
impl FromStr for NormpathBuf {
type Err = ConvertError<String>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
imp::normalize_new_buf(s.to_string())
}
}
impl FromStr for Box<Normpath> {
type Err = ConvertError<String>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
imp::normalize_new_buf(s.to_string()).map(|p| p.into_boxed_path())
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'a, 'de: 'a> Deserialize<'de> for &'a Normpath {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let path = <&Path>::deserialize(deserializer)?;
imp::validate(path).map_err(serde::de::Error::custom)
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'de> Deserialize<'de> for NormpathBuf {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let path = PathBuf::deserialize(deserializer)?;
imp::normalize_new_buf(path).map_err(serde::de::Error::custom)
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'de> Deserialize<'de> for Box<Normpath> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let path = PathBuf::deserialize(deserializer)?;
imp::normalize_new_buf(path)
.map(|p| p.into_boxed_path())
.map_err(serde::de::Error::custom)
}
}
#[must_use]
#[inline]
pub fn canonicalize_lexically(path: PathBuf) -> PathBuf {
NormpathBuf::normalize(path)
.map(|it| it.into_path_buf())
.unwrap_or_else(|e| e.value)
}
#[cfg(test)]
mod tests {
use std::{iter, ops::RangeBounds, path::Component};
use fastrand::Rng;
use crate::draw;
use super::{ConvertError as Ec, Error as E, *};
const MIN_ABS: usize = if cfg!(unix) { 1 } else { 3 };
#[cfg(unix)]
fn make_root() -> NormpathBuf {
NormpathBuf::root()
}
#[cfg(windows)]
fn make_root() -> NormpathBuf {
let letter = char::from(fastrand::u8(b'A'..=b'Z'));
NormpathBuf::try_from(format!(r"{letter}:\")).unwrap()
}
fn make_paths(
bound: impl RangeBounds<usize> + Clone,
mut draw: impl FnMut(usize) -> String,
) -> impl Iterator<Item = String> {
let mut rng = Rng::new();
iter::from_fn(move || Some(draw(rng.usize(bound.clone()))))
}
#[cfg(unix)]
fn to_canonical(path: impl AsRef<Path>) -> PathBuf {
let collected = path
.as_ref()
.components()
.filter(|c| !matches!(c, Component::CurDir))
.collect::<PathBuf>();
if collected.as_os_str().is_empty() {
PathBuf::from(".")
} else {
collected
}
}
#[cfg(windows)]
fn to_canonical(path: impl AsRef<Path>) -> PathBuf {
use std::path::Prefix;
let path = path.as_ref();
let mut out = PathBuf::with_capacity(path.as_os_str().len());
for component in path.components() {
use {std::path::Prefix::*, Component::*};
match component {
Prefix(component) => match component.kind() {
Disk(c) => {
let letter = char::from(c.to_ascii_uppercase());
out.push(format!("{letter}:"));
}
DeviceNS(name) => {
let name = name.to_str().unwrap();
out.push(format!(r"\\.\{name}"));
}
UNC(server, share) => {
let server = server.to_str().unwrap();
let share = share.to_str().unwrap();
out.push(format!(r"\\{server}\{share}"));
}
_ => return path.into(),
},
RootDir => out.push("\\"),
CurDir => continue,
ParentDir if out.file_name().is_some() => assert!(out.pop()),
ParentDir => out.push(".."),
Normal(name) => out.push(name),
}
}
let mut components = out.components();
let first = components.next();
let tail = components.as_path();
let is_phony = match first {
Some(Component::Prefix(prefix)) => {
matches!(prefix.kind(), Prefix::DeviceNS(_) | Prefix::UNC(_, _))
&& tail.as_os_str() == "\\"
}
_ => false,
};
let is_blank = match first {
Some(Component::Prefix(prefix)) => {
matches!(prefix.kind(), Prefix::Disk(_)) && tail.as_os_str().is_empty()
}
Some(_) => false,
None => true,
};
assert!(!is_phony || !is_blank);
if is_phony {
let mut bytes = std::mem::take(&mut out)
.into_os_string()
.into_encoded_bytes();
bytes.pop();
out = unsafe { OsString::from_encoded_bytes_unchecked(bytes) }.into();
} else if is_blank {
out.push(".");
}
out
}
fn is_canonical(path: impl AsRef<Path>) -> bool {
let path = path.as_ref();
path.as_os_str() == to_canonical(path).as_os_str()
}
#[cfg(unix)]
fn is_parentless(path: impl AsRef<Path>) -> bool {
path.as_ref()
.components()
.all(|c| !matches!(c, Component::ParentDir))
}
#[cfg(windows)]
fn is_parentless(path: impl AsRef<Path>) -> bool {
use Component::*;
let path = path.as_ref();
let mut components = path.components();
let start = match components.next() {
Some(Prefix(it)) if it.kind().is_verbatim() => return true,
Some(ParentDir) => return false,
Some(Normal(_)) => 1u32,
Some(_) => 0u32,
_ => return true,
};
path.components()
.map(|c| match c {
ParentDir => -1,
Normal(_) => 1,
_ => 0,
})
.try_fold(start, |acc, step| acc.checked_add_signed(step))
.is_some()
}
fn is_normalized(path: impl AsRef<Path>) -> bool {
let path = path.as_ref();
path.is_absolute() && is_canonical(path) && is_parentless(path)
}
fn into_source<T>(error: Ec<T>) -> E {
error.error
}
#[cfg(unix)]
#[test]
pub fn root() {
Normpath::validate("/").unwrap();
NormpathBuf::try_from("/").unwrap();
}
#[cfg(windows)]
#[test]
pub fn root() {
let paths = ('A'..='Z').map(|c| format!(r"{c}:\"));
for path in paths {
Normpath::validate(&path).unwrap();
NormpathBuf::try_from(path).unwrap();
}
}
#[test]
pub fn empty() {
assert_eq!(Normpath::validate(""), Err(E::NotAbsolute));
assert_eq!(
NormpathBuf::try_from("").map_err(into_source),
Err(E::NotAbsolute),
);
}
fn test_normalize(path: &str) -> OsString {
let in_place = NormpathBuf::normalize(path.into()).map(|p| p.into_os_string());
let new = Normpath::normalize(path).map(|p| p.into_owned().into_os_string());
match (in_place, new) {
(Ok(in_place), Ok(new)) => {
assert_eq!(in_place, new, "inconsistent on {:?}", path);
in_place
}
(Err(ec), Err(_)) => ec.value.into(),
(a, b) => panic!(
"inconsistent on {:?}: in-place = {:?}, new = {:?}",
path, a, b
),
}
}
#[cfg(unix)]
#[test]
pub fn example_normalize() {
assert_eq!(test_normalize("/foo/bar"), "/foo/bar");
assert_eq!(test_normalize("//foo/./..//bar//"), "/foo/../bar");
assert_eq!(test_normalize("//./"), "/");
assert_eq!(test_normalize("foo/bar"), "foo/bar");
assert_eq!(test_normalize(".//foo/.//bar/..//"), "foo/bar/..");
assert_eq!(test_normalize("././/."), ".");
assert_eq!(test_normalize(""), ".");
}
#[cfg(windows)]
#[test]
pub fn example_normalize() {
assert_eq!(test_normalize(r"C:\foo\bar"), r"C:\foo\bar");
assert_eq!(test_normalize(r"c:/foo/.."), r"C:\");
assert_eq!(test_normalize(r"c:\/foo\..\./../bar/.."), r"C:\..");
assert_eq!(test_normalize(r"foo\bar"), r"foo\bar");
assert_eq!(test_normalize(r".\/foo\./\../"), r".");
assert_eq!(test_normalize(r".\/foo/..\bar/../..//"), r"..");
assert_eq!(test_normalize(r"\/.\dev\foo"), r"\\.\dev\foo");
assert_eq!(test_normalize(r"\\./dev"), r"\\.\dev");
assert_eq!(test_normalize(r"//./dev/foo\/bar\../"), r"\\.\dev\foo");
assert_eq!(test_normalize(r"\/s\s\foo\bar"), r"\\s\s\foo\bar");
assert_eq!(test_normalize(r"\\s/s\foo/\.\..\bar\/"), r"\\s\s\bar");
assert_eq!(test_normalize(r"//s\s/foo\../\./../\bar/.."), r"\\s\s\..");
assert_eq!(test_normalize(r"C:foo\bar"), r"C:foo\bar");
assert_eq!(test_normalize(r"c:.\\foo\../\bar//"), r"C:bar");
assert_eq!(test_normalize(r"c:.\foo\../bar/.."), r"C:.");
assert_eq!(test_normalize(r""), r".");
assert_eq!(test_normalize(r"C:"), r"C:.");
assert_eq!(
test_normalize(r"\\?\C:\foo/..\/bar/."),
r"\\?\C:\foo/..\/bar/."
);
assert_eq!(
test_normalize(r"\\?\UNC\s\s\foo\./..\bar/"),
r"\\?\UNC\s\s\foo\./..\bar/"
);
}
#[test]
pub fn normalize() {
let paths = make_paths(1..64, draw::common);
for path in paths.take(1024) {
let reference = to_canonical(&path);
let ours = test_normalize(&path);
assert_eq!(reference.as_os_str(), ours, "original: {path:?}");
}
}
#[cfg(unix)]
fn test_push(subject: &mut NormpathBuf, reference: &mut PathBuf, component: &str) {
if is_parentless(component) {
if component != "." {
reference.push(component);
}
subject.push(component).unwrap();
assert_eq!(subject.as_path(), reference.as_path());
} else {
let copy = subject.clone();
let error = subject.push(component).unwrap_err();
assert_eq!(error, E::ContainsParent);
assert_eq!(*subject, copy);
}
}
#[cfg(windows)]
fn test_push(subject: &mut NormpathBuf, reference: &mut PathBuf, component: &str) {
let peek = reference.join(component);
if peek.is_absolute() && is_parentless(&peek) {
*reference = to_canonical(&peek);
subject.push(component).unwrap();
assert_eq!(subject.as_path(), reference.as_path());
} else {
let copy = subject.clone();
let error = subject.push(component).unwrap_err();
if !peek.is_absolute() {
assert_eq!(error, E::NotAbsolute);
} else {
assert_eq!(error, E::ContainsParent);
}
assert_eq!(*subject, copy);
}
}
#[test]
pub fn push_relative() {
for _ in 0..128 {
let components = make_paths(1..16, draw::relative);
let mut path = make_root();
let mut twin = path.clone().into_path_buf();
for component in components.take(64) {
test_push(&mut path, &mut twin, &component);
}
}
}
#[test]
pub fn push_absolute() {
for _ in 0..128 {
let components = make_paths(MIN_ABS..32, draw::absolute);
let mut path = make_root();
let mut twin = path.clone().into_path_buf();
for component in components.take(32) {
test_push(&mut path, &mut twin, &component);
}
}
}
#[cfg(windows)]
#[test]
pub fn push_partial() {
for path in make_paths(MIN_ABS..64, draw::normal).take(128) {
let roots = make_paths(1..32, draw::root_only);
let mut path = NormpathBuf::try_from(path).unwrap();
let mut twin = path.clone().into_path_buf();
for root in roots.take(32) {
test_push(&mut path, &mut twin, &root);
}
}
for path in make_paths(MIN_ABS..64, draw::normal).take(64) {
let prefixes = iter::from_fn(|| Some(draw::disk_only()));
let mut path = NormpathBuf::try_from(path).unwrap();
let mut twin = path.clone().into_path_buf();
for prefix in prefixes.take(16) {
test_push(&mut path, &mut twin, &prefix);
}
}
}
#[cfg(unix)]
fn make_normal_paths() -> impl Iterator<Item = String> {
make_paths(MIN_ABS..64, draw::normal).take(256)
}
#[cfg(windows)]
fn make_normal_paths() -> impl Iterator<Item = String> {
let genuine = make_paths(MIN_ABS..64, draw::normal);
let verbatim = make_paths(7..64, draw::verbatim);
genuine.take(224).chain(verbatim.take(32))
}
fn test_normal(path: &str) {
let ref_validated = Normpath::validate(path).unwrap();
assert!(is_normalized(ref_validated.as_path()));
assert_eq!(ref_validated, Path::new(path));
let ref_validated_c = Normpath::validate_canonical(path).unwrap();
assert_eq!(ref_validated_c, ref_validated);
let ref_validated_p = Normpath::validate_parentless(path).unwrap();
assert_eq!(ref_validated_p, ref_validated);
let ref_normalized = Normpath::normalize(path).unwrap();
assert_eq!(&*ref_normalized, ref_validated);
let buf_validated = NormpathBuf::validate(path).unwrap();
assert_eq!(&buf_validated, ref_validated);
let buf_normalized = NormpathBuf::normalize(PathBuf::from(path)).unwrap();
assert_eq!(&buf_normalized, ref_validated);
let buf_converted = NormpathBuf::try_from(path).unwrap();
assert_eq!(&buf_converted, ref_validated);
}
#[cfg(unix)]
#[test]
pub fn examples_normal() {
test_normal("/");
test_normal("/foo/bar");
}
#[cfg(windows)]
#[test]
pub fn examples_normal() {
test_normal(r"C:\");
test_normal(r"C:\foo\bar");
test_normal(r"\\.\dev");
test_normal(r"\\.\dev\foo\bar");
test_normal(r"\\s\s");
test_normal(r"\\s\s\foo\bar");
test_normal(r"\\?\C:.\../foo\bar//");
test_normal(r"\\?\UNC\s\s/foo\/.\..\bar\/");
}
#[test]
pub fn normal() {
for path in make_normal_paths() {
test_normal(&path);
}
}
fn test_err_single(path: &str, error: E) {
assert_eq!(Normpath::validate(&path), Err(error));
assert_eq!(Normpath::validate_canonical(&path), Err(error));
assert_eq!(Normpath::validate_parentless(&path), Err(error));
assert_eq!(Normpath::normalize(&path), Err(error));
assert_eq!(
NormpathBuf::validate(&path).map_err(into_source),
Err(error),
);
assert_eq!(
NormpathBuf::normalize(PathBuf::from(&path)).map_err(into_source),
Err(error),
);
let conv_err = NormpathBuf::try_from(path).unwrap_err();
assert_eq!(conv_err.error, error);
assert_eq!(conv_err.value, path);
}
fn test_err_single_canon(path: &str, canonical: impl AsRef<Path>) {
assert_eq!(Normpath::validate(&path), Err(E::NotCanonical));
assert_eq!(Normpath::validate_canonical(&path), Err(E::NotCanonical));
assert_eq!(Normpath::validate_parentless(&path), Err(E::NotCanonical));
assert_eq!(
NormpathBuf::validate(&path).map_err(into_source),
Err(E::NotCanonical),
);
let ref_normalized = Normpath::normalize(&path).unwrap();
assert_eq!(&*ref_normalized, canonical.as_ref());
let buf_normalized = NormpathBuf::normalize(PathBuf::from(&path)).unwrap();
assert_eq!(&buf_normalized, canonical.as_ref());
let buf_converted = NormpathBuf::try_from(path.to_string()).unwrap();
assert_eq!(&buf_converted, canonical.as_ref());
let buf_converted_ref = NormpathBuf::try_from(path).unwrap();
assert_eq!(&buf_converted_ref, canonical.as_ref());
}
fn test_err_has_canon(path: &str) {
assert_eq!(Normpath::validate_canonical(path), Err(E::NotCanonical));
assert!(Normpath::validate(&path).is_err());
assert!(Normpath::validate_parentless(&path).is_err());
assert!(NormpathBuf::validate(&path).is_err());
}
fn test_err_has_parent(path: &str) {
assert_eq!(Normpath::validate_parentless(path), Err(E::ContainsParent));
assert!(Normpath::validate(&path).is_err());
assert!(Normpath::validate_canonical(&path).is_err());
assert!(NormpathBuf::validate(&path).is_err());
assert!(Normpath::normalize(&path).is_err());
assert!(NormpathBuf::normalize(PathBuf::from(&path)).is_err());
assert!(NormpathBuf::try_from(path).is_err());
assert!(NormpathBuf::try_from(path.to_string()).is_err());
}
fn test_err_has_both(path: &str) {
test_err_has_canon(path);
test_err_has_parent(path);
}
#[cfg(unix)]
#[test]
pub fn examples_err_single_relative() {
test_err_single(".", E::NotAbsolute);
test_err_single("foo/bar", E::NotAbsolute);
}
#[cfg(windows)]
#[test]
pub fn examples_err_single_relative() {
test_err_single(".", E::NotAbsolute);
test_err_single(r"foo\bar", E::NotAbsolute);
test_err_single(r"\", E::NotAbsolute);
test_err_single(r"\foo\bar", E::NotAbsolute);
test_err_single(r"C:.", E::NotAbsolute);
test_err_single(r"C:foo\bar", E::NotAbsolute);
}
#[test]
pub fn err_single_relative() {
let paths = make_paths(1..64, draw::relative) .filter(|p| is_parentless(p) && is_canonical(p));
for path in paths.take(256) {
test_err_single(&path, E::NotAbsolute);
}
}
#[cfg(unix)]
#[test]
pub fn examples_err_single_parent() {
test_err_single("/..", E::ContainsParent);
test_err_single("/foo/../bar", E::ContainsParent);
}
#[cfg(windows)]
#[test]
pub fn examples_err_single_parent() {
test_err_single(r"C:\..", E::ContainsParent);
test_err_single(r"\\.\dev\..", E::ContainsParent);
test_err_single(r"\\s\s\..", E::ContainsParent);
}
#[test]
pub fn err_single_parent() {
let paths = make_paths(MIN_ABS..64, draw::absolute)
.filter(|p| !is_parentless(p) && is_canonical(p));
for path in paths.take(256) {
test_err_single(&path, E::ContainsParent);
}
}
#[cfg(unix)]
#[test]
pub fn examples_err_single_canon() {
test_err_single_canon("//", "/");
test_err_single_canon("/.", "/");
test_err_single_canon("/foo//bar", "/foo/bar");
test_err_single_canon("/foo/./bar", "/foo/bar");
test_err_single_canon("/foo/bar/", "/foo/bar");
test_err_single_canon("/.../..../", "/.../....");
}
#[cfg(windows)]
#[test]
pub fn examples_err_single_canon() {
test_err_single_canon(r"c:\", r"C:\");
test_err_single_canon(r"C:\\", r"C:\");
test_err_single_canon(r"C:\.", r"C:\");
test_err_single_canon(r"C:\foo\..", r"C:\");
test_err_single_canon(r"C:/foo/bar", r"C:\foo\bar");
test_err_single_canon(r"C:\foo\\bar", r"C:\foo\bar");
test_err_single_canon(r"C:\foo\.\bar", r"C:\foo\bar");
test_err_single_canon(r"C:\foo\bar\", r"C:\foo\bar");
test_err_single_canon(r"C:/.../....", r"C:\...\....");
test_err_single_canon(r"\\.\dev\\", r"\\.\dev\");
test_err_single_canon(r"\\.\dev\.", r"\\.\dev\");
test_err_single_canon(r"\\s\s\\", r"\\s\s\");
test_err_single_canon(r"\\s\s\.", r"\\s\s\");
}
#[test]
pub fn err_single_canon() {
let paths = make_paths(MIN_ABS..64, draw::absolute)
.filter(|p| !is_canonical(p) && is_parentless(p));
for path in paths.take(256) {
test_err_single_canon(&path, to_canonical(&path));
}
}
#[cfg(unix)]
#[test]
pub fn examples_err_focus() {
test_err_has_canon("");
test_err_has_both("/foo/../bar/");
test_err_has_both("//foo/../bar");
test_err_has_both("foo/../bar/");
test_err_has_both("./foo/../bar");
}
#[cfg(windows)]
#[test]
pub fn examples_err_focus() {
test_err_has_canon(r"");
test_err_has_canon(r"C:");
test_err_has_both(r"C:\..\foo\bar\");
test_err_has_both(r"C:\\foo\..\..\bar");
test_err_has_both(r"C:\foo\..\..");
test_err_has_both(r"..\foo\bar\");
test_err_has_both(r".\foo\..\..\bar");
test_err_has_both(r"foo\..\..");
test_err_has_both(r"\\.\dev\..\foo\bar\");
test_err_has_both(r"\\.\dev\\foo\..\..\bar");
test_err_has_both(r"\\s\s\..\foo\bar\");
test_err_has_both(r"\\s\s\\foo\..\..\bar");
}
#[test]
pub fn err_focus_canon() {
let paths = make_paths(MIN_ABS..64, draw::common).filter(|p| !is_canonical(p));
for path in paths.take(256) {
test_err_has_canon(&path);
}
}
#[test]
pub fn err_focus_parent() {
let paths = make_paths(MIN_ABS..64, draw::common).filter(|p| !is_parentless(p));
for path in paths.take(256) {
test_err_has_parent(&path);
}
}
}