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:
- 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.
Custom functions are added using the add_function method on an Enforcer instance:
use casbin::prelude::*;
use rhai::Dynamic;
// Create your enforcer
let mut e = Enforcer::new("model.conf", "policy.csv").await?;
// Add a custom function
e.add_function(
"myFunction",
OperatorFunction::Arg2(|arg1: Dynamic, arg2: Dynamic| {
// Your custom logic here
true.into() // Return a Dynamic value
}),
);For custom functions that work with strings, you can use the helper function dynamic_to_str:
use casbin::model::function_map::dynamic_to_str;
e.add_function(
"stringContains",
OperatorFunction::Arg2(|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:
e.add_function(
"stringMatch",
OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| {
let str1 = s1.to_string();
let str2 = s2.to_string();
(str1 == str2).into()
}),
);e.add_function(
"greaterThan",
OperatorFunction::Arg2(|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()
}),
);e.add_function(
"customAnd",
OperatorFunction::Arg2(|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()
}),
);e.add_function(
"between",
OperatorFunction::Arg3(|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()
}),
);e.add_function(
"complexCheck",
OperatorFunction::Arg3(|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);
// Custom logic with different types
let result = name_str.len() > 3 && age_int >= 18 && admin_bool;
result.into()
}),
);Once registered, custom functions can be used in your policy matchers:
[matchers]
m = greaterThan(r.age, 18) && stringContains(r.path, p.path)
The OperatorFunction enum supports functions with 0 to 6 arguments:
Arg0:fn() -> DynamicArg1:fn(Dynamic) -> DynamicArg2:fn(Dynamic, Dynamic) -> DynamicArg3:fn(Dynamic, Dynamic, Dynamic) -> DynamicArg4:fn(Dynamic, Dynamic, Dynamic, Dynamic) -> DynamicArg5:fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> DynamicArg6:fn(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic) -> Dynamic
Rhai's Dynamic type provides several methods to extract values:
as_int()- Extract as integer (returnsResult<i64, &str>)as_bool()- Extract as boolean (returnsResult<bool, &str>)as_float()- Extract as float (returnsResult<f64, &str>)is_string()- Check if it's a stringinto_immutable_string()- Convert to ImmutableString (consumes the Dynamic)to_string()- Convert to String (works for any type)
All existing code continues to work. The change from ImmutableString to Dynamic is backward compatible because:
- Strings are automatically converted to
Dynamicby Rhai - The
dynamic_to_strhelper function makes string extraction easy - All built-in functions have been updated and tested
If you have existing custom functions using ImmutableString, update them like this:
Before:
e.add_function(
"myFunc",
OperatorFunction::Arg2(
|s1: ImmutableString, s2: ImmutableString| {
// logic here
true.into()
}
),
);After:
e.add_function(
"myFunc",
OperatorFunction::Arg2(|s1: Dynamic, s2: Dynamic| {
let str1 = s1.to_string();
let str2 = s2.to_string();
// logic here
true.into()
}),
);- Casbin Documentation
- Rhai Documentation
- Test:
test_custom_function_with_dynamic_typesinsrc/enforcer.rs