Skip to main content

qubit_metadata/
metadata_filter.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! Provides [`MetadataFilter`].
10
11use serde::{Deserialize, Serialize};
12
13use crate::{
14    Condition, FilterMatchOptions, Metadata, MetadataFilterBuilder, MetadataResult,
15    MissingKeyPolicy, NumberComparisonPolicy,
16};
17
18/// Internal expression tree used by [`MetadataFilter`].
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub(crate) enum FilterExpr {
21    /// A leaf condition.
22    Condition(Condition),
23    /// All child expressions must match.
24    And(Vec<FilterExpr>),
25    /// At least one child expression must match.
26    Or(Vec<FilterExpr>),
27    /// Negates the child expression.
28    Not(Box<FilterExpr>),
29    /// Constant false expression.
30    False,
31}
32
33impl FilterExpr {
34    /// Appends one expression to an AND node, flattening nested AND nodes.
35    #[inline]
36    fn append_and_child(children: &mut Vec<FilterExpr>, expr: FilterExpr) {
37        match expr {
38            FilterExpr::And(mut nested) => children.append(&mut nested),
39            other => children.push(other),
40        }
41    }
42
43    /// Appends one expression to an OR node, flattening nested OR nodes.
44    #[inline]
45    fn append_or_child(children: &mut Vec<FilterExpr>, expr: FilterExpr) {
46        match expr {
47            FilterExpr::Or(mut nested) => children.append(&mut nested),
48            other => children.push(other),
49        }
50    }
51
52    /// Builds an optimized AND expression from two child expressions.
53    #[inline]
54    pub(crate) fn and(lhs: FilterExpr, rhs: FilterExpr) -> FilterExpr {
55        if matches!(lhs, FilterExpr::False) || matches!(rhs, FilterExpr::False) {
56            return FilterExpr::False;
57        }
58        let mut children = Vec::new();
59        Self::append_and_child(&mut children, lhs);
60        Self::append_and_child(&mut children, rhs);
61        FilterExpr::And(children)
62    }
63
64    /// Builds an optimized OR expression from two child expressions.
65    #[inline]
66    pub(crate) fn or(lhs: FilterExpr, rhs: FilterExpr) -> FilterExpr {
67        if matches!(lhs, FilterExpr::False) {
68            return rhs;
69        }
70        if matches!(rhs, FilterExpr::False) {
71            return lhs;
72        }
73        let mut children = Vec::new();
74        Self::append_or_child(&mut children, lhs);
75        Self::append_or_child(&mut children, rhs);
76        FilterExpr::Or(children)
77    }
78
79    /// Evaluates this expression tree against one metadata object.
80    #[inline]
81    fn matches(&self, meta: &Metadata, options: FilterMatchOptions) -> bool {
82        match self {
83            FilterExpr::Condition(condition) => condition.matches(
84                meta,
85                options.missing_key_policy,
86                options.number_comparison_policy,
87            ),
88            FilterExpr::And(children) => children.iter().all(|expr| expr.matches(meta, options)),
89            FilterExpr::Or(children) => children.iter().any(|expr| expr.matches(meta, options)),
90            FilterExpr::Not(inner) => !inner.matches(meta, options),
91            FilterExpr::False => false,
92        }
93    }
94
95    /// Visits all leaf conditions in this expression tree.
96    fn visit_conditions<F>(&self, visitor: &mut F) -> MetadataResult<()>
97    where
98        F: FnMut(&Condition) -> MetadataResult<()>,
99    {
100        match self {
101            FilterExpr::Condition(condition) => visitor(condition),
102            FilterExpr::And(children) | FilterExpr::Or(children) => {
103                for child in children {
104                    child.visit_conditions(visitor)?;
105                }
106                Ok(())
107            }
108            FilterExpr::Not(inner) => inner.visit_conditions(visitor),
109            FilterExpr::False => Ok(()),
110        }
111    }
112}
113
114/// An immutable, composable filter expression over [`Metadata`].
115///
116/// Construct filters with [`MetadataFilter::builder`]. An empty builder builds a
117/// match-all filter, which makes the default behavior explicit while keeping the
118/// built filter immutable.
119#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
120pub struct MetadataFilter {
121    /// Root expression tree. `None` means match all.
122    #[serde(default, skip_serializing_if = "Option::is_none")]
123    expr: Option<FilterExpr>,
124    /// Match policies used by [`MetadataFilter::matches`].
125    #[serde(default)]
126    options: FilterMatchOptions,
127}
128
129impl MetadataFilter {
130    /// Creates a filter from expression and options.
131    #[inline]
132    pub(crate) fn new(expr: Option<FilterExpr>, options: FilterMatchOptions) -> Self {
133        Self { expr, options }
134    }
135
136    /// Creates a builder for a metadata filter.
137    #[inline]
138    #[must_use]
139    pub fn builder() -> MetadataFilterBuilder {
140        MetadataFilterBuilder::default()
141    }
142
143    /// Creates a filter that matches every metadata object.
144    #[inline]
145    #[must_use]
146    pub fn all() -> Self {
147        Self::default()
148    }
149
150    /// Creates a filter that matches no metadata object.
151    #[inline]
152    #[must_use]
153    pub fn none() -> Self {
154        Self {
155            expr: Some(FilterExpr::False),
156            options: FilterMatchOptions::default(),
157        }
158    }
159
160    /// Returns the current match options.
161    #[inline]
162    #[must_use]
163    pub fn options(&self) -> FilterMatchOptions {
164        self.options
165    }
166
167    /// Replaces the current match options and returns a new filter.
168    #[inline]
169    #[must_use]
170    pub fn with_options(mut self, options: FilterMatchOptions) -> Self {
171        self.options = options;
172        self
173    }
174
175    /// Returns a new filter with the supplied missing-key policy.
176    #[inline]
177    #[must_use]
178    pub fn with_missing_key_policy(mut self, missing_key_policy: MissingKeyPolicy) -> Self {
179        self.options.missing_key_policy = missing_key_policy;
180        self
181    }
182
183    /// Returns a new filter with the supplied number-comparison policy.
184    #[inline]
185    #[must_use]
186    pub fn with_number_comparison_policy(
187        mut self,
188        number_comparison_policy: NumberComparisonPolicy,
189    ) -> Self {
190        self.options.number_comparison_policy = number_comparison_policy;
191        self
192    }
193
194    /// Returns a new filter that negates this filter.
195    #[allow(clippy::should_implement_trait)]
196    #[inline]
197    #[must_use]
198    pub fn not(mut self) -> Self {
199        self.expr = MetadataFilterBuilder::negate_expr(self.expr);
200        self
201    }
202
203    /// Returns `true` if `meta` satisfies this filter.
204    #[inline]
205    #[must_use]
206    pub fn matches(&self, meta: &Metadata) -> bool {
207        self.matches_with_options(meta, self.options)
208    }
209
210    /// Returns `true` if `meta` satisfies this filter with explicit options.
211    #[inline]
212    #[must_use]
213    pub fn matches_with_options(&self, meta: &Metadata, options: FilterMatchOptions) -> bool {
214        self.expr
215            .as_ref()
216            .is_none_or(|expr| expr.matches(meta, options))
217    }
218
219    /// Visits all leaf conditions in this filter.
220    pub(crate) fn visit_conditions<F>(&self, mut visitor: F) -> MetadataResult<()>
221    where
222        F: FnMut(&Condition) -> MetadataResult<()>,
223    {
224        if let Some(expr) = &self.expr {
225            expr.visit_conditions(&mut visitor)?;
226        }
227        Ok(())
228    }
229}
230
231impl std::ops::Not for MetadataFilter {
232    type Output = MetadataFilter;
233
234    #[inline]
235    fn not(self) -> Self::Output {
236        MetadataFilter::not(self)
237    }
238}