Skip to main content

qubit_metadata/filter/
metadata_filter.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! [`MetadataFilter`].
11use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
12
13use super::filter_expr::FilterExpr;
14use super::metadata_filter_builder::MetadataFilterBuilder;
15use super::wire::MetadataFilterWire;
16use crate::metadata::Metadata;
17use crate::{
18    Condition, FilterMatchOptions, MetadataResult, MissingKeyPolicy, NumberComparisonPolicy,
19};
20
21/// An immutable, composable filter expression over [`Metadata`].
22///
23/// Construct filters with [`MetadataFilter::builder`]. An empty builder builds a
24/// match-all filter, while structurally invalid expressions such as empty groups
25/// are rejected by [`MetadataFilterBuilder::build`].
26#[derive(Debug, Clone, PartialEq, Default)]
27pub struct MetadataFilter {
28    /// Root expression tree. `None` means match all.
29    pub(crate) expr: Option<FilterExpr>,
30    /// Match policies used by [`MetadataFilter::matches`].
31    pub(crate) options: FilterMatchOptions,
32}
33
34impl MetadataFilter {
35    /// Creates a filter from expression and options.
36    #[inline]
37    pub(crate) fn new(expr: Option<FilterExpr>, options: FilterMatchOptions) -> Self {
38        Self { expr, options }
39    }
40
41    /// Creates a builder for a metadata filter.
42    #[inline]
43    #[must_use]
44    pub fn builder() -> MetadataFilterBuilder {
45        MetadataFilterBuilder::default()
46    }
47
48    /// Creates a filter that matches every metadata object.
49    #[inline]
50    #[must_use]
51    pub fn all() -> Self {
52        Self::default()
53    }
54
55    /// Creates a filter that matches no metadata object.
56    #[inline]
57    #[must_use]
58    pub fn none() -> Self {
59        Self {
60            expr: Some(FilterExpr::False),
61            options: FilterMatchOptions::default(),
62        }
63    }
64
65    /// Returns the current match options.
66    #[inline]
67    #[must_use]
68    pub fn options(&self) -> FilterMatchOptions {
69        self.options
70    }
71
72    /// Replaces the current match options and returns a new filter.
73    #[inline]
74    #[must_use]
75    pub fn with_options(mut self, options: FilterMatchOptions) -> Self {
76        self.options = options;
77        self
78    }
79
80    /// Returns a new filter with the supplied missing-key policy.
81    #[inline]
82    #[must_use]
83    pub fn with_missing_key_policy(mut self, missing_key_policy: MissingKeyPolicy) -> Self {
84        self.options.missing_key_policy = missing_key_policy;
85        self
86    }
87
88    /// Returns a new filter with the supplied number-comparison policy.
89    #[inline]
90    #[must_use]
91    pub fn with_number_comparison_policy(
92        mut self,
93        number_comparison_policy: NumberComparisonPolicy,
94    ) -> Self {
95        self.options.number_comparison_policy = number_comparison_policy;
96        self
97    }
98
99    /// Returns a new filter that negates this filter.
100    #[allow(clippy::should_implement_trait)]
101    #[inline]
102    #[must_use]
103    pub fn not(mut self) -> Self {
104        self.expr = MetadataFilterBuilder::negate_expr(self.expr);
105        self
106    }
107
108    /// Returns `true` if `meta` satisfies this filter.
109    #[inline]
110    #[must_use]
111    pub fn matches(&self, meta: &Metadata) -> bool {
112        self.matches_with_options(meta, self.options)
113    }
114
115    /// Returns `true` if `meta` satisfies this filter with explicit options.
116    #[inline]
117    #[must_use]
118    pub fn matches_with_options(&self, meta: &Metadata, options: FilterMatchOptions) -> bool {
119        self.expr
120            .as_ref()
121            .is_none_or(|expr| expr.matches(meta, options))
122    }
123
124    /// Visits all leaf conditions in this filter.
125    pub(crate) fn visit_conditions<F>(&self, mut visitor: F) -> MetadataResult<()>
126    where
127        F: FnMut(&Condition) -> MetadataResult<()>,
128    {
129        if let Some(expr) = &self.expr {
130            expr.visit_conditions(&mut visitor)?;
131        }
132        Ok(())
133    }
134}
135
136impl Serialize for MetadataFilter {
137    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
138    where
139        S: Serializer,
140    {
141        MetadataFilterWire::from(self).serialize(serializer)
142    }
143}
144
145impl<'de> Deserialize<'de> for MetadataFilter {
146    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
147    where
148        D: Deserializer<'de>,
149    {
150        MetadataFilterWire::deserialize(deserializer)?
151            .into_filter()
152            .map_err(de::Error::custom)
153    }
154}
155
156impl std::ops::Not for MetadataFilter {
157    type Output = MetadataFilter;
158
159    #[inline]
160    fn not(self) -> Self::Output {
161        MetadataFilter::not(self)
162    }
163}