Skip to content

Latest commit

 

History

History
203 lines (159 loc) · 5.17 KB

File metadata and controls

203 lines (159 loc) · 5.17 KB

Custom Functions in Casbin-RS

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:

  • 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.

Basic Usage

Adding a Custom Function

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
    }),
);

Examples

1. String-based Custom Function

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()
    }),
);

2. Integer-based Custom Function

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()
    }),
);

3. Boolean-based Custom Function

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()
    }),
);

4. Multi-argument Custom Function

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()
    }),
);

5. Mixed-type Custom Function

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()
    }),
);

Using Custom Functions in Matchers

Once registered, custom functions can be used in your policy matchers:

[matchers]
m = greaterThan(r.age, 18) && stringContains(r.path, p.path)

OperatorFunction Variants

The OperatorFunction enum supports functions with 0 to 6 arguments:

  • 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

Working with Dynamic Types

Rhai's Dynamic type provides several methods to extract values:

  • as_int() - Extract as integer (returns Result<i64, &str>)
  • as_bool() - Extract as boolean (returns Result<bool, &str>)
  • as_float() - Extract as float (returns Result<f64, &str>)
  • is_string() - Check if it's a string
  • 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:

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()
    }),
);

See Also