password-as-service 0.1.4

a very poorly considered http/rest service
Documentation
use crate::Query;
use serde::{Deserialize, Serialize};
/// A group, contianing zero or more members.
#[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),
        }
    }
}

// A [Query] for a [Group] which matches all elements where the Query is some. It is invalid for all vaues of a Query to be None. Build a Query using a QueryBuilder.
#[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 {
    /// Create a new (blank) GroupQueryBuilder. Empty queries cannot be built.
    pub const fn new() -> Self {
        Self {
            name: None,
            gid: None,
            members: None,
        }
    }

    /// build the query if at least one element is not 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))
        }
    }

    /// add a `gid` to the query, replacing it if it already exists.
    #[allow(dead_code)]
    pub fn with_gid(self, gid: u64) -> Self {
        Self { gid: Some(gid), ..self }
    }

    /// add or replace the members in the query, replacing them if it already exists.
    #[allow(dead_code)]
    pub fn with_members(self, members: Vec<String>) -> Self {
        Self {
            members: Some(members),
            ..self
        }
    }

    /// add or replace the name in the query, replacing them if it already exists.
    #[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())
}