rusty_rules/
matcher.rs

1use std::collections::HashSet;
2use std::fmt;
3use std::net::IpAddr;
4use std::result::Result as StdResult;
5use std::str::FromStr;
6
7use ipnet::IpNet;
8use ipnet_trie::IpnetTrie;
9use regex::{Regex, RegexSet};
10use serde_json::{Map, Value as JsonValue, json};
11
12use crate::types::{AsyncCheckFn, BoxFuture, CheckFn, DynError, MaybeSend, MaybeSync};
13use crate::{Error, JsonValueExt as _, Result, Value};
14
15/// Represents an operator that used to check if a fetched value satisfies the condition.
16pub enum Operator<Ctx: ?Sized> {
17    Equal(Value<'static>),
18    LessThan(Value<'static>),
19    LessThanOrEqual(Value<'static>),
20    GreaterThan(Value<'static>),
21    GreaterThanOrEqual(Value<'static>),
22    InSet(HashSet<Value<'static>>),
23    Regex(regex::Regex),
24    RegexSet(regex::RegexSet),
25    IpSet(IpnetTrie<()>),
26    Custom(Box<CheckFn<Ctx>>),
27    CustomAsync(Box<AsyncCheckFn<Ctx>>),
28}
29
30impl<Ctx: ?Sized> fmt::Debug for Operator<Ctx> {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        match self {
33            Operator::Equal(v) => write!(f, "Equal({v:?})"),
34            Operator::LessThan(v) => write!(f, "LessThan({v:?})"),
35            Operator::LessThanOrEqual(v) => write!(f, "LessThanOrEqual({v:?})"),
36            Operator::GreaterThan(v) => write!(f, "GreaterThan({v:?})"),
37            Operator::GreaterThanOrEqual(v) => write!(f, "GreaterThanOrEqual({v:?})"),
38            Operator::InSet(set) => write!(f, "InSet({set:?})",),
39            Operator::Regex(regex) => write!(f, "Regex({regex:?})"),
40            Operator::RegexSet(regex_set) => write!(f, "RegexSet({regex_set:?})"),
41            Operator::IpSet(_) => f.write_str("IpSet"),
42            Operator::Custom(_) => f.write_str("Custom"),
43            Operator::CustomAsync(_) => f.write_str("CustomAsync"),
44        }
45    }
46}
47
48impl<Ctx: ?Sized> Operator<Ctx> {
49    /// Creates a new operator that checks if the fetched value is equal to the given value.
50    pub fn new<F>(func: F) -> Self
51    where
52        F: Fn(&Ctx, Value) -> StdResult<bool, DynError> + MaybeSend + MaybeSync + 'static,
53    {
54        Operator::Custom(Box::new(func))
55    }
56
57    /// Creates a new async operator that checks if the fetched value is equal to the given value.
58    pub fn new_async<F>(func: F) -> Self
59    where
60        F: for<'a> Fn(&'a Ctx, Value<'a>) -> BoxFuture<'a, StdResult<bool, DynError>>
61            + MaybeSend
62            + MaybeSync
63            + 'static,
64    {
65        Operator::CustomAsync(Box::new(func))
66    }
67}
68
69/// Trait for types matchers
70pub trait Matcher<Ctx: ?Sized>: MaybeSend + MaybeSync {
71    /// Compiles the JSON configuration and returns an [`Operator`].
72    fn compile(&self, value: &JsonValue) -> Result<Operator<Ctx>>;
73
74    /// Returns a JSON Schema that describes valid inputs for this matcher.
75    fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
76        let _ = custom_ops;
77        json!({})
78    }
79}
80
81macro_rules! operator_error {
82    ($op:expr, $($arg:tt)*) => {
83        Err(Error::operator($op, format!($($arg)*)))
84    };
85}
86
87macro_rules! check_operator {
88    ($map:expr) => {{
89        let len = $map.len();
90        if len != 1 {
91            let msg = format!("operator object must have exactly one key (got {len})");
92            return Err(Error::json(msg));
93        }
94        $map.iter().next().unwrap()
95    }};
96}
97
98const IPV4_PATTERN: &str =
99    r"(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)(?:/\d{1,2})?";
100const IPV6_PATTERN: &str = r"(?:[0-9a-f]{1,4}:){1,7}[0-9a-f]{0,4}|::(?:[0-9a-f:]{1,})?|[0-9a-f]{1,4}::(?:[0-9a-f:]{1,})?(?:/\d{1,3})?";
101
102/// A flexible matcher without strict types.
103pub struct DefaultMatcher;
104
105impl<Ctx: ?Sized> Matcher<Ctx> for DefaultMatcher {
106    fn compile(&self, value: &JsonValue) -> Result<Operator<Ctx>> {
107        match value {
108            JsonValue::Null | JsonValue::Bool(_) | JsonValue::Number(_) | JsonValue::String(_) => {
109                Ok(Operator::Equal(Value::from(value).into_static()))
110            }
111            JsonValue::Array(seq) => Ok(Operator::InSet(Self::make_hashset(seq))),
112            JsonValue::Object(map) => Self::compile_op(map),
113        }
114    }
115
116    fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
117        DefaultMatcher::json_schema(self, custom_ops)
118    }
119}
120
121impl DefaultMatcher {
122    fn compile_op<Ctx: ?Sized>(map: &Map<String, JsonValue>) -> Result<Operator<Ctx>> {
123        let (op, value) = check_operator!(map);
124        match (op.as_str(), value) {
125            ("<", v) => Ok(Operator::LessThan(Value::from(v).into_static())),
126            ("<=", v) => Ok(Operator::LessThanOrEqual(Value::from(v).into_static())),
127            (">", v) => Ok(Operator::GreaterThan(Value::from(v).into_static())),
128            (">=", v) => Ok(Operator::GreaterThanOrEqual(Value::from(v).into_static())),
129            ("==", v) => Ok(Operator::Equal(Value::from(v).into_static())),
130            ("in", JsonValue::Array(arr)) => Ok(Operator::InSet(Self::make_hashset(arr))),
131            ("in", _) => operator_error!(op, "expected array, got {}", value.type_name()),
132            ("re", JsonValue::String(pattern)) => {
133                let regex = Regex::new(pattern).map_err(|err| Error::operator(op, err))?;
134                Ok(Operator::Regex(regex))
135            }
136            ("re", JsonValue::Array(patterns)) => RegexMatcher::make_regex_set(patterns)
137                .map(Operator::RegexSet)
138                .map_err(|err| Error::operator(op, err)),
139            ("re", _) => operator_error!(op, "expected string or array, got {}", value.type_name()),
140            ("ip", JsonValue::Array(arr)) => IpMatcher::make_ipnet(arr)
141                .map(Operator::IpSet)
142                .map_err(|err| Error::operator(op, err)),
143            ("ip", _) => operator_error!(op, "expected array, got {}", value.type_name()),
144            _ => Err(Error::UnknownOperator(op.clone())),
145        }
146    }
147
148    /// Creates a [`HashSet`] from a list of values.
149    fn make_hashset(arr: &[JsonValue]) -> HashSet<Value<'static>> {
150        arr.iter().map(|v| Value::from(v).into_static()).collect()
151    }
152
153    /// Provides a JSON Schema for default matcher inputs.
154    fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
155        // Standard schemas
156        let any_schema = json!({ "type": ["null", "boolean", "number", "string"] });
157        let any_array_schema = json!({ "type": "array", "items": any_schema });
158        let string_schema = json!({ "type": "string" });
159        let string_array_schema = json!({ "type": "array", "items": string_schema });
160        let ip_schema = json!({ "type": "string", "pattern": format!(r"^(?:{IPV4_PATTERN}|(?i:{IPV6_PATTERN}))") });
161        let ip_array_schema = json!({ "type": "array", "items": ip_schema });
162
163        // Add operator schemas
164        let mut properties = Map::new();
165        properties.insert("<".to_string(), any_schema.clone());
166        properties.insert("<=".to_string(), any_schema.clone());
167        properties.insert(">".to_string(), any_schema.clone());
168        properties.insert(">=".to_string(), any_schema.clone());
169        properties.insert("==".to_string(), any_schema.clone());
170        properties.insert("in".to_string(), any_array_schema.clone());
171        properties.insert(
172            "re".to_string(),
173            json!({ "oneOf": [string_schema, string_array_schema] }),
174        );
175        properties.insert("ip".to_string(), ip_array_schema);
176
177        // Add custom operators
178        for (op, schema) in custom_ops {
179            properties.insert(op.to_string(), schema.clone());
180        }
181
182        json!({
183            "oneOf": [
184                any_schema,
185                any_array_schema,
186                // Object with a single operator
187                {
188                    "type": "object",
189                    "properties": properties,
190                    "additionalProperties": false,
191                    "minProperties": 1,
192                    "maxProperties": 1
193                }
194            ]
195        })
196    }
197}
198
199/// A matcher for string values.
200///
201/// It supports custom operators.
202pub struct StringMatcher;
203
204impl<Ctx: ?Sized> Matcher<Ctx> for StringMatcher {
205    fn compile(&self, value: &JsonValue) -> Result<Operator<Ctx>> {
206        match value {
207            JsonValue::String(s) => Ok(Operator::Equal(Value::from(s.clone()))),
208            JsonValue::Array(seq) => Ok(Operator::InSet(Self::make_hashset(seq)?)),
209            JsonValue::Object(map) => Self::compile_op(map),
210            _ => {
211                let msg = format!("unexpected JSON {}", value.type_name());
212                Err(Error::json(msg))
213            }
214        }
215    }
216
217    fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
218        StringMatcher::json_schema(self, custom_ops)
219    }
220}
221
222impl StringMatcher {
223    fn compile_op<Ctx: ?Sized>(map: &Map<String, JsonValue>) -> Result<Operator<Ctx>> {
224        let (op, value) = check_operator!(map);
225        match (op.as_str(), value) {
226            ("<", JsonValue::String(s)) => Ok(Operator::LessThan(Value::from(s).into_static())),
227            ("<=", JsonValue::String(s)) => {
228                Ok(Operator::LessThanOrEqual(Value::from(s).into_static()))
229            }
230            (">", JsonValue::String(s)) => Ok(Operator::GreaterThan(Value::from(s).into_static())),
231            (">=", JsonValue::String(s)) => {
232                Ok(Operator::GreaterThanOrEqual(Value::from(s).into_static()))
233            }
234            ("==", JsonValue::String(s)) => Ok(Operator::Equal(Value::from(s).into_static())),
235            ("<" | "<=" | ">" | ">=" | "==", _) => {
236                operator_error!(op, "expected string, got {}", value.type_name())
237            }
238            ("in", JsonValue::Array(arr)) => Self::make_hashset(arr)
239                .map(Operator::InSet)
240                .map_err(|err| Error::operator(op, err)),
241            ("in", _) => operator_error!(op, "expected array, got {}", value.type_name()),
242            ("re", JsonValue::String(pattern)) => {
243                let regex = Regex::new(pattern).map_err(|err| Error::operator(op, err))?;
244                Ok(Operator::Regex(regex))
245            }
246            ("re", JsonValue::Array(patterns)) => RegexMatcher::make_regex_set(patterns)
247                .map(Operator::RegexSet)
248                .map_err(|err| Error::operator(op, err)),
249            ("re", _) => operator_error!(op, "expected string or array, got {}", value.type_name()),
250            _ => Err(Error::UnknownOperator(op.clone())),
251        }
252    }
253
254    /// Creates a [`HashSet`] from a list of strings.
255    fn make_hashset(arr: &[JsonValue]) -> Result<HashSet<Value<'static>>> {
256        arr.iter()
257            .map(|v| match v {
258                JsonValue::String(s) => Ok(Value::from(s.clone())),
259                _ => {
260                    let msg = format!("got {} in string array", v.type_name());
261                    Err(Error::json(msg))
262                }
263            })
264            .collect()
265    }
266
267    /// Provides a JSON Schema for string matcher inputs.
268    fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
269        // Standard schemas
270        let string_schema = json!({ "type": "string" });
271        let string_array_schema = json!({ "type": "array", "items": string_schema });
272
273        // Add operator schemas
274        let mut properties = Map::new();
275        properties.insert("<".to_string(), string_schema.clone());
276        properties.insert("<=".to_string(), string_schema.clone());
277        properties.insert(">".to_string(), string_schema.clone());
278        properties.insert(">=".to_string(), string_schema.clone());
279        properties.insert("==".to_string(), string_schema.clone());
280        properties.insert("in".to_string(), string_array_schema.clone());
281        properties.insert(
282            "re".to_string(),
283            json!({ "oneOf": [string_schema, string_array_schema] }),
284        );
285
286        // Add custom operators
287        for (op, schema) in custom_ops {
288            properties.insert(op.to_string(), schema.clone());
289        }
290
291        json!({
292            "oneOf": [
293                string_schema,
294                string_array_schema,
295                // Object with a single operator
296                {
297                    "type": "object",
298                    "properties": properties,
299                    "additionalProperties": false,
300                    "minProperties": 1,
301                    "maxProperties": 1
302                }
303            ]
304        })
305    }
306}
307
308/// A matcher for string values with regular expressions by default.
309///
310/// It supports custom operators.
311pub struct RegexMatcher;
312
313impl<Ctx: ?Sized> Matcher<Ctx> for RegexMatcher {
314    fn compile(&self, value: &JsonValue) -> Result<Operator<Ctx>> {
315        match value {
316            JsonValue::String(pattern) => Ok(Operator::Regex(Regex::new(pattern)?)),
317            JsonValue::Array(patterns) => Ok(Operator::RegexSet(Self::make_regex_set(patterns)?)),
318            JsonValue::Object(map) => Self::compile_op(map),
319            _ => {
320                let msg = format!("unexpected JSON {}", value.type_name());
321                Err(Error::json(msg))
322            }
323        }
324    }
325
326    fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
327        RegexMatcher::json_schema(self, custom_ops)
328    }
329}
330
331impl RegexMatcher {
332    fn compile_op<Ctx: ?Sized>(map: &Map<String, JsonValue>) -> Result<Operator<Ctx>> {
333        let (op, _value) = check_operator!(map);
334        Err(Error::UnknownOperator(op.clone()))
335    }
336
337    /// Creates a [`RegexSet`] from a list of patterns.
338    fn make_regex_set(patterns: &[JsonValue]) -> Result<RegexSet> {
339        let patterns = (patterns.iter())
340            .map(|v| match v {
341                JsonValue::String(s) => Ok(s),
342                _ => {
343                    let msg = format!("expected string, got {} in regex array", v.type_name());
344                    Err(Error::json(msg))
345                }
346            })
347            .collect::<Result<Vec<_>>>()?;
348        Ok(RegexSet::new(&patterns)?)
349    }
350
351    /// Provides a JSON Schema for regex matcher inputs.
352    fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
353        // Standard schemas
354        let string_schema = json!({ "type": "string" });
355        let string_array_schema = json!({ "type": "array", "items": string_schema });
356
357        // Add operator schemas
358        let mut properties = Map::new();
359
360        // Add custom operators
361        for (op, schema) in custom_ops {
362            properties.insert(op.to_string(), schema.clone());
363        }
364
365        json!({
366            "oneOf": [
367                string_schema,
368                string_array_schema,
369                // Object with a single operator
370                {
371                    "type": "object",
372                    "properties": properties,
373                    "additionalProperties": false,
374                    "minProperties": 1,
375                    "maxProperties": 1
376                }
377            ]
378        })
379    }
380}
381
382/// A matcher for number values.
383///
384/// It supports custom operators.
385pub struct NumberMatcher;
386
387impl<Ctx: ?Sized> Matcher<Ctx> for NumberMatcher {
388    fn compile(&self, value: &JsonValue) -> Result<Operator<Ctx>> {
389        match value {
390            JsonValue::Number(n) => Ok(Operator::Equal(Value::Number(n.clone()))),
391            JsonValue::Array(seq) => Ok(Operator::InSet(Self::make_hashset(seq)?)),
392            JsonValue::Object(map) => Self::compile_op(map),
393            _ => {
394                let msg = format!("unexpected JSON {}", value.type_name());
395                Err(Error::json(msg))
396            }
397        }
398    }
399
400    fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
401        NumberMatcher::json_schema(self, custom_ops)
402    }
403}
404
405impl NumberMatcher {
406    fn compile_op<Ctx: ?Sized>(map: &Map<String, JsonValue>) -> Result<Operator<Ctx>> {
407        let (op, value) = check_operator!(map);
408        match (op.as_str(), value) {
409            ("<", JsonValue::Number(n)) => Ok(Operator::LessThan(Value::Number(n.clone()))),
410            ("<=", JsonValue::Number(n)) => Ok(Operator::LessThanOrEqual(Value::Number(n.clone()))),
411            (">", JsonValue::Number(n)) => Ok(Operator::GreaterThan(Value::Number(n.clone()))),
412            (">=", JsonValue::Number(n)) => {
413                Ok(Operator::GreaterThanOrEqual(Value::Number(n.clone())))
414            }
415            ("==", JsonValue::Number(n)) => Ok(Operator::Equal(Value::Number(n.clone()))),
416            ("<" | "<=" | ">" | ">=" | "==", _) => {
417                operator_error!(op, "expected number, got {}", value.type_name())
418            }
419            ("in", JsonValue::Array(seq)) => Self::make_hashset(seq)
420                .map(Operator::InSet)
421                .map_err(|err| Error::operator(op, err)),
422            ("in", _) => operator_error!(op, "expected array, got {}", value.type_name()),
423            _ => Err(Error::UnknownOperator(op.clone())),
424        }
425    }
426
427    /// Creates a [`HashSet`] from a list of numbers.
428    fn make_hashset(arr: &[JsonValue]) -> Result<HashSet<Value<'static>>> {
429        arr.iter()
430            .map(|v| match v {
431                JsonValue::Number(n) => Ok(Value::Number(n.clone())),
432                _ => {
433                    let msg = format!("got {} in number array", v.type_name());
434                    Err(Error::json(msg))
435                }
436            })
437            .collect()
438    }
439
440    /// Provides a JSON Schema for number matcher inputs.
441    fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
442        // Standard schemas
443        let number_schema = json!({ "type": "number" });
444        let number_array_schema = json!({ "type": "array", "items": number_schema });
445
446        // Add operator schemas
447        let mut properties = Map::new();
448        properties.insert("<".to_string(), number_schema.clone());
449        properties.insert("<=".to_string(), number_schema.clone());
450        properties.insert(">".to_string(), number_schema.clone());
451        properties.insert(">=".to_string(), number_schema.clone());
452        properties.insert("==".to_string(), number_schema.clone());
453        properties.insert("in".to_string(), number_array_schema.clone());
454
455        // Add custom operators
456        for (op, schema) in custom_ops {
457            properties.insert(op.to_string(), schema.clone());
458        }
459
460        json!({
461            "oneOf": [
462                number_schema,
463                number_array_schema,
464                // Object with a single operator
465                {
466                    "type": "object",
467                    "properties": properties,
468                    "additionalProperties": false,
469                    "minProperties": 1,
470                    "maxProperties": 1
471                }
472            ]
473        })
474    }
475}
476
477/// A matcher for boolean values.
478///
479/// Does not support custom operators.
480pub struct BoolMatcher;
481
482impl<Ctx: ?Sized> Matcher<Ctx> for BoolMatcher {
483    fn compile(&self, value: &JsonValue) -> Result<Operator<Ctx>> {
484        match value {
485            JsonValue::Bool(b) => Ok(Operator::Equal(Value::Bool(*b))),
486            _ => {
487                let msg = format!("expected boolean, got {}", value.type_name());
488                Err(Error::json(msg))
489            }
490        }
491    }
492
493    fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
494        BoolMatcher::json_schema(self, custom_ops)
495    }
496}
497
498impl BoolMatcher {
499    /// Provides a JSON Schema for boolean matcher inputs.
500    fn json_schema(&self, _custom_ops: &[(&str, JsonValue)]) -> JsonValue {
501        // Boolean matcher only accepts boolean values
502        json!({ "type": "boolean" })
503    }
504}
505
506/// A matcher for IP subnets.
507///
508/// It supports custom operators.
509pub struct IpMatcher;
510
511impl<Ctx: ?Sized> Matcher<Ctx> for IpMatcher {
512    fn compile(&self, value: &JsonValue) -> Result<Operator<Ctx>> {
513        match value {
514            JsonValue::String(_) => {
515                let value_slice = std::slice::from_ref(value);
516                Ok(Operator::IpSet(Self::make_ipnet(value_slice)?))
517            }
518            JsonValue::Array(addrs) => Ok(Operator::IpSet(Self::make_ipnet(addrs)?)),
519            JsonValue::Object(map) => Self::compile_op(map),
520            _ => {
521                let msg = format!("unexpected JSON {}", value.type_name());
522                Err(Error::json(msg))
523            }
524        }
525    }
526
527    fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
528        IpMatcher::json_schema(self, custom_ops)
529    }
530}
531
532impl IpMatcher {
533    fn compile_op<Ctx: ?Sized>(map: &Map<String, JsonValue>) -> Result<Operator<Ctx>> {
534        let (op, _value) = check_operator!(map);
535        Err(Error::UnknownOperator(op.clone()))
536    }
537
538    /// Creates an [`IpnetTrie`] from a list of IP addresses or CIDR ranges.
539    fn make_ipnet(addrs: &[JsonValue]) -> Result<IpnetTrie<()>> {
540        let mut table = IpnetTrie::new();
541        for addr in addrs {
542            let addr = match addr {
543                JsonValue::String(s) => s,
544                _ => {
545                    let msg = format!("got {} in ipnet array", addr.type_name());
546                    return Err(Error::json(msg));
547                }
548            };
549            let net = if addr.contains('/') {
550                IpNet::from_str(addr)?
551            } else {
552                IpNet::from(IpAddr::from_str(addr)?)
553            };
554            table.insert(net, ());
555        }
556        Ok(table)
557    }
558
559    /// Provides a JSON Schema for IP matcher inputs
560    fn json_schema(&self, custom_ops: &[(&str, JsonValue)]) -> JsonValue {
561        // IP address pattern
562        let ip_pattern = format!(r"^(?:{IPV4_PATTERN}|(?i:{IPV6_PATTERN}))");
563
564        // Standard schemas
565        let ip_schema = json!({ "type": "string", "pattern": ip_pattern });
566        let ip_array_schema = json!({ "type": "array", "items": ip_schema });
567
568        // Add operator schemas
569        let mut properties = Map::new();
570
571        // Add custom operators
572        for (op, schema) in custom_ops {
573            properties.insert(op.to_string(), schema.clone());
574        }
575
576        json!({
577            "oneOf": [
578                ip_schema,
579                ip_array_schema,
580                // Object with a single operator
581                {
582                    "type": "object",
583                    "properties": properties,
584                    "additionalProperties": false,
585                    "minProperties": 1,
586                    "maxProperties": 1
587                }
588            ]
589        })
590    }
591}
592
593#[cfg(test)]
594mod tests {
595    use std::any::{Any, TypeId};
596
597    use serde_json::json;
598
599    use super::*;
600
601    /// Helper to test if compilation resulted in an error
602    #[track_caller]
603    fn assert_compile_error<M>(matcher: M, value: JsonValue, expected_msg: &str)
604    where
605        M: Matcher<()>,
606    {
607        let result = matcher.compile(&value);
608        assert!(result.is_err());
609        let err = result.unwrap_err().to_string();
610        assert!(
611            err.contains(expected_msg),
612            "Expected error message to contain `{expected_msg}` but got `{err}`",
613        );
614    }
615
616    // Helper function to compile JSON value and extract operator
617    #[track_caller]
618    fn compile_op<T: Any>(matcher: impl Matcher<()>, value: JsonValue) -> (T, &'static str) {
619        let type_id = TypeId::of::<T>();
620        let op = (matcher.compile(&value)).expect("Failed to compile operator");
621        let (boxed, variant): (Box<dyn Any>, &'static str) = match op {
622            Operator::Equal(val) if type_id == val.type_id() => (Box::new(val), "Equal"),
623            Operator::LessThan(val) if type_id == val.type_id() => (Box::new(val), "LessThan"),
624            Operator::LessThanOrEqual(val) if type_id == val.type_id() => {
625                (Box::new(val), "LessThanOrEqual")
626            }
627            Operator::GreaterThan(val) if type_id == val.type_id() => {
628                (Box::new(val), "GreaterThan")
629            }
630            Operator::GreaterThanOrEqual(val) if type_id == val.type_id() => {
631                (Box::new(val), "GreaterThanOrEqual")
632            }
633            Operator::InSet(val) if type_id == val.type_id() => (Box::new(val), "InSet"),
634            Operator::Regex(val) if type_id == val.type_id() => (Box::new(val), "Regex"),
635            Operator::RegexSet(val) if type_id == val.type_id() => (Box::new(val), "RegexSet"),
636            Operator::IpSet(val) if type_id == TypeId::of::<IpnetTrie<()>>() => {
637                (Box::new(val), "IpSet")
638            }
639            op => panic!("Unexpected operator type or value type mismatch: {op:?}"),
640        };
641        // Downcast to the expected type
642        (*boxed.downcast::<T>().unwrap(), variant)
643    }
644
645    #[test]
646    fn test_default_matcher() {
647        #[track_caller]
648        fn assert_default_compile_error(value: JsonValue, expected_msg: &str) {
649            assert_compile_error(DefaultMatcher, value, expected_msg);
650        }
651
652        // Test with primitive types (all should create Equal operators)
653
654        // Test with null value
655        let (v, variant) = compile_op::<Value>(DefaultMatcher, json!(null));
656        assert_eq!(variant, "Equal");
657        assert_eq!(v, Value::None);
658
659        // Test with boolean value
660        let (v, variant) = compile_op::<Value>(DefaultMatcher, json!(true));
661        assert_eq!(variant, "Equal");
662        assert_eq!(v, Value::Bool(true));
663
664        // Test with number value
665        let (v, variant) = compile_op::<Value>(DefaultMatcher, json!(42));
666        assert_eq!(variant, "Equal");
667        assert_eq!(v, Value::from(42));
668
669        // Test with string value
670        let (v, variant) = compile_op::<Value>(DefaultMatcher, json!("hello"));
671        assert_eq!(variant, "Equal");
672        assert_eq!(v, Value::from("hello"));
673
674        // Test with array of mixed values (creates InSet operator)
675        let (set, variant) =
676            compile_op::<HashSet<Value>>(DefaultMatcher, json!([1, "hello", true]));
677        assert_eq!(variant, "InSet");
678        assert_eq!(set.len(), 3);
679        assert!(set.contains(&Value::from(1)));
680        assert!(set.contains(&Value::from("hello")));
681        assert!(set.contains(&Value::Bool(true)));
682
683        // Test comparison operators with different value types
684
685        // Less than with number
686        let (v, variant) = compile_op::<Value>(DefaultMatcher, json!({"<": 100}));
687        assert_eq!(variant, "LessThan");
688        assert_eq!(v, Value::from(100));
689
690        // Less than or equal with string
691        let (v, variant) = compile_op::<Value>(DefaultMatcher, json!({"<=": "hello"}));
692        assert_eq!(variant, "LessThanOrEqual");
693        assert_eq!(v, Value::from("hello"));
694
695        // Greater than with number
696        let (v, variant) = compile_op::<Value>(DefaultMatcher, json!({">": 100}));
697        assert_eq!(variant, "GreaterThan");
698        assert_eq!(v, Value::from(100));
699
700        // Greater than or equal with boolean
701        let (v, variant) = compile_op::<Value>(DefaultMatcher, json!({">=": true}));
702        assert_eq!(variant, "GreaterThanOrEqual");
703        assert_eq!(v, Value::Bool(true));
704
705        // Equal with null
706        let (v, variant) = compile_op::<Value>(DefaultMatcher, json!({"==": null}));
707        assert_eq!(variant, "Equal");
708        assert_eq!(v, Value::None);
709
710        // Test "in" operator with mixed array
711        let (set, variant) =
712            compile_op::<HashSet<Value>>(DefaultMatcher, json!({"in": [1, "hello", true, null]}));
713        assert_eq!(variant, "InSet");
714        assert_eq!(set.len(), 4);
715        assert!(set.contains(&Value::from(1)));
716        assert!(set.contains(&Value::from("hello")));
717        assert!(set.contains(&Value::Bool(true)));
718        assert!(set.contains(&Value::None));
719
720        // Test regex operator
721        let (re, variant) = compile_op::<Regex>(DefaultMatcher, json!({"re": "^hello$"}));
722        assert_eq!(variant, "Regex");
723        assert!(re.is_match("hello"));
724        assert!(!re.is_match("hello world"));
725
726        // Test regex set
727        let (re_set, variant) =
728            compile_op::<RegexSet>(DefaultMatcher, json!({"re": ["^hello$", "^world$"]}));
729        assert_eq!(variant, "RegexSet");
730        assert!(re_set.is_match("hello"));
731        assert!(re_set.is_match("world"));
732        assert!(!re_set.is_match("hello world"));
733
734        // Test IP set
735        let (_, variant) = compile_op::<IpnetTrie<()>>(
736            DefaultMatcher,
737            json!({"ip": ["192.168.1.1", "10.0.0.0/8"]}),
738        );
739        assert_eq!(variant, "IpSet");
740
741        // Error cases
742        assert_default_compile_error(
743            json!({"in": true}),
744            "error in 'in' operator: expected array, got boolean",
745        );
746        assert_default_compile_error(
747            json!({"re": true}),
748            "error in 're' operator: expected string or array, got boolean",
749        );
750        assert_default_compile_error(
751            json!({"ip": true}),
752            "error in 'ip' operator: expected array, got boolean",
753        );
754        assert_default_compile_error(json!({"unknown": "value"}), "unknown operator 'unknown'");
755    }
756
757    #[test]
758    fn test_string_matcher() {
759        #[track_caller]
760        fn assert_str_compile_error(value: JsonValue, expected_msg: &str) {
761            assert_compile_error(StringMatcher, value, expected_msg);
762        }
763
764        // Test equality with a string literal
765        let (s, variant) = compile_op::<Value>(StringMatcher, json!("hello"));
766        assert_eq!(variant, "Equal");
767        assert_eq!(s, Value::from("hello"));
768
769        // Test array of strings (creates InSet operator)
770        let (set, variant) = compile_op::<HashSet<Value>>(StringMatcher, json!(["hello", "world"]));
771        assert_eq!(variant, "InSet");
772        assert_eq!(set.len(), 2);
773        assert!(set.contains(&Value::from("hello")));
774        assert!(set.contains(&Value::from("world")));
775
776        // Test comparison operators
777        let (s, variant) = compile_op::<Value>(StringMatcher, json!({"<": "hello"}));
778        assert_eq!(variant, "LessThan");
779        assert_eq!(s, Value::from("hello"));
780
781        let (s, variant) = compile_op::<Value>(StringMatcher, json!({"<=": "hello"}));
782        assert_eq!(variant, "LessThanOrEqual");
783        assert_eq!(s, Value::from("hello"));
784
785        let (s, variant) = compile_op::<Value>(StringMatcher, json!({">": "hello"}));
786        assert_eq!(variant, "GreaterThan");
787        assert_eq!(s, Value::from("hello"));
788
789        let (s, variant) = compile_op::<Value>(StringMatcher, json!({">=": "hello"}));
790        assert_eq!(variant, "GreaterThanOrEqual");
791        assert_eq!(s, Value::from("hello"));
792
793        let (s, variant) = compile_op::<Value>(StringMatcher, json!({"==": "hello"}));
794        assert_eq!(variant, "Equal");
795        assert_eq!(s, Value::from("hello"));
796
797        // Test in operator
798        let (set, variant) =
799            compile_op::<HashSet<Value>>(StringMatcher, json!({"in": ["hello", "world"]}));
800        assert_eq!(variant, "InSet");
801        assert_eq!(set.len(), 2);
802        assert!(set.contains(&Value::from("hello")));
803        assert!(set.contains(&Value::from("world")));
804
805        // Test regex operator with single pattern
806        let (re, variant) = compile_op::<Regex>(StringMatcher, json!({"re": "^hello$"}));
807        assert_eq!(variant, "Regex");
808        assert!(re.is_match("hello"));
809
810        // Test regex operator with multiple patterns
811        let (re, variant) =
812            compile_op::<RegexSet>(StringMatcher, json!({"re": ["^hello$", "^world$"]}));
813        assert_eq!(variant, "RegexSet");
814        assert!(re.is_match("hello"));
815        assert!(!re.is_match("hello world"));
816
817        // Test error cases
818        assert_str_compile_error(json!(true), "unexpected JSON boolean");
819        assert_str_compile_error(json!({"in": true}), "expected array, got boolean");
820        assert_str_compile_error(
821            json!({"<": true}),
822            "error in '<' operator: expected string, got boolean",
823        );
824        assert_str_compile_error(
825            json!({"re": true}),
826            "error in 're' operator: expected string or array, got boolean",
827        );
828        assert_str_compile_error(json!({"unknown": "value"}), "unknown operator 'unknown'");
829    }
830
831    #[test]
832    fn test_regex_matcher() {
833        #[track_caller]
834        fn assert_regex_compile_error(value: JsonValue, expected_msg: &str) {
835            assert_compile_error(RegexMatcher, value, expected_msg);
836        }
837
838        // Test with a single regex pattern
839        let (re, variant) = compile_op::<Regex>(RegexMatcher, json!("^hello$"));
840        assert_eq!(variant, "Regex");
841        assert!(re.is_match("hello"));
842        assert!(!re.is_match("hello world"));
843
844        // Test with multiple patterns as array
845        let (re_set, variant) = compile_op::<RegexSet>(RegexMatcher, json!(["^hello$", "^world$"]));
846        assert_eq!(variant, "RegexSet");
847        assert!(re_set.is_match("hello"));
848        assert!(re_set.is_match("world"));
849        assert!(!re_set.is_match("hello world"));
850
851        // Test error cases
852        assert_regex_compile_error(json!(123), "unexpected JSON number");
853        assert_regex_compile_error(json!(true), "unexpected JSON boolean");
854        assert_regex_compile_error(json!({"invalid": "pattern"}), "unknown operator 'invalid'");
855        assert_regex_compile_error(json!("(invalid"), "regex parse error");
856    }
857
858    #[test]
859    fn test_number_matcher() {
860        #[track_caller]
861        fn assert_num_compile_error(value: JsonValue, expected_msg: &str) {
862            assert_compile_error(NumberMatcher, value, expected_msg);
863        }
864
865        // Test equality with a number literal
866        let (n, variant) = compile_op::<Value>(NumberMatcher, json!(42));
867        assert_eq!(variant, "Equal");
868        assert_eq!(n, Value::from(42));
869
870        // Test with a decimal number
871        let (n, variant) = compile_op::<Value>(NumberMatcher, json!(3.14));
872        assert_eq!(variant, "Equal");
873        assert_eq!(n, Value::try_from(3.14).unwrap());
874
875        // Test array of numbers (creates InSet operator)
876        let (set, variant) = compile_op::<HashSet<Value>>(NumberMatcher, json!([1, 3]));
877        assert_eq!(variant, "InSet");
878        assert_eq!(set.len(), 2);
879        assert!(set.contains(&Value::from(1)));
880        assert!(!set.contains(&Value::from(2)));
881        assert!(set.contains(&Value::from(3)));
882
883        // Test comparison operators
884        let (n, variant) = compile_op::<Value>(NumberMatcher, json!({"<": 100}));
885        assert_eq!(variant, "LessThan");
886        assert_eq!(n, Value::from(100));
887
888        let (n, variant) = compile_op::<Value>(NumberMatcher, json!({"<=": 100}));
889        assert_eq!(variant, "LessThanOrEqual");
890        assert_eq!(n, Value::from(100));
891
892        let (n, variant) = compile_op::<Value>(NumberMatcher, json!({">": 100}));
893        assert_eq!(variant, "GreaterThan");
894        assert_eq!(n, Value::from(100));
895
896        let (n, variant) = compile_op::<Value>(NumberMatcher, json!({">=": 100}));
897        assert_eq!(variant, "GreaterThanOrEqual");
898        assert_eq!(n, Value::from(100));
899
900        let (n, variant) = compile_op::<Value>(NumberMatcher, json!({"==": 100}));
901        assert_eq!(variant, "Equal");
902        assert_eq!(n, Value::from(100));
903
904        // Test in operator
905        let (set, variant) = compile_op::<HashSet<Value>>(NumberMatcher, json!({"in": [1, 3]}));
906        assert_eq!(variant, "InSet");
907        assert_eq!(set.len(), 2);
908        assert!(set.contains(&Value::from(1)));
909        assert!(!set.contains(&Value::from(2)));
910        assert!(set.contains(&Value::from(3)));
911
912        // Test in operator with decimal numbers
913        let (set, variant) = compile_op::<HashSet<Value>>(NumberMatcher, json!({"in": [1.5, 3.5]}));
914        assert_eq!(variant, "InSet");
915        assert_eq!(set.len(), 2);
916        assert!(set.contains(&Value::try_from(1.5).unwrap()));
917        assert!(!set.contains(&Value::try_from(2.5).unwrap()));
918        assert!(set.contains(&Value::try_from(3.5).unwrap()));
919
920        // Test error cases
921        assert_num_compile_error(json!("string"), "unexpected JSON string");
922        assert_num_compile_error(json!(true), "unexpected JSON boolean");
923        assert_num_compile_error(
924            json!({"<": "string"}),
925            "error in '<' operator: expected number, got string",
926        );
927        assert_num_compile_error(
928            json!({"in": true}),
929            "error in 'in' operator: expected array, got boolean",
930        );
931        assert_num_compile_error(
932            json!({"in": [1, "string"]}),
933            "error in 'in' operator: got string in number array",
934        );
935        assert_num_compile_error(json!({"unknown": 100}), "unknown operator 'unknown'");
936    }
937
938    #[test]
939    fn test_bool_matcher() {
940        // Test with true value
941        let (b, variant) = compile_op::<Value>(BoolMatcher, json!(true));
942        assert_eq!(variant, "Equal");
943        assert_eq!(b, Value::Bool(true));
944
945        // Test with false value
946        let (b, variant) = compile_op::<Value>(BoolMatcher, json!(false));
947        assert_eq!(variant, "Equal");
948        assert_eq!(b, Value::Bool(false));
949
950        // Test error cases
951        assert_compile_error(BoolMatcher, json!("string"), "expected boolean, got string");
952        assert_compile_error(BoolMatcher, json!(123), "expected boolean, got number");
953        assert_compile_error(BoolMatcher, json!([true]), "expected boolean, got array");
954        assert_compile_error(
955            BoolMatcher,
956            json!({"==": true}),
957            "expected boolean, got object",
958        );
959    }
960
961    #[test]
962    fn test_ip_matcher() {
963        // IP Matcher specific test helpers
964        fn ip(s: &str) -> IpNet {
965            IpNet::from(IpAddr::from_str(s).unwrap())
966        }
967
968        #[track_caller]
969        fn assert_ip_compile_error(value: JsonValue, expected_msg: &str) {
970            assert_compile_error(IpMatcher, value, expected_msg);
971        }
972
973        #[track_caller]
974        fn assert_ip_matches(trie: &IpnetTrie<()>, addr: &str) {
975            let ip = ip(addr);
976            assert!(trie.longest_match(&ip).is_some(), "{addr} should match");
977        }
978
979        #[track_caller]
980        fn assert_ip_not_matches(trie: &IpnetTrie<()>, addr: &str) {
981            let ip = ip(addr);
982            assert!(trie.longest_match(&ip).is_none(), "{addr} should not match");
983        }
984
985        // Test with a single IP address string
986        let (trie, variant) = compile_op::<IpnetTrie<()>>(IpMatcher, json!("192.168.1.1"));
987        assert_eq!(variant, "IpSet");
988        assert_ip_matches(&trie, "192.168.1.1");
989        assert_ip_not_matches(&trie, "192.168.1.2");
990
991        // Test with a CIDR notation string
992        let (trie, variant) = compile_op::<IpnetTrie<()>>(IpMatcher, json!("192.168.1.0/24"));
993        assert_eq!(variant, "IpSet");
994        assert_ip_matches(&trie, "192.168.1.1");
995        assert_ip_matches(&trie, "192.168.1.254");
996        assert_ip_not_matches(&trie, "192.168.2.1");
997
998        // Test with an array of mixed IP addresses and CIDR notations
999        let (trie, variant) =
1000            compile_op::<IpnetTrie<()>>(IpMatcher, json!(["192.168.1.1", "10.0.0.0/8"]));
1001        assert_eq!(variant, "IpSet");
1002        assert_ip_matches(&trie, "192.168.1.1");
1003        assert_ip_matches(&trie, "10.1.2.3");
1004        assert_ip_not_matches(&trie, "11.0.0.1");
1005
1006        // Test with IPv6 addresses
1007        let (trie, variant) =
1008            compile_op::<IpnetTrie<()>>(IpMatcher, json!(["2001:db8::1", "2001:db8::/32"]));
1009        assert_eq!(variant, "IpSet");
1010        assert_ip_matches(&trie, "2001:db8:1::1");
1011
1012        // Test error cases
1013        assert_ip_compile_error(json!("invalid-ip"), "invalid IP address syntax");
1014        assert_ip_compile_error(json!(123), "unexpected JSON number");
1015        assert_ip_compile_error(json!({"invalid": "pattern"}), "unknown operator 'invalid'");
1016    }
1017}