rusty_rules/
lib.rs

1//! # Rusty Rules
2//!
3//! A blazingly fast, flexible, and extensible rules engine written in Rust.
4//! Evaluate complex logical rules against custom data structures using a simple JSON-based DSL.
5//!
6//! ## Features
7//!
8//! - **Composable rules**: Combine conditions with `all`, `any`, and `not` logical blocks for complex rule hierarchies
9//! - **Custom fetchers**: Extract values from data structures with named fetchers that accept arguments
10//! - **Matcher support**: String, regex, IP address, numeric, and boolean matchers out of the box
11//! - **Custom operators**: Define operators for advanced matching and domain-specific logic
12//! - **Async support**: Register async fetchers and operators for use with async/await contexts
13//! - **JSON-schema validation**: Validate rules with automatically generated JSON schema (requires `validation` feature)
14//! - **Thread-safety option**: Optional `Send`/`Sync` trait bounds with the `send` feature flag
15//! - **Performance-focused**: Designed for high-throughput rule evaluation with minimal overhead
16//!
17//! ## Basic Usage
18//!
19//! Here's how to use Rusty Rules with a custom context type:
20//!
21//! ```rust
22//! use std::collections::HashMap;
23//! use std::net::IpAddr;
24//! use rusty_rules::{Engine, Value};
25//! use serde_json::json;
26//!
27//! // 1. Define context type
28//! struct MyContext {
29//!     method: String,
30//!     path: String,
31//!     headers: HashMap<String, String>,
32//!     addr: IpAddr,
33//! }
34//!
35//! // 2. Create a new engine
36//! let mut engine = Engine::new();
37//!
38//! // 3. Register fetchers to extract values from context
39//! engine.register_fetcher("method", |ctx: &MyContext, _args| {
40//!     Ok(Value::from(&ctx.method))
41//! });
42//!
43//! engine.register_fetcher("header", |ctx: &MyContext, args| {
44//!     Ok(args.first().and_then(|name| ctx.headers.get(name)).into())
45//! });
46//!
47//! engine.register_fetcher("addr", |ctx: &MyContext, _args| {
48//!     Ok(Value::Ip(ctx.addr))
49//! });
50//!
51//! // 4. Compile a rule from JSON
52//! let rule = engine.compile_rule(&json!({
53//!     "all": [
54//!         {"method": "GET"},
55//!         {"header(host)": "www.example.com"},
56//!         {"addr": {"ip": ["10.0.0.0/8"]}}
57//!     ]
58//! })).unwrap();
59//!
60//! // 5. Evaluate the rule against a context
61//! let ctx = MyContext {
62//!     method: "GET".to_string(),
63//!     path: "/api/v1/users".to_string(),
64//!     headers: {
65//!         let mut h = HashMap::new();
66//!         h.insert("host".to_string(), "www.example.com".to_string());
67//!         h
68//!     },
69//!     addr: "10.1.2.3".parse().unwrap(),
70//! };
71//!
72//! assert!(rule.evaluate(&ctx).unwrap());
73//! ```
74//!
75//! ## Rule Composition
76//!
77//! Rules can be composed using logical operators:
78//!
79//! ```json
80//! {
81//!     "all": [              // All conditions must match (logical AND)
82//!         { "method": "GET" },
83//!         { "path": { "regex": "^/api/v\\d+" } },
84//!         {
85//!             "any": [      // Any condition must match (logical OR)
86//!                 { "header(auth)": { "exists": true } },
87//!                 { "ip": { "cidr": "10.0.0.0/8" } }
88//!             ]
89//!         },
90//!         {
91//!             "not": [      // Negate the condition (logical NOT)
92//!                 { "header(user-agent)": "BadBot/1.0" }
93//!             ]
94//!         }
95//!     ]
96//! }
97//! ```
98//!
99//! ## Custom Operators
100//!
101//! You can extend the engine with custom operators:
102//!
103//! ```rust
104//! # use rusty_rules::{Engine, Operator, Value};
105//! # use serde_json::{json, Value as JsonValue};
106//! # struct MyContext {}
107//! # let mut engine = Engine::new();
108//! #
109//! # engine.register_fetcher("path", |ctx: &MyContext, _args| {
110//! #     Ok(Value::from("/api/v1/users"))
111//! # });
112//! #
113//! // Register a custom string prefix operator
114//! engine.register_operator("starts_with", |value: JsonValue| {
115//!     let prefix = value.as_str().ok_or("prefix must be a string")?.to_string();
116//!     Ok(Operator::new(move |_, value| {
117//!         Ok(value.as_str()
118//!             .map(|s| s.starts_with(&prefix))
119//!             .unwrap_or_default())
120//!     }))
121//! });
122//!
123//! // Use the custom operator in a rule
124//! let rule = engine.compile_rule(&json!({
125//!     "path": {
126//!         "starts_with": "/api/v1"
127//!     }
128//! })).unwrap();
129//!
130//! # assert!(rule.evaluate(&MyContext {}).unwrap());
131//! ```
132//!
133//! ## JSON Schema Validation
134//!
135//! With the `validation` feature enabled, you can validate rules against a dynamically generated schema:
136//!
137//! ```rust
138//! # #[cfg(feature = "validation")]
139//! # {
140//! # use rusty_rules::Engine;
141//! # use serde_json::json;
142//! # struct MyContext {}
143//! #
144//! # let engine = Engine::<MyContext>::new();
145//! let rule = json!({
146//!     "all": [
147//!         {"method": "GET"},
148//!         {"path": {
149//!             "re": "^/api/v\\d+"
150//!         }}
151//!     ]
152//! });
153//!
154//! // Validate the rule against the engine's schema
155//! let result = engine.validate_rule(&rule);
156//! # _ = result;
157//! # }
158//! ```
159//!
160//! ## Feature Flags
161//!
162//! - **send** - Enables `Send` and `Sync` trait bounds on all public types, making them safe to use across thread boundaries
163//! - **validation** - Enables JSON schema generation and validation functionality (adds `jsonschema` dependency)
164
165#![cfg_attr(docsrs, feature(doc_cfg))]
166
167use std::collections::HashMap;
168use std::fmt::Debug;
169use std::result::Result as StdResult;
170use std::sync::Arc;
171
172use ipnet::IpNet;
173use serde_json::{Map, Value as JsonValue, json};
174
175// Re-export commonly used types from external crates
176#[cfg(feature = "validation")]
177#[cfg_attr(docsrs, doc(cfg(feature = "validation")))]
178pub use jsonschema::ValidationError;
179
180// Re-export public types
181pub use error::Error;
182pub use matcher::{
183    BoolMatcher, DefaultMatcher, IpMatcher, Matcher, NumberMatcher, Operator, RegexMatcher,
184    StringMatcher,
185};
186pub use types::{AsyncCheckFn, BoxFuture, CheckFn, MaybeSend, MaybeSync, ToOperator};
187pub use value::Value;
188
189use crate::types::{AsyncEvalFn, AsyncFetcherFn, DynError, EvalFn, FetcherFn};
190
191pub(crate) type Result<T> = StdResult<T, error::Error>;
192
193/// Represents a rule, which can be a condition or a logical combination of other rules.
194///
195/// Rules can be composed using logical operators:
196/// - `Any`: At least one sub-rule must evaluate to `true`
197/// - `All`: All sub-rules must evaluate to `true`
198/// - `Not`: Negates the result of the contained rule
199/// - `Leaf`: A single condition that evaluates to a boolean
200pub enum Rule<Ctx: ?Sized + 'static> {
201    Any(Vec<Self>),
202    All(Vec<Self>),
203    Not(Box<Self>),
204    Leaf(Condition<Ctx>),
205}
206
207impl<Ctx: ?Sized> Debug for Rule<Ctx> {
208    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209        match self {
210            Rule::Any(rules) => f.debug_tuple("Any").field(rules).finish(),
211            Rule::All(rules) => f.debug_tuple("All").field(rules).finish(),
212            Rule::Not(rule) => f.debug_tuple("Not").field(rule).finish(),
213            Rule::Leaf(_) => f.debug_tuple("Leaf").finish(),
214        }
215    }
216}
217
218impl<Ctx: ?Sized> Clone for Rule<Ctx> {
219    fn clone(&self) -> Self {
220        match self {
221            Rule::Any(rules) => Rule::Any(rules.clone()),
222            Rule::All(rules) => Rule::All(rules.clone()),
223            Rule::Not(rule) => Rule::Not(rule.clone()),
224            Rule::Leaf(condition) => Rule::Leaf(condition.clone()),
225        }
226    }
227}
228
229/// Represents a condition that can be evaluated.
230///
231/// The condition is a wrapper around a function that takes a context and returns a boolean.
232#[doc(hidden)]
233pub struct Condition<Ctx: ?Sized>(AnyEvalFn<Ctx>);
234
235impl<Ctx: ?Sized> Clone for Condition<Ctx> {
236    fn clone(&self) -> Self {
237        Condition(self.0.clone())
238    }
239}
240
241impl<Ctx: ?Sized> Rule<Ctx> {
242    #[inline(always)]
243    fn any(mut rules: Vec<Rule<Ctx>>) -> Self {
244        if rules.len() == 1 {
245            return rules.pop().unwrap();
246        }
247        Rule::Any(rules)
248    }
249
250    #[inline(always)]
251    fn all(mut rules: Vec<Rule<Ctx>>) -> Self {
252        if rules.len() == 1 {
253            return rules.pop().unwrap();
254        }
255        Rule::All(rules)
256    }
257
258    #[inline(always)]
259    fn not(mut rules: Vec<Rule<Ctx>>) -> Self {
260        if rules.len() == 1 {
261            return Rule::Not(Box::new(rules.pop().unwrap()));
262        }
263        Rule::Not(Box::new(Rule::All(rules)))
264    }
265
266    #[inline(always)]
267    fn leaf(eval_fn: AnyEvalFn<Ctx>) -> Self {
268        Rule::Leaf(Condition(eval_fn))
269    }
270}
271
272/// Represents a fetcher key like `header(host)` with name and arguments.
273#[derive(Debug)]
274pub(crate) struct FetcherKey {
275    name: String,
276    args: Vec<String>,
277}
278
279enum AnyFetcherFn<Ctx: ?Sized> {
280    Sync(Arc<FetcherFn<Ctx>>),
281    Async(Arc<AsyncFetcherFn<Ctx>>),
282}
283
284impl<Ctx: ?Sized> Clone for AnyFetcherFn<Ctx> {
285    fn clone(&self) -> Self {
286        match self {
287            AnyFetcherFn::Sync(func) => AnyFetcherFn::Sync(func.clone()),
288            AnyFetcherFn::Async(func) => AnyFetcherFn::Async(func.clone()),
289        }
290    }
291}
292
293enum AnyEvalFn<Ctx: ?Sized> {
294    Sync(EvalFn<Ctx>),
295    Async(AsyncEvalFn<Ctx>),
296}
297
298impl<Ctx: ?Sized> Clone for AnyEvalFn<Ctx> {
299    fn clone(&self) -> Self {
300        match self {
301            AnyEvalFn::Sync(func) => AnyEvalFn::Sync(func.clone()),
302            AnyEvalFn::Async(func) => AnyEvalFn::Async(func.clone()),
303        }
304    }
305}
306
307/// Holds a fetcher's required matcher type and function.
308///
309/// A fetcher is responsible for extracting values from the context type.
310/// Each fetcher has:
311/// - A function that extracts values from the context
312/// - A matcher that determines how to compare these values to the rule conditions
313pub struct Fetcher<Ctx: ?Sized> {
314    matcher: Arc<dyn Matcher<Ctx>>,
315    func: AnyFetcherFn<Ctx>,
316    raw_args: bool,
317}
318
319impl<Ctx: ?Sized> Clone for Fetcher<Ctx> {
320    fn clone(&self) -> Self {
321        Fetcher {
322            matcher: self.matcher.clone(),
323            func: self.func.clone(),
324            raw_args: self.raw_args,
325        }
326    }
327}
328
329impl<Ctx: ?Sized> Fetcher<Ctx> {
330    /// Changes the fetcher's matcher
331    pub fn with_matcher<M>(&mut self, matcher: M) -> &mut Self
332    where
333        M: Matcher<Ctx> + 'static,
334    {
335        self.matcher = Arc::new(matcher);
336        self
337    }
338
339    /// Sets whether the fetcher should receive raw arguments instead of splitting them.
340    pub fn with_raw_args(&mut self, raw_args: bool) -> &mut Self {
341        self.raw_args = raw_args;
342        self
343    }
344}
345
346/// Rules engine for registering fetchers/operators and parsing rules.
347///
348/// # Type Parameters
349///
350/// - `Ctx`: The context type that rules will be evaluated against
351///
352/// # Example
353///
354/// ```rust
355/// # use std::collections::HashMap;
356/// # use rusty_rules::{Engine, Value};
357/// # use serde_json::json;
358/// struct User {
359///     name: String,
360///     age: u32,
361///     roles: Vec<String>,
362/// }
363///
364/// let mut engine = Engine::new();
365///
366/// engine.register_fetcher("name", |user: &User, _args| {
367///     Ok(Value::from(&user.name))
368/// });
369///
370/// engine.register_fetcher("age", |user: &User, _args| {
371///     Ok(Value::from(user.age))
372/// });
373///
374/// engine.register_fetcher("has_role", |user: &User, args| {
375///     let role = args.first().ok_or("Role name required")?;
376///     Ok(Value::from(user.roles.contains(&role)))
377/// });
378///
379/// let rule = engine.compile_rule(&json!([
380///     {"age": {">=": 18}},
381///     {"has_role(admin)": true}
382/// ])).unwrap();
383/// ```
384pub struct Engine<Ctx: MaybeSync + ?Sized + 'static> {
385    fetchers: HashMap<String, Fetcher<Ctx>>,
386    operators: HashMap<String, Arc<dyn ToOperator<Ctx>>>,
387}
388
389impl<Ctx: MaybeSync + ?Sized> Default for Engine<Ctx> {
390    fn default() -> Self {
391        Self::new()
392    }
393}
394
395impl<Ctx: MaybeSync + ?Sized> Clone for Engine<Ctx> {
396    fn clone(&self) -> Self {
397        Engine {
398            fetchers: self.fetchers.clone(),
399            operators: self.operators.clone(),
400        }
401    }
402}
403
404impl<Ctx: MaybeSync + ?Sized> Engine<Ctx> {
405    /// Creates a new rules engine instance.
406    pub fn new() -> Self {
407        Engine {
408            fetchers: HashMap::new(),
409            operators: HashMap::new(),
410        }
411    }
412
413    /// Registers a synchronous fetcher with its name and function, using the default matcher.
414    ///
415    /// A fetcher is a function that extracts values from the context type. The fetcher name is used
416    /// in rule definitions to reference this fetcher. By default, the `DefaultMatcher` is used, which
417    /// supports basic equality and comparison operations.
418    ///
419    /// # Returns
420    ///
421    /// A mutable reference to the created `Fetcher`, allowing you to customize it (e.g., change the matcher)
422    pub fn register_fetcher<F>(&mut self, name: &str, func: F) -> &mut Fetcher<Ctx>
423    where
424        F: for<'a> Fn(&'a Ctx, &[String]) -> StdResult<Value<'a>, DynError>
425            + MaybeSend
426            + MaybeSync
427            + 'static,
428    {
429        let fetcher = Fetcher {
430            matcher: Arc::new(DefaultMatcher),
431            func: AnyFetcherFn::Sync(Arc::new(func)),
432            raw_args: false,
433        };
434        self.fetchers
435            .entry(name.to_string())
436            .insert_entry(fetcher)
437            .into_mut()
438    }
439
440    /// Registers an async fetcher with its name and function, using the default matcher.
441    ///
442    /// See [`Self::register_fetcher`] for more details.
443    pub fn register_async_fetcher<F>(&mut self, name: &str, func: F) -> &mut Fetcher<Ctx>
444    where
445        F: for<'a> Fn(&'a Ctx, Arc<[String]>) -> BoxFuture<'a, StdResult<Value<'a>, DynError>>
446            + MaybeSend
447            + MaybeSync
448            + 'static,
449    {
450        let fetcher = Fetcher {
451            matcher: Arc::new(DefaultMatcher),
452            func: AnyFetcherFn::Async(Arc::new(func)),
453            raw_args: false,
454        };
455        self.fetchers
456            .entry(name.to_string())
457            .insert_entry(fetcher)
458            .into_mut()
459    }
460
461    /// Registers a custom operator
462    pub fn register_operator<O>(&mut self, name: &str, op: O)
463    where
464        O: ToOperator<Ctx> + 'static,
465    {
466        self.operators.insert(name.to_string(), Arc::new(op));
467    }
468
469    /// Compiles a JSON value into a [`Rule::All`] using the registered fetchers and operators.
470    pub fn compile_rule(&self, value: &JsonValue) -> Result<Rule<Ctx>> {
471        self.compile_rule_inner(value).map(Rule::all)
472    }
473
474    /// Complines a JSON value into a vector of sub-rules.
475    fn compile_rule_inner(&self, value: &JsonValue) -> Result<Vec<Rule<Ctx>>> {
476        match value {
477            JsonValue::Object(map) => {
478                let mut subrules = Vec::with_capacity(map.len());
479                for (key, value) in map {
480                    match key.as_str() {
481                        "any" => subrules.push(Rule::any(self.compile_rule_inner(value)?)),
482                        "all" => subrules.push(Rule::all(self.compile_rule_inner(value)?)),
483                        "not" => subrules.push(Rule::not(self.compile_rule_inner(value)?)),
484                        _ => {
485                            let FetcherKey { name, args } = Self::parse_fetcher_key(key)?;
486                            let fetcher = (self.fetchers.get(&name)).ok_or_else(|| {
487                                Error::fetcher(&name, "fetcher is not registered")
488                            })?;
489                            let args = Self::parse_fetcher_args(args.clone(), fetcher.raw_args);
490
491                            let mut operator = fetcher.matcher.compile(value);
492                            // Try custom operator
493                            if let Err(Error::UnknownOperator(ref op)) = operator
494                                && let Some(op_builder) = self.operators.get(op)
495                            {
496                                operator = op_builder
497                                    .to_operator(&value[op])
498                                    .map_err(|err| Error::operator(op, err));
499                            }
500                            let operator = operator.map_err(|err| Error::matcher(&name, err))?;
501                            let fetcher_fn = fetcher.func.clone();
502                            let eval_fn =
503                                Self::compile_condition(fetcher_fn, args.into(), operator);
504
505                            subrules.push(Rule::leaf(eval_fn));
506                        }
507                    }
508                }
509                Ok(subrules)
510            }
511            JsonValue::Array(seq) => {
512                (seq.iter()).try_fold(Vec::with_capacity(seq.len()), |mut subrules, inner| {
513                    subrules.push(self.compile_rule_inner(inner).map(Rule::all)?);
514                    Result::Ok(subrules)
515                })
516            }
517            _ => Err(Error::json("rule must be a JSON object or array")),
518        }
519    }
520
521    /// Validates a JSON rule against dynamically generated JSON Schema of this engine.
522    #[cfg(feature = "validation")]
523    #[cfg_attr(docsrs, doc(cfg(feature = "validation")))]
524    #[allow(clippy::result_large_err)]
525    pub fn validate_rule<'a>(&self, value: &'a JsonValue) -> StdResult<(), ValidationError<'a>> {
526        // build dynamic JSON Schema based on registered fetchers
527        let schema = self.json_schema();
528        let validator = jsonschema::options()
529            .with_pattern_options(jsonschema::PatternOptions::regex())
530            .build(&schema)?;
531        validator.validate(value)
532    }
533
534    /// Builds a JSON Schema for rules, including dynamic properties.
535    pub fn json_schema(&self) -> JsonValue {
536        let mut pattern_props = Map::new();
537
538        // Get custom operator schemas
539        let custom_ops: Vec<(&str, JsonValue)> = (self.operators.iter())
540            .map(|(k, v)| (k.as_str(), v.json_schema()))
541            .collect();
542
543        // For each fetcher, get its matcher's schema or use a default
544        for (name, fetcher) in &self.fetchers {
545            let pattern = format!(r"^{}(:?\(([^)]*)\))?$", regex::escape(name));
546            let schema = fetcher.matcher.json_schema(&custom_ops);
547            pattern_props.insert(pattern, schema);
548        }
549
550        json!({
551            "$schema": "http://json-schema.org/draft-07/schema",
552            "$ref": "#/definitions/rule_object",
553            "definitions": {
554                "rule_object": {
555                    "type": "object",
556                    "properties": {
557                        "any": { "$ref": "#/definitions/rule" },
558                        "all": { "$ref": "#/definitions/rule" },
559                        "not": { "$ref": "#/definitions/rule" }
560                    },
561                    "patternProperties": pattern_props,
562                    "additionalProperties": false,
563                },
564                "rule_array": {
565                    "type": "array",
566                    "minItems": 1,
567                    "items": { "$ref": "#/definitions/rule_object" },
568                },
569                "rule": {
570                    "if": { "type": "array" },
571                    "then": { "$ref": "#/definitions/rule_array" },
572                    "else": { "$ref": "#/definitions/rule_object" }
573                },
574            }
575        })
576    }
577
578    fn compile_condition(
579        fetcher_fn: AnyFetcherFn<Ctx>,
580        fetcher_args: Arc<[String]>,
581        operator: Operator<Ctx>,
582    ) -> AnyEvalFn<Ctx> {
583        match (fetcher_fn, operator) {
584            // Equal
585            (AnyFetcherFn::Sync(fetcher_fn), Operator::Equal(right)) => {
586                AnyEvalFn::Sync(Arc::new(move |ctx| {
587                    Ok(fetcher_fn(ctx, &fetcher_args)? == right)
588                }))
589            }
590            (AnyFetcherFn::Async(fetcher_fn), Operator::Equal(right)) => {
591                let right = Arc::new(right);
592                AnyEvalFn::Async(Arc::new(move |ctx| {
593                    let right = right.clone();
594                    let value = fetcher_fn(ctx, fetcher_args.clone());
595                    Box::pin(async move { Ok(value.await? == *right) })
596                }))
597            }
598
599            // LessThan
600            (AnyFetcherFn::Sync(fetcher_fn), Operator::LessThan(right)) => {
601                AnyEvalFn::Sync(Arc::new(move |ctx| {
602                    Ok(fetcher_fn(ctx, &fetcher_args)? < right)
603                }))
604            }
605            (AnyFetcherFn::Async(fetcher_fn), Operator::LessThan(right)) => {
606                let right = Arc::new(right);
607                AnyEvalFn::Async(Arc::new(move |ctx| {
608                    let right = right.clone();
609                    let value = fetcher_fn(ctx, fetcher_args.clone());
610                    Box::pin(async move { Ok(value.await? < *right) })
611                }))
612            }
613
614            // LessThanOrEqual
615            (AnyFetcherFn::Sync(fetcher_fn), Operator::LessThanOrEqual(right)) => {
616                AnyEvalFn::Sync(Arc::new(move |ctx| {
617                    Ok(fetcher_fn(ctx, &fetcher_args)? <= right)
618                }))
619            }
620            (AnyFetcherFn::Async(fetcher_fn), Operator::LessThanOrEqual(right)) => {
621                let right = Arc::new(right);
622                AnyEvalFn::Async(Arc::new(move |ctx| {
623                    let right = right.clone();
624                    let value = fetcher_fn(ctx, fetcher_args.clone());
625                    Box::pin(async move { Ok(value.await? <= *right) })
626                }))
627            }
628
629            // GreaterThan
630            (AnyFetcherFn::Sync(fetcher_fn), Operator::GreaterThan(right)) => {
631                AnyEvalFn::Sync(Arc::new(move |ctx| {
632                    Ok(fetcher_fn(ctx, &fetcher_args)? > right)
633                }))
634            }
635            (AnyFetcherFn::Async(fetcher_fn), Operator::GreaterThan(right)) => {
636                let right = Arc::new(right);
637                AnyEvalFn::Async(Arc::new(move |ctx| {
638                    let right = right.clone();
639                    let value = fetcher_fn(ctx, fetcher_args.clone());
640                    Box::pin(async move { Ok(value.await? > *right) })
641                }))
642            }
643
644            // GreaterThanOrEqual
645            (AnyFetcherFn::Sync(fetcher_fn), Operator::GreaterThanOrEqual(right)) => {
646                AnyEvalFn::Sync(Arc::new(move |ctx| {
647                    Ok(fetcher_fn(ctx, &fetcher_args)? >= right)
648                }))
649            }
650            (AnyFetcherFn::Async(fetcher_fn), Operator::GreaterThanOrEqual(right)) => {
651                let right = Arc::new(right);
652                AnyEvalFn::Async(Arc::new(move |ctx| {
653                    let right = right.clone();
654                    let value = fetcher_fn(ctx, fetcher_args.clone());
655                    Box::pin(async move { Ok(value.await? >= *right) })
656                }))
657            }
658
659            // InSet
660            (AnyFetcherFn::Sync(fetcher_fn), Operator::InSet(set)) => {
661                AnyEvalFn::Sync(Arc::new(move |ctx| {
662                    fetcher_fn(ctx, &fetcher_args).map(|val| set.contains(&val))
663                }))
664            }
665            (AnyFetcherFn::Async(fetcher_fn), Operator::InSet(set)) => {
666                let set = Arc::new(set);
667                AnyEvalFn::Async(Arc::new(move |ctx| {
668                    let set = set.clone();
669                    let value = fetcher_fn(ctx, fetcher_args.clone());
670                    Box::pin(async move { value.await.map(|val| set.contains(&val)) })
671                }))
672            }
673
674            // Regex
675            (AnyFetcherFn::Sync(fetcher_fn), Operator::Regex(regex)) => {
676                AnyEvalFn::Sync(Arc::new(move |ctx| {
677                    fetcher_fn(ctx, &fetcher_args)
678                        .map(|val| val.as_str().map(|s| regex.is_match(s)).unwrap_or(false))
679                }))
680            }
681            (AnyFetcherFn::Async(fetcher_fn), Operator::Regex(regex)) => {
682                let regex = Arc::new(regex);
683                AnyEvalFn::Async(Arc::new(move |ctx| {
684                    let regex = regex.clone();
685                    let value = fetcher_fn(ctx, fetcher_args.clone());
686                    Box::pin(async move {
687                        (value.await)
688                            .map(|val| val.as_str().map(|s| regex.is_match(s)).unwrap_or(false))
689                    })
690                }))
691            }
692
693            // RegexSet
694            (AnyFetcherFn::Sync(fetcher_fn), Operator::RegexSet(regex_set)) => {
695                AnyEvalFn::Sync(Arc::new(move |ctx| {
696                    fetcher_fn(ctx, &fetcher_args)
697                        .map(|val| val.as_str().map(|s| regex_set.is_match(s)).unwrap_or(false))
698                }))
699            }
700            (AnyFetcherFn::Async(fetcher_fn), Operator::RegexSet(regex_set)) => {
701                let regex_set = Arc::new(regex_set);
702                AnyEvalFn::Async(Arc::new(move |ctx| {
703                    let regex_set = regex_set.clone();
704                    let value = fetcher_fn(ctx, fetcher_args.clone());
705                    Box::pin(async move {
706                        (value.await)
707                            .map(|val| val.as_str().map(|s| regex_set.is_match(s)).unwrap_or(false))
708                    })
709                }))
710            }
711
712            // IpSet
713            (AnyFetcherFn::Sync(fetcher_fn), Operator::IpSet(set)) => {
714                AnyEvalFn::Sync(Arc::new(move |ctx| {
715                    Ok((fetcher_fn(ctx, &fetcher_args)?.to_ip())
716                        .map(|ip| set.longest_match(&IpNet::from(ip)).is_some())
717                        .unwrap_or(false))
718                }))
719            }
720            (AnyFetcherFn::Async(fetcher_fn), Operator::IpSet(set)) => {
721                let set = Arc::new(set);
722                AnyEvalFn::Async(Arc::new(move |ctx| {
723                    let set = set.clone();
724                    let value = fetcher_fn(ctx, fetcher_args.clone());
725                    Box::pin(async move {
726                        Ok((value.await?.to_ip())
727                            .map(|ip| set.longest_match(&IpNet::from(ip)).is_some())
728                            .unwrap_or(false))
729                    })
730                }))
731            }
732
733            // Custom operator
734            (AnyFetcherFn::Sync(fetcher_fn), Operator::Custom(op_fn)) => {
735                AnyEvalFn::Sync(Arc::new(move |ctx| {
736                    let value = fetcher_fn(ctx, &fetcher_args)?;
737                    op_fn(ctx, value)
738                }))
739            }
740            (AnyFetcherFn::Async(fetcher_fn), Operator::Custom(op_fn)) => {
741                let op_fn: Arc<CheckFn<Ctx>> = op_fn.into();
742                AnyEvalFn::Async(Arc::new(move |ctx| {
743                    let op_fn = op_fn.clone();
744                    let value = fetcher_fn(ctx, fetcher_args.clone());
745                    Box::pin(async move { op_fn(ctx, value.await?) })
746                }))
747            }
748
749            // Custom async operator
750            (AnyFetcherFn::Sync(fetcher_fn), Operator::CustomAsync(op_fn)) => {
751                let op_fn: Arc<AsyncCheckFn<Ctx>> = op_fn.into();
752                AnyEvalFn::Async(Arc::new(move |ctx| {
753                    let op_fn = op_fn.clone();
754                    let value = fetcher_fn(ctx, &fetcher_args);
755                    Box::pin(async move { op_fn(ctx, value?).await })
756                }))
757            }
758            (AnyFetcherFn::Async(fetcher_fn), Operator::CustomAsync(op_fn)) => {
759                let op_fn: Arc<AsyncCheckFn<Ctx>> = op_fn.into();
760                AnyEvalFn::Async(Arc::new(move |ctx| {
761                    let op_fn = op_fn.clone();
762                    let value = fetcher_fn(ctx, fetcher_args.clone());
763                    Box::pin(async move { op_fn(ctx, value.await?).await })
764                }))
765            }
766        }
767    }
768
769    /// Parses a key like "header(host)" into [`FetcherKey`]
770    fn parse_fetcher_key(key: &str) -> Result<FetcherKey> {
771        if let Some((name, args_str)) = key.split_once('(') {
772            if !args_str.ends_with(')') {
773                return Err(Error::fetcher(name, "missing closing parenthesis"));
774            }
775            let args_str = &args_str[..args_str.len() - 1];
776            let args = if args_str.is_empty() {
777                vec![]
778            } else {
779                vec![args_str.to_string()]
780            };
781            Ok(FetcherKey {
782                name: name.to_string(),
783                args,
784            })
785        } else {
786            Ok(FetcherKey {
787                name: key.to_string(),
788                args: Vec::new(),
789            })
790        }
791    }
792
793    /// Parses fetcher arguments, splitting them and trimming whitespace if `raw` is false.
794    fn parse_fetcher_args(mut args: Vec<String>, raw: bool) -> Vec<String> {
795        if raw || args.is_empty() {
796            args
797        } else {
798            let arg = args.pop().unwrap_or_default();
799            arg.split(',').map(|s| s.trim().to_string()).collect()
800        }
801    }
802}
803
804impl<Ctx: ?Sized> Rule<Ctx> {
805    /// Evaluates a rule synchronously using the provided context.
806    ///
807    /// This method evaluates the rule against the provided context and returns
808    /// a boolean result indicating whether the rule matched. If the rule contains
809    /// any async operations, this method will return an error.
810    pub fn evaluate(&self, context: &Ctx) -> StdResult<bool, DynError> {
811        match self {
812            Rule::Leaf(Condition(AnyEvalFn::Sync(eval_fn))) => eval_fn(context),
813            Rule::Leaf(Condition(AnyEvalFn::Async(_))) => {
814                Err("async operations are not supported in sync context".into())
815            }
816            Rule::Any(subrules) => {
817                for rule in subrules {
818                    if rule.evaluate(context)? {
819                        return Ok(true);
820                    }
821                }
822                Ok(false)
823            }
824            Rule::All(subrules) => {
825                for rule in subrules {
826                    if !rule.evaluate(context)? {
827                        return Ok(false);
828                    }
829                }
830                Ok(true)
831            }
832            Rule::Not(subrule) => Ok(!subrule.evaluate(context)?),
833        }
834    }
835
836    /// Evaluates a rule asynchronously using the provided context.
837    ///
838    /// This method evaluates the rule against the provided context and returns
839    /// a boolean result indicating whether the rule matched. It supports rules
840    /// containing both synchronous and asynchronous operations.
841    pub async fn evaluate_async(&self, context: &Ctx) -> StdResult<bool, DynError> {
842        match self {
843            Rule::Leaf(Condition(AnyEvalFn::Sync(eval_fn))) => eval_fn(context),
844            Rule::Leaf(Condition(AnyEvalFn::Async(eval_fn))) => eval_fn(context).await,
845            Rule::Any(subrules) => {
846                for rule in subrules {
847                    if Box::pin(rule.evaluate_async(context)).await? {
848                        return Ok(true);
849                    }
850                }
851                Ok(false)
852            }
853            Rule::All(subrules) => {
854                for rule in subrules {
855                    if !Box::pin(rule.evaluate_async(context)).await? {
856                        return Ok(false);
857                    }
858                }
859                Ok(true)
860            }
861            Rule::Not(subrule) => Ok(!Box::pin(subrule.evaluate_async(context)).await?),
862        }
863    }
864}
865
866pub(crate) trait JsonValueExt {
867    fn type_name(&self) -> &'static str;
868}
869
870impl JsonValueExt for JsonValue {
871    fn type_name(&self) -> &'static str {
872        match self {
873            JsonValue::String(_) => "string",
874            JsonValue::Number(_) => "number",
875            JsonValue::Bool(_) => "boolean",
876            JsonValue::Array(_) => "array",
877            JsonValue::Object(_) => "object",
878            JsonValue::Null => "null",
879        }
880    }
881}
882
883mod error;
884mod matcher;
885mod types;
886mod value;
887
888#[cfg(test)]
889mod tests {
890    #[cfg(feature = "send")]
891    static_assertions::assert_impl_all!(super::Engine<()>: Send, Sync);
892    #[cfg(feature = "send")]
893    static_assertions::assert_impl_all!(super::Rule<()>: Send, Sync);
894}