use std::{
fs::{File, create_dir_all},
io::{BufWriter, Write},
path::{Path, PathBuf},
};
use log::trace;
use crate::{
Error,
identifiers::{Context, Os, Purpose, Technology},
};
pub trait VerifierWriter {
fn to_bytes(&self) -> Result<Vec<u8>, Error>;
fn technology(&self) -> Technology;
fn file_name(&self) -> PathBuf;
fn write_to_hierarchy(
&self,
path: impl AsRef<Path>,
os: Os,
purpose: Purpose,
context: Option<Context>,
) -> Result<(), Error> {
let context = context.unwrap_or_default();
let path = path.as_ref();
let target_dir = path
.join(os.to_string())
.join(purpose.to_string())
.join(context.to_string())
.join(self.technology().to_string());
trace!("Create parent directory for verifier: {target_dir:?}");
create_dir_all(&target_dir).map_err(|source| Error::IoPath {
path: target_dir.clone(),
context: "creating the directory",
source,
})?;
let file_path = target_dir.join(self.file_name());
trace!("Write verifier data to file: {file_path:?}");
if file_path.exists() && !file_path.is_file() {
return Err(Error::ExpectedFile {
path: file_path.to_path_buf(),
});
}
let mut writer =
BufWriter::new(File::create(&file_path).map_err(|source| Error::IoPath {
path: file_path.clone(),
context: "creating file for writing",
source,
})?);
writer
.write_all(&self.to_bytes()?)
.map_err(|source| Error::IoPath {
path: file_path,
context: "writing the verifier contents",
source,
})?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::{fs::read_to_string, os::unix::fs::symlink};
use log::debug;
use simplelog::{ColorChoice, Config, LevelFilter, TermLogger, TerminalMode};
use tempfile::tempdir;
use testresult::TestResult;
use super::*;
use crate::identifiers::CustomTechnology;
const VERIFIER_DATA: &str = "test";
struct TestWriter;
impl VerifierWriter for TestWriter {
fn to_bytes(&self) -> Result<Vec<u8>, Error> {
Ok(VERIFIER_DATA.as_bytes().to_vec())
}
fn technology(&self) -> Technology {
Technology::Custom(CustomTechnology::new("technology".parse().unwrap()))
}
fn file_name(&self) -> PathBuf {
PathBuf::from("dummy.test")
}
}
fn init_logger() {
if TermLogger::init(
LevelFilter::Trace,
Config::default(),
TerminalMode::Stderr,
ColorChoice::Auto,
)
.is_err()
{
debug!("Not initializing another logger, as one is initialized already.");
}
}
#[test]
fn write_to_hierarchy_succeeds() -> TestResult {
init_logger();
let test_dir = tempdir()?;
let path = test_dir.path();
let test_writer = TestWriter;
test_writer.write_to_hierarchy(path, "os".parse()?, "purpose".parse()?, None)?;
let target_dir = path
.join("os")
.join("purpose")
.join("default")
.join("technology");
assert!(target_dir.is_dir());
let verifier_file = target_dir.join(test_writer.file_name());
assert!(verifier_file.is_file());
let verifier_contents = read_to_string(verifier_file)?;
assert_eq!(verifier_contents, VERIFIER_DATA);
Ok(())
}
#[test]
fn write_to_hierarchy_fails_on_target_is_dir() -> TestResult {
init_logger();
let test_dir = tempdir()?;
let path = test_dir.path();
let test_writer = TestWriter;
let target_file = path
.join("os")
.join("purpose")
.join("default")
.join("technology")
.join(test_writer.file_name());
create_dir_all(&target_file)?;
assert!(target_file.is_dir());
match test_writer.write_to_hierarchy(path, "os".parse()?, "purpose".parse()?, None) {
Ok(()) => {
panic!(
"Should have failed but succeeded to write a verifier to the VOA hierarchy at {target_file:?}"
);
}
Err(error) => match error {
Error::ExpectedFile { .. } => {}
error => {
panic!("Expected Error::ExpectedFile, but got:\n{error}");
}
},
}
Ok(())
}
#[test]
fn write_to_hierarchy_fails_on_target_is_symlink() -> TestResult {
init_logger();
let test_dir = tempdir()?;
let path = test_dir.path();
let test_writer = TestWriter;
let parent_dir = path
.join("os")
.join("purpose")
.join("default")
.join("technology");
create_dir_all(&parent_dir)?;
let target_file = parent_dir.join(test_writer.file_name());
symlink("/dev/null", &target_file)?;
assert!(target_file.is_symlink());
match test_writer.write_to_hierarchy(path, "os".parse()?, "purpose".parse()?, None) {
Ok(()) => {
panic!(
"Should have failed but succeeded to write a verifier to the VOA hierarchy at {target_file:?}"
);
}
Err(error) => match error {
Error::ExpectedFile { .. } => {}
error => {
panic!("Expected Error::ExpectedFile, but got:\n{error}");
}
},
}
Ok(())
}
#[test]
fn write_to_hierarchy_fails_on_dir_structure_has_file() -> TestResult {
init_logger();
let test_dir = tempdir()?;
let path = test_dir.path();
let test_writer = TestWriter;
let parent_dir = path.join("os").join("purpose").join("default");
create_dir_all(&parent_dir)?;
let parent_file = parent_dir.join("technology");
let mut file = File::create(&parent_file)?;
file.write_all(b"Occupied!")?;
assert!(parent_file.is_file());
let target_file = parent_file.join(test_writer.file_name());
match test_writer.write_to_hierarchy(path, "os".parse()?, "purpose".parse()?, None) {
Ok(()) => {
panic!(
"Should have failed but succeeded to write a verifier to the VOA hierarchy at {target_file:?}"
);
}
Err(error) => match error {
Error::IoPath { .. } => {}
error => {
panic!("Expected Error::IoPath, but got:\n{error}");
}
},
}
Ok(())
}
#[test]
fn write_to_hierarchy_fails_on_dir_structure_has_symlink() -> TestResult {
init_logger();
let test_dir = tempdir()?;
let path = test_dir.path();
let test_writer = TestWriter;
let parent_dir = path.join("os").join("purpose").join("default");
create_dir_all(&parent_dir)?;
let parent_symlink = parent_dir.join("technology");
symlink("/dev/null", &parent_symlink)?;
assert!(parent_symlink.is_symlink());
let target_file = parent_symlink.join(test_writer.file_name());
match test_writer.write_to_hierarchy(path, "os".parse()?, "purpose".parse()?, None) {
Ok(()) => {
panic!(
"Should have failed but succeeded to write a verifier to the VOA hierarchy at {target_file:?}"
);
}
Err(error) => match error {
Error::IoPath { .. } => {}
error => {
panic!("Expected Error::IoPath, but got:\n{error}");
}
},
}
Ok(())
}
}