vahanafs/src/fs/id_mapping.rs
2025-03-03 12:45:04 -06:00

424 lines
11 KiB
Rust

use char_enum::{char_enum_derive::FromChar, FromChar, FromStrError, ToChar};
use std::{error::Error, fmt::Display, num::ParseIntError, str::FromStr};
use itertools::{peek_nth, Itertools};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, FromChar)]
pub enum IdKind {
#[value = "u"]
Uid,
#[value = "g"]
Gid,
#[default]
#[value = "b"]
Both,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ParseTypedIdRangeError {
IdRange(ParseUntypedIdRangeError),
MissingType(FromStrError),
}
impl Display for ParseTypedIdRangeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ParseTypedIdRangeError::IdRange(err) => write!(f, "{err}"),
ParseTypedIdRangeError::MissingType(err) => write!(f, "{err}"),
}
}
}
impl Error for ParseTypedIdRangeError {}
impl From<FromStrError> for ParseTypedIdRangeError {
fn from(value: FromStrError) -> Self {
Self::MissingType(value)
}
}
impl From<ParseUntypedIdRangeError> for ParseTypedIdRangeError {
fn from(value: ParseUntypedIdRangeError) -> Self {
Self::IdRange(value)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TypedIdRange(pub IdKind, pub UntypedIdRange);
impl FromStr for TypedIdRange {
type Err = ParseTypedIdRangeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.split_once(":") {
Some((kind, rest)) => Ok(Self(
IdKind::from_str(kind)?,
UntypedIdRange::from_str(rest)?,
)),
None => Ok(Self(IdKind::default(), UntypedIdRange::from_str(s)?)),
}
}
}
impl Display for TypedIdRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.0, self.1)
}
}
impl From<UntypedIdRange> for TypedIdRange {
fn from(value: UntypedIdRange) -> Self {
Self(IdKind::Both, value)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ParseUntypedIdRangeError {
InvalidArguments(usize),
NotANumber(ParseIntError),
}
impl Display for ParseUntypedIdRangeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ParseUntypedIdRangeError::InvalidArguments(args) => write!(
f,
"invalid number of arguments given (got {args}, expected 3)"
),
ParseUntypedIdRangeError::NotANumber(err) => {
write!(f, "could not parse argument: {err}")
}
}
}
}
impl Error for ParseUntypedIdRangeError {}
impl From<ParseIntError> for ParseUntypedIdRangeError {
fn from(value: ParseIntError) -> Self {
Self::NotANumber(value)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct UntypedIdRange(pub usize, pub usize, pub usize);
impl FromStr for UntypedIdRange {
type Err = ParseUntypedIdRangeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split(':').collect();
let len = parts.len();
if len != 3 {
return Err(ParseUntypedIdRangeError::InvalidArguments(len));
}
let range: Vec<usize> = parts
.iter()
.map(|n| usize::from_str_radix(n, 10))
.process_results(|iter| iter.collect())?;
Ok(Self(range[0], range[1], range[2]))
}
}
impl Display for UntypedIdRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}:{}", self.0, self.1, self.2)
}
}
impl From<TypedIdRange> for UntypedIdRange {
fn from(value: TypedIdRange) -> Self {
value.1
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseIdRangeError(String);
impl Display for ParseIdRangeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Error for ParseIdRangeError {}
impl From<ParseUntypedIdRangeError> for ParseIdRangeError {
fn from(value: ParseUntypedIdRangeError) -> Self {
Self(value.to_string())
}
}
impl From<ParseTypedIdRangeError> for ParseIdRangeError {
fn from(value: ParseTypedIdRangeError) -> Self {
Self(value.to_string())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum IdRange {
Typed(TypedIdRange),
Untyped(UntypedIdRange),
}
impl FromStr for IdRange {
type Err = ParseIdRangeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
TypedIdRange::from_str(s)
.map(IdRange::Typed)
.or_else(|_| Ok(UntypedIdRange::from_str(s)?.into()))
}
}
impl Display for IdRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IdRange::Typed(range) => write!(f, "{range}"),
IdRange::Untyped(range) => write!(f, "{range}"),
}
}
}
impl From<TypedIdRange> for IdRange {
fn from(value: TypedIdRange) -> Self {
Self::Typed(value)
}
}
impl From<UntypedIdRange> for IdRange {
fn from(value: UntypedIdRange) -> Self {
Self::Untyped(value)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Delimiter(char);
impl Default for Delimiter {
fn default() -> Self {
Self(',')
}
}
impl Display for Delimiter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<char> for Delimiter {
fn from(value: char) -> Self {
Self(value)
}
}
fn try_split(s: &str, delim: char) -> Option<impl Iterator<Item = &str>> {
let mut iter = peek_nth(s.split(delim));
match iter.peek_nth(1) {
Some(_) => Some(iter),
None => None,
}
}
fn split<'a, 'b>(s: &'a str, delim: &'b [char]) -> Option<(char, impl Iterator<Item = &'a str>)> {
let mut found = None;
for d in delim {
if let Some(iter) = try_split(s, *d) {
found = Some((*d, iter));
break;
}
}
found
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct IdMapping {
ranges: Vec<IdRange>,
delimiter: Delimiter,
}
impl IdMapping {
pub fn new(
ranges: impl Iterator<Item = impl Into<IdRange>>,
delimiter: impl Into<Delimiter>,
) -> Self {
Self {
ranges: ranges.map_into().collect(),
delimiter: delimiter.into(),
}
}
pub fn from_iter<'a>(s: impl IntoIterator<Item = &'a str>) -> Result<Self, ParseIdRangeError> {
Ok(IdMapping {
ranges: s
.into_iter()
.map_while(|v| IdRange::from_str(v).ok())
.collect(),
..Default::default()
})
}
pub fn with_delimiter(self, delimiter: impl Into<Delimiter>) -> Self {
Self {
delimiter: delimiter.into(),
..self
}
}
}
impl FromStr for IdMapping {
type Err = ParseIdRangeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match split(s, &[',', ' ']) {
Some((d, iter)) => Ok(Self::from_iter(iter)?.with_delimiter(d)),
None => Err(ParseIdRangeError(
"none of the provided delimiters matched string".to_string(),
)),
}
}
}
impl Display for IdMapping {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut iter = self.ranges.iter();
let delim = self.delimiter;
if let Some(head) = iter.next() {
write!(f, "{head}")?;
for item in iter {
write!(f, "{delim}{item}")?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::fs::id_mapping::{
IdKind, IdRange, ParseTypedIdRangeError, ParseUntypedIdRangeError,
};
use super::{IdMapping, TypedIdRange, UntypedIdRange};
#[test]
fn untyped_id_range_from_str() {
let three_args = UntypedIdRange::from_str("1:100:3");
assert_eq!(three_args, Ok(UntypedIdRange(1, 100, 3)));
let too_many = UntypedIdRange::from_str("1:100:3:4");
assert_eq!(too_many, Err(ParseUntypedIdRangeError::InvalidArguments(4)));
let too_few = UntypedIdRange::from_str("1:100");
assert_eq!(too_few, Err(ParseUntypedIdRangeError::InvalidArguments(2)));
}
#[test]
fn typed_id_range_from_str() {
let uid_range = TypedIdRange::from_str("u:1:100:3");
assert_eq!(
uid_range,
Ok(TypedIdRange(IdKind::Uid, UntypedIdRange(1, 100, 3)))
);
let gid_range = TypedIdRange::from_str("g:1:100:3");
assert_eq!(
gid_range,
Ok(TypedIdRange(IdKind::Gid, UntypedIdRange(1, 100, 3)))
);
let both_range = TypedIdRange::from_str("b:1:100:3");
assert_eq!(
both_range,
Ok(TypedIdRange(IdKind::Both, UntypedIdRange(1, 100, 3)))
);
let too_many = TypedIdRange::from_str("u:1:100:3:4");
assert_eq!(
too_many,
Err(ParseTypedIdRangeError::IdRange(
ParseUntypedIdRangeError::InvalidArguments(4)
))
);
}
#[test]
fn id_mapping_from_str() {
let map = IdMapping::from_str("1:100:3,2:101:5");
assert_eq!(
map,
Ok(IdMapping::new(
vec![UntypedIdRange(1, 100, 3), UntypedIdRange(2, 101, 5)].into_iter(),
','
))
);
let map = IdMapping::from_str("u:1000:0:1 g:1001:1:2 5000:1000:2");
assert_eq!(
map,
Ok(IdMapping::new(
vec![
IdRange::Typed(TypedIdRange(IdKind::Uid, UntypedIdRange(1000, 0, 1))),
IdRange::Typed(TypedIdRange(IdKind::Gid, UntypedIdRange(1001, 1, 2))),
IdRange::Untyped(UntypedIdRange(5000, 1000, 2)),
]
.into_iter(),
' '
))
);
}
#[test]
fn from_str_and_display_are_the_same() {
assert_eq!(IdKind::from_str("u").unwrap().to_string(), "u");
assert_eq!(IdKind::from_str("g").unwrap().to_string(), "g");
assert_eq!(IdKind::from_str("b").unwrap().to_string(), "b");
assert_eq!(
UntypedIdRange::from_str("1000:0:1").unwrap().to_string(),
"1000:0:1"
);
assert_eq!(
TypedIdRange::from_str("u:1000:0:1").unwrap().to_string(),
"u:1000:0:1"
);
assert_eq!(
IdRange::from_str("u:1000:0:1").unwrap().to_string(),
"u:1000:0:1"
);
assert_eq!(
IdRange::from_str("1000:0:1").unwrap().to_string(),
"1000:0:1"
);
assert_eq!(
IdMapping::from_str("1000:0:1,2:1001:5")
.unwrap()
.to_string(),
"1000:0:1,2:1001:5"
);
assert_eq!(
IdMapping::from_str("b:1000:0:1 g:2:1001:5")
.unwrap()
.to_string(),
"b:1000:0:1 g:2:1001:5"
);
}
}