Skip to main content

mail_query/
options.rs

1use std::collections::HashSet;
2use std::fmt;
3
4use crate::parser::canonical_filter_name;
5
6/// Caller-provided configuration for the parser.
7///
8/// # Custom filters
9///
10/// The built-in filter vocabulary (`is:unread`, `has:attachment`, etc.)
11/// is fixed at compile time. Anything else — `is:owed-reply`,
12/// `has:reaction`, `is:my-app-flag` — returns
13/// [`ParseError::UnknownFilter`][crate::ParseError::UnknownFilter] by
14/// default. Register the names you want to accept so they parse as
15/// [`FilterKind::Custom`][crate::FilterKind::Custom] instead.
16///
17/// ```
18/// use mail_query::{parse_with, FilterKind, ParserOptions, QueryNode};
19///
20/// let mut options = ParserOptions::default();
21/// options.register_custom_filter("owed-reply");
22///
23/// let ast = parse_with("is:owed-reply", &options).expect("parses");
24/// assert_eq!(
25///     ast,
26///     QueryNode::Filter(FilterKind::Custom("owed-reply".into()))
27/// );
28/// ```
29#[derive(Default)]
30pub struct ParserOptions {
31    custom_filters: HashSet<String>,
32}
33
34impl fmt::Debug for ParserOptions {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        f.debug_struct("ParserOptions")
37            .field("custom_filters", &self.custom_filters)
38            .finish()
39    }
40}
41
42impl ParserOptions {
43    /// New empty options. Equivalent to [`Default::default`].
44    pub fn new() -> Self {
45        Self::default()
46    }
47
48    /// Register a custom filter name. The crate canonicalises to
49    /// lowercase + hyphenated form (`Reply_Later` becomes `reply-later`),
50    /// so callers can pass any casing.
51    pub fn register_custom_filter(&mut self, name: impl Into<String>) -> &mut Self {
52        let canonical = canonical_filter_name(&name.into());
53        self.custom_filters.insert(canonical);
54        self
55    }
56
57    /// Register many custom filter names at once.
58    pub fn register_custom_filters<I, S>(&mut self, names: I) -> &mut Self
59    where
60        I: IntoIterator<Item = S>,
61        S: Into<String>,
62    {
63        for name in names {
64            self.register_custom_filter(name);
65        }
66        self
67    }
68
69    /// True if `name` (already canonicalised) is registered.
70    pub(crate) fn has_custom_filter(&self, canonical: &str) -> bool {
71        self.custom_filters.contains(canonical)
72    }
73}