use anyhow::Result as AResult;
use std::path::{Path, PathBuf};
struct State {
name: String,
path: PathBuf,
valid: Validity,
}
pub struct Chroot<M: Verification> {
state: State,
marker: std::marker::PhantomData<M>,
}
pub trait Verification {}
pub struct Unverified;
impl Verification for Unverified {}
pub type ChrootUnverified = Chroot<Unverified>;
pub struct Verified;
impl Verification for Verified {}
pub type ChrootVerified = Chroot<Verified>;
#[derive(PartialEq)]
enum Validity {
Exists,
Absent,
IsFile,
AbsoluteName,
MultipleLevels,
InvalidName,
}
impl<M: Verification> Chroot<M> {
pub(super) fn new(name: &Path, workspace: &Path) -> ChrootUnverified {
use Validity::*;
let path = workspace.join(name);
let valid = if name.is_absolute() {
AbsoluteName
} else if name.parent() != Some(Path::new("")) {
MultipleLevels
} else if name.file_name().is_none() {
InvalidName
} else if path.is_dir() {
Exists
} else if path.is_file() {
IsFile
} else {
Absent
};
let name = name.to_string_lossy().into_owned();
let state = State { name, path, valid };
let marker = std::marker::PhantomData;
ChrootUnverified { state, marker }
}
}
impl ChrootUnverified {
pub fn verify(self, must_exist: bool) -> AResult<ChrootVerified> {
use Validity::*;
let state = self.state;
let exist1 = must_exist && state.valid == Exists;
let exist2 = !must_exist && state.valid == Absent;
if exist1 || exist2 {
let marker = std::marker::PhantomData;
Ok(Chroot { state, marker })
} else {
let name = state.name.as_str();
let msg = match state.valid {
Exists => format!("Chroot '{name}' already exists in workspace"),
Absent => format!("Chroot '{name}' doesn't exist in workspace"),
IsFile => format!("'{name}' is a file in the workspace"),
AbsoluteName => "Absolute paths are not valid chroot names".to_string(),
MultipleLevels => "Chroot name shouldn't have multiple levels".to_string(),
InvalidName => "Chroot name is invalid".to_string(),
};
anyhow::bail!(msg);
}
}
}
impl<M: Verification> std::fmt::Display for Chroot<M> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.state.name)
}
}
impl std::ops::Deref for ChrootVerified {
type Target = Path;
#[inline]
fn deref(&self) -> &Self::Target {
self.state.path.as_path()
}
}