use std::{fmt, str::FromStr};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use typed_path::Utf8UnixPathBuf;
use crate::MicrosandboxError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PathPair {
Distinct {
host: Utf8UnixPathBuf,
guest: Utf8UnixPathBuf,
},
Same(Utf8UnixPathBuf),
}
impl PathPair {
pub fn with_same(path: Utf8UnixPathBuf) -> Self {
Self::Same(path)
}
pub fn with_distinct(host: Utf8UnixPathBuf, guest: Utf8UnixPathBuf) -> Self {
Self::Distinct { host, guest }
}
pub fn get_host(&self) -> &Utf8UnixPathBuf {
match self {
Self::Distinct { host, .. } | Self::Same(host) => host,
}
}
pub fn get_guest(&self) -> &Utf8UnixPathBuf {
match self {
Self::Distinct { guest, .. } | Self::Same(guest) => guest,
}
}
}
impl FromStr for PathPair {
type Err = MicrosandboxError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Err(MicrosandboxError::InvalidPathPair(s.to_string()));
}
if s.contains(':') {
let (host, guest) = s.split_once(':').unwrap();
if guest.is_empty() || host.is_empty() {
return Err(MicrosandboxError::InvalidPathPair(s.to_string()));
}
if guest == host {
return Ok(Self::Same(host.into()));
} else {
return Ok(Self::Distinct {
host: host.into(),
guest: guest.into(),
});
}
}
Ok(Self::Same(s.into()))
}
}
impl fmt::Display for PathPair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Distinct { host, guest } => {
write!(f, "{}:{}", host, guest)
}
Self::Same(path) => write!(f, "{}:{}", path, path),
}
}
}
impl Serialize for PathPair {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for PathPair {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Self::from_str(&s).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path_pair_from_str() {
assert_eq!(
"/data".parse::<PathPair>().unwrap(),
PathPair::Same("/data".into())
);
assert_eq!(
"/data:/data".parse::<PathPair>().unwrap(),
PathPair::Same("/data".into())
);
assert_eq!(
"/host/data:/container/data".parse::<PathPair>().unwrap(),
PathPair::Distinct {
host: "/host/data".into(),
guest: "/container/data".into()
}
);
assert!("".parse::<PathPair>().is_err());
assert!(":".parse::<PathPair>().is_err());
assert!(":/data".parse::<PathPair>().is_err());
assert!("/data:".parse::<PathPair>().is_err());
}
#[test]
fn test_path_pair_display() {
assert_eq!(PathPair::Same("/data".into()).to_string(), "/data:/data");
assert_eq!(
PathPair::Distinct {
host: "/host/data".into(),
guest: "/container/data".into()
}
.to_string(),
"/host/data:/container/data"
);
}
#[test]
fn test_path_pair_getters() {
let same = PathPair::Same("/data".into());
assert_eq!(same.get_host().as_str(), "/data");
assert_eq!(same.get_guest().as_str(), "/data");
let distinct = PathPair::Distinct {
host: "/host/data".into(),
guest: "/container/data".into(),
};
assert_eq!(distinct.get_host().as_str(), "/host/data");
assert_eq!(distinct.get_guest().as_str(), "/container/data");
}
#[test]
fn test_path_pair_constructors() {
assert_eq!(
PathPair::with_same("/data".into()),
PathPair::Same("/data".into())
);
assert_eq!(
PathPair::with_distinct("/host/data".into(), "/container/data".into()),
PathPair::Distinct {
host: "/host/data".into(),
guest: "/container/data".into()
}
);
}
}