Skip to main content

stix_matcher/
compare.rs

1//! Scalar comparison between a resolved `StixValue` and a pattern `Literal`.
2
3use std::cmp::Ordering;
4
5use stix_model::StixValue;
6use stix_pattern::ast::Literal;
7
8/// The string contents of a string-like literal, if any.
9fn literal_str(lit: &Literal) -> Option<&str> {
10    match lit {
11        Literal::String(s) | Literal::Timestamp(s) | Literal::Binary(s) | Literal::Hex(s) => {
12            Some(s)
13        }
14        _ => None,
15    }
16}
17
18/// The numeric value of a numeric literal, if any.
19fn literal_f64(lit: &Literal) -> Option<f64> {
20    match lit {
21        Literal::Integer(n) => Some(*n as f64),
22        Literal::Float(f) => Some(*f),
23        _ => None,
24    }
25}
26
27/// Compares a resolved value with a pattern literal for equality.
28///
29/// Integers and floats compare numerically across the int/float divide; string-like
30/// literals (string, timestamp, binary, hex) compare as strings. Timestamps are NOT
31/// parsed — they compare lexicographically.
32pub fn value_eq_literal(value: &StixValue, lit: &Literal) -> bool {
33    match (value, lit) {
34        (StixValue::Bool(b), Literal::Boolean(l)) => b == l,
35        _ => {
36            if let (Some(v), Some(l)) = (value.as_str(), literal_str(lit)) {
37                return v == l;
38            }
39            if let (Some(v), Some(l)) = (value.as_f64(), literal_f64(lit)) {
40                return v == l;
41            }
42            false
43        }
44    }
45}
46
47/// Orders a resolved value against a pattern literal.
48///
49/// Returns `None` when the two are not comparable (e.g. bool vs number). Numeric
50/// comparison promotes integers to floats; strings compare lexicographically.
51pub fn value_cmp_literal(value: &StixValue, lit: &Literal) -> Option<Ordering> {
52    if let (Some(v), Some(l)) = (value.as_f64(), literal_f64(lit)) {
53        return v.partial_cmp(&l);
54    }
55    if let (Some(v), Some(l)) = (value.as_str(), literal_str(lit)) {
56        return Some(v.cmp(l));
57    }
58    None
59}
60
61/// Returns true if the value equals any literal in the set (the `IN` operator).
62pub fn value_in_set(value: &StixValue, set: &[Literal]) -> bool {
63    set.iter().any(|lit| value_eq_literal(value, lit))
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use stix_model::StixValue;
70    use stix_pattern::ast::Literal;
71
72    #[test]
73    fn string_equality() {
74        assert!(value_eq_literal(
75            &StixValue::String("a".into()),
76            &Literal::String("a".into())
77        ));
78        assert!(!value_eq_literal(
79            &StixValue::String("a".into()),
80            &Literal::String("b".into())
81        ));
82    }
83
84    #[test]
85    fn numeric_equality_crosses_int_float() {
86        assert!(value_eq_literal(
87            &StixValue::Integer(3),
88            &Literal::Integer(3)
89        ));
90        assert!(value_eq_literal(
91            &StixValue::Integer(3),
92            &Literal::Float(3.0)
93        ));
94        assert!(value_eq_literal(
95            &StixValue::Float(3.0),
96            &Literal::Integer(3)
97        ));
98        assert!(!value_eq_literal(
99            &StixValue::Integer(3),
100            &Literal::Integer(4)
101        ));
102    }
103
104    #[test]
105    fn typed_literals_compare_as_strings() {
106        assert!(value_eq_literal(
107            &StixValue::String("2020-01-01T00:00:00Z".into()),
108            &Literal::Timestamp("2020-01-01T00:00:00Z".into())
109        ));
110        assert!(value_eq_literal(
111            &StixValue::String("cafe".into()),
112            &Literal::Hex("cafe".into())
113        ));
114    }
115
116    #[test]
117    fn ordering() {
118        use std::cmp::Ordering;
119        assert_eq!(
120            value_cmp_literal(&StixValue::Integer(2), &Literal::Integer(5)),
121            Some(Ordering::Less)
122        );
123        assert_eq!(
124            value_cmp_literal(&StixValue::String("b".into()), &Literal::String("a".into())),
125            Some(Ordering::Greater)
126        );
127        assert_eq!(
128            value_cmp_literal(&StixValue::Bool(true), &Literal::Integer(1)),
129            None
130        );
131    }
132
133    #[test]
134    fn membership() {
135        let set = vec![Literal::Integer(1), Literal::Integer(2)];
136        assert!(value_in_set(&StixValue::Integer(2), &set));
137        assert!(!value_in_set(&StixValue::Integer(3), &set));
138    }
139}