Skip to main content

zrx_id/id/
expression.rs

1// Copyright (c) 2025-2026 Zensical and contributors
2
3// SPDX-License-Identifier: MIT
4// All contributions are certified under the DCO
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to
8// deal in the Software without restriction, including without limitation the
9// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10// sell copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22// IN THE SOFTWARE.
23
24// ----------------------------------------------------------------------------
25
26//! Expression.
27
28use std::vec::IntoIter;
29
30use zrx_scheduler::Value;
31
32mod builder;
33mod error;
34pub mod filter;
35mod operand;
36
37pub use builder::Builder;
38pub use error::{Error, Result};
39pub use filter::Filter;
40pub use operand::{Operand, Operator, Term};
41
42// ----------------------------------------------------------------------------
43// Structs
44// ----------------------------------------------------------------------------
45
46/// Expression.
47///
48/// Expressions allow to build trees of [`Id`][] and [`Selector`][] instances
49/// combined with logical operators, enabling complex matching and filtering.
50///
51/// The following operators are supported:
52///
53/// - [`Expression::any`]: Logical `OR` - any operand must match.
54/// - [`Expression::all`]: Logical `AND` - all operands must match.
55/// - [`Expression::not`]: Logical `NOT` - no operand must match.
56///
57/// [`Id`]: crate::id::Id
58/// [`Selector`]: crate::id::selector::Selector
59///
60/// # Examples
61///
62/// ```
63/// # use std::error::Error;
64/// # fn main() -> Result<(), Box<dyn Error>> {
65/// use zrx_id::{selector, Expression};
66///
67/// // Create expression
68/// let expr = Expression::all(|expr| {
69///     expr.with(selector!(location = "**/*.md")?)?
70///         .with(Expression::not(|expr| {
71///             expr.with(selector!(provider = "file")?)
72///         })
73///     )
74/// })?;
75/// # Ok(())
76/// # }
77/// ```
78#[derive(Clone, Debug, PartialEq, Eq)]
79pub struct Expression {
80    /// Expression operator.
81    operator: Operator,
82    /// Expression operands.
83    operands: Vec<Operand>,
84}
85
86// ----------------------------------------------------------------------------
87// Implementations
88// ----------------------------------------------------------------------------
89
90#[allow(clippy::must_use_candidate)]
91impl Expression {
92    /// Returns the operator.
93    #[inline]
94    pub fn operator(&self) -> Operator {
95        self.operator
96    }
97
98    /// Returns a reference to the operands.
99    #[inline]
100    pub fn operands(&self) -> &[Operand] {
101        &self.operands
102    }
103
104    /// Returns the number of operands.
105    #[inline]
106    pub fn len(&self) -> usize {
107        self.operands.len()
108    }
109
110    /// Returns whether there are any operands.
111    #[inline]
112    pub fn is_empty(&self) -> bool {
113        self.operands.is_empty()
114    }
115}
116
117// ----------------------------------------------------------------------------
118// Trait implementations
119// ----------------------------------------------------------------------------
120
121impl Value for Expression {}
122
123// ----------------------------------------------------------------------------
124
125impl<T> From<T> for Expression
126where
127    T: Into<Term>,
128{
129    /// Creates an expression from a term.
130    #[inline]
131    fn from(term: T) -> Self {
132        let term = Operand::from(term.into());
133        Expression {
134            operator: Operator::Any,
135            operands: Vec::from([term]),
136        }
137    }
138}
139
140// ----------------------------------------------------------------------------
141
142impl IntoIterator for Expression {
143    type Item = Operand;
144    type IntoIter = IntoIter<Self::Item>;
145
146    /// Creates a consuming iterator over the expression.
147    ///
148    /// # Examples
149    ///
150    /// ```
151    /// # use std::error::Error;
152    /// # fn main() -> Result<(), Box<dyn Error>> {
153    /// use zrx_id::{selector, Expression};
154    ///
155    /// // Create expression
156    /// let expr = Expression::any(|expr| {
157    ///     expr.with(selector!(location = "**/*.jpg")?)?
158    ///         .with(selector!(location = "**/*.png")?)
159    /// })?;
160    ///
161    /// // Create iterator over expression
162    /// for operand in expr {
163    ///     println!("{operand:?}");
164    /// }
165    /// # Ok(())
166    /// # }
167    /// ```
168    #[inline]
169    fn into_iter(self) -> Self::IntoIter {
170        self.operands.into_iter()
171    }
172}
173
174// ----------------------------------------------------------------------------
175
176impl Default for Expression {
177    /// Creates an expression that matches everything.
178    ///
179    /// While it may seem counterintuitive to have the default expression match
180    /// everything, it is designed this way to align with the concept of vacuous
181    /// truth in logic. An expression with no operands is considered to be true,
182    /// as there are no conditions to violate it.
183    ///
184    /// In our implementation, we use an empty expression with a logical `NOT`
185    /// operator to represent this concept, to be distinguishable as a marker.
186    ///
187    /// # Examples
188    ///
189    /// ```
190    /// use zrx_id::Expression;
191    ///
192    /// // Create empty expression
193    /// let expr = Expression::default();
194    /// assert!(expr.is_empty());
195    /// ```
196    #[inline]
197    fn default() -> Self {
198        Self {
199            operator: Operator::Not,
200            operands: Vec::new(),
201        }
202    }
203}