use std::fmt::{self, Display, Formatter};
use std::num::ParseIntError;
use std::str::FromStr;
#[derive(Clone, Debug)]
pub enum IdMap {
Guest {
from_guest: u32,
to_host: u32,
count: u32,
},
Host {
from_host: u32,
to_guest: u32,
count: u32,
},
SquashGuest {
from_guest: u32,
to_host: u32,
count: u32,
},
SquashHost {
from_host: u32,
to_guest: u32,
count: u32,
},
Bidirectional {
guest: u32,
host: u32,
count: u32,
},
ForbidGuest {
from_guest: u32,
count: u32,
},
}
#[derive(Debug)]
pub enum IdMapError {
InvalidPrefix(
String,
),
InvalidLength {
expected: usize,
seen: usize,
},
InvalidValue {
value: String,
error: ParseIntError,
},
}
impl std::error::Error for IdMapError {}
impl Display for IdMapError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
IdMapError::InvalidPrefix(prefix) => write!(f, "Invalid ID map prefix {prefix}"),
IdMapError::InvalidLength { expected, seen } => write!(
f,
"Invalid ID map length (expected {expected} elements, got {seen} elements)"
),
IdMapError::InvalidValue { value, error } => {
write!(f, "Invalid value {value} in ID map: {error}")
}
}
}
}
impl FromStr for IdMap {
type Err = IdMapError;
fn from_str(s: &str) -> Result<Self, IdMapError> {
let (prefix, fields) = Self::pre_parse(s)?;
match prefix.as_str() {
"guest" => {
Self::check_arg_count(&fields, 3)?;
Ok(IdMap::Guest {
from_guest: fields[0],
to_host: fields[1],
count: fields[2],
})
}
"host" => {
Self::check_arg_count(&fields, 3)?;
Ok(IdMap::Host {
from_host: fields[0],
to_guest: fields[1],
count: fields[2],
})
}
"squash-guest" => {
Self::check_arg_count(&fields, 3)?;
Ok(IdMap::SquashGuest {
from_guest: fields[0],
to_host: fields[1],
count: fields[2],
})
}
"squash-host" => {
Self::check_arg_count(&fields, 3)?;
Ok(IdMap::SquashHost {
from_host: fields[0],
to_guest: fields[1],
count: fields[2],
})
}
"forbid-guest" => {
Self::check_arg_count(&fields, 2)?;
Ok(IdMap::ForbidGuest {
from_guest: fields[0],
count: fields[1],
})
}
"map" => {
Self::check_arg_count(&fields, 3)?;
Ok(IdMap::Bidirectional {
guest: fields[0],
host: fields[1],
count: fields[2],
})
}
_ => Err(IdMapError::InvalidPrefix(prefix)),
}
}
}
impl Display for IdMap {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
IdMap::Guest {
from_guest,
to_host,
count,
} => {
write!(f, "guest:{from_guest}:{to_host}:{count}")
}
IdMap::Host {
from_host,
to_guest,
count,
} => {
write!(f, "host:{from_host}:{to_guest}:{count}")
}
IdMap::SquashGuest {
from_guest,
to_host,
count,
} => {
write!(f, "squash-guest:{from_guest}:{to_host}:{count}")
}
IdMap::SquashHost {
from_host,
to_guest,
count,
} => {
write!(f, "squash-host:{from_host}:{to_guest}:{count}")
}
IdMap::ForbidGuest { from_guest, count } => {
write!(f, "forbid-guest:{from_guest}:{count}")
}
IdMap::Bidirectional { guest, host, count } => {
write!(f, "map:{guest}:{host}:{count}")
}
}
}
}
impl IdMap {
fn pre_parse(s: &str) -> Result<(String, Vec<u32>), IdMapError> {
let mut chars = s.chars();
let mut prefix = String::new();
let separator = loop {
let Some(c) = chars.next() else {
return Err(IdMapError::InvalidLength {
expected: 1,
seen: 0,
});
};
if c.is_alphanumeric() || c == '-' || c == '_' {
for c in c.to_lowercase() {
prefix.push(c);
}
} else {
break c;
}
};
let values: Vec<&str> = chars.as_str().split(separator).collect();
let values = values
.into_iter()
.map(|v| {
v.parse().map_err(|error| IdMapError::InvalidValue {
value: String::from(v),
error,
})
})
.collect::<Result<Vec<u32>, IdMapError>>()?;
Ok((prefix, values))
}
fn check_arg_count(args: &[u32], expected_count: usize) -> Result<(), IdMapError> {
if args.len() != expected_count {
Err(IdMapError::InvalidLength {
expected: expected_count,
seen: args.len(),
})
} else {
Ok(())
}
}
}