use std::{
marker::PhantomData,
path::{Path, PathBuf},
};
use crate::fs::{Error, Result};
use super::{DirHandle, FileHandle, PathHandle};
pub trait PathType {}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PathMarker;
impl PathType for PathMarker {}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct FileMarker;
impl PathType for FileMarker {}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DirMarker;
impl PathType for DirMarker {}
#[derive(Clone, Debug, Default)]
pub struct PathReal<T: PathType> {
base: PathBuf,
path: PathBuf,
_phanton: PhantomData<T>,
pub(super) error: Option<Error>,
}
impl<T: PathType> PathReal<T> {
pub(super) fn new(base: impl Into<PathBuf>, path: impl Into<PathBuf>) -> Self {
let base: PathBuf = base.into();
let path: PathBuf = path.into();
let error = PathReal::<T>::validate(&base, &path);
Self {
base,
path,
_phanton: PhantomData::<T>,
error,
}
}
pub fn as_pathbuf(&self) -> PathBuf {
self.base.join(&self.path)
}
pub(super) fn put(&mut self, error: Error) {
if self.error.is_none() {
self.error.replace(error);
}
}
fn validate(base: &Path, path: &Path) -> Option<Error> {
match PathReal::<PathMarker>::clean_path(path) {
Err(error) => Some(error),
Ok(path) => {
if !path.starts_with(base) {
return Some(Error::PathTraversal {
base: base.to_path_buf(),
path,
});
}
None
}
}
}
fn clean_path(path: &Path) -> Result<PathBuf> {
use path_clean::PathClean;
let abs_path = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir().expect("current_dir").join(path)
}
.clean();
Ok(abs_path)
}
pub(super) fn check_error(&self) -> Result<()> {
if let Some(error) = &self.error {
Err(error.clone())
} else {
Ok(())
}
}
pub fn exists(&self) -> Result<bool> {
self.check_error()?;
Ok(self.as_pathbuf().exists())
}
pub fn is_dir(&self) -> Result<bool> {
self.check_error()?;
Ok(self.as_pathbuf().is_dir())
}
pub fn is_file(&self) -> Result<bool> {
self.check_error()?;
Ok(self.as_pathbuf().is_file())
}
pub fn as_dir(&self) -> Result<Option<DirHandle>> {
self.check_error()?;
if self.as_pathbuf().is_dir() {
Ok(Some(PathReal::new(&self.base, &self.path)))
} else {
Ok(None)
}
}
pub fn as_file(&self) -> Result<Option<FileHandle>> {
self.check_error()?;
if self.as_pathbuf().is_file() {
Ok(Some(PathReal::new(&self.base, &self.path)))
} else {
Ok(None)
}
}
pub fn rename(&self, dest: &PathHandle<T>) -> Result<()> {
self.check_error()?;
std::fs::rename(self.as_pathbuf(), dest.as_pathbuf()).map_err(Error::Io)
}
pub fn metadata(&self) -> Result<std::fs::Metadata> {
self.check_error()?;
std::fs::metadata(self.as_pathbuf()).map_err(Error::Io)
}
pub fn soft_link(&self, link: &PathReal<PathMarker>) -> Result<()> {
self.check_error()?;
std::os::unix::fs::symlink(self.as_pathbuf(), link.as_pathbuf()).map_err(Error::Io)
}
pub fn is_link(&self) -> Result<bool> {
self.check_error()?;
Ok(self.as_pathbuf().is_symlink())
}
pub fn canonicalize(&self) -> Result<PathBuf> {
self.check_error()?;
self.as_pathbuf().canonicalize().map_err(Error::Io)
}
pub fn symlink_metadata(&self) -> Result<std::fs::Metadata> {
self.check_error()?;
std::fs::symlink_metadata(self.as_pathbuf()).map_err(Error::Io)
}
pub fn set_permissions(&self, perms: std::fs::Permissions) -> Result<()> {
self.check_error()?;
std::fs::set_permissions(self.as_pathbuf(), perms).map_err(Error::Io)
}
pub fn read_link(&self) -> Result<PathReal<PathMarker>> {
self.check_error()?;
let read_path = std::fs::read_link(self.as_pathbuf()).map_err(Error::Io)?;
let path = read_path.strip_prefix(&self.base).unwrap().to_path_buf();
Ok(PathReal::new(&self.base, &path))
}
}
impl From<PathHandle<PathMarker>> for PathBuf {
fn from(path: PathReal<PathMarker>) -> Self {
path.base.join(path.path)
}
}
impl From<DirHandle> for PathHandle<PathMarker> {
fn from(dir: DirHandle) -> Self {
PathReal::new(dir.base, dir.path)
}
}
impl From<FileHandle> for PathHandle<PathMarker> {
fn from(file: FileHandle) -> Self {
PathReal::new(file.base, file.path)
}
}