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
30mod builder;
31mod error;
32mod operand;
33
34pub use builder::Builder;
35pub use error::{Error, Result};
36pub use operand::{Operand, Operator, Term};
37
38// ----------------------------------------------------------------------------
39// Structs
40// ----------------------------------------------------------------------------
41
42/// Expression.
43///
44/// Expressions allow to build trees of [`Id`][] and [`Selector`][] instances
45/// combined with logical operators, enabling complex matching and filtering.
46///
47/// The following operators are supported:
48///
49/// - [`Expression::any`]: Logical `OR` - any operand must match.
50/// - [`Expression::all`]: Logical `AND` - all operands must match.
51/// - [`Expression::not`]: Logical `NOT` - no operand must match.
52///
53/// [`Id`]: crate::id::Id
54/// [`Selector`]: crate::id::selector::Selector
55///
56/// # Examples
57///
58/// ```
59/// # use std::error::Error;
60/// # fn main() -> Result<(), Box<dyn Error>> {
61/// use zrx_id::{selector, Expression};
62///
63/// // Create expression
64/// let expr = Expression::all(|expr| {
65/// expr.with(selector!(location = "**/*.md")?)?
66/// .with(Expression::not(|expr| {
67/// expr.with(selector!(provider = "file")?)
68/// })
69/// )
70/// })?;
71/// # Ok(())
72/// # }
73/// ```
74#[derive(Clone, Debug, PartialEq, Eq)]
75pub struct Expression {
76 /// Expression operator.
77 operator: Operator,
78 /// Expression operands.
79 operands: Vec<Operand>,
80}
81
82// ----------------------------------------------------------------------------
83// Implementations
84// ----------------------------------------------------------------------------
85
86#[allow(clippy::must_use_candidate)]
87impl Expression {
88 /// Returns the operator.
89 #[inline]
90 pub fn operator(&self) -> Operator {
91 self.operator
92 }
93
94 /// Returns a reference to the operands.
95 #[inline]
96 pub fn operands(&self) -> &[Operand] {
97 &self.operands
98 }
99
100 /// Returns the number of operands.
101 #[inline]
102 pub fn len(&self) -> usize {
103 self.operands.len()
104 }
105
106 /// Returns whether there are any operands.
107 #[inline]
108 pub fn is_empty(&self) -> bool {
109 self.operands.is_empty()
110 }
111}
112
113// ----------------------------------------------------------------------------
114// Trait implementations
115// ----------------------------------------------------------------------------
116
117impl<T> From<T> for Expression
118where
119 T: Into<Term>,
120{
121 /// Creates an expression from a term.
122 #[inline]
123 fn from(term: T) -> Self {
124 let term = Operand::from(term.into());
125 Expression {
126 operator: Operator::Any,
127 operands: Vec::from([term]),
128 }
129 }
130}
131
132// ----------------------------------------------------------------------------
133
134impl IntoIterator for Expression {
135 type Item = Operand;
136 type IntoIter = IntoIter<Self::Item>;
137
138 /// Creates a consuming iterator over the expression.
139 ///
140 /// # Examples
141 ///
142 /// ```
143 /// # use std::error::Error;
144 /// # fn main() -> Result<(), Box<dyn Error>> {
145 /// use zrx_id::{selector, Expression};
146 ///
147 /// // Create expression
148 /// let expr = Expression::any(|expr| {
149 /// expr.with(selector!(location = "**/*.jpg")?)?
150 /// .with(selector!(location = "**/*.png")?)
151 /// })?;
152 ///
153 /// // Create iterator over expression
154 /// for operand in expr {
155 /// println!("{operand:?}");
156 /// }
157 /// # Ok(())
158 /// # }
159 /// ```
160 #[inline]
161 fn into_iter(self) -> Self::IntoIter {
162 self.operands.into_iter()
163 }
164}
165
166// ----------------------------------------------------------------------------
167
168impl Default for Expression {
169 /// Creates an expression that matches everything.
170 ///
171 /// While it may seem counterintuitive to have the default expression match
172 /// everything, it is designed this way to align with the concept of vacuous
173 /// truth in logic. An expression with no operands is considered to be true,
174 /// as there are no conditions to violate it.
175 ///
176 /// In our implementation, we use an empty expression with a logical `NOT`
177 /// operator to represent this concept, to be distinguishable as a marker.
178 ///
179 /// # Examples
180 ///
181 /// ```
182 /// use zrx_id::Expression;
183 ///
184 /// // Create empty expression
185 /// let expr = Expression::default();
186 /// assert!(expr.is_empty());
187 /// ```
188 #[inline]
189 fn default() -> Self {
190 Self {
191 operator: Operator::Not,
192 operands: Vec::new(),
193 }
194 }
195}