facebook / starlark-rust

A Rust implementation of the Starlark language

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Question] Creating a function using the `Dict` type as an argument.

hulto opened this issue · comments

commented

Sorry if this is the wrong place but I'm having issues using the rust Dict type.

I'm creating a template function to expand the starlark stdlibrary.
The function will take a JINJA2 template and using Tera format it with arbitrary variables.
I'm currently using a serede_json object to pass the template vars but I would like to switch to the Starlark Dict type so the values can be more easily worked with.

file.rs

mod template_impl;

use starlark::environment::{Methods, MethodsBuilder, MethodsStatic};
use starlark::values::{StarlarkValue, Value, UnpackValue, ValueLike, dict::Dict};
use starlark::values::none::NoneType;
use starlark::{starlark_type, starlark_simple_value, starlark_module};

...
#[starlark_module]
fn methods(builder: &mut MethodsBuilder) {
...
     fn template(_this: FileLibrary, template_path: String, dst_path: String, args: Dict, autoescape: bool) -> NoneType {
        template_impl::template(template_path, dst_path, args, autoescape)?;
        Ok(NoneType{})
    }
    fn write(_this: FileLibrary, path: String, content: String) -> NoneType {
        write_impl::write(path, content)?;
        Ok(NoneType{})
    }
}

This gives the following error.

image


I came across these two issues:

  • #14 - My understanding is this is a good place to look if I wanted to create my own types but I'd like to use the Dict type.
  • #22 - I see how I could unpack & create a Dict from a rust SmallMap but i'm still not sure how to define a rust function to take a Starlark Dict type.

What rust type should I specify in my function definition if I want to create a function that accepts a Starlark Dict?

If performance is not critical, try SmallMap<String, Value> instead of Dict.

You can also use DictOf<...>.

commented

Thanks you so much for the quick response @stepancheg :)
That did the trick! I have a follow up question:

I'm currently iterating over each key in the SmallMap and inserting the value into the tera::Context with serde_json I'm able to serialize the Value type.

fn build_context_json(json_data: String) -> Result<Context> {
    let serde_dict: serde_json::Value = serde_json::from_str(json_data.as_str())?;
    let mut context = Context::new();
    for (key, value) in serde_dict.as_object().unwrap() {
        context.insert(key.as_str(), &value);
    }
    return Ok(context);
}

It looks like the starlark Value type doesn't have a serialize / deserialize function.
My first thought is to iterate through and extract each type String, Int, etc. and pass those in using the get_type() function and <Type>::unpack_value() functions.

fn build_context_smallmap(dict_data: SmallMap<String, Value>) -> Result<Context> {
    let mut context = Context::new();
    for (key, value) in dict_data {
        println!("{}", value.get_type() );
        if value.get_type() == "string" {
            let tmp = String::unpack_value(value);
            context.insert(key.as_str(), &tmp);
        } else if value.get_type() == "int" {
            let tmp = i32::unpack_value(value);
            context.insert(key.as_str(), &tmp);
        } else if value.get_type() == "bool" {
            let tmp = bool::unpack_value(value);
            context.insert(key.as_str(), &tmp);
        }

    }
    return Ok(context);
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_template_build_context() -> anyhow::Result<()>{
        let map: SmallMap<String, Value> = smallmap! {
            "name".to_string() => Value::new_frozen(const_frozen_string!("greg").unpack()),
            "age".to_string() => Value::new_int(29),
            "admin".to_string() => Value::new_bool(true),
        };
        let res = build_context(map)?;
        println!("{:?}", res);
        Ok(())
    }
}

Is there a better way to extract a serializable object from the Value type?
Is there a way to create a Value from a SmallMap or List/Vec to create a test for nested JSON types?
Eg.

{
    "jobs": [
        { 
            "name":"test",
            "jobid":1
        },
        { 
            "name":"test2",
            "jobid":2
        },
        { 
            "name":"job3",
            "jobid":3
        }
    ]
}
commented

Ah thank you looks like were using too old of a version 0.6.0.
I'll try updating 🙂
Thank you!