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 for ParseTypedIdRangeError { fn from(value: FromStrError) -> Self { Self::MissingType(value) } } impl From 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 { 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 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 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 { let parts: Vec<&str> = s.split(':').collect(); let len = parts.len(); if len != 3 { return Err(ParseUntypedIdRangeError::InvalidArguments(len)); } let range: Vec = 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 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 for ParseIdRangeError { fn from(value: ParseUntypedIdRangeError) -> Self { Self(value.to_string()) } } impl From 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 { 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 for IdRange { fn from(value: TypedIdRange) -> Self { Self::Typed(value) } } impl From 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 for Delimiter { fn from(value: char) -> Self { Self(value) } } fn try_split(s: &str, delim: char) -> Option> { 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)> { 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, delimiter: Delimiter, } impl IdMapping { pub fn new( ranges: impl Iterator>, delimiter: impl Into, ) -> Self { Self { ranges: ranges.map_into().collect(), delimiter: delimiter.into(), } } pub fn from_iter<'a>(s: impl IntoIterator) -> Result { 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) -> Self { Self { delimiter: delimiter.into(), ..self } } } impl FromStr for IdMapping { type Err = ParseIdRangeError; fn from_str(s: &str) -> Result { 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" ); } }