#![warn(missing_docs)]
#![deny(warnings)]
#![deny(clippy::all)]
#![doc = include_str!("../Readme.md")]
use thiserror::Error;
#[derive(Error, Debug)]
pub enum GecosError {
#[error("String contains invalid char, which is not allowed inside a Gecos field! (Chars ',', ':', '=', '\\', '\"', '\\n' are not allowed)")]
IllegalPasswdChar(char),
}
#[derive(Clone, Debug)]
pub struct Gecos {
pub full_name: Option<GecosSanitizedString>,
pub room: Option<GecosSanitizedString>,
pub work_phone: Option<GecosSanitizedString>,
pub home_phone: Option<GecosSanitizedString>,
pub other: Vec<GecosSanitizedString>,
}
#[derive(Clone, Debug)]
pub struct GecosSanitizedString {
str: String,
}
impl GecosSanitizedString {
pub fn new(value: String) -> Result<Self, GecosError> {
const INVALID_CHARS: [&char; 6] = [&',', &':', &'=', &'\\', &'\"', &'\n'];
for character in INVALID_CHARS {
if value.contains(*character) {
return Err(GecosError::IllegalPasswdChar(*character));
}
}
Ok(Self { str: value })
}
}
impl TryFrom<String> for GecosSanitizedString {
type Error = GecosError;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl<'a> From<&'a GecosSanitizedString> for &'a String {
fn from(value: &'a GecosSanitizedString) -> Self {
&value.str
}
}
impl std::fmt::Display for GecosSanitizedString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.str)
}
}
impl PartialEq for GecosSanitizedString {
fn eq(&self, other: &Self) -> bool {
self.str == other.str
}
}
impl Gecos {
pub fn to_gecos_string(&self) -> String {
macro_rules! gecos_element_to_string {
($sts:expr) => {
$sts.as_ref().unwrap_or(&"".to_string().try_into().unwrap())
};
}
format!(
"{},{},{},{},{}",
gecos_element_to_string!(self.full_name),
gecos_element_to_string!(self.room),
gecos_element_to_string!(self.work_phone),
gecos_element_to_string!(self.home_phone),
self.other
.iter()
.map(|val| val.into())
.cloned()
.collect::<Vec<String>>()
.join(","),
)
}
pub fn from_gecos_string(input: &str) -> Result<Self, GecosError> {
let mut splitted = input
.split(',')
.map(|val| -> Result<GecosSanitizedString, GecosError> { val.to_string().try_into() });
macro_rules! gecos_string_element_to_gecos_object_element {
($sts:expr) => {
match $sts {
Some(option_val) => {
match option_val {
Ok(val) => {
if val.to_string() == "" {
None
} else {
Some(val)
}
}
Err(err) => return Err(err),
}
}
None => None,
}
};
}
Ok(Self {
full_name: gecos_string_element_to_gecos_object_element!(splitted.next()),
room: gecos_string_element_to_gecos_object_element!(splitted.next()),
work_phone: gecos_string_element_to_gecos_object_element!(splitted.next()),
home_phone: gecos_string_element_to_gecos_object_element!(splitted.next()),
other: splitted.collect::<Result<Vec<GecosSanitizedString>, GecosError>>()?,
})
}
}