use std::{
fmt::Display,
path::{Path, PathBuf},
str::FromStr,
};
use voa_core::identifiers::Purpose;
use crate::Error;
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum DirOrFileType {
Dir,
File,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DirOrFile {
path: PathBuf,
pub typ: DirOrFileType,
}
impl AsRef<Path> for DirOrFile {
fn as_ref(&self) -> &Path {
&self.path
}
}
impl TryFrom<PathBuf> for DirOrFile {
type Error = Error;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
if value.is_dir() {
Ok(Self {
path: value,
typ: DirOrFileType::Dir,
})
} else if value.is_file() {
Ok(Self {
path: value,
typ: DirOrFileType::File,
})
} else {
Err(crate::Error::PathIsNotDirOrFile { path: value })
}
}
}
impl TryFrom<&Path> for DirOrFile {
type Error = Error;
fn try_from(value: &Path) -> Result<Self, Self::Error> {
Self::try_from(value.to_path_buf())
}
}
impl FromStr for DirOrFile {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(PathBuf::from(s))
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct RegularFile(PathBuf);
impl AsRef<Path> for RegularFile {
fn as_ref(&self) -> &Path {
&self.0
}
}
impl TryFrom<PathBuf> for RegularFile {
type Error = Error;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
if !value.is_file() {
return Err(Error::PathIsNotAFile { path: value });
}
Ok(Self(value))
}
}
impl FromStr for RegularFile {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::try_from(PathBuf::from(s))
}
}
#[derive(Clone, Debug)]
pub struct ArtifactVerifierPurpose(Purpose);
impl ArtifactVerifierPurpose {
pub fn new(purpose: Purpose) -> Result<Self, Error> {
if purpose.is_trust_anchor() {
return Err(Error::PurposeIsATrustAnchor { purpose });
}
Ok(Self(purpose))
}
}
impl AsRef<Purpose> for ArtifactVerifierPurpose {
fn as_ref(&self) -> &Purpose {
&self.0
}
}
impl Display for ArtifactVerifierPurpose {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for ArtifactVerifierPurpose {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(Purpose::from_str(s)?)
}
}
#[cfg(test)]
mod tests {
use tempfile::{NamedTempFile, TempDir};
use testresult::TestResult;
use super::*;
#[test]
fn dir_or_file_from_str_is_dir() -> TestResult {
let temp = TempDir::new()?;
let Some(path) = temp.path().to_str() else {
panic!("Could not convert temporary dir to string slice");
};
let dir = DirOrFile::from_str(path)?;
assert_eq!(dir.typ, DirOrFileType::Dir);
assert_eq!(dir.as_ref(), temp.path());
Ok(())
}
#[test]
fn dir_or_file_from_str_is_file() -> TestResult {
let temp = NamedTempFile::new()?;
let Some(path) = temp.path().to_str() else {
panic!("Could not convert temporary dir to string slice");
};
let dir = DirOrFile::from_str(path)?;
assert_eq!(dir.typ, DirOrFileType::File);
assert_eq!(dir.as_ref(), temp.path());
Ok(())
}
#[test]
#[cfg(target_os = "linux")]
fn dir_or_file_from_str_fails_on_not_a_dir_or_a_file() -> TestResult {
let result = DirOrFile::from_str("/dev/urandom");
match result {
Ok(path) => {
panic!("Succeeded to create a DirOrFile from {path:?} but should have failed");
}
Err(Error::PathIsNotDirOrFile { .. }) => {}
Err(error) => {
panic!("Should have returned Error::PathIsNotDirOrFile, but returned: {error}");
}
}
Ok(())
}
#[test]
fn regular_file_from_str_succeeds() -> TestResult {
let temp = NamedTempFile::new()?;
let Some(path) = temp.path().to_str() else {
panic!("Could not convert temporary dir to string slice");
};
let file = RegularFile::from_str(path)?;
assert_eq!(file.as_ref(), temp.path());
Ok(())
}
#[test]
fn regular_file_from_str_fails_on_dir() -> TestResult {
let temp = TempDir::new()?;
let Some(path) = temp.path().to_str() else {
panic!("Could not convert temporary dir to string slice");
};
let result = RegularFile::from_str(path);
match result {
Ok(path) => {
panic!("Succeeded to create a RegularFile from {path:?} but should have failed");
}
Err(Error::PathIsNotAFile { .. }) => {}
Err(error) => {
panic!("Should have returned Error::PathIsNotAFile, but returned: {error}");
}
}
Ok(())
}
#[test]
fn artifact_verifier_purpose_from_str_succeeds() -> TestResult {
let artifact_verifier_purpose = ArtifactVerifierPurpose::from_str("some-purpose")?;
let _purpose = artifact_verifier_purpose.as_ref();
println!("{artifact_verifier_purpose}");
Ok(())
}
#[test]
fn artifact_verifier_purpose_from_str_fails() -> TestResult {
match ArtifactVerifierPurpose::from_str("trust-anchor-some-purpose") {
Err(Error::PurposeIsATrustAnchor { .. }) => {}
Ok(purpose) => panic!(
"Should have failed with Error::PurposeIsATrustAnchor but succeeded instead: {purpose}"
),
Err(error) => panic!(
"Should have failed with Error::PurposeIsATrustAnchor but failed differently: {error}"
),
}
Ok(())
}
}