Skip to main content

io_jmap/rfc8620/
filter.rs

1//! JMAP filter combinator (RFC 8620 §5.5): a protocol-specific condition or
2//! a logical AND / OR / NOT over sub-filters.
3
4use alloc::vec::Vec;
5
6use serde::{Deserialize, Serialize};
7
8/// A JMAP filter (RFC 8620 §5.5): a protocol-specific condition (e.g.
9/// [`crate::rfc8621::email::query::JmapEmailFilter`]) or a logical
10/// combinator.
11///
12/// `untagged` serde: presence of `operator` picks [`JmapFilterOperator`].
13#[derive(Clone, Debug, Serialize, Deserialize)]
14#[serde(untagged)]
15pub enum JmapFilter<C> {
16    /// A logical combinator over sub-filters.
17    Operator(JmapFilterOperator<C>),
18    /// A protocol-specific filter condition.
19    Condition(C),
20}
21
22impl<C> From<C> for JmapFilter<C> {
23    fn from(condition: C) -> Self {
24        JmapFilter::Condition(condition)
25    }
26}
27
28impl<C> JmapFilter<C> {
29    /// Wraps `conditions` in an AND combinator.
30    pub fn and(conditions: Vec<JmapFilter<C>>) -> Self {
31        JmapFilter::Operator(JmapFilterOperator {
32            operator: JmapFilterOperatorKind::And,
33            conditions,
34        })
35    }
36
37    /// Wraps `conditions` in an OR combinator.
38    pub fn or(conditions: Vec<JmapFilter<C>>) -> Self {
39        JmapFilter::Operator(JmapFilterOperator {
40            operator: JmapFilterOperatorKind::Or,
41            conditions,
42        })
43    }
44
45    /// Wraps `conditions` in a NOT combinator. Satisfied when no condition
46    /// matches (RFC 8620 §5.5), so a single-element vec models unary negation.
47    pub fn not(conditions: Vec<JmapFilter<C>>) -> Self {
48        JmapFilter::Operator(JmapFilterOperator {
49            operator: JmapFilterOperatorKind::Not,
50            conditions,
51        })
52    }
53}
54
55/// Logical combinator over a list of sub-filters.
56#[derive(Clone, Debug, Serialize, Deserialize)]
57#[serde(rename_all = "camelCase")]
58pub struct JmapFilterOperator<C> {
59    /// The logical operator combining the conditions.
60    pub operator: JmapFilterOperatorKind,
61    /// The sub-filters the operator applies to.
62    pub conditions: Vec<JmapFilter<C>>,
63}
64
65/// AND / OR / NOT, serialized as RFC 8620 §5.5 spells them.
66#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
67#[serde(rename_all = "UPPERCASE")]
68pub enum JmapFilterOperatorKind {
69    /// All conditions must match.
70    And,
71    /// At least one condition must match.
72    Or,
73    /// No condition may match.
74    Not,
75}
76
77#[cfg(test)]
78mod tests {
79    use alloc::{string::String, vec};
80
81    use serde::{Deserialize, Serialize};
82    use serde_json::json;
83
84    use crate::rfc8620::filter::*;
85
86    #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
87    struct Cond {
88        from: Option<String>,
89    }
90
91    #[test]
92    fn condition_serializes_flat() {
93        let f: JmapFilter<Cond> = JmapFilter::Condition(Cond {
94            from: Some("alice".into()),
95        });
96        assert_eq!(
97            serde_json::to_value(&f).unwrap(),
98            json!({ "from": "alice" })
99        );
100    }
101
102    #[test]
103    fn and_serializes_with_operator_key() {
104        let f: JmapFilter<Cond> = JmapFilter::and(vec![
105            JmapFilter::Condition(Cond {
106                from: Some("a".into()),
107            }),
108            JmapFilter::Condition(Cond {
109                from: Some("b".into()),
110            }),
111        ]);
112        assert_eq!(
113            serde_json::to_value(&f).unwrap(),
114            json!({
115                "operator": "AND",
116                "conditions": [
117                    { "from": "a" },
118                    { "from": "b" },
119                ],
120            }),
121        );
122    }
123
124    #[test]
125    fn not_wraps_a_single_subfilter() {
126        let f: JmapFilter<Cond> = JmapFilter::not(vec![JmapFilter::Condition(Cond {
127            from: Some("a".into()),
128        })]);
129        assert_eq!(
130            serde_json::to_value(&f).unwrap(),
131            json!({
132                "operator": "NOT",
133                "conditions": [{ "from": "a" }],
134            }),
135        );
136    }
137
138    #[test]
139    fn deserialize_discriminates_on_operator_key() {
140        let v = json!({ "from": "alice" });
141        let f: JmapFilter<Cond> = serde_json::from_value(v).unwrap();
142        assert!(matches!(f, JmapFilter::Condition(Cond { from: Some(_) })));
143
144        let v = json!({
145            "operator": "OR",
146            "conditions": [{ "from": "a" }, { "from": "b" }],
147        });
148        let f: JmapFilter<Cond> = serde_json::from_value(v).unwrap();
149        assert!(matches!(
150            f,
151            JmapFilter::Operator(JmapFilterOperator {
152                operator: JmapFilterOperatorKind::Or,
153                ..
154            })
155        ));
156    }
157}