diff --git a/CUSTOM_FUNCTIONS.md b/CUSTOM_FUNCTIONS.md index ab0111ea..d92e9ee5 100644 --- a/CUSTOM_FUNCTIONS.md +++ b/CUSTOM_FUNCTIONS.md @@ -2,27 +2,25 @@ ## Overview -Custom functions in Casbin-RS now support flexible argument types through Rhai's `Dynamic` type. This means you can create custom functions that work with: +Custom functions in Casbin-RS support flexible argument types through Rhai's `Dynamic` type and can capture external state. This means you can create custom functions that: -- **Strings** (as `ImmutableString`) -- **Integers** (i32 or i64) -- **Booleans** -- **Floats** (f32 or f64) -- **Arrays** -- **Maps** -- And more... - -This improvement addresses the limitation where custom functions previously only accepted `ImmutableString` arguments. +- Work with **Strings** (as `ImmutableString`) +- Work with **Integers** (i32 or i64) +- Work with **Booleans** +- Work with **Floats** (f32 or f64) +- Work with **Arrays** and **Maps** +- **Capture external state** like database connections, configuration, or shared data ## Basic Usage ### Adding a Custom Function -Custom functions are added using the `add_function` method on an `Enforcer` instance: +Custom functions are added using the `add_function` method on an `Enforcer` instance. Functions are wrapped in `Arc::new()` to support cloning and thread-safety: ```rust use casbin::prelude::*; use rhai::Dynamic; +use std::sync::Arc; // Create your enforcer let mut e = Enforcer::new("model.conf", "policy.csv").await?; @@ -30,10 +28,10 @@ let mut e = Enforcer::new("model.conf", "policy.csv").await?; // Add a custom function e.add_function( "myFunction", - OperatorFunction::Arg2(|arg1: Dynamic, arg2: Dynamic| { + OperatorFunction::Arg2(Arc::new(|arg1: Dynamic, arg2: Dynamic| { // Your custom logic here true.into() // Return a Dynamic value - }), + })), ); ``` @@ -45,76 +43,87 @@ For custom functions that work with strings, you can use the helper function `dy ```rust use casbin::model::function_map::dynamic_to_str; +use std::sync::Arc; e.add_function( "stringContains", - OperatorFunction::Arg2(|haystack: Dynamic, needle: Dynamic| { + OperatorFunction::Arg2(Arc::new(|haystack: Dynamic, needle: Dynamic| { let haystack_str = dynamic_to_str(&haystack); let needle_str = dynamic_to_str(&needle); haystack_str.contains(needle_str.as_ref()).into() - }), + })), ); ``` Or simply convert to String: ```rust +use std::sync::Arc; + e.add_function( "stringMatch", - OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { + OperatorFunction::Arg2(Arc::new(|s1: Dynamic, s2: Dynamic| { let str1 = s1.to_string(); let str2 = s2.to_string(); (str1 == str2).into() - }), + })), ); ``` ### 2. Integer-based Custom Function ```rust +use std::sync::Arc; + e.add_function( "greaterThan", - OperatorFunction::Arg2(|a: Dynamic, b: Dynamic| { + OperatorFunction::Arg2(Arc::new(|a: Dynamic, b: Dynamic| { let a_int = a.as_int().unwrap_or(0); let b_int = b.as_int().unwrap_or(0); (a_int > b_int).into() - }), + })), ); ``` ### 3. Boolean-based Custom Function ```rust +use std::sync::Arc; + e.add_function( "customAnd", - OperatorFunction::Arg2(|a: Dynamic, b: Dynamic| { + OperatorFunction::Arg2(Arc::new(|a: Dynamic, b: Dynamic| { let a_bool = a.as_bool().unwrap_or(false); let b_bool = b.as_bool().unwrap_or(false); (a_bool && b_bool).into() - }), + })), ); ``` ### 4. Multi-argument Custom Function ```rust +use std::sync::Arc; + e.add_function( "between", - OperatorFunction::Arg3(|val: Dynamic, min: Dynamic, max: Dynamic| { + OperatorFunction::Arg3(Arc::new(|val: Dynamic, min: Dynamic, max: Dynamic| { let val_int = val.as_int().unwrap_or(0); let min_int = min.as_int().unwrap_or(0); let max_int = max.as_int().unwrap_or(0); (val_int >= min_int && val_int <= max_int).into() - }), + })), ); ``` ### 5. Mixed-type Custom Function ```rust +use std::sync::Arc; + e.add_function( "complexCheck", - OperatorFunction::Arg3(|name: Dynamic, age: Dynamic, is_admin: Dynamic| { + OperatorFunction::Arg3(Arc::new(|name: Dynamic, age: Dynamic, is_admin: Dynamic| { let name_str = name.to_string(); let age_int = age.as_int().unwrap_or(0); let admin_bool = is_admin.as_bool().unwrap_or(false); @@ -122,10 +131,84 @@ e.add_function( // Custom logic with different types let result = name_str.len() > 3 && age_int >= 18 && admin_bool; result.into() - }), + })), ); ``` +### 6. Capturing External State (Database Connection, etc.) + +One of the key features is the ability to capture external state in custom functions. This is useful for accessing database connections, configuration, or any shared data: + +```rust +use std::sync::Arc; + +// Example: Using with a database connection pool +// This could be r2d2, deadpool, bb8, or any other connection pool +struct AppState { + // db_pool: Pool, + allowed_resources: Vec, +} + +let app_state = Arc::new(AppState { + allowed_resources: vec![1, 2, 3, 5, 8], +}); + +// Clone the Arc to move into the closure +let state_clone = app_state.clone(); + +e.add_function( + "checkResourceAccess", + OperatorFunction::Arg1(Arc::new(move |resource_id: Dynamic| { + let id = resource_id.as_int().unwrap_or(0) as i32; + // Access the captured state + state_clone.allowed_resources.contains(&id).into() + })), +); +``` + +#### Real-world Example with r2d2 Connection Pool + +```rust +use r2d2::{self, Pool}; +use r2d2_sqlite::SqliteConnectionManager; +use std::sync::{Arc, Mutex}; + +#[derive(Clone)] +struct AppState { + db_pool: Pool, + casbin_enforcer: Arc>, +} + +impl AppState { + fn create_storage_check_function(&self) -> OperatorFunction { + let pool = self.db_pool.clone(); + + OperatorFunction::Arg1(Arc::new(move |product_id: Dynamic| { + let product_id_int = product_id.as_int().unwrap_or(0) as i64; + + // Get a connection from the pool + let conn = pool.get().expect("Failed to get connection"); + + // Query the database + let has_storage: bool = conn + .query_row( + "SELECT EXISTS(SELECT 1 FROM storages WHERE product_id = ?)", + [product_id_int], + |row| row.get(0), + ) + .unwrap_or(false); + + has_storage.into() + })) + } +} + +// Usage (assuming app_state is already constructed): +// let app_state: AppState = /* your initialization */; +let check_fn = app_state.create_storage_check_function(); +enforcer.add_function("matchProductHasStorages", check_fn); +``` + ## Using Custom Functions in Matchers Once registered, custom functions can be used in your policy matchers: @@ -135,17 +218,24 @@ Once registered, custom functions can be used in your policy matchers: m = greaterThan(r.age, 18) && stringContains(r.path, p.path) ``` +Or with captured state: + +```conf +[matchers] +m = r.sub == p.sub && checkResourceAccess(r.resource_id) +``` + ## OperatorFunction Variants -The `OperatorFunction` enum supports functions with 0 to 6 arguments: +The `OperatorFunction` enum supports functions with 0 to 6 arguments. Each variant wraps an `Arc`: -- `Arg0`: `fn() -> Dynamic` -- `Arg1`: `fn(Dynamic) -> Dynamic` -- `Arg2`: `fn(Dynamic, Dynamic) -> Dynamic` -- `Arg3`: `fn(Dynamic, Dynamic, Dynamic) -> Dynamic` -- `Arg4`: `fn(Dynamic, Dynamic, Dynamic, Dynamic) -> Dynamic` -- `Arg5`: `fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> Dynamic` -- `Arg6`: `fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> Dynamic` +- `Arg0`: `Arc Dynamic + Send + Sync>` +- `Arg1`: `Arc Dynamic + Send + Sync>` +- `Arg2`: `Arc Dynamic + Send + Sync>` +- `Arg3`: `Arc Dynamic + Send + Sync>` +- `Arg4`: `Arc Dynamic + Send + Sync>` +- `Arg5`: `Arc Dynamic + Send + Sync>` +- `Arg6`: `Arc Dynamic + Send + Sync>` ## Working with Dynamic Types @@ -158,41 +248,35 @@ Rhai's `Dynamic` type provides several methods to extract values: - `into_immutable_string()` - Convert to ImmutableString (consumes the Dynamic) - `to_string()` - Convert to String (works for any type) -## Backward Compatibility - -All existing code continues to work. The change from `ImmutableString` to `Dynamic` is backward compatible because: - -1. Strings are automatically converted to `Dynamic` by Rhai -2. The `dynamic_to_str` helper function makes string extraction easy -3. All built-in functions have been updated and tested - ## Migration Guide -If you have existing custom functions using `ImmutableString`, update them like this: +If you have existing custom functions, update them to wrap closures with `Arc::new()`: **Before:** ```rust e.add_function( "myFunc", - OperatorFunction::Arg2( - |s1: ImmutableString, s2: ImmutableString| { - // logic here - true.into() - } - ), + OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { + let str1 = s1.to_string(); + let str2 = s2.to_string(); + // logic here + true.into() + }), ); ``` **After:** ```rust +use std::sync::Arc; + e.add_function( "myFunc", - OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { + OperatorFunction::Arg2(Arc::new(|s1: Dynamic, s2: Dynamic| { let str1 = s1.to_string(); let str2 = s2.to_string(); // logic here true.into() - }), + })), ); ``` @@ -200,4 +284,6 @@ e.add_function( - [Casbin Documentation](https://casbin.org/docs/function) - [Rhai Documentation](https://rhai.rs/book/) -- Test: `test_custom_function_with_dynamic_types` in `src/enforcer.rs` +- Tests in `src/enforcer.rs`: + - `test_custom_function_with_dynamic_types` + - `test_custom_function_with_captured_state` diff --git a/examples/rbac_policy.csv b/examples/rbac_policy.csv index f93d6df8..14439c81 100644 --- a/examples/rbac_policy.csv +++ b/examples/rbac_policy.csv @@ -1,5 +1,5 @@ -p, alice, data1, read -p, bob, data2, write -p, data2_admin, data2, read -p, data2_admin, data2, write -g, alice, data2_admin \ No newline at end of file +p, alice,data1,read +p, bob,data2,write +p, data2_admin,data2,read +p, data2_admin,data2,write +g, alice,data2_admin diff --git a/src/enforcer.rs b/src/enforcer.rs index c63cf36c..002b3658 100644 --- a/src/enforcer.rs +++ b/src/enforcer.rs @@ -383,25 +383,48 @@ impl Enforcer { fn register_function(engine: &mut Engine, key: &str, f: OperatorFunction) { match f { OperatorFunction::Arg0(func) => { - engine.register_fn(key, func); + engine.register_fn(key, move || func()); } OperatorFunction::Arg1(func) => { - engine.register_fn(key, func); + engine.register_fn(key, move |a: Dynamic| func(a)); } OperatorFunction::Arg2(func) => { - engine.register_fn(key, func); + engine.register_fn(key, move |a: Dynamic, b: Dynamic| func(a, b)); } OperatorFunction::Arg3(func) => { - engine.register_fn(key, func); + engine.register_fn( + key, + move |a: Dynamic, b: Dynamic, c: Dynamic| func(a, b, c), + ); } OperatorFunction::Arg4(func) => { - engine.register_fn(key, func); + engine.register_fn( + key, + move |a: Dynamic, b: Dynamic, c: Dynamic, d: Dynamic| { + func(a, b, c, d) + }, + ); } OperatorFunction::Arg5(func) => { - engine.register_fn(key, func); + engine.register_fn( + key, + move |a: Dynamic, + b: Dynamic, + c: Dynamic, + d: Dynamic, + e: Dynamic| { func(a, b, c, d, e) }, + ); } OperatorFunction::Arg6(func) => { - engine.register_fn(key, func); + engine.register_fn( + key, + move |a: Dynamic, + b: Dynamic, + c: Dynamic, + d: Dynamic, + e: Dynamic, + g: Dynamic| { func(a, b, c, d, e, g) }, + ); } } } @@ -434,8 +457,8 @@ impl CoreApi for Enforcer { engine.register_global_module(CASBIN_PACKAGE.as_shared_module()); - for (key, &func) in fm.get_functions() { - Self::register_function(&mut engine, key, func); + for (key, func) in fm.get_functions() { + Self::register_function(&mut engine, key, func.clone()); } let mut e = Self { @@ -488,7 +511,7 @@ impl CoreApi for Enforcer { #[inline] fn add_function(&mut self, fname: &str, f: OperatorFunction) { - self.fm.add_function(fname, f); + self.fm.add_function(fname, f.clone()); Self::register_function(&mut self.engine, fname, f); } @@ -1421,6 +1444,7 @@ mod tests { )] async fn test_keymatch_custom_model() { use crate::model::key_match; + use std::sync::Arc; let m1 = DefaultModel::from_file("examples/keymatch_custom_model.conf") .await @@ -1430,11 +1454,11 @@ mod tests { e.add_function( "keyMatchCustom", - OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { + OperatorFunction::Arg2(Arc::new(|s1: Dynamic, s2: Dynamic| { let s1_str = s1.to_string(); let s2_str = s2.to_string(); key_match(&s1_str, &s2_str).into() - }), + })), ); assert_eq!( @@ -1877,6 +1901,7 @@ mod tests { )] async fn test_custom_function_with_dynamic_types() { use crate::prelude::*; + use std::sync::Arc; let m = DefaultModel::from_str( r#" @@ -1902,40 +1927,42 @@ m = r.sub == p.sub && r.obj == p.obj && r.act == p.act // Test 1: Custom function that takes integer arguments e.add_function( "greaterThan", - OperatorFunction::Arg2(|a: Dynamic, b: Dynamic| { + OperatorFunction::Arg2(Arc::new(|a: Dynamic, b: Dynamic| { // Dynamic can hold integers - extract and compare let a_int = a.as_int().unwrap_or(0); let b_int = b.as_int().unwrap_or(0); (a_int > b_int).into() - }), + })), ); // Test 2: Custom function that works with booleans e.add_function( "customAnd", - OperatorFunction::Arg2(|a: Dynamic, b: Dynamic| { + OperatorFunction::Arg2(Arc::new(|a: Dynamic, b: Dynamic| { // Dynamic can hold booleans - extract and perform logic let a_bool = a.as_bool().unwrap_or(false); let b_bool = b.as_bool().unwrap_or(false); (a_bool && b_bool).into() - }), + })), ); // Test 3: Custom function that works with strings e.add_function( "stringContains", - OperatorFunction::Arg2(|haystack: Dynamic, needle: Dynamic| { - // Dynamic can hold strings - convert and check - let haystack_str = haystack.to_string(); - let needle_str = needle.to_string(); - haystack_str.contains(&needle_str).into() - }), + OperatorFunction::Arg2(Arc::new( + |haystack: Dynamic, needle: Dynamic| { + // Dynamic can hold strings - convert and check + let haystack_str = haystack.to_string(); + let needle_str = needle.to_string(); + haystack_str.contains(&needle_str).into() + }, + )), ); // Test 4: Custom function with 3 arguments e.add_function( "between", - OperatorFunction::Arg3( + OperatorFunction::Arg3(Arc::new( |val: Dynamic, min: Dynamic, max: Dynamic| { // Check if val is between min and max (inclusive) let val_int = val.as_int().unwrap_or(0); @@ -1943,7 +1970,7 @@ m = r.sub == p.sub && r.obj == p.obj && r.act == p.act let max_int = max.as_int().unwrap_or(0); (val_int >= min_int && val_int <= max_int).into() }, - ), + )), ); // Verify that custom functions are registered without errors @@ -1962,4 +1989,73 @@ m = r.sub == p.sub && r.obj == p.obj && r.act == p.act assert_eq!(false, e.enforce(("alice", "data1", "write")).unwrap()); assert_eq!(false, e.enforce(("bob", "data1", "read")).unwrap()); } + + #[cfg(not(target_arch = "wasm32"))] + #[cfg_attr( + all(feature = "runtime-async-std", not(target_arch = "wasm32")), + async_std::test + )] + #[cfg_attr( + all(feature = "runtime-tokio", not(target_arch = "wasm32")), + tokio::test + )] + async fn test_custom_function_with_captured_state() { + use crate::prelude::*; + use std::sync::Arc; + + let m = DefaultModel::from_str( + r#" +[request_definition] +r = sub, resource_id + +[policy_definition] +p = sub + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = r.sub == p.sub && checkResourceAccess(r.resource_id) +"#, + ) + .await + .unwrap(); + + let adapter = MemoryAdapter::default(); + let mut e = Enforcer::new(m, adapter).await.unwrap(); + + // Simulate an external data source (like a DB connection) + // This could be a connection pool, Arc>, etc. + let allowed_resources: Arc> = Arc::new(vec![1, 2, 3, 5, 8]); + let resources_clone = allowed_resources.clone(); + + // Create a custom function that captures external state + e.add_function( + "checkResourceAccess", + OperatorFunction::Arg1(Arc::new(move |resource_id: Dynamic| { + let id = resource_id.as_int().unwrap_or(0) as i32; + // Access the captured state to check if resource is allowed + resources_clone.contains(&id).into() + })), + ); + + // Add policy for alice + e.add_policy(vec!["alice".to_owned()]) + .await + .unwrap(); + + // Test enforcement with captured state + // Resource IDs 1, 2, 3, 5, 8 are allowed + assert_eq!(true, e.enforce(("alice", 1)).unwrap()); + assert_eq!(true, e.enforce(("alice", 2)).unwrap()); + assert_eq!(true, e.enforce(("alice", 3)).unwrap()); + assert_eq!(false, e.enforce(("alice", 4)).unwrap()); // Not in allowed list + assert_eq!(true, e.enforce(("alice", 5)).unwrap()); + assert_eq!(false, e.enforce(("alice", 6)).unwrap()); // Not in allowed list + assert_eq!(false, e.enforce(("alice", 7)).unwrap()); // Not in allowed list + assert_eq!(true, e.enforce(("alice", 8)).unwrap()); + + // Bob is not in policy, so should fail regardless of resource + assert_eq!(false, e.enforce(("bob", 1)).unwrap()); + } } diff --git a/src/model/function_map.rs b/src/model/function_map.rs index ee828e87..0f5014b8 100644 --- a/src/model/function_map.rs +++ b/src/model/function_map.rs @@ -15,7 +15,7 @@ use rhai::Dynamic; static MAT_B: Lazy = Lazy::new(|| Regex::new(r":[^/]*").unwrap()); static MAT_P: Lazy = Lazy::new(|| Regex::new(r"\{[^/]*\}").unwrap()); -use std::{borrow::Cow, collections::HashMap}; +use std::{borrow::Cow, collections::HashMap, sync::Arc}; /// Represents a custom operator function that can be registered with Casbin. /// @@ -29,6 +29,7 @@ use std::{borrow::Cow, collections::HashMap}; /// - And more... /// /// This allows for flexible custom functions that can work with different types. +/// Functions can also capture external state (like database connections) using closures. /// /// # Example /// @@ -37,29 +38,45 @@ use std::{borrow::Cow, collections::HashMap}; /// use rhai::Dynamic; /// /// // Function that works with integers -/// let int_fn = OperatorFunction::Arg2(|a: Dynamic, b: Dynamic| { +/// let int_fn = OperatorFunction::Arg2(Arc::new(|a: Dynamic, b: Dynamic| { /// let a_int = a.as_int().unwrap_or(0); /// let b_int = b.as_int().unwrap_or(0); /// (a_int > b_int).into() -/// }); +/// })); /// /// // Function that works with strings -/// let str_fn = OperatorFunction::Arg2(|a: Dynamic, b: Dynamic| { +/// let str_fn = OperatorFunction::Arg2(Arc::new(|a: Dynamic, b: Dynamic| { /// use casbin::model::function_map::dynamic_to_str; /// let a_str = dynamic_to_str(&a); /// let b_str = dynamic_to_str(&b); /// a_str.contains(b_str.as_ref()).into() -/// }); +/// })); +/// +/// // Function that captures external state +/// let my_data = Arc::new(vec![1, 2, 3]); +/// let data_clone = my_data.clone(); +/// let stateful_fn = OperatorFunction::Arg1(Arc::new(move |idx: Dynamic| { +/// let idx = idx.as_int().unwrap_or(0) as usize; +/// data_clone.get(idx).copied().unwrap_or(0).into() +/// })); /// ``` -#[derive(Clone, Copy)] +#[derive(Clone)] pub enum OperatorFunction { - Arg0(fn() -> Dynamic), - Arg1(fn(Dynamic) -> Dynamic), - Arg2(fn(Dynamic, Dynamic) -> Dynamic), - Arg3(fn(Dynamic, Dynamic, Dynamic) -> Dynamic), - Arg4(fn(Dynamic, Dynamic, Dynamic, Dynamic) -> Dynamic), - Arg5(fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> Dynamic), - Arg6(fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> Dynamic), + Arg0(Arc Dynamic + Send + Sync>), + Arg1(Arc Dynamic + Send + Sync>), + Arg2(Arc Dynamic + Send + Sync>), + Arg3(Arc Dynamic + Send + Sync>), + Arg4(Arc Dynamic + Send + Sync>), + Arg5( + Arc Dynamic + Send + Sync>, + ), + Arg6( + Arc< + dyn Fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> Dynamic + + Send + + Sync, + >, + ), } pub struct FunctionMap { @@ -71,83 +88,87 @@ impl Default for FunctionMap { let mut fm: HashMap = HashMap::new(); fm.insert( "keyMatch".to_owned(), - OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { + OperatorFunction::Arg2(Arc::new(|s1: Dynamic, s2: Dynamic| { key_match(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() - }), + })), ); fm.insert( "keyGet".to_owned(), - OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { + OperatorFunction::Arg2(Arc::new(|s1: Dynamic, s2: Dynamic| { key_get(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() - }), + })), ); fm.insert( "keyMatch2".to_owned(), - OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { + OperatorFunction::Arg2(Arc::new(|s1: Dynamic, s2: Dynamic| { key_match2(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() - }), + })), ); fm.insert( "keyGet2".to_owned(), - OperatorFunction::Arg3(|s1: Dynamic, s2: Dynamic, s3: Dynamic| { - key_get2( - &dynamic_to_str(&s1), - &dynamic_to_str(&s2), - &dynamic_to_str(&s3), - ) - .into() - }), + OperatorFunction::Arg3(Arc::new( + |s1: Dynamic, s2: Dynamic, s3: Dynamic| { + key_get2( + &dynamic_to_str(&s1), + &dynamic_to_str(&s2), + &dynamic_to_str(&s3), + ) + .into() + }, + )), ); fm.insert( "keyMatch3".to_owned(), - OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { + OperatorFunction::Arg2(Arc::new(|s1: Dynamic, s2: Dynamic| { key_match3(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() - }), + })), ); fm.insert( "keyGet3".to_owned(), - OperatorFunction::Arg3(|s1: Dynamic, s2: Dynamic, s3: Dynamic| { - key_get3( - &dynamic_to_str(&s1), - &dynamic_to_str(&s2), - &dynamic_to_str(&s3), - ) - .into() - }), + OperatorFunction::Arg3(Arc::new( + |s1: Dynamic, s2: Dynamic, s3: Dynamic| { + key_get3( + &dynamic_to_str(&s1), + &dynamic_to_str(&s2), + &dynamic_to_str(&s3), + ) + .into() + }, + )), ); fm.insert( "keyMatch4".to_owned(), - OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { + OperatorFunction::Arg2(Arc::new(|s1: Dynamic, s2: Dynamic| { key_match4(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() - }), + })), ); fm.insert( "keyMatch5".to_owned(), - OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { + OperatorFunction::Arg2(Arc::new(|s1: Dynamic, s2: Dynamic| { key_match5(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() - }), + })), ); fm.insert( "regexMatch".to_owned(), - OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { + OperatorFunction::Arg2(Arc::new(|s1: Dynamic, s2: Dynamic| { regex_match(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() - }), + })), ); #[cfg(feature = "glob")] fm.insert( "globMatch".to_owned(), - OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { + OperatorFunction::Arg2(Arc::new(|s1: Dynamic, s2: Dynamic| { glob_match(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() - }), + })), ); #[cfg(feature = "ip")] fm.insert( "ipMatch".to_owned(), - OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| { + OperatorFunction::Arg2(Arc::new(|s1: Dynamic, s2: Dynamic| { ip_match(&dynamic_to_str(&s1), &dynamic_to_str(&s2)).into() - }), + })), ); FunctionMap { fm }