use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum LevelFilter {
All,
Include(Vec<serde_json::Value>),
Exclude(Vec<serde_json::Value>),
}
impl Default for LevelFilter {
fn default() -> Self {
Self::All
}
}
impl LevelFilter {
pub fn includes_all(&self) -> bool {
matches!(self, Self::All)
}
pub fn has_restrictions(&self) -> bool {
match self {
Self::All => false,
Self::Include(v) | Self::Exclude(v) => !v.is_empty(),
}
}
pub fn included_levels(&self) -> Option<&Vec<serde_json::Value>> {
match self {
Self::Include(levels) => Some(levels),
_ => None,
}
}
pub fn excluded_levels(&self) -> Option<&Vec<serde_json::Value>> {
match self {
Self::Exclude(levels) => Some(levels),
_ => None,
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct ComponentFilter {
pub levels: LevelFilter,
}
impl ComponentFilter {
pub fn all() -> Self {
Self {
levels: LevelFilter::All,
}
}
pub fn include(levels: Vec<serde_json::Value>) -> Self {
Self {
levels: LevelFilter::Include(levels),
}
}
pub fn exclude(levels: Vec<serde_json::Value>) -> Self {
Self {
levels: LevelFilter::Exclude(levels),
}
}
pub fn includes_all(&self) -> bool {
self.levels.includes_all()
}
pub fn has_restrictions(&self) -> bool {
self.levels.has_restrictions()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct ComponentFilters {
filters: HashMap<String, ComponentFilter>,
}
impl ComponentFilters {
pub fn new() -> Self {
Self::default()
}
pub fn include_all(mut self, component: impl Into<String>) -> Self {
self.filters
.insert(component.into(), ComponentFilter::all());
self
}
pub fn include_levels(
mut self,
component: impl Into<String>,
levels: Vec<serde_json::Value>,
) -> Self {
self.filters
.insert(component.into(), ComponentFilter::include(levels));
self
}
pub fn exclude_levels(
mut self,
component: impl Into<String>,
levels: Vec<serde_json::Value>,
) -> Self {
self.filters
.insert(component.into(), ComponentFilter::exclude(levels));
self
}
pub fn with_filter(mut self, component: impl Into<String>, filter: ComponentFilter) -> Self {
self.filters.insert(component.into(), filter);
self
}
pub fn is_empty(&self) -> bool {
self.filters.is_empty()
}
pub fn len(&self) -> usize {
self.filters.len()
}
pub fn includes(&self, component: &str) -> bool {
self.filters.contains_key(component)
}
pub fn get(&self, component: &str) -> Option<&ComponentFilter> {
self.filters.get(component)
}
pub fn get_level_filter(&self, component: &str) -> Option<&LevelFilter> {
self.filters.get(component).map(|f| &f.levels)
}
pub fn included_components(&self) -> impl Iterator<Item = &str> {
self.filters.keys().map(|s| s.as_str())
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &ComponentFilter)> {
self.filters.iter().map(|(k, v)| (k.as_str(), v))
}
pub fn components_with_restrictions(&self) -> impl Iterator<Item = (&str, &LevelFilter)> {
self.filters
.iter()
.filter(|(_, f)| f.has_restrictions())
.map(|(k, f)| (k.as_str(), &f.levels))
}
pub fn as_map(&self) -> &HashMap<String, ComponentFilter> {
&self.filters
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn test_level_filter_all() {
let filter = LevelFilter::All;
assert!(filter.includes_all());
assert!(!filter.has_restrictions());
assert!(filter.included_levels().is_none());
assert!(filter.excluded_levels().is_none());
}
#[test]
fn test_level_filter_include() {
let filter = LevelFilter::Include(vec![json!("red"), json!("blue")]);
assert!(!filter.includes_all());
assert!(filter.has_restrictions());
assert_eq!(filter.included_levels().unwrap().len(), 2);
assert!(filter.excluded_levels().is_none());
}
#[test]
fn test_level_filter_exclude() {
let filter = LevelFilter::Exclude(vec![json!("unknown")]);
assert!(!filter.includes_all());
assert!(filter.has_restrictions());
assert!(filter.included_levels().is_none());
assert_eq!(filter.excluded_levels().unwrap().len(), 1);
}
#[test]
fn test_component_filter_constructors() {
let all = ComponentFilter::all();
assert!(all.includes_all());
let include = ComponentFilter::include(vec![json!("a"), json!("b")]);
assert!(!include.includes_all());
assert!(include.has_restrictions());
let exclude = ComponentFilter::exclude(vec![json!("x")]);
assert!(!exclude.includes_all());
assert!(exclude.has_restrictions());
}
#[test]
fn test_component_filters_empty() {
let filters = ComponentFilters::new();
assert!(filters.is_empty());
assert_eq!(filters.len(), 0);
assert!(!filters.includes("color"));
}
#[test]
fn test_component_filters_include_all() {
let filters = ComponentFilters::new()
.include_all("color")
.include_all("size");
assert!(!filters.is_empty());
assert_eq!(filters.len(), 2);
assert!(filters.includes("color"));
assert!(filters.includes("size"));
assert!(!filters.includes("region"));
let color_filter = filters.get("color").unwrap();
assert!(color_filter.includes_all());
}
#[test]
fn test_component_filters_include_levels() {
let filters =
ComponentFilters::new().include_levels("color", vec![json!("red"), json!("blue")]);
assert!(filters.includes("color"));
let level_filter = filters.get_level_filter("color").unwrap();
assert!(matches!(level_filter, LevelFilter::Include(_)));
let levels = level_filter.included_levels().unwrap();
assert_eq!(levels.len(), 2);
}
#[test]
fn test_component_filters_exclude_levels() {
let filters = ComponentFilters::new()
.exclude_levels("color", vec![json!("unknown"), json!("invalid")]);
assert!(filters.includes("color"));
let level_filter = filters.get_level_filter("color").unwrap();
assert!(matches!(level_filter, LevelFilter::Exclude(_)));
let levels = level_filter.excluded_levels().unwrap();
assert_eq!(levels.len(), 2);
}
#[test]
fn test_component_filters_mixed() {
let filters = ComponentFilters::new()
.include_all("color")
.include_levels("size", vec![json!("small"), json!("medium")])
.exclude_levels("region", vec![json!("unknown")]);
assert_eq!(filters.len(), 3);
assert!(filters.get("color").unwrap().includes_all());
let size_filter = filters.get_level_filter("size").unwrap();
assert!(matches!(size_filter, LevelFilter::Include(_)));
let region_filter = filters.get_level_filter("region").unwrap();
assert!(matches!(region_filter, LevelFilter::Exclude(_)));
}
#[test]
fn test_components_with_restrictions() {
let filters = ComponentFilters::new()
.include_all("color")
.include_levels("size", vec![json!("small")])
.exclude_levels("region", vec![json!("unknown")]);
let restricted: Vec<_> = filters.components_with_restrictions().collect();
assert_eq!(restricted.len(), 2);
let restricted_names: Vec<&str> = restricted.iter().map(|(name, _)| *name).collect();
assert!(restricted_names.contains(&"size"));
assert!(restricted_names.contains(&"region"));
assert!(!restricted_names.contains(&"color"));
}
#[test]
fn test_iteration() {
let filters = ComponentFilters::new()
.include_all("a")
.include_all("b")
.include_all("c");
let names: Vec<&str> = filters.included_components().collect();
assert_eq!(names.len(), 3);
let pairs: Vec<_> = filters.iter().collect();
assert_eq!(pairs.len(), 3);
}
#[test]
fn test_serialization() {
let filters =
ComponentFilters::new().include_levels("color", vec![json!("red"), json!("blue")]);
let json = serde_json::to_string(&filters).unwrap();
let deserialized: ComponentFilters = serde_json::from_str(&json).unwrap();
assert_eq!(filters, deserialized);
}
}