Skip to content

How to derive UnpackValue for a struct #141

Open
@Timmmm

Description

@Timmmm

Hi, I'm considering using Starlark as a config file format - where you might normally use JSON or YAML or TOML via Serde.

So for example instead of a config file like

{
  "servers": [
    {
     "name": "foo",
     "url": "foo.example.com",
     "port": 80
     },
    {
     "name": "bar",
     "url": "bar.example.com",
     "port": 80
     },
  ]
}

You could have

def server(name):
    return {
        "name": name,
        "url": name + ".example.com",
        "port": 80,
    }

{
    "servers": [server("foo"), server("bar")],
}

I got as far as this:

use std::path::Path;

use allocative::Allocative;
use starlark::any::ProvidesStaticType;
use starlark::environment::Globals;
use starlark::environment::Module;
use starlark::eval::Evaluator;
use starlark::syntax::AstModule;
use starlark::syntax::Dialect;
use starlark::values::starlark_value;
use starlark::values::NoSerialize;
use starlark::values::StarlarkValue;
use starlark::values::UnpackValue;

use anyhow::{anyhow, Result};

#[derive(
    Debug,
    derive_more::Display,
    Allocative,
    NoSerialize,
    ProvidesStaticType,
    UnpackValue,
)]
struct Config {
    test: String,
}

#[starlark_value(type = "Config", UnpackValue, StarlarkTypeRepr)]
impl<'v> StarlarkValue<'v> for Config {}

pub fn read_config(path: &Path) -> Result<Config> {
    let content = std::fs::read_to_string(path)?;

    let file_name = path.file_name().ok_or(anyhow!("No file name in path"))?;
    let file_name = file_name.to_str().ok_or(anyhow!("File name isn't valid UTF-8"))?;

    read_config_text(content, file_name)
}

pub fn read_config_text(content: String, file_name: &str) -> Result<Config> {
    let dialect = Dialect {
        enable_f_strings: true,
        ..Dialect::Extended
    };

    let ast = AstModule::parse(file_name, content, &dialect).map_err(|e| anyhow!("Parse error: {e}"))?;

    let globals = Globals::standard();
    let module = Module::new();
    let mut eval = Evaluator::new(&module);
    let value = eval.eval_module(ast, &globals).map_err(|e| anyhow!("Eval error: {e}"))?;
    UnpackValue::unpack_value_err(value)
}

Unfortunately #[derive(UnpackValue)] apparently only works for enums. Is this kind of use case supported?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions