rhaiscript / rhai

Rhai - An embedded scripting language for Rust.
https://crates.io/crates/rhai
Apache License 2.0
3.79k stars 177 forks source link

register_type, register_get is not convinient #131

Closed GopherJ closed 4 years ago

GopherJ commented 4 years ago

See also: https://github.com/casbin/casbin-rs/issues/78.

While implementing ABAC for casbin-rs, we don't know in advance how many types will we get and what are they and what are the getter used by them.

It's the users of casbin-rs who define their types. How can we solve this?

schungx commented 4 years ago

OK. I read your issue and I see what you're trying to get at. There is a very simple solution.

Rhai supports object maps, in the syntax: #{a: 1, b: "hello", c: [1, 2, true]}

An object map, converted into Rust, is HashMap<String, Dynamic>. See https://github.com/jonathandturner/rhai/blob/master/README.md#object-maps

All you need to do is to create this type and then set it as the value of a Scope variable or return it from a registered function (the function simply returns HashMap<String, Dynamic>).

For example:

let mut object: HashMap<String, Dynamic> = HashMap::new();

object.insert("foo".to_string(), true.into_dynamic());
object.insert("bar".to_string(), "hello".to_string().into_dynamic());
object.insert("baz".to_string(), 123.into_dynamic());

let mut scope = Scope::new();
scope.push_constant("my_object", object);

let result = engine.eval_expression_with_scope::<bool>(&mut scope, "my_object.baz < 42")?;

If you don't want to even provide the values in Rust, your users can define their own objects in Rhai script, then you read it back as a HashMap with all values already set. You can make sure your user only provides a valid object map by parsing it as an expression eval_expression::<Map> which will error out if the result is not a valid object map (i.e. the HashMap you need).

Since Rhai object maps has a syntax that is exactly the same as JSON (except for the leading #), your user can use serde etc. to serialize a data structure into JSON, then turn it into a Rhai object map script by prepending it with #.

This HashMap can then be passed to Enforce and checked by a separate Rhai script as an object map. This way it round-trips.

schungx commented 4 years ago

Thinking of this some more... First of all, it looks like your user can define their own access control objects through some form of meta description, which is then turned into some objects in your code.

You can easily build up a HashMap with fields from the object metadata, especially when if it is only one level deep (i.e. consisting of only attributes of primitive types).

On the other hand, you may think of transparently round-tripping the user's AC object with all the attributes, if you don't really act on those attributes in your code. This way your code needs not even care about the structure of these user objects. The way to do it is to have the user submit a valid piece of JSON as their object representation, which the user can get easily by serializing their own data type, or from a web service backend on the user's side.

Either way, access the fields of this object as an object map in Rhai.

GopherJ commented 4 years ago

Let's close this one, great thanks @schungx on your quick help.