Skip to main content

libmagic_rs/evaluator/operators/
comparison.rs

1// Copyright (c) 2025-2026 the libmagic-rs contributors
2// SPDX-License-Identifier: Apache-2.0
3
4//! Comparison operators for magic rule evaluation
5
6use std::cmp::Ordering;
7
8use crate::parser::ast::Value;
9
10/// Compare two values and return their ordering, if comparable
11///
12/// Returns `Some(Ordering)` for same-type comparisons (integers, strings, bytes)
13/// and cross-type integer comparisons (via `i128` coercion). Returns `None` for
14/// incomparable type combinations.
15///
16/// # Examples
17///
18/// ```
19/// use std::cmp::Ordering;
20/// use libmagic_rs::parser::ast::Value;
21/// use libmagic_rs::evaluator::operators::compare_values;
22///
23/// assert_eq!(compare_values(&Value::Uint(5), &Value::Uint(10)), Some(Ordering::Less));
24/// assert_eq!(compare_values(&Value::Int(-1), &Value::Uint(0)), Some(Ordering::Less));
25/// assert_eq!(compare_values(&Value::Uint(42), &Value::Int(42)), Some(Ordering::Equal));
26/// assert_eq!(compare_values(&Value::Uint(1), &Value::String("1".to_string())), None);
27/// ```
28#[must_use]
29pub fn compare_values(left: &Value, right: &Value) -> Option<Ordering> {
30    match (left, right) {
31        (Value::Uint(a), Value::Uint(b)) => Some(a.cmp(b)),
32        (Value::Int(a), Value::Int(b)) => Some(a.cmp(b)),
33        (Value::Uint(a), Value::Int(b)) => Some(i128::from(*a).cmp(&i128::from(*b))),
34        (Value::Int(a), Value::Uint(b)) => Some(i128::from(*a).cmp(&i128::from(*b))),
35        (Value::String(a), Value::String(b)) => Some(a.cmp(b)),
36        (Value::Bytes(a), Value::Bytes(b)) => Some(a.cmp(b)),
37        _ => None,
38    }
39}
40
41/// Apply less-than comparison between two values
42///
43/// # Examples
44///
45/// ```
46/// use libmagic_rs::parser::ast::Value;
47/// use libmagic_rs::evaluator::operators::apply_less_than;
48///
49/// assert!(apply_less_than(&Value::Uint(5), &Value::Uint(10)));
50/// assert!(!apply_less_than(&Value::Uint(10), &Value::Uint(10)));
51/// assert!(apply_less_than(&Value::Int(-1), &Value::Uint(0)));
52/// ```
53#[must_use]
54pub fn apply_less_than(left: &Value, right: &Value) -> bool {
55    compare_values(left, right) == Some(Ordering::Less)
56}
57
58/// Apply greater-than comparison between two values
59///
60/// # Examples
61///
62/// ```
63/// use libmagic_rs::parser::ast::Value;
64/// use libmagic_rs::evaluator::operators::apply_greater_than;
65///
66/// assert!(apply_greater_than(&Value::Uint(10), &Value::Uint(5)));
67/// assert!(!apply_greater_than(&Value::Uint(10), &Value::Uint(10)));
68/// assert!(apply_greater_than(&Value::Uint(0), &Value::Int(-1)));
69/// ```
70#[must_use]
71pub fn apply_greater_than(left: &Value, right: &Value) -> bool {
72    compare_values(left, right) == Some(Ordering::Greater)
73}
74
75/// Apply less-than-or-equal comparison between two values
76///
77/// # Examples
78///
79/// ```
80/// use libmagic_rs::parser::ast::Value;
81/// use libmagic_rs::evaluator::operators::apply_less_equal;
82///
83/// assert!(apply_less_equal(&Value::Uint(10), &Value::Uint(10)));
84/// assert!(apply_less_equal(&Value::Uint(5), &Value::Uint(10)));
85/// assert!(!apply_less_equal(&Value::Uint(10), &Value::Uint(5)));
86/// ```
87#[must_use]
88pub fn apply_less_equal(left: &Value, right: &Value) -> bool {
89    matches!(
90        compare_values(left, right),
91        Some(Ordering::Less | Ordering::Equal)
92    )
93}
94
95/// Apply greater-than-or-equal comparison between two values
96///
97/// # Examples
98///
99/// ```
100/// use libmagic_rs::parser::ast::Value;
101/// use libmagic_rs::evaluator::operators::apply_greater_equal;
102///
103/// assert!(apply_greater_equal(&Value::Uint(10), &Value::Uint(10)));
104/// assert!(apply_greater_equal(&Value::Uint(10), &Value::Uint(5)));
105/// assert!(!apply_greater_equal(&Value::Uint(5), &Value::Uint(10)));
106/// ```
107#[must_use]
108pub fn apply_greater_equal(left: &Value, right: &Value) -> bool {
109    matches!(
110        compare_values(left, right),
111        Some(Ordering::Greater | Ordering::Equal)
112    )
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_compare_values_ordering() {
121        use std::cmp::Ordering::*;
122
123        // Same-type integer comparisons
124        assert_eq!(
125            compare_values(&Value::Uint(5), &Value::Uint(10)),
126            Some(Less)
127        );
128        assert_eq!(
129            compare_values(&Value::Uint(10), &Value::Uint(10)),
130            Some(Equal)
131        );
132        assert_eq!(
133            compare_values(&Value::Uint(10), &Value::Uint(5)),
134            Some(Greater)
135        );
136        assert_eq!(
137            compare_values(&Value::Int(-10), &Value::Int(-5)),
138            Some(Less)
139        );
140        assert_eq!(
141            compare_values(&Value::Int(i64::MIN), &Value::Int(0)),
142            Some(Less)
143        );
144
145        // Cross-type integer comparisons via i128
146        assert_eq!(compare_values(&Value::Int(-1), &Value::Uint(0)), Some(Less));
147        assert_eq!(
148            compare_values(&Value::Uint(42), &Value::Int(42)),
149            Some(Equal)
150        );
151        assert_eq!(
152            compare_values(&Value::Uint(u64::MAX), &Value::Int(-1)),
153            Some(Greater)
154        );
155
156        // String comparisons
157        assert_eq!(
158            compare_values(&Value::String("abc".into()), &Value::String("abd".into())),
159            Some(Less)
160        );
161        assert_eq!(
162            compare_values(&Value::String("abc".into()), &Value::String("abc".into())),
163            Some(Equal)
164        );
165
166        // Bytes comparisons (lexicographic, including different lengths)
167        assert_eq!(
168            compare_values(&Value::Bytes(vec![1]), &Value::Bytes(vec![2])),
169            Some(Less)
170        );
171        assert_eq!(
172            compare_values(&Value::Bytes(vec![1]), &Value::Bytes(vec![1])),
173            Some(Equal)
174        );
175        assert_eq!(
176            compare_values(&Value::Bytes(vec![1]), &Value::Bytes(vec![1, 2])),
177            Some(Less)
178        );
179        assert_eq!(
180            compare_values(&Value::Bytes(vec![]), &Value::Bytes(vec![1])),
181            Some(Less)
182        );
183
184        // Incomparable types return None
185        assert_eq!(
186            compare_values(&Value::Uint(1), &Value::String("1".into())),
187            None
188        );
189        assert_eq!(compare_values(&Value::Int(1), &Value::Bytes(vec![1])), None);
190    }
191
192    #[test]
193    fn test_comparison_operators_consistency() {
194        // Verify all four comparison functions agree with compare_values
195        let pairs = vec![
196            (Value::Uint(5), Value::Uint(10)),
197            (Value::Uint(10), Value::Uint(10)),
198            (Value::Uint(10), Value::Uint(5)),
199            (Value::Int(-10), Value::Int(-5)),
200            (Value::Int(-1), Value::Uint(0)),
201            (Value::Uint(u64::MAX), Value::Int(-1)),
202            (Value::String("abc".into()), Value::String("abd".into())),
203            (Value::Bytes(vec![1, 2]), Value::Bytes(vec![1, 3])),
204            (Value::Bytes(vec![1]), Value::Bytes(vec![1, 2])),
205            (Value::Uint(1), Value::String("1".into())), // incomparable
206        ];
207
208        for (left, right) in &pairs {
209            let ord = compare_values(left, right);
210            assert_eq!(
211                apply_less_than(left, right),
212                ord == Some(Ordering::Less),
213                "< for {left:?}, {right:?}"
214            );
215            assert_eq!(
216                apply_greater_than(left, right),
217                ord == Some(Ordering::Greater),
218                "> for {left:?}, {right:?}"
219            );
220            assert_eq!(
221                apply_less_equal(left, right),
222                matches!(ord, Some(Ordering::Less | Ordering::Equal)),
223                "<= for {left:?}, {right:?}"
224            );
225            assert_eq!(
226                apply_greater_equal(left, right),
227                matches!(ord, Some(Ordering::Greater | Ordering::Equal)),
228                ">= for {left:?}, {right:?}"
229            );
230        }
231    }
232}