zrx_id/id/specificity/convert.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//! Specificity computation.
27
28use crate::id::expression::{Expression, Operand, Operator, Term};
29use crate::id::selector::Selector;
30use crate::id::Id;
31
32use super::segment::atom::{Character, Wildcard};
33use super::segment::{Atom, Segment, Segments, ToSegments};
34use super::Specificity;
35
36// ----------------------------------------------------------------------------
37// Traits
38// ----------------------------------------------------------------------------
39
40/// Computation of [`Specificity`].
41pub trait ToSpecificity {
42 /// Computes the specificity of the value.
43 fn to_specificity(&self) -> Specificity;
44}
45
46// ----------------------------------------------------------------------------
47// Trait implementations
48// ----------------------------------------------------------------------------
49
50impl ToSpecificity for Expression {
51 /// Computes the specificity of the expression.
52 ///
53 /// # Examples
54 ///
55 /// ```
56 /// # use std::error::Error;
57 /// # fn main() -> Result<(), Box<dyn Error>> {
58 /// use zrx_id::specificity::ToSpecificity;
59 /// use zrx_id::{selector, Expression};
60 ///
61 /// // Create expression and compute specificity
62 /// let expr = Expression::any(|expr| {
63 /// expr.with(selector!(location = "**/*.jpg")?)?
64 /// .with(selector!(location = "**/*.png")?)
65 /// })?;
66 /// assert_eq!(expr.to_specificity(), (0, 1, 1, 4).into());
67 /// # Ok(())
68 /// # }
69 /// ```
70 #[inline]
71 fn to_specificity(&self) -> Specificity {
72 let iter = self.operands().iter().map(ToSpecificity::to_specificity);
73 match self.operator() {
74 Operator::Any => iter.reduce(Specificity::min).unwrap_or_default(),
75 Operator::All => iter.reduce(Specificity::sum).unwrap_or_default(),
76 Operator::Not => Specificity::default(),
77 }
78 }
79}
80
81impl ToSpecificity for Term {
82 /// Computes the specificity of the term.
83 ///
84 /// # Examples
85 ///
86 /// ```
87 /// # use std::error::Error;
88 /// # fn main() -> Result<(), Box<dyn Error>> {
89 /// use zrx_id::expression::Term;
90 /// use zrx_id::selector;
91 /// use zrx_id::specificity::ToSpecificity;
92 ///
93 /// // Create term and compute specificity
94 /// let term = Term::from(selector!(location = "**/*.md")?);
95 /// assert_eq!(term.to_specificity(), (0, 1, 1, 3).into());
96 /// # Ok(())
97 /// # }
98 /// ```
99 #[inline]
100 fn to_specificity(&self) -> Specificity {
101 match self {
102 Term::Id(id) => id.to_specificity(),
103 Term::Selector(selector) => selector.to_specificity(),
104 }
105 }
106}
107
108impl ToSpecificity for Operand {
109 /// Computes the specificity of the operand.
110 ///
111 /// # Examples
112 ///
113 /// ```
114 /// # use std::error::Error;
115 /// # fn main() -> Result<(), Box<dyn Error>> {
116 /// use zrx_id::expression::Operand;
117 /// use zrx_id::selector;
118 /// use zrx_id::specificity::ToSpecificity;
119 ///
120 /// // Create operand and compute specificity
121 /// let operand = Operand::from(selector!(location = "**/*.md")?);
122 /// assert_eq!(operand.to_specificity(), (0, 1, 1, 3).into());
123 /// # Ok(())
124 /// # }
125 /// ```
126 #[inline]
127 fn to_specificity(&self) -> Specificity {
128 match self {
129 Operand::Expression(expr) => expr.to_specificity(),
130 Operand::Term(term) => term.to_specificity(),
131 }
132 }
133}
134
135// ----------------------------------------------------------------------------
136
137impl ToSpecificity for Id {
138 /// Computes the specificity of the identifier.
139 ///
140 /// # Examples
141 ///
142 /// ```
143 /// # use std::error::Error;
144 /// # fn main() -> Result<(), Box<dyn Error>> {
145 /// use zrx_id::id;
146 /// use zrx_id::specificity::ToSpecificity;
147 ///
148 /// // Create identifier and compute specificity
149 /// let id = id!(provider = "file", context = ".", location = "index.md")?;
150 /// assert_eq!(id.to_specificity(), (3, 0, 0, 13).into());
151 /// # Ok(())
152 /// # }
153 /// ```
154 #[inline]
155 fn to_specificity(&self) -> Specificity {
156 let iter = 1..7;
157 iter.map(|index| self.as_ref().get(index).to_specificity())
158 .reduce(Specificity::sum)
159 .unwrap_or_default()
160 }
161}
162
163impl ToSpecificity for Selector {
164 /// Computes the specificity of the selector.
165 ///
166 /// # Examples
167 ///
168 /// ```
169 /// # use std::error::Error;
170 /// # fn main() -> Result<(), Box<dyn Error>> {
171 /// use zrx_id::selector;
172 /// use zrx_id::specificity::ToSpecificity;
173 ///
174 /// // Create selector and compute specificity
175 /// let selector = selector!(location = "**/*.md")?;
176 /// assert_eq!(selector.to_specificity(), (0, 1, 1, 3).into());
177 /// # Ok(())
178 /// # }
179 /// ```
180 #[inline]
181 fn to_specificity(&self) -> Specificity {
182 let iter = 1..7;
183 iter.map(|index| self.as_ref().get(index).to_specificity())
184 .reduce(Specificity::sum)
185 .unwrap_or_default()
186 }
187}
188
189// ----------------------------------------------------------------------------
190
191impl ToSpecificity for Segments<'_> {
192 /// Computes the specificity of the segment set.
193 #[inline]
194 fn to_specificity(&self) -> Specificity {
195 self.iter()
196 .map(ToSpecificity::to_specificity)
197 .reduce(Specificity::sum)
198 .unwrap_or_default()
199 }
200}
201
202impl ToSpecificity for Segment<'_> {
203 /// Computes the specificity of the segment.
204 #[inline]
205 fn to_specificity(&self) -> Specificity {
206 self.iter()
207 .map(ToSpecificity::to_specificity)
208 .reduce(Specificity::min_sum_len)
209 .unwrap_or_default()
210 }
211}
212
213// ----------------------------------------------------------------------------
214
215impl ToSpecificity for Atom<'_> {
216 /// Computes the specificity of the atom.
217 #[inline]
218 fn to_specificity(&self) -> Specificity {
219 match self {
220 Atom::Literal(literal) => {
221 let len = u16::try_from(literal.len()).unwrap_or(u16::MAX);
222 Specificity(1, 0, 0, len)
223 }
224 Atom::Wildcard(wildcard) => wildcard.to_specificity(),
225 Atom::Character(character) => character.to_specificity(),
226 Atom::Group(data) => data
227 .iter()
228 .map(ToSpecificity::to_specificity)
229 .reduce(Specificity::min)
230 .unwrap_or_default(),
231 }
232 }
233}
234
235impl ToSpecificity for Wildcard {
236 /// Computes the specificity of the wildcard.
237 #[inline]
238 fn to_specificity(&self) -> Specificity {
239 match self {
240 Wildcard::Character => Specificity(0, 1, 0, 0),
241 Wildcard::Sequence => Specificity(0, 1, 0, 0),
242 Wildcard::Traversal => Specificity(0, 0, 1, 0),
243 }
244 }
245}
246
247impl ToSpecificity for Character<'_> {
248 /// Computes the specificity of the character class.
249 #[inline]
250 fn to_specificity(&self) -> Specificity {
251 Specificity(0, 1, 0, 1)
252 }
253}
254
255// ----------------------------------------------------------------------------
256// Blanket implementations
257// ----------------------------------------------------------------------------
258
259impl<T> ToSpecificity for T
260where
261 T: ToSegments,
262{
263 #[inline]
264 fn to_specificity(&self) -> Specificity {
265 self.to_segments().to_specificity()
266 }
267}