async_mpd/client/
filter.rs

1use crate::Tag;
2use itertools::Itertools;
3
4pub trait ToFilterExpr {
5    /// Tag equals
6    fn equals<T: ToString>(self, s: T) -> FilterExpr;
7
8    /// Tag contains
9    fn contains<T: ToString>(self, s: T) -> FilterExpr;
10}
11
12impl ToFilterExpr for Tag {
13    fn equals<T: ToString>(self, s: T) -> FilterExpr {
14        FilterExpr::Equals(self, s.to_string())
15    }
16
17    fn contains<T: ToString>(self, s: T) -> FilterExpr {
18        FilterExpr::Contains(self, s.to_string())
19    }
20}
21
22/// Filter expression used by search function
23pub enum FilterExpr {
24    Equals(Tag, String),
25    Contains(Tag, String),
26    Not(Box<FilterExpr>),
27}
28
29impl FilterExpr {
30    pub fn to_query(&self) -> String {
31        match self {
32            FilterExpr::Equals(tag, s) => format!("({:?} == \"{}\")", tag, s),
33            FilterExpr::Contains(tag, s) => format!("({:?} contains \"{}\")", tag, s),
34            FilterExpr::Not(exp) => format!("!{}", exp.to_query()),
35        }
36    }
37}
38
39/// Abstraction over search filter
40#[derive(Default)]
41pub struct Filter {
42    exprs: Vec<FilterExpr>,
43}
44
45impl Filter {
46    pub fn new() -> Self {
47        Self { exprs: Vec::new() }
48    }
49
50    pub fn with(filter: FilterExpr) -> Self {
51        Self {
52            exprs: vec![filter],
53        }
54    }
55
56    pub fn and(mut self, other: FilterExpr) -> Filter {
57        self.exprs.push(other);
58        self
59    }
60
61    pub fn and_not(mut self, other: FilterExpr) -> Self {
62        self.exprs.push(FilterExpr::Not(Box::new(other)));
63        self
64    }
65
66    pub fn to_query(&self) -> Option<String> {
67        if self.exprs.is_empty() {
68            return None;
69        }
70
71        let joined = self
72            .exprs
73            .iter()
74            .map(|filter| filter.to_query())
75            .join(" AND ");
76
77        Some(format!("({})", escape(&joined)))
78    }
79}
80
81fn escape(s: &str) -> String {
82    s.replace('\\', "\\\\")
83        .replace('\"', "\\\"")
84        .replace('\'', "\\\'")
85}