rusty_rules/
matcher.rs

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