use super::ValidationError;
use crate::{ElicitCommunicator, ElicitResult, Elicitation, Prompt};
use elicitation_macros::instrumented_impl;
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PathBufExists(PathBuf);
#[cfg_attr(not(kani), instrumented_impl)]
impl PathBufExists {
pub fn new(path: PathBuf) -> Result<Self, ValidationError> {
if path.exists() {
Ok(Self(path))
} else {
Err(ValidationError::PathDoesNotExist(
path.display().to_string(),
))
}
}
pub fn get(&self) -> &PathBuf {
&self.0
}
pub fn into_inner(self) -> PathBuf {
self.0
}
}
#[cfg_attr(not(kani), instrumented_impl)]
impl Prompt for PathBufExists {
fn prompt() -> Option<&'static str> {
Some("Please provide a path that exists on the filesystem:")
}
}
#[cfg_attr(not(kani), instrumented_impl)]
impl Elicitation for PathBufExists {
type Style = <PathBuf as Elicitation>::Style;
#[tracing::instrument(skip(communicator))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
tracing::debug!("Eliciting PathBufExists");
loop {
let path = PathBuf::elicit(communicator).await?;
match Self::new(path) {
Ok(valid) => {
tracing::debug!(path = ?valid.0, "Valid existing path");
return Ok(valid);
}
Err(e) => {
tracing::warn!(error = %e, "Path does not exist, re-prompting");
continue;
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PathBufReadable(PathBuf);
#[cfg_attr(not(kani), instrumented_impl)]
impl PathBufReadable {
pub fn new(path: PathBuf) -> Result<Self, ValidationError> {
match path.metadata() {
Ok(_) => Ok(Self(path)),
Err(_) => Err(ValidationError::PathNotReadable(path.display().to_string())),
}
}
pub fn get(&self) -> &PathBuf {
&self.0
}
pub fn into_inner(self) -> PathBuf {
self.0
}
}
#[cfg_attr(not(kani), instrumented_impl)]
impl Prompt for PathBufReadable {
fn prompt() -> Option<&'static str> {
Some("Please provide a readable path:")
}
}
#[cfg_attr(not(kani), instrumented_impl)]
impl Elicitation for PathBufReadable {
type Style = <PathBuf as Elicitation>::Style;
#[tracing::instrument(skip(communicator))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
tracing::debug!("Eliciting PathBufReadable");
loop {
let path = PathBuf::elicit(communicator).await?;
match Self::new(path) {
Ok(valid) => {
tracing::debug!(path = ?valid.0, "Valid readable path");
return Ok(valid);
}
Err(e) => {
tracing::warn!(error = %e, "Path not readable, re-prompting");
continue;
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PathBufIsDir(PathBuf);
#[cfg_attr(not(kani), instrumented_impl)]
impl PathBufIsDir {
pub fn new(path: PathBuf) -> Result<Self, ValidationError> {
if path.is_dir() {
Ok(Self(path))
} else if path.exists() {
Err(ValidationError::PathNotDirectory(
path.display().to_string(),
))
} else {
Err(ValidationError::PathDoesNotExist(
path.display().to_string(),
))
}
}
pub fn get(&self) -> &PathBuf {
&self.0
}
pub fn into_inner(self) -> PathBuf {
self.0
}
}
#[cfg_attr(not(kani), instrumented_impl)]
impl Prompt for PathBufIsDir {
fn prompt() -> Option<&'static str> {
Some("Please provide a directory path:")
}
}
#[cfg_attr(not(kani), instrumented_impl)]
impl Elicitation for PathBufIsDir {
type Style = <PathBuf as Elicitation>::Style;
#[tracing::instrument(skip(communicator))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
tracing::debug!("Eliciting PathBufIsDir");
loop {
let path = PathBuf::elicit(communicator).await?;
match Self::new(path) {
Ok(valid) => {
tracing::debug!(path = ?valid.0, "Valid directory path");
return Ok(valid);
}
Err(e) => {
tracing::warn!(error = %e, "Path not directory, re-prompting");
continue;
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PathBufIsFile(PathBuf);
#[cfg_attr(not(kani), instrumented_impl)]
impl PathBufIsFile {
pub fn new(path: PathBuf) -> Result<Self, ValidationError> {
if path.is_file() {
Ok(Self(path))
} else if path.exists() {
Err(ValidationError::PathNotFile(path.display().to_string()))
} else {
Err(ValidationError::PathDoesNotExist(
path.display().to_string(),
))
}
}
pub fn get(&self) -> &PathBuf {
&self.0
}
pub fn into_inner(self) -> PathBuf {
self.0
}
}
#[cfg_attr(not(kani), instrumented_impl)]
impl Prompt for PathBufIsFile {
fn prompt() -> Option<&'static str> {
Some("Please provide a file path:")
}
}
#[cfg_attr(not(kani), instrumented_impl)]
impl Elicitation for PathBufIsFile {
type Style = <PathBuf as Elicitation>::Style;
#[tracing::instrument(skip(communicator))]
async fn elicit<C: ElicitCommunicator>(communicator: &C) -> ElicitResult<Self> {
tracing::debug!("Eliciting PathBufIsFile");
loop {
let path = PathBuf::elicit(communicator).await?;
match Self::new(path) {
Ok(valid) => {
tracing::debug!(path = ?valid.0, "Valid file path");
return Ok(valid);
}
Err(e) => {
tracing::warn!(error = %e, "Path not file, re-prompting");
continue;
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
#[test]
fn test_path_exists_valid() {
let mut path = env::current_dir().unwrap();
path.push("Cargo.toml");
let result = PathBufExists::new(path);
assert!(result.is_ok());
}
#[test]
fn test_path_exists_invalid() {
let path = PathBuf::from("/this/path/does/not/exist/hopefully");
let result = PathBufExists::new(path);
assert!(result.is_err());
}
#[test]
fn test_path_readable_valid() {
let mut path = env::current_dir().unwrap();
path.push("Cargo.toml");
let result = PathBufReadable::new(path);
assert!(result.is_ok());
}
#[test]
fn test_path_is_dir_valid() {
let mut path = env::current_dir().unwrap();
path.push("src");
let result = PathBufIsDir::new(path);
assert!(result.is_ok());
}
#[test]
fn test_path_is_dir_file() {
let mut path = env::current_dir().unwrap();
path.push("Cargo.toml");
let result = PathBufIsDir::new(path);
assert!(result.is_err());
}
#[test]
fn test_path_is_file_valid() {
let mut path = env::current_dir().unwrap();
path.push("Cargo.toml");
let result = PathBufIsFile::new(path);
assert!(result.is_ok());
}
#[test]
fn test_path_is_file_dir() {
let mut path = env::current_dir().unwrap();
path.push("src");
let result = PathBufIsFile::new(path);
assert!(result.is_err());
}
#[test]
fn test_path_into_inner() {
let mut original = env::current_dir().unwrap();
original.push("Cargo.toml");
let exists = PathBufExists::new(original.clone()).unwrap();
assert_eq!(exists.into_inner(), original);
}
}