Module hcl::eval

source ·
Expand description

Evaluate HCL templates and expressions.

This module provides the Evaluate trait which enables HCL template and expression evaluation. It is implemented for various types that either directly or transitively contain templates or expressions that need to be evaluated.

Additionally, the Context type is used to declare variables and functions to make them available during expression evaluation.

For convenience, the from_str and to_string functions are provided which enable expression evaluation during (de-)serialization directly. Check out their function docs for usage examples.

§Examples

HCL expressions can contain variables and functions which are made available through the Context value passed to Evaluate::evaluate.

Here’s a short example which evaluates a template expression that contains a variable:

use hcl::Value;
use hcl::eval::{Context, Evaluate};
use hcl::expr::TemplateExpr;

let expr = TemplateExpr::from("Hello ${name}!");

let mut ctx = Context::new();
ctx.declare_var("name", "World");

assert_eq!(expr.evaluate(&ctx)?, Value::from("Hello World!"));

Template directives like for loops can be evaluated as well, this time using a Template instead of TemplateExpr:

use hcl::Template;
use hcl::eval::{Context, Evaluate};
use std::str::FromStr;

let input = r#"
Bill of materials:
%{ for item in items ~}
- ${item}
%{ endfor ~}
"#;

let template = Template::from_str(input)?;

let mut ctx = Context::new();
ctx.declare_var("items", vec!["time", "code", "sweat"]);

let evaluated = r#"
Bill of materials:
- time
- code
- sweat
"#;

assert_eq!(template.evaluate(&ctx)?, evaluated);

If you need to include the literal representation of variable reference, you can escape ${ with $${:

use hcl::eval::{Context, Evaluate};
use hcl::Template;
use std::str::FromStr;

let template = Template::from_str("Value: ${value}, escaped: $${value}")?;
let mut ctx = Context::new();
ctx.declare_var("value", 1);

let evaluated = "Value: 1, escaped: ${value}";
assert_eq!(template.evaluate(&ctx)?, evaluated);

Here’s another example which evaluates some attribute expressions using from_str as described in the deserialization example below:

use hcl::Body;
use hcl::eval::Context;

let input = r#"
operation   = 1 + 1
conditional = cond ? "yes" : "no"
for_expr    = [for item in items: item if item <= 3]
"#;

let mut ctx = Context::new();
ctx.declare_var("cond", true);
ctx.declare_var("items", vec![1, 2, 3, 4, 5]);

let body: Body = hcl::eval::from_str(input, &ctx)?;

let expected = Body::builder()
    .add_attribute(("operation", 2))
    .add_attribute(("conditional", "yes"))
    .add_attribute(("for_expr", vec![1, 2, 3]))
    .build();

assert_eq!(body, expected);

§Function calls in expressions

To evaluate functions calls, you need to create a function definition and make it available to the evaluation context. Function definitions are created via the FuncDef type which contains more information in its type-level documentation.

Here’s the example from above, updated to also include a function call to make the name uppercase:

use hcl::Value;
use hcl::eval::{Context, Evaluate, FuncArgs, FuncDef, ParamType};
use hcl::expr::TemplateExpr;

// A template expression which needs to be evaluated. It needs access
// to the `uppercase` function and `name` variable.
let expr = TemplateExpr::from("Hello ${uppercase(name)}!");

// A function that is made available to expressions via the `Context` value.
fn uppercase(args: FuncArgs) -> Result<Value, String> {
    // We know that there is one argument and it is of type `String`
    // because the function arguments are validated using the parameter
    // type information in the `FuncDef` before calling the function.
    Ok(Value::from(args[0].as_str().unwrap().to_uppercase()))
}

// Create a definition for the `uppercase` function.
let uppercase_func = FuncDef::builder()
    .param(ParamType::String)
    .build(uppercase);

// Create the context and add variables and functions to it.
let mut ctx = Context::new();
ctx.declare_var("name", "world");
ctx.declare_func("uppercase", uppercase_func);

// Evaluate the expression.
assert_eq!(expr.evaluate(&ctx)?, Value::from("Hello WORLD!"));

§Expression evaluation during (de-)serialization

It’s possible to evaluate expressions directly when deserializing HCL into a Rust value, or when serializing a Rust value that contains HCL expressions into HCL.

For these use cases the convenience functions hcl::eval::from_str and hcl::eval::to_string are provided. Their usage is similar to hcl::from_str and hcl::to_string but they receive a reference to a Context value as second parameter.

Here’s a deserialization example using from_str:

use hcl::Body;
use hcl::eval::Context;

let input = r#"hello_world = "Hello, ${name}!""#;

let mut ctx = Context::new();
ctx.declare_var("name", "Rust");

let body: Body = hcl::eval::from_str(input, &ctx)?;

let expected = Body::builder()
    .add_attribute(("hello_world", "Hello, Rust!"))
    .build();

assert_eq!(body, expected);

And here’s how expression evaluation during serialization via to_string works:

use hcl::Body;
use hcl::eval::Context;
use hcl::expr::TemplateExpr;

let expr = TemplateExpr::from("Hello, ${name}!");

let body = Body::builder()
    .add_attribute(("hello_world", expr))
    .build();

let mut ctx = Context::new();
ctx.declare_var("name", "Rust");

let string = hcl::eval::to_string(&body, &ctx)?;

assert_eq!(string, "hello_world = \"Hello, Rust!\"\n");

Structs§

Enums§

  • An enum representing all kinds of errors that can happen during the evaluation of HCL expressions and templates.
  • A type hint for a function parameter.

Traits§

  • A trait for evaluating the HCL template and expression sub-languages.

Functions§

  • Deserialize an instance of type T from a string of HCL text and evaluate all expressions using the given context.
  • Serialize the given value as an HCL string after evaluating all expressions using the given context.

Type Aliases§

  • The result type used by this module.
  • A type alias for the signature of functions expected by the FuncDef type.