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}