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