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    #[inline(always)]
272    fn into_vec(self) -> Vec<Self> {
273        match self {
274            Rule::Any(rules) | Rule::All(rules) => rules,
275            Rule::Not(_) | Rule::Leaf(_) => vec![self],
276        }
277    }
278}
279
280/// Represents a fetcher key like `header(host)` with name and arguments.
281#[derive(Debug)]
282pub(crate) struct FetcherKey {
283    name: String,
284    args: Vec<String>,
285}
286
287enum AnyFetcherFn<Ctx: ?Sized> {
288    Sync(Arc<FetcherFn<Ctx>>),
289    Async(Arc<AsyncFetcherFn<Ctx>>),
290}
291
292impl<Ctx: ?Sized> Clone for AnyFetcherFn<Ctx> {
293    fn clone(&self) -> Self {
294        match self {
295            AnyFetcherFn::Sync(func) => AnyFetcherFn::Sync(func.clone()),
296            AnyFetcherFn::Async(func) => AnyFetcherFn::Async(func.clone()),
297        }
298    }
299}
300
301enum AnyEvalFn<Ctx: ?Sized> {
302    Sync(EvalFn<Ctx>),
303    Async(AsyncEvalFn<Ctx>),
304}
305
306impl<Ctx: ?Sized> Clone for AnyEvalFn<Ctx> {
307    fn clone(&self) -> Self {
308        match self {
309            AnyEvalFn::Sync(func) => AnyEvalFn::Sync(func.clone()),
310            AnyEvalFn::Async(func) => AnyEvalFn::Async(func.clone()),
311        }
312    }
313}
314
315/// Holds a fetcher's required matcher type and function.
316///
317/// A fetcher is responsible for extracting values from the context type.
318/// Each fetcher has:
319/// - A function that extracts values from the context
320/// - A matcher that determines how to compare these values to the rule conditions
321pub struct Fetcher<Ctx: ?Sized> {
322    matcher: Arc<dyn Matcher<Ctx>>,
323    func: AnyFetcherFn<Ctx>,
324    raw_args: bool,
325}
326
327impl<Ctx: ?Sized> Clone for Fetcher<Ctx> {
328    fn clone(&self) -> Self {
329        Fetcher {
330            matcher: self.matcher.clone(),
331            func: self.func.clone(),
332            raw_args: self.raw_args,
333        }
334    }
335}
336
337impl<Ctx: ?Sized> Fetcher<Ctx> {
338    /// Changes the fetcher's matcher
339    pub fn with_matcher<M>(&mut self, matcher: M) -> &mut Self
340    where
341        M: Matcher<Ctx> + 'static,
342    {
343        self.matcher = Arc::new(matcher);
344        self
345    }
346
347    /// Sets whether the fetcher should receive raw arguments instead of splitting them.
348    pub fn with_raw_args(&mut self, raw_args: bool) -> &mut Self {
349        self.raw_args = raw_args;
350        self
351    }
352}
353
354/// Rules engine for registering fetchers/operators and parsing rules.
355///
356/// # Type Parameters
357///
358/// - `Ctx`: The context type that rules will be evaluated against
359///
360/// # Example
361///
362/// ```rust
363/// # use std::collections::HashMap;
364/// # use rusty_rules::{Engine, Value};
365/// # use serde_json::json;
366/// struct User {
367///     name: String,
368///     age: u32,
369///     roles: Vec<String>,
370/// }
371///
372/// let mut engine = Engine::new();
373///
374/// engine.register_fetcher("name", |user: &User, _args| {
375///     Ok(Value::from(&user.name))
376/// });
377///
378/// engine.register_fetcher("age", |user: &User, _args| {
379///     Ok(Value::from(user.age))
380/// });
381///
382/// engine.register_fetcher("has_role", |user: &User, args| {
383///     let role = args.first().ok_or("Role name required")?;
384///     Ok(Value::from(user.roles.contains(&role)))
385/// });
386///
387/// let rule = engine.compile_rule(&json!([
388///     {"age": {">=": 18}},
389///     {"has_role(admin)": true}
390/// ])).unwrap();
391/// ```
392pub struct Engine<Ctx: MaybeSync + ?Sized + 'static> {
393    fetchers: HashMap<String, Fetcher<Ctx>>,
394    operators: HashMap<String, Arc<dyn ToOperator<Ctx>>>,
395}
396
397impl<Ctx: MaybeSync + ?Sized> Default for Engine<Ctx> {
398    fn default() -> Self {
399        Self::new()
400    }
401}
402
403impl<Ctx: MaybeSync + ?Sized> Clone for Engine<Ctx> {
404    fn clone(&self) -> Self {
405        Engine {
406            fetchers: self.fetchers.clone(),
407            operators: self.operators.clone(),
408        }
409    }
410}
411
412impl<Ctx: MaybeSync + ?Sized> Engine<Ctx> {
413    /// Creates a new rules engine instance.
414    pub fn new() -> Self {
415        Engine {
416            fetchers: HashMap::new(),
417            operators: HashMap::new(),
418        }
419    }
420
421    /// Registers a synchronous fetcher with its name and function, using the default matcher.
422    ///
423    /// A fetcher is a function that extracts values from the context type. The fetcher name is used
424    /// in rule definitions to reference this fetcher. By default, the `DefaultMatcher` is used, which
425    /// supports basic equality and comparison operations.
426    ///
427    /// # Returns
428    ///
429    /// A mutable reference to the created `Fetcher`, allowing you to customize it (e.g., change the matcher)
430    pub fn register_fetcher<F>(&mut self, name: &str, func: F) -> &mut Fetcher<Ctx>
431    where
432        F: for<'a> Fn(&'a Ctx, &[String]) -> StdResult<Value<'a>, DynError>
433            + MaybeSend
434            + MaybeSync
435            + 'static,
436    {
437        let fetcher = Fetcher {
438            matcher: Arc::new(DefaultMatcher),
439            func: AnyFetcherFn::Sync(Arc::new(func)),
440            raw_args: false,
441        };
442        self.fetchers
443            .entry(name.to_string())
444            .insert_entry(fetcher)
445            .into_mut()
446    }
447
448    /// Registers an async fetcher with its name and function, using the default matcher.
449    ///
450    /// See [`Self::register_fetcher`] for more details.
451    pub fn register_async_fetcher<F>(&mut self, name: &str, func: F) -> &mut Fetcher<Ctx>
452    where
453        F: for<'a> Fn(&'a Ctx, Arc<[String]>) -> BoxFuture<'a, StdResult<Value<'a>, DynError>>
454            + MaybeSend
455            + MaybeSync
456            + 'static,
457    {
458        let fetcher = Fetcher {
459            matcher: Arc::new(DefaultMatcher),
460            func: AnyFetcherFn::Async(Arc::new(func)),
461            raw_args: false,
462        };
463        self.fetchers
464            .entry(name.to_string())
465            .insert_entry(fetcher)
466            .into_mut()
467    }
468
469    /// Registers a custom operator
470    pub fn register_operator<O>(&mut self, name: &str, op: O)
471    where
472        O: ToOperator<Ctx> + 'static,
473    {
474        self.operators.insert(name.to_string(), Arc::new(op));
475    }
476
477    /// Compiles a JSON value into a [`Rule::All`] using the registered fetchers and operators.
478    pub fn compile_rule(&self, value: &JsonValue) -> Result<Rule<Ctx>> {
479        match value {
480            JsonValue::Object(map) => {
481                let mut subrules = Vec::with_capacity(map.len());
482                for (key, value) in map {
483                    match key.as_str() {
484                        "any" => subrules.push(Rule::any(self.compile_rule(value)?.into_vec())),
485                        "all" => subrules.push(Rule::all(self.compile_rule(value)?.into_vec())),
486                        "not" => subrules.push(Rule::not(self.compile_rule(value)?.into_vec())),
487                        _ => {
488                            let FetcherKey { name, args } = Self::parse_fetcher_key(key)?;
489                            let fetcher = (self.fetchers.get(&name)).ok_or_else(|| {
490                                Error::fetcher(&name, "fetcher is not registered")
491                            })?;
492                            let args = Self::parse_fetcher_args(args.clone(), fetcher.raw_args);
493
494                            let mut operator = fetcher.matcher.compile(value);
495                            // Try custom operator
496                            if let Err(Error::UnknownOperator(ref op)) = operator
497                                && let Some(op_builder) = self.operators.get(op)
498                            {
499                                operator = op_builder
500                                    .to_operator(&value[op])
501                                    .map_err(|err| Error::operator(op, err));
502                            }
503                            let operator = operator.map_err(|err| Error::matcher(&name, err))?;
504                            let fetcher_fn = fetcher.func.clone();
505                            let eval_fn =
506                                Self::compile_condition(fetcher_fn, args.into(), operator);
507
508                            subrules.push(Rule::leaf(eval_fn));
509                        }
510                    }
511                }
512                Ok(Rule::all(subrules))
513            }
514            JsonValue::Array(seq) => (seq.iter())
515                .try_fold(Vec::with_capacity(seq.len()), |mut subrules, v| {
516                    subrules.push(self.compile_rule(v)?);
517                    Result::Ok(subrules)
518                })
519                .map(Rule::all),
520            _ => Err(Error::json("rule must be a JSON object or array")),
521        }
522    }
523
524    /// Validates a JSON rule against dynamically generated JSON Schema of this engine.
525    #[cfg(feature = "validation")]
526    #[cfg_attr(docsrs, doc(cfg(feature = "validation")))]
527    #[allow(clippy::result_large_err)]
528    pub fn validate_rule<'a>(&self, value: &'a JsonValue) -> StdResult<(), ValidationError<'a>> {
529        // build dynamic JSON Schema based on registered fetchers
530        let schema = self.json_schema();
531        let validator = jsonschema::options()
532            .with_pattern_options(jsonschema::PatternOptions::regex())
533            .build(&schema)?;
534        validator.validate(value)
535    }
536
537    /// Builds a JSON Schema for rules, including dynamic properties.
538    pub fn json_schema(&self) -> JsonValue {
539        let mut pattern_props = Map::new();
540
541        // Get custom operator schemas
542        let custom_ops: Vec<(&str, JsonValue)> = (self.operators.iter())
543            .map(|(k, v)| (k.as_str(), v.json_schema()))
544            .collect();
545
546        // For each fetcher, get its matcher's schema or use a default
547        for (name, fetcher) in &self.fetchers {
548            let pattern = format!(r"^{}(:?\(([^)]*)\))?$", regex::escape(name));
549            let schema = fetcher.matcher.json_schema(&custom_ops);
550            pattern_props.insert(pattern, schema);
551        }
552
553        json!({
554            "$schema": "http://json-schema.org/draft-07/schema",
555            "$ref": "#/definitions/rule_object",
556            "definitions": {
557                "rule_object": {
558                    "type": "object",
559                    "properties": {
560                        "any": { "$ref": "#/definitions/rule" },
561                        "all": { "$ref": "#/definitions/rule" },
562                        "not": { "$ref": "#/definitions/rule" }
563                    },
564                    "patternProperties": pattern_props,
565                    "additionalProperties": false,
566                },
567                "rule_array": {
568                    "type": "array",
569                    "minItems": 1,
570                    "items": { "$ref": "#/definitions/rule_object" },
571                },
572                "rule": {
573                    "if": { "type": "array" },
574                    "then": { "$ref": "#/definitions/rule_array" },
575                    "else": { "$ref": "#/definitions/rule_object" }
576                },
577            }
578        })
579    }
580
581    fn compile_condition(
582        fetcher_fn: AnyFetcherFn<Ctx>,
583        fetcher_args: Arc<[String]>,
584        operator: Operator<Ctx>,
585    ) -> AnyEvalFn<Ctx> {
586        match (fetcher_fn, operator) {
587            // Equal
588            (AnyFetcherFn::Sync(fetcher_fn), Operator::Equal(right)) => {
589                AnyEvalFn::Sync(Arc::new(move |ctx| {
590                    Ok(fetcher_fn(ctx, &fetcher_args)? == right)
591                }))
592            }
593            (AnyFetcherFn::Async(fetcher_fn), Operator::Equal(right)) => {
594                let right = Arc::new(right);
595                AnyEvalFn::Async(Arc::new(move |ctx| {
596                    let right = right.clone();
597                    let value = fetcher_fn(ctx, fetcher_args.clone());
598                    Box::pin(async move { Ok(value.await? == *right) })
599                }))
600            }
601
602            // LessThan
603            (AnyFetcherFn::Sync(fetcher_fn), Operator::LessThan(right)) => {
604                AnyEvalFn::Sync(Arc::new(move |ctx| {
605                    Ok(fetcher_fn(ctx, &fetcher_args)? < right)
606                }))
607            }
608            (AnyFetcherFn::Async(fetcher_fn), Operator::LessThan(right)) => {
609                let right = Arc::new(right);
610                AnyEvalFn::Async(Arc::new(move |ctx| {
611                    let right = right.clone();
612                    let value = fetcher_fn(ctx, fetcher_args.clone());
613                    Box::pin(async move { Ok(value.await? < *right) })
614                }))
615            }
616
617            // LessThanOrEqual
618            (AnyFetcherFn::Sync(fetcher_fn), Operator::LessThanOrEqual(right)) => {
619                AnyEvalFn::Sync(Arc::new(move |ctx| {
620                    Ok(fetcher_fn(ctx, &fetcher_args)? <= right)
621                }))
622            }
623            (AnyFetcherFn::Async(fetcher_fn), Operator::LessThanOrEqual(right)) => {
624                let right = Arc::new(right);
625                AnyEvalFn::Async(Arc::new(move |ctx| {
626                    let right = right.clone();
627                    let value = fetcher_fn(ctx, fetcher_args.clone());
628                    Box::pin(async move { Ok(value.await? <= *right) })
629                }))
630            }
631
632            // GreaterThan
633            (AnyFetcherFn::Sync(fetcher_fn), Operator::GreaterThan(right)) => {
634                AnyEvalFn::Sync(Arc::new(move |ctx| {
635                    Ok(fetcher_fn(ctx, &fetcher_args)? > right)
636                }))
637            }
638            (AnyFetcherFn::Async(fetcher_fn), Operator::GreaterThan(right)) => {
639                let right = Arc::new(right);
640                AnyEvalFn::Async(Arc::new(move |ctx| {
641                    let right = right.clone();
642                    let value = fetcher_fn(ctx, fetcher_args.clone());
643                    Box::pin(async move { Ok(value.await? > *right) })
644                }))
645            }
646
647            // GreaterThanOrEqual
648            (AnyFetcherFn::Sync(fetcher_fn), Operator::GreaterThanOrEqual(right)) => {
649                AnyEvalFn::Sync(Arc::new(move |ctx| {
650                    Ok(fetcher_fn(ctx, &fetcher_args)? >= right)
651                }))
652            }
653            (AnyFetcherFn::Async(fetcher_fn), Operator::GreaterThanOrEqual(right)) => {
654                let right = Arc::new(right);
655                AnyEvalFn::Async(Arc::new(move |ctx| {
656                    let right = right.clone();
657                    let value = fetcher_fn(ctx, fetcher_args.clone());
658                    Box::pin(async move { Ok(value.await? >= *right) })
659                }))
660            }
661
662            // InSet
663            (AnyFetcherFn::Sync(fetcher_fn), Operator::InSet(set)) => {
664                AnyEvalFn::Sync(Arc::new(move |ctx| {
665                    fetcher_fn(ctx, &fetcher_args).map(|val| set.contains(&val))
666                }))
667            }
668            (AnyFetcherFn::Async(fetcher_fn), Operator::InSet(set)) => {
669                let set = Arc::new(set);
670                AnyEvalFn::Async(Arc::new(move |ctx| {
671                    let set = set.clone();
672                    let value = fetcher_fn(ctx, fetcher_args.clone());
673                    Box::pin(async move { value.await.map(|val| set.contains(&val)) })
674                }))
675            }
676
677            // Regex
678            (AnyFetcherFn::Sync(fetcher_fn), Operator::Regex(regex)) => {
679                AnyEvalFn::Sync(Arc::new(move |ctx| {
680                    fetcher_fn(ctx, &fetcher_args)
681                        .map(|val| val.as_str().map(|s| regex.is_match(s)).unwrap_or(false))
682                }))
683            }
684            (AnyFetcherFn::Async(fetcher_fn), Operator::Regex(regex)) => {
685                let regex = Arc::new(regex);
686                AnyEvalFn::Async(Arc::new(move |ctx| {
687                    let regex = regex.clone();
688                    let value = fetcher_fn(ctx, fetcher_args.clone());
689                    Box::pin(async move {
690                        (value.await)
691                            .map(|val| val.as_str().map(|s| regex.is_match(s)).unwrap_or(false))
692                    })
693                }))
694            }
695
696            // RegexSet
697            (AnyFetcherFn::Sync(fetcher_fn), Operator::RegexSet(regex_set)) => {
698                AnyEvalFn::Sync(Arc::new(move |ctx| {
699                    fetcher_fn(ctx, &fetcher_args)
700                        .map(|val| val.as_str().map(|s| regex_set.is_match(s)).unwrap_or(false))
701                }))
702            }
703            (AnyFetcherFn::Async(fetcher_fn), Operator::RegexSet(regex_set)) => {
704                let regex_set = Arc::new(regex_set);
705                AnyEvalFn::Async(Arc::new(move |ctx| {
706                    let regex_set = regex_set.clone();
707                    let value = fetcher_fn(ctx, fetcher_args.clone());
708                    Box::pin(async move {
709                        (value.await)
710                            .map(|val| val.as_str().map(|s| regex_set.is_match(s)).unwrap_or(false))
711                    })
712                }))
713            }
714
715            // IpSet
716            (AnyFetcherFn::Sync(fetcher_fn), Operator::IpSet(set)) => {
717                AnyEvalFn::Sync(Arc::new(move |ctx| {
718                    Ok((fetcher_fn(ctx, &fetcher_args)?.to_ip())
719                        .map(|ip| set.longest_match(&IpNet::from(ip)).is_some())
720                        .unwrap_or(false))
721                }))
722            }
723            (AnyFetcherFn::Async(fetcher_fn), Operator::IpSet(set)) => {
724                let set = Arc::new(set);
725                AnyEvalFn::Async(Arc::new(move |ctx| {
726                    let set = set.clone();
727                    let value = fetcher_fn(ctx, fetcher_args.clone());
728                    Box::pin(async move {
729                        Ok((value.await?.to_ip())
730                            .map(|ip| set.longest_match(&IpNet::from(ip)).is_some())
731                            .unwrap_or(false))
732                    })
733                }))
734            }
735
736            // Custom operator
737            (AnyFetcherFn::Sync(fetcher_fn), Operator::Custom(op_fn)) => {
738                AnyEvalFn::Sync(Arc::new(move |ctx| {
739                    let value = fetcher_fn(ctx, &fetcher_args)?;
740                    op_fn(ctx, value)
741                }))
742            }
743            (AnyFetcherFn::Async(fetcher_fn), Operator::Custom(op_fn)) => {
744                let op_fn: Arc<CheckFn<Ctx>> = op_fn.into();
745                AnyEvalFn::Async(Arc::new(move |ctx| {
746                    let op_fn = op_fn.clone();
747                    let value = fetcher_fn(ctx, fetcher_args.clone());
748                    Box::pin(async move { op_fn(ctx, value.await?) })
749                }))
750            }
751
752            // Custom async operator
753            (AnyFetcherFn::Sync(fetcher_fn), Operator::CustomAsync(op_fn)) => {
754                let op_fn: Arc<AsyncCheckFn<Ctx>> = op_fn.into();
755                AnyEvalFn::Async(Arc::new(move |ctx| {
756                    let op_fn = op_fn.clone();
757                    let value = fetcher_fn(ctx, &fetcher_args);
758                    Box::pin(async move { op_fn(ctx, value?).await })
759                }))
760            }
761            (AnyFetcherFn::Async(fetcher_fn), Operator::CustomAsync(op_fn)) => {
762                let op_fn: Arc<AsyncCheckFn<Ctx>> = op_fn.into();
763                AnyEvalFn::Async(Arc::new(move |ctx| {
764                    let op_fn = op_fn.clone();
765                    let value = fetcher_fn(ctx, fetcher_args.clone());
766                    Box::pin(async move { op_fn(ctx, value.await?).await })
767                }))
768            }
769        }
770    }
771
772    /// Parses a key like "header(host)" into [`FetcherKey`]
773    fn parse_fetcher_key(key: &str) -> Result<FetcherKey> {
774        if let Some((name, args_str)) = key.split_once('(') {
775            if !args_str.ends_with(')') {
776                return Err(Error::fetcher(name, "missing closing parenthesis"));
777            }
778            let args_str = &args_str[..args_str.len() - 1];
779            let args = if args_str.is_empty() {
780                vec![]
781            } else {
782                vec![args_str.to_string()]
783            };
784            Ok(FetcherKey {
785                name: name.to_string(),
786                args,
787            })
788        } else {
789            Ok(FetcherKey {
790                name: key.to_string(),
791                args: Vec::new(),
792            })
793        }
794    }
795
796    /// Parses fetcher arguments, splitting them and trimming whitespace if `raw` is false.
797    fn parse_fetcher_args(mut args: Vec<String>, raw: bool) -> Vec<String> {
798        if raw || args.is_empty() {
799            args
800        } else {
801            let arg = args.pop().unwrap_or_default();
802            arg.split(',').map(|s| s.trim().to_string()).collect()
803        }
804    }
805}
806
807impl<Ctx: ?Sized> Rule<Ctx> {
808    /// Evaluates a rule synchronously using the provided context.
809    ///
810    /// This method evaluates the rule against the provided context and returns
811    /// a boolean result indicating whether the rule matched. If the rule contains
812    /// any async operations, this method will return an error.
813    pub fn evaluate(&self, context: &Ctx) -> StdResult<bool, DynError> {
814        match self {
815            Rule::Leaf(Condition(AnyEvalFn::Sync(eval_fn))) => eval_fn(context),
816            Rule::Leaf(Condition(AnyEvalFn::Async(_))) => {
817                Err("async operations are not supported in sync context".into())
818            }
819            Rule::Any(subrules) => {
820                for rule in subrules {
821                    if rule.evaluate(context)? {
822                        return Ok(true);
823                    }
824                }
825                Ok(false)
826            }
827            Rule::All(subrules) => {
828                for rule in subrules {
829                    if !rule.evaluate(context)? {
830                        return Ok(false);
831                    }
832                }
833                Ok(true)
834            }
835            Rule::Not(subrule) => Ok(!subrule.evaluate(context)?),
836        }
837    }
838
839    /// Evaluates a rule asynchronously using the provided context.
840    ///
841    /// This method evaluates the rule against the provided context and returns
842    /// a boolean result indicating whether the rule matched. It supports rules
843    /// containing both synchronous and asynchronous operations.
844    pub async fn evaluate_async(&self, context: &Ctx) -> StdResult<bool, DynError> {
845        match self {
846            Rule::Leaf(Condition(AnyEvalFn::Sync(eval_fn))) => eval_fn(context),
847            Rule::Leaf(Condition(AnyEvalFn::Async(eval_fn))) => eval_fn(context).await,
848            Rule::Any(subrules) => {
849                for rule in subrules {
850                    if Box::pin(rule.evaluate_async(context)).await? {
851                        return Ok(true);
852                    }
853                }
854                Ok(false)
855            }
856            Rule::All(subrules) => {
857                for rule in subrules {
858                    if !Box::pin(rule.evaluate_async(context)).await? {
859                        return Ok(false);
860                    }
861                }
862                Ok(true)
863            }
864            Rule::Not(subrule) => Ok(!Box::pin(subrule.evaluate_async(context)).await?),
865        }
866    }
867}
868
869pub(crate) trait JsonValueExt {
870    fn type_name(&self) -> &'static str;
871}
872
873impl JsonValueExt for JsonValue {
874    fn type_name(&self) -> &'static str {
875        match self {
876            JsonValue::String(_) => "string",
877            JsonValue::Number(_) => "number",
878            JsonValue::Bool(_) => "boolean",
879            JsonValue::Array(_) => "array",
880            JsonValue::Object(_) => "object",
881            JsonValue::Null => "null",
882        }
883    }
884}
885
886mod error;
887mod matcher;
888mod types;
889mod value;
890
891#[cfg(test)]
892mod tests {
893    #[cfg(feature = "send")]
894    static_assertions::assert_impl_all!(super::Engine<()>: Send, Sync);
895    #[cfg(feature = "send")]
896    static_assertions::assert_impl_all!(super::Rule<()>: Send, Sync);
897}