#![deny(
warnings,
missing_docs,
trivial_numeric_casts,
unused_import_braces,
unused_qualifications
)]
#![doc(html_root_url = "https://docs.rs/canonical-path/2.0.2")]
use std::{
borrow::Borrow,
env,
ffi::{OsStr, OsString},
fs::{Metadata, ReadDir},
io::{Error, ErrorKind, Result},
path::{Components, Display, Iter, Path, PathBuf},
};
macro_rules! impl_path {
() => {
#[inline]
pub fn as_path(&self) -> &Path {
self.0.as_ref()
}
#[inline]
pub fn as_os_str(&self) -> &OsStr {
self.0.as_os_str()
}
#[inline]
pub fn to_str(&self) -> Option<&str> {
self.0.to_str()
}
pub fn parent(&self) -> Result<CanonicalPathBuf> {
CanonicalPathBuf::new(&self.0
.parent()
.ok_or_else(|| Error::new(ErrorKind::InvalidInput, "can't get parent of '/'"))?)
}
#[inline]
pub fn file_name(&self) -> Option<&OsStr> {
self.0.file_name()
}
#[inline]
pub fn starts_with<P: AsRef<Path>>(&self, base: P) -> bool {
self.0.starts_with(base)
}
#[inline]
pub fn ends_with<P: AsRef<Path>>(&self, child: P) -> bool {
self.0.ends_with(child)
}
#[inline]
pub fn file_stem(&self) -> Option<&OsStr> {
self.0.file_stem()
}
#[inline]
pub fn extension(&self) -> Option<&OsStr> {
self.0.extension()
}
#[inline]
pub fn with_file_name<S: AsRef<OsStr>>(&self, file_name: S) -> Result<CanonicalPathBuf> {
CanonicalPathBuf::new(&self.0.with_file_name(file_name))
}
#[inline]
pub fn with_extension<S: AsRef<OsStr>>(&self, extension: S) -> Result<CanonicalPathBuf> {
CanonicalPathBuf::new(&self.0.with_extension(extension))
}
#[inline]
pub fn components(&self) -> Components {
self.0.components()
}
#[inline]
pub fn iter(&self) -> Iter {
self.0.iter()
}
#[inline]
pub fn display(&self) -> Display {
self.0.display()
}
#[inline]
pub fn metadata(&self) -> Result<Metadata> {
self.0.symlink_metadata()
}
#[inline]
pub fn join<P: AsRef<Path>>(&self, path: P) -> Result<CanonicalPathBuf> {
CanonicalPathBuf::new(&self.0.join(path))
}
#[inline]
pub fn read_dir(&self) -> Result<ReadDir> {
self.0.read_dir()
}
#[inline]
pub fn exists(&self) -> bool {
self.0.exists()
}
#[inline]
pub fn is_file(&self) -> bool {
self.0.is_file()
}
#[inline]
pub fn is_dir(&self) -> bool {
self.0.is_file()
}
}
}
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct CanonicalPathBuf(PathBuf);
impl CanonicalPathBuf {
pub fn canonicalize<P>(path: P) -> Result<Self>
where
P: AsRef<Path>,
{
Ok(CanonicalPathBuf(path.as_ref().canonicalize()?))
}
#[allow(clippy::new_ret_no_self)]
pub fn new<P>(path: P) -> Result<Self>
where
P: AsRef<Path>,
{
let p = path.as_ref();
let canonical_path_buf = Self::canonicalize(p)?;
if canonical_path_buf.as_path() != p {
return Err(Error::new(
ErrorKind::InvalidInput,
format!("non-canonical input path: {}", p.display()),
));
}
Ok(canonical_path_buf)
}
#[inline]
pub fn as_canonical_path(&self) -> &CanonicalPath {
unsafe { CanonicalPath::from_path_unchecked(&self.0) }
}
pub fn set_file_name<S: AsRef<OsStr>>(&mut self, file_name: S) {
self.0.set_file_name(file_name);
}
pub fn set_extension<S: AsRef<OsStr>>(&mut self, extension: S) -> bool {
self.0.set_extension(extension)
}
pub fn into_path_buf(self) -> PathBuf {
self.0
}
pub fn into_os_string(self) -> OsString {
self.0.into_os_string()
}
impl_path!();
}
impl AsRef<Path> for CanonicalPathBuf {
fn as_ref(&self) -> &Path {
self.as_path()
}
}
impl AsRef<CanonicalPath> for CanonicalPathBuf {
fn as_ref(&self) -> &CanonicalPath {
self.as_canonical_path()
}
}
impl AsRef<OsStr> for CanonicalPathBuf {
fn as_ref(&self) -> &OsStr {
self.as_os_str()
}
}
impl Borrow<CanonicalPath> for CanonicalPathBuf {
fn borrow(&self) -> &CanonicalPath {
self.as_canonical_path()
}
}
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct CanonicalPath(Path);
impl CanonicalPath {
pub fn new<P>(path: &P) -> Result<&Self>
where
P: AsRef<Path> + ?Sized,
{
let p = path.as_ref();
if p != p.canonicalize()? {
return Err(Error::new(
ErrorKind::InvalidInput,
format!("non-canonical input path: {}", p.display()),
));
}
Ok(unsafe { Self::from_path_unchecked(p) })
}
pub unsafe fn from_path_unchecked<P>(path: &P) -> &Self
where
P: AsRef<Path> + ?Sized,
{
&*(path.as_ref() as *const Path as *const CanonicalPath)
}
pub fn to_canonical_path_buf(&self) -> CanonicalPathBuf {
CanonicalPathBuf(self.0.to_owned())
}
impl_path!();
}
impl AsRef<Path> for CanonicalPath {
fn as_ref(&self) -> &Path {
&self.0
}
}
impl ToOwned for CanonicalPath {
type Owned = CanonicalPathBuf;
fn to_owned(&self) -> CanonicalPathBuf {
self.to_canonical_path_buf()
}
}
pub fn current_exe() -> Result<CanonicalPathBuf> {
let p = env::current_exe()?;
Ok(CanonicalPathBuf::canonicalize(p)?)
}
#[cfg(all(test, not(windows)))]
mod tests {
use std::fs::File;
use std::os::unix::fs;
use std::path::PathBuf;
use super::{CanonicalPath, CanonicalPathBuf};
use tempfile::TempDir;
const CANONICAL_FILENAME: &str = "canonical-file";
const NON_CANONICAL_FILENAME: &str = "non-canonical-file";
struct TestFixtureDir {
pub tempdir: TempDir,
pub base_path: PathBuf,
pub canonical_path: PathBuf,
pub symlink_path: PathBuf,
}
impl TestFixtureDir {
pub fn new() -> Self {
let tempdir = TempDir::new().unwrap();
let base_path = tempdir.path().canonicalize().unwrap();
let canonical_path = base_path.join(CANONICAL_FILENAME);
File::create(&canonical_path).unwrap();
let symlink_path = base_path.join(NON_CANONICAL_FILENAME);
fs::symlink(&canonical_path, &symlink_path).unwrap();
Self {
tempdir,
base_path,
canonical_path,
symlink_path,
}
}
}
#[test]
fn create_canonical_path() {
let test_fixtures = TestFixtureDir::new();
let canonical_path = CanonicalPath::new(&test_fixtures.canonical_path).unwrap();
assert_eq!(
canonical_path.as_path(),
test_fixtures.canonical_path.as_path()
);
}
#[test]
fn create_canonical_path_buf() {
let test_fixtures = TestFixtureDir::new();
let canonical_path_buf = CanonicalPathBuf::new(&test_fixtures.canonical_path).unwrap();
assert_eq!(
canonical_path_buf.as_path(),
test_fixtures.canonical_path.as_path()
);
}
#[test]
fn reject_canonical_path_symlinks() {
let test_fixtures = TestFixtureDir::new();
let result = CanonicalPath::new(&test_fixtures.symlink_path);
assert!(result.is_err(), "symlinks aren't canonical paths!");
}
#[test]
fn reject_canonical_path_buf_symlinks() {
let test_fixtures = TestFixtureDir::new();
let result = CanonicalPathBuf::new(&test_fixtures.symlink_path);
assert!(result.is_err(), "symlinks aren't canonical paths!");
}
}