csh-ldap 0.2.1

LDAP bindings for CSH projects
Documentation
use lazy_static::lazy_static;
use ldap3::SearchEntry;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Debug;
use std::str::FromStr;

#[derive(Debug, Serialize, Deserialize, Clone)]
#[allow(non_snake_case)]
pub struct LdapUser {
    pub dn: String,
    pub cn: String,
    pub uid: String,
    pub groups: Vec<String>,
    pub krbPrincipalName: String,
    pub mail: Vec<String>,
    pub mobile: Vec<String>,
    pub drinkBalance: Option<i64>,
    pub ibutton: Vec<String>,
}

impl LdapUser {
    #[must_use]
    pub fn from_entry(entry: &SearchEntry) -> Self {
        let user_attrs = &entry.attrs;
        LdapUser {
            dn: entry.dn.clone(),
            cn: get_one(user_attrs, "cn").unwrap(),
            uid: get_one(user_attrs, "uid").unwrap(),
            groups: get_groups(get_vec(user_attrs, "memberOf")),
            krbPrincipalName: get_one(user_attrs, "krbPrincipalName").unwrap(),
            mail: get_vec(user_attrs, "mail"),
            mobile: get_vec(user_attrs, "mobile"),
            ibutton: get_vec(user_attrs, "ibutton"),
            drinkBalance: get_one(user_attrs, "drinkBalance"),
        }
    }
}

fn get_one<T>(entry: &HashMap<String, Vec<String>>, field: &str) -> Option<T>
where
    T: FromStr,
    <T as FromStr>::Err: Debug,
{
    match entry.get(field).map(|f| f.get(0).unwrap().parse::<T>()) {
        Some(result) => match result {
            Ok(r) => Some(r),
            Err(_) => None,
        },
        None => None,
    }
}

fn get_vec<T>(entry: &HashMap<String, Vec<String>>, field: &str) -> Vec<T>
where
    T: FromStr,
    <T as FromStr>::Err: Debug,
{
    match entry.get(field) {
        Some(v) => v.iter().map(|f| f.parse::<T>().unwrap()).collect(),
        None => vec![],
    }
}

#[must_use]
pub fn get_groups(member_of: Vec<String>) -> Vec<String> {
    lazy_static! {
        static ref GROUP_REGEX: Regex =
            Regex::new(r"cn=(?P<name>\w+),cn=groups,cn=accounts,dc=csh,dc=rit,dc=edu").unwrap();
    }
    member_of
        .iter()
        .filter_map(|group| {
            GROUP_REGEX
                .captures(group)
                .map(|cap| cap["name"].to_owned())
        })
        .collect()
}

#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct LdapUserChangeSet {
    pub dn: String,
    pub drinkBalance: Option<i64>,
    pub ibutton: Option<Vec<String>>,
}