use anyhow::{anyhow, Result};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, thiserror::Error)]
pub enum PathValidationError {
#[error("Path does not exist: {path}")]
NotFound { path: PathBuf },
#[error("Path is not a file: {path}")]
NotFile { path: PathBuf },
#[error("Path is not a directory: {path}")]
NotDirectory { path: PathBuf },
#[error("Path is not readable: {path}")]
NotReadable { path: PathBuf },
#[error("Invalid path: {path}")]
Invalid { path: PathBuf },
}
pub struct PathValidator;
impl PathValidator {
pub fn ensure_exists(path: &Path) -> Result<(), PathValidationError> {
if !path.exists() {
return Err(PathValidationError::NotFound {
path: path.to_path_buf(),
});
}
Ok(())
}
pub fn ensure_file(path: &Path) -> Result<(), PathValidationError> {
Self::ensure_exists(path)?;
if !path.is_file() {
return Err(PathValidationError::NotFile {
path: path.to_path_buf(),
});
}
Ok(())
}
pub fn ensure_directory(path: &Path) -> Result<(), PathValidationError> {
Self::ensure_exists(path)?;
if !path.is_dir() {
return Err(PathValidationError::NotDirectory {
path: path.to_path_buf(),
});
}
Ok(())
}
pub fn ensure_readable(path: &Path) -> Result<(), PathValidationError> {
Self::ensure_exists(path)?;
match std::fs::metadata(path) {
Ok(_) => Ok(()),
Err(_) => Err(PathValidationError::NotReadable {
path: path.to_path_buf(),
}),
}
}
pub fn get_valid_parent(path: &Path) -> Result<&Path, PathValidationError> {
Self::ensure_exists(path)?;
if path.is_file() {
path.parent().ok_or_else(|| PathValidationError::Invalid {
path: path.to_path_buf(),
})
} else if path.is_dir() {
Ok(path)
} else {
Err(PathValidationError::Invalid {
path: path.to_path_buf(),
})
}
}
#[must_use]
pub fn is_source_file(path: &Path) -> bool {
if !path.is_file() {
return false;
}
path.extension()
.and_then(|ext| ext.to_str())
.is_some_and(|ext| {
matches!(
ext,
"rs" | "py" | "js" | "ts" | "go" | "c" | "cpp" | "h" | "hpp"
)
})
}
pub fn validate_exists_anyhow(path: &Path) -> Result<()> {
if !path.exists() {
return Err(anyhow!("Path does not exist: {}", path.display()));
}
Ok(())
}
pub fn validate_file_anyhow(path: &Path) -> Result<()> {
Self::validate_exists_anyhow(path)?;
if !path.is_file() {
return Err(anyhow!("Path is not a file: {}", path.display()));
}
Ok(())
}
pub fn validate_directory_anyhow(path: &Path) -> Result<()> {
Self::validate_exists_anyhow(path)?;
if !path.is_dir() {
return Err(anyhow!("Path is not a directory: {}", path.display()));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_ensure_exists_valid_file() {
let path = Path::new("Cargo.toml");
assert!(PathValidator::ensure_exists(path).is_ok());
}
#[test]
fn test_ensure_exists_invalid_file() {
let path = Path::new("nonexistent_file.txt");
assert!(PathValidator::ensure_exists(path).is_err());
}
#[test]
fn test_ensure_file_valid() {
let path = Path::new("Cargo.toml");
assert!(PathValidator::ensure_file(path).is_ok());
}
#[test]
fn test_ensure_directory_valid() {
let path = Path::new("src");
assert!(PathValidator::ensure_directory(path).is_ok());
}
#[test]
fn test_get_valid_parent() -> Result<()> {
let temp_dir = TempDir::new()?;
let file_path = temp_dir.path().join("test_file.txt");
fs::write(&file_path, "test content")?;
let parent = PathValidator::get_valid_parent(&file_path)?;
assert_eq!(parent, temp_dir.path());
Ok(())
}
#[test]
fn test_is_source_file() -> Result<()> {
let temp_dir = TempDir::new()?;
let rs_file = temp_dir.path().join("test.rs");
fs::write(&rs_file, "fn main() {}")?;
assert!(PathValidator::is_source_file(&rs_file));
let txt_file = temp_dir.path().join("test.txt");
fs::write(&txt_file, "text content")?;
assert!(!PathValidator::is_source_file(&txt_file));
Ok(())
}
#[test]
fn test_validate_anyhow_methods() {
let path = Path::new("Cargo.toml");
assert!(PathValidator::validate_exists_anyhow(path).is_ok());
assert!(PathValidator::validate_file_anyhow(path).is_ok());
let dir_path = Path::new("src");
assert!(PathValidator::validate_directory_anyhow(dir_path).is_ok());
let bad_path = Path::new("nonexistent");
assert!(PathValidator::validate_exists_anyhow(bad_path).is_err());
}
}