Skip to main content

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}