use crate::Query;
use serde::{Deserialize, Serialize};
#[allow(missing_docs)]
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, Debug)]
pub struct Group {
pub name: String,
pub gid: u64,
pub members: Vec<String>,
}
#[derive(Clone, PartialEq, Debug)]
pub enum ParseError {
Format,
Numeric(std::num::ParseIntError),
}
impl std::error::Error for ParseError {}
impl From<std::num::ParseIntError> for ParseError {
fn from(err: std::num::ParseIntError) -> Self {
ParseError::Numeric(err)
}
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ParseError::Format => write!(f, "bad format; a group must be exactly five colon-separated strings, in the form \"NAME:PASS:GID:MEMBERS\""),
ParseError::Numeric(err) => write!(f, "could not parse gid as u64: {}", err),
}
}
}
impl std::fmt::Display for Group {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}::{}:{}", self.name, self.gid, self.members.join(","))
}
}
impl std::str::FromStr for Group {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, ParseError> {
let mut it = s.split(':');
match (it.next(), it.next(), it.next(), it.next(), it.next()) {
(Some(name), _, Some(gid), Some(members), None) => Ok(Group {
name: name.to_string(),
gid: gid.parse()?,
members: members.split(',').map(str::to_string).collect(),
}),
_ => Err(ParseError::Format),
}
}
}
#[derive(Clone, Debug, PartialEq, Default, Eq)]
struct GroupQuery(GroupQueryBuilder);
#[allow(missing_docs)]
#[derive(Clone, Debug, PartialEq, Default, Eq)]
pub struct GroupQueryBuilder {
pub name: Option<String>,
pub gid: Option<u64>,
pub members: Option<Vec<String>>,
}
#[allow(dead_code)]
impl GroupQueryBuilder {
pub const fn new() -> Self {
Self {
name: None,
gid: None,
members: None,
}
}
pub fn build(self) -> Option<impl Query<Group>> {
if self.name.is_none() && self.gid.is_none() && self.members.is_none() {
None
} else {
Some(GroupQuery(self))
}
}
#[allow(dead_code)]
pub fn with_gid(self, gid: u64) -> Self {
Self { gid: Some(gid), ..self }
}
#[allow(dead_code)]
pub fn with_members(self, members: Vec<String>) -> Self {
Self {
members: Some(members),
..self
}
}
#[allow(dead_code)]
pub fn with_name(self, name: impl AsRef<str>) -> Self {
Self {
name: Some(name.as_ref().to_string()),
..self
}
}
}
impl<'a> Query<Group> for GroupQuery {
fn is_match(&self, group: &Group) -> bool {
self.0
.name
.as_ref()
.and_then(|name| Some(name == &group.name))
.unwrap_or(true)
&& (self.0.members.as_ref())
.and_then(|members| Some(**members == *group.members))
.unwrap_or(true)
&& self.0.gid.and_then(|gid| Some(gid == group.gid)).unwrap_or(true)
}
}
#[test]
fn test_parse_and_display() {
let admins = Group {
name: "admins".to_string(),
gid: 0,
members: vec!["root".to_string()],
};
assert_eq!(admins, format!("{}", admins).parse::<Group>().unwrap())
}