Skip to main content

mail_query/
lib.rs

1//! Parser and typed AST for Gmail-style email search queries.
2//!
3//! ```
4//! use mail_query::{parse, FilterKind, QueryField, QueryNode};
5//!
6//! let ast = parse("from:alice is:unread -has:attachment").expect("parses");
7//! match ast {
8//!     QueryNode::And(_, _) => {} // top-level is a conjunction
9//!     other => panic!("expected And, got {other:?}"),
10//! }
11//! ```
12//!
13//! The crate covers Gmail's documented operator surface
14//! (<https://support.google.com/mail/answer/7190>): address fields
15//! (`from:`, `to:`, `cc:`, `bcc:`, `deliveredto:`, `rfc822msgid:`,
16//! `list:`), content fields (`subject:`, `body:`, `filename:`), `is:`
17//! and `has:` filters, `label:` and `category:`, size and date operators
18//! with relative durations (`older_than:5d`), boolean operators (`AND`,
19//! `OR`, `NOT`, `-`), and proximity (`AROUND<n>`). It also recognises
20//! `+word` as an exact-match (no-stemming) hint.
21//!
22//! # What this crate does *not* do
23//!
24//! - It does not execute queries. The output is a portable
25//!   [`QueryNode`]; backends translate it to their own query language
26//!   (tantivy, meilisearch, SQL FTS, IMAP SEARCH, …).
27//! - It does not resolve `older_than:5d` to a concrete date at parse
28//!   time. Backends do that when building an executable query, using
29//!   their own `now`. This is what lets a saved query mean the same
30//!   thing tomorrow as today and lets the AST round-trip through
31//!   [`Display`][std::fmt::Display] without embedding a date.
32//! - It does not implement IMAP SEARCH grammar (RFC 3501 §6.4.4) — that
33//!   is a separate, future crate. The vocabularies overlap but the
34//!   grammars do not.
35//!
36//! # Extensibility
37//!
38//! Filter names that Gmail adds over time, or your application's own
39//! `is:owed-reply`, route through [`FilterKind::Custom`]. Register the
40//! names you want to accept via [`ParserOptions::register_custom_filter`]
41//! before calling [`parse_with`].
42//!
43//! ```
44//! use mail_query::{parse_with, FilterKind, ParserOptions, QueryNode};
45//!
46//! let mut options = ParserOptions::new();
47//! options.register_custom_filter("owed-reply");
48//!
49//! let ast = parse_with("is:owed-reply", &options).expect("parses");
50//! assert_eq!(
51//!     ast,
52//!     QueryNode::Filter(FilterKind::Custom("owed-reply".into()))
53//! );
54//! ```
55//!
56//! # Feature flags
57//!
58//! - `serde` — adds `Serialize`/`Deserialize` derives to every AST type.
59//!
60//! # Forward compatibility
61//!
62//! Every public enum is `#[non_exhaustive]`. New variants (for new Gmail
63//! operators) are non-breaking additions. Pattern-matching callers must
64//! include a `_ => …` arm.
65
66#![cfg_attr(docsrs, feature(doc_cfg))]
67#![deny(unsafe_code, unused_must_use)]
68
69mod ast;
70mod display;
71mod error;
72mod options;
73mod parser;
74mod visitor;
75
76pub use ast::{DateBound, DateValue, FilterKind, QueryField, QueryNode, RelativeUnit, SizeOp};
77pub use error::ParseError;
78pub use options::ParserOptions;
79pub use parser::{parse, parse_with};
80pub use visitor::Visitor;