Skip to main content

qubit_metadata/
metadata_filter_builder.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! [`MetadataFilterBuilder`] — fluent builder for composable filters.
10
11use qubit_value::{Value, ValueConstructor};
12
13use crate::{
14    Condition, FilterMatchOptions, MetadataFilter, MetadataResult, MetadataSchema,
15    MissingKeyPolicy, NumberComparisonPolicy, metadata_filter::FilterExpr,
16};
17
18/// Builder for [`MetadataFilter`].
19///
20/// Predicates without an explicit connector (`eq`, `gt`, `exists`, and so on)
21/// are appended with logical AND. Use `or_*` methods or group methods for more
22/// complex expressions.
23#[derive(Debug, Clone, PartialEq, Default)]
24pub struct MetadataFilterBuilder {
25    /// Root expression being built. `None` means match all.
26    pub(crate) expr: Option<FilterExpr>,
27    /// Match policies copied into the built filter.
28    pub(crate) options: FilterMatchOptions,
29}
30
31impl MetadataFilterBuilder {
32    /// Builds an immutable [`MetadataFilter`].
33    #[inline]
34    #[must_use]
35    pub fn build(self) -> MetadataFilter {
36        MetadataFilter::new(self.expr, self.options)
37    }
38
39    /// Builds an immutable filter and validates it against `schema`.
40    ///
41    /// # Errors
42    ///
43    /// Returns an error when the filter references unknown schema fields, uses
44    /// range operators on non-comparable field types, or compares a field with an
45    /// incompatible value type.
46    #[inline]
47    pub fn build_checked(self, schema: &MetadataSchema) -> MetadataResult<MetadataFilter> {
48        let filter = self.build();
49        schema.validate_filter(&filter)?;
50        Ok(filter)
51    }
52
53    /// Replaces the match options used by the built filter.
54    #[inline]
55    #[must_use]
56    pub fn with_options(mut self, options: FilterMatchOptions) -> Self {
57        self.options = options;
58        self
59    }
60
61    /// Sets how the built filter treats missing keys in negative predicates.
62    #[inline]
63    #[must_use]
64    pub fn missing_key_policy(mut self, missing_key_policy: MissingKeyPolicy) -> Self {
65        self.options.missing_key_policy = missing_key_policy;
66        self
67    }
68
69    /// Sets how the built filter handles mixed numeric comparisons.
70    #[inline]
71    #[must_use]
72    pub fn number_comparison_policy(
73        mut self,
74        number_comparison_policy: NumberComparisonPolicy,
75    ) -> Self {
76        self.options.number_comparison_policy = number_comparison_policy;
77        self
78    }
79
80    /// Appends an equality predicate with AND: `key == value`.
81    #[inline]
82    #[must_use]
83    pub fn eq<T>(self, key: &str, value: T) -> Self
84    where
85        Value: ValueConstructor<T>,
86    {
87        self.and_eq(key, value)
88    }
89
90    /// Appends a not-equal predicate with AND: `key != value`.
91    #[inline]
92    #[must_use]
93    pub fn ne<T>(self, key: &str, value: T) -> Self
94    where
95        Value: ValueConstructor<T>,
96    {
97        self.and_ne(key, value)
98    }
99
100    /// Appends a less-than predicate with AND: `key < value`.
101    #[inline]
102    #[must_use]
103    pub fn lt<T>(self, key: &str, value: T) -> Self
104    where
105        Value: ValueConstructor<T>,
106    {
107        self.and_lt(key, value)
108    }
109
110    /// Appends a less-than-or-equal predicate with AND: `key <= value`.
111    #[inline]
112    #[must_use]
113    pub fn le<T>(self, key: &str, value: T) -> Self
114    where
115        Value: ValueConstructor<T>,
116    {
117        self.and_le(key, value)
118    }
119
120    /// Appends a greater-than predicate with AND: `key > value`.
121    #[inline]
122    #[must_use]
123    pub fn gt<T>(self, key: &str, value: T) -> Self
124    where
125        Value: ValueConstructor<T>,
126    {
127        self.and_gt(key, value)
128    }
129
130    /// Appends a greater-than-or-equal predicate with AND: `key >= value`.
131    #[inline]
132    #[must_use]
133    pub fn ge<T>(self, key: &str, value: T) -> Self
134    where
135        Value: ValueConstructor<T>,
136    {
137        self.and_ge(key, value)
138    }
139
140    /// Appends an inclusion predicate with AND: `key` is in `values`.
141    #[inline]
142    #[must_use]
143    pub fn in_set<I, T>(self, key: &str, values: I) -> Self
144    where
145        I: IntoIterator<Item = T>,
146        Value: ValueConstructor<T>,
147    {
148        self.and_in_set(key, values)
149    }
150
151    /// Appends an exclusion predicate with AND: `key` is not in `values`.
152    #[inline]
153    #[must_use]
154    pub fn not_in_set<I, T>(self, key: &str, values: I) -> Self
155    where
156        I: IntoIterator<Item = T>,
157        Value: ValueConstructor<T>,
158    {
159        self.and_not_in_set(key, values)
160    }
161
162    /// Appends an existence predicate with AND.
163    #[inline]
164    #[must_use]
165    pub fn exists(self, key: &str) -> Self {
166        self.and_exists(key)
167    }
168
169    /// Appends a non-existence predicate with AND.
170    #[inline]
171    #[must_use]
172    pub fn not_exists(self, key: &str) -> Self {
173        self.and_not_exists(key)
174    }
175
176    /// Appends an equality predicate with AND: `key == value`.
177    #[inline]
178    #[must_use]
179    pub fn and_eq<T>(self, key: &str, value: T) -> Self
180    where
181        Value: ValueConstructor<T>,
182    {
183        self.and_condition(Condition::Equal {
184            key: key.to_string(),
185            value: to_value(value),
186        })
187    }
188
189    /// Appends a not-equal predicate with AND: `key != value`.
190    #[inline]
191    #[must_use]
192    pub fn and_ne<T>(self, key: &str, value: T) -> Self
193    where
194        Value: ValueConstructor<T>,
195    {
196        self.and_condition(Condition::NotEqual {
197            key: key.to_string(),
198            value: to_value(value),
199        })
200    }
201
202    /// Appends a less-than predicate with AND: `key < value`.
203    #[inline]
204    #[must_use]
205    pub fn and_lt<T>(self, key: &str, value: T) -> Self
206    where
207        Value: ValueConstructor<T>,
208    {
209        self.and_condition(Condition::Less {
210            key: key.to_string(),
211            value: to_value(value),
212        })
213    }
214
215    /// Appends a less-than-or-equal predicate with AND: `key <= value`.
216    #[inline]
217    #[must_use]
218    pub fn and_le<T>(self, key: &str, value: T) -> Self
219    where
220        Value: ValueConstructor<T>,
221    {
222        self.and_condition(Condition::LessEqual {
223            key: key.to_string(),
224            value: to_value(value),
225        })
226    }
227
228    /// Appends a greater-than predicate with AND: `key > value`.
229    #[inline]
230    #[must_use]
231    pub fn and_gt<T>(self, key: &str, value: T) -> Self
232    where
233        Value: ValueConstructor<T>,
234    {
235        self.and_condition(Condition::Greater {
236            key: key.to_string(),
237            value: to_value(value),
238        })
239    }
240
241    /// Appends a greater-than-or-equal predicate with AND: `key >= value`.
242    #[inline]
243    #[must_use]
244    pub fn and_ge<T>(self, key: &str, value: T) -> Self
245    where
246        Value: ValueConstructor<T>,
247    {
248        self.and_condition(Condition::GreaterEqual {
249            key: key.to_string(),
250            value: to_value(value),
251        })
252    }
253
254    /// Appends an inclusion predicate with AND: `key` is in `values`.
255    #[inline]
256    #[must_use]
257    pub fn and_in_set<I, T>(self, key: &str, values: I) -> Self
258    where
259        I: IntoIterator<Item = T>,
260        Value: ValueConstructor<T>,
261    {
262        self.and_condition(Condition::In {
263            key: key.to_string(),
264            values: Self::collect_values(values),
265        })
266    }
267
268    /// Appends an exclusion predicate with AND: `key` is not in `values`.
269    #[inline]
270    #[must_use]
271    pub fn and_not_in_set<I, T>(self, key: &str, values: I) -> Self
272    where
273        I: IntoIterator<Item = T>,
274        Value: ValueConstructor<T>,
275    {
276        self.and_condition(Condition::NotIn {
277            key: key.to_string(),
278            values: Self::collect_values(values),
279        })
280    }
281
282    /// Appends an existence predicate with AND.
283    #[inline]
284    #[must_use]
285    pub fn and_exists(self, key: &str) -> Self {
286        self.and_condition(Condition::Exists {
287            key: key.to_string(),
288        })
289    }
290
291    /// Appends a non-existence predicate with AND.
292    #[inline]
293    #[must_use]
294    pub fn and_not_exists(self, key: &str) -> Self {
295        self.and_condition(Condition::NotExists {
296            key: key.to_string(),
297        })
298    }
299
300    /// Appends an equality predicate with OR: `key == value`.
301    #[inline]
302    #[must_use]
303    pub fn or_eq<T>(self, key: &str, value: T) -> Self
304    where
305        Value: ValueConstructor<T>,
306    {
307        self.or_condition(Condition::Equal {
308            key: key.to_string(),
309            value: to_value(value),
310        })
311    }
312
313    /// Appends a not-equal predicate with OR: `key != value`.
314    #[inline]
315    #[must_use]
316    pub fn or_ne<T>(self, key: &str, value: T) -> Self
317    where
318        Value: ValueConstructor<T>,
319    {
320        self.or_condition(Condition::NotEqual {
321            key: key.to_string(),
322            value: to_value(value),
323        })
324    }
325
326    /// Appends a less-than predicate with OR: `key < value`.
327    #[inline]
328    #[must_use]
329    pub fn or_lt<T>(self, key: &str, value: T) -> Self
330    where
331        Value: ValueConstructor<T>,
332    {
333        self.or_condition(Condition::Less {
334            key: key.to_string(),
335            value: to_value(value),
336        })
337    }
338
339    /// Appends a less-than-or-equal predicate with OR: `key <= value`.
340    #[inline]
341    #[must_use]
342    pub fn or_le<T>(self, key: &str, value: T) -> Self
343    where
344        Value: ValueConstructor<T>,
345    {
346        self.or_condition(Condition::LessEqual {
347            key: key.to_string(),
348            value: to_value(value),
349        })
350    }
351
352    /// Appends a greater-than predicate with OR: `key > value`.
353    #[inline]
354    #[must_use]
355    pub fn or_gt<T>(self, key: &str, value: T) -> Self
356    where
357        Value: ValueConstructor<T>,
358    {
359        self.or_condition(Condition::Greater {
360            key: key.to_string(),
361            value: to_value(value),
362        })
363    }
364
365    /// Appends a greater-than-or-equal predicate with OR: `key >= value`.
366    #[inline]
367    #[must_use]
368    pub fn or_ge<T>(self, key: &str, value: T) -> Self
369    where
370        Value: ValueConstructor<T>,
371    {
372        self.or_condition(Condition::GreaterEqual {
373            key: key.to_string(),
374            value: to_value(value),
375        })
376    }
377
378    /// Appends an inclusion predicate with OR: `key` is in `values`.
379    #[inline]
380    #[must_use]
381    pub fn or_in_set<I, T>(self, key: &str, values: I) -> Self
382    where
383        I: IntoIterator<Item = T>,
384        Value: ValueConstructor<T>,
385    {
386        self.or_condition(Condition::In {
387            key: key.to_string(),
388            values: Self::collect_values(values),
389        })
390    }
391
392    /// Appends an exclusion predicate with OR: `key` is not in `values`.
393    #[inline]
394    #[must_use]
395    pub fn or_not_in_set<I, T>(self, key: &str, values: I) -> Self
396    where
397        I: IntoIterator<Item = T>,
398        Value: ValueConstructor<T>,
399    {
400        self.or_condition(Condition::NotIn {
401            key: key.to_string(),
402            values: Self::collect_values(values),
403        })
404    }
405
406    /// Appends an existence predicate with OR.
407    #[inline]
408    #[must_use]
409    pub fn or_exists(self, key: &str) -> Self {
410        self.or_condition(Condition::Exists {
411            key: key.to_string(),
412        })
413    }
414
415    /// Appends a non-existence predicate with OR.
416    #[inline]
417    #[must_use]
418    pub fn or_not_exists(self, key: &str) -> Self {
419        self.or_condition(Condition::NotExists {
420            key: key.to_string(),
421        })
422    }
423
424    /// Appends a grouped expression with AND.
425    ///
426    /// The closure receives a fresh builder for the group. Policies configured
427    /// inside the group are ignored; configure policies on the outer builder.
428    #[inline]
429    #[must_use]
430    pub fn and<F>(self, build: F) -> Self
431    where
432        F: FnOnce(Self) -> Self,
433    {
434        let group = build(Self::default()).expr;
435        self.and_expr(group)
436    }
437
438    /// Appends a grouped expression with OR.
439    ///
440    /// The closure receives a fresh builder for the group. Policies configured
441    /// inside the group are ignored; configure policies on the outer builder.
442    #[inline]
443    #[must_use]
444    pub fn or<F>(self, build: F) -> Self
445    where
446        F: FnOnce(Self) -> Self,
447    {
448        let group = build(Self::default()).expr;
449        self.or_expr(group)
450    }
451
452    /// Appends a negated grouped expression with AND.
453    #[inline]
454    #[must_use]
455    pub fn and_not<F>(self, build: F) -> Self
456    where
457        F: FnOnce(Self) -> Self,
458    {
459        let group = build(Self::default()).expr;
460        self.and_expr(Self::negate_expr(group))
461    }
462
463    /// Appends a negated grouped expression with OR.
464    #[inline]
465    #[must_use]
466    pub fn or_not<F>(self, build: F) -> Self
467    where
468        F: FnOnce(Self) -> Self,
469    {
470        let group = build(Self::default()).expr;
471        self.or_expr(Self::negate_expr(group))
472    }
473
474    /// Negates the entire builder expression.
475    #[allow(clippy::should_implement_trait)]
476    #[inline]
477    #[must_use]
478    pub fn not(mut self) -> Self {
479        self.expr = Self::negate_expr(self.expr);
480        self
481    }
482
483    /// Converts a sequence of typed values into stored values.
484    #[inline]
485    fn collect_values<I, T>(values: I) -> Vec<Value>
486    where
487        I: IntoIterator<Item = T>,
488        Value: ValueConstructor<T>,
489    {
490        values.into_iter().map(to_value).collect()
491    }
492
493    /// Combines the current expression with `expr` using logical AND.
494    #[inline]
495    fn and_expr(mut self, expr: Option<FilterExpr>) -> Self {
496        self.expr = match (self.expr, expr) {
497            (None, rhs) => rhs,
498            (lhs, None) => lhs,
499            (Some(lhs), Some(rhs)) => Some(FilterExpr::and(lhs, rhs)),
500        };
501        self
502    }
503
504    /// Combines the current expression with `expr` using logical OR.
505    #[inline]
506    fn or_expr(mut self, expr: Option<FilterExpr>) -> Self {
507        self.expr = match (self.expr, expr) {
508            (None, rhs) => rhs,
509            (lhs, None) => lhs,
510            (Some(lhs), Some(rhs)) => Some(FilterExpr::or(lhs, rhs)),
511        };
512        self
513    }
514
515    /// Appends a condition with logical AND.
516    #[inline]
517    fn and_condition(self, condition: Condition) -> Self {
518        self.and_expr(Some(FilterExpr::Condition(condition)))
519    }
520
521    /// Appends a condition with logical OR.
522    #[inline]
523    fn or_condition(self, condition: Condition) -> Self {
524        self.or_expr(Some(FilterExpr::Condition(condition)))
525    }
526
527    /// Negates an optional expression.
528    #[inline]
529    pub(crate) fn negate_expr(expr: Option<FilterExpr>) -> Option<FilterExpr> {
530        match expr {
531            None => Some(FilterExpr::False),
532            Some(FilterExpr::False) => None,
533            Some(FilterExpr::Not(inner)) => Some(*inner),
534            Some(other) => Some(FilterExpr::Not(Box::new(other))),
535        }
536    }
537}
538
539#[inline]
540fn to_value<T>(value: T) -> Value
541where
542    Value: ValueConstructor<T>,
543{
544    <Value as ValueConstructor<T>>::from_type(value)
545}