felicia 0.5.1

service for accessing and sharing lists of bad actors
Documentation
use std::collections::{HashMap, HashSet};

use serde::{Deserialize, Serialize};

pub mod mastodon;

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Domain {
    pub domain: String,
    pub level: Level,
    pub notes: Option<String>,
    pub reason: Option<String>,
    pub source: String,
}

impl Domain {
    pub fn key(&self) -> String {
        format!("{}:{}", self.domain, self.source,)
    }

    pub fn domain(&self) -> &str {
        self.domain.as_ref()
    }

    pub fn level(&self) -> &Level {
        &self.level
    }

    pub fn notes(&self) -> Option<&str> {
        self.notes.as_deref()
    }

    pub fn reason(&self) -> Option<&str> {
        self.reason.as_deref()
    }

    pub fn source(&self) -> &str {
        self.source.as_ref()
    }
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Level {
    Low,
    Medium,
    Severe,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct List {
    pub domains: Vec<Domain>,
}

impl From<Vec<Domain>> for List {
    fn from(domains: Vec<Domain>) -> Self {
        Self { domains }
    }
}

impl List {
    pub fn empty() -> Self {
        Self { domains: vec![] }
    }

    pub fn merge(&mut self, mut other: List) {
        self.domains.append(&mut other.domains);
    }
}

#[derive(Debug, Default, Deserialize)]
pub struct FilterOpt {
    pub dedup: Option<bool>,
}

impl From<Search> for FilterOpt {
    fn from(value: Search) -> Self {
        Self { dedup: value.dedup }
    }
}

#[derive(Debug, Default, Clone, Deserialize)]
pub struct Search {
    pub threshold: Option<usize>,
    pub domain: Option<String>,
    pub source: Option<String>,
    pub level: Option<Level>,
    pub dedup: Option<bool>,
}

#[derive(Debug)]
pub struct SearchState {
    pub sources: HashMap<String, HashSet<String>>,
}

impl SearchState {
    pub fn new(list: &List) -> Self {
        let mut count: HashMap<String, HashSet<String>> = HashMap::new();
        for d in list.domains.iter() {
            if let Some(sources) = count.get_mut(d.domain()) {
                sources.insert(d.source().to_string());
            } else {
                count.insert(
                    d.domain().to_string(),
                    HashSet::from([d.source().to_string()]),
                );
            }
        }

        Self { sources: count }
    }
}

impl Search {
    pub fn criteria() -> Vec<fn(&Self, &Domain, &SearchState) -> bool> {
        vec![
            Self::threshold_filter,
            Self::domain_filter,
            Self::source_filter,
            Self::level_filter,
        ]
    }

    fn threshold_filter(&self, domain: &Domain, state: &SearchState) -> bool {
        if let Some(threshold_filter) = self.threshold {
            let n: usize = state.sources.get(domain.domain()).map_or(0, |s| s.len());

            n >= threshold_filter
        } else {
            true
        }
    }

    fn domain_filter(&self, domain: &Domain, _: &SearchState) -> bool {
        if let Some(domain_filter) = &self.domain {
            domain.domain.contains(domain_filter)
        } else {
            true
        }
    }

    fn source_filter(&self, domain: &Domain, _: &SearchState) -> bool {
        if let Some(source_filter) = &self.source {
            domain.source.contains(source_filter)
        } else {
            true
        }
    }

    fn level_filter(&self, domain: &Domain, _: &SearchState) -> bool {
        if let Some(level_filter) = &self.level {
            domain.level().eq(level_filter)
        } else {
            true
        }
    }
}