hcl/eval/mod.rs
1//! Evaluate HCL templates and expressions.
2//!
3//! This module provides the [`Evaluate`] trait which enables HCL template and expression
4//! evaluation. It is implemented for various types that either directly or transitively contain
5//! templates or expressions that need to be evaluated.
6//!
7//! Additionally, the [`Context`] type is used to declare variables and functions to make them
8//! available during expression evaluation.
9//!
10//! For convenience, the [`from_str`] and [`to_string`] functions are provided which enable
11//! expression evaluation during (de-)serialization directly. Check out their function docs for
12//! usage examples.
13//!
14//! # Examples
15//!
16//! HCL expressions can contain variables and functions which are made available through the
17//! [`Context`] value passed to [`Evaluate::evaluate`].
18//!
19//! Here's a short example which evaluates a template expression that contains a variable:
20//!
21//! ```
22//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
23//! use hcl::Value;
24//! use hcl::eval::{Context, Evaluate};
25//! use hcl::expr::TemplateExpr;
26//!
27//! let expr = TemplateExpr::from("Hello ${name}!");
28//!
29//! let mut ctx = Context::new();
30//! ctx.declare_var("name", "World");
31//!
32//! assert_eq!(expr.evaluate(&ctx)?, Value::from("Hello World!"));
33//! # Ok(())
34//! # }
35//! ```
36//!
37//! Template directives like `for` loops can be evaluated as well, this time using a [`Template`]
38//! instead of [`TemplateExpr`]:
39//!
40//! ```
41//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
42//! use hcl::Template;
43//! use hcl::eval::{Context, Evaluate};
44//! use std::str::FromStr;
45//!
46//! let input = r#"
47//! Bill of materials:
48//! %{ for item in items ~}
49//! - ${item}
50//! %{ endfor ~}
51//! "#;
52//!
53//! let template = Template::from_str(input)?;
54//!
55//! let mut ctx = Context::new();
56//! ctx.declare_var("items", vec!["time", "code", "sweat"]);
57//!
58//! let evaluated = r#"
59//! Bill of materials:
60//! - time
61//! - code
62//! - sweat
63//! "#;
64//!
65//! assert_eq!(template.evaluate(&ctx)?, evaluated);
66//! # Ok(())
67//! # }
68//! ```
69//!
70//! If you need to include the literal representation of variable reference, you can escape `${`
71//! with `$${`:
72//!
73//! ```
74//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
75//! use hcl::eval::{Context, Evaluate};
76//! use hcl::Template;
77//! use std::str::FromStr;
78//!
79//! let template = Template::from_str("Value: ${value}, escaped: $${value}")?;
80//! let mut ctx = Context::new();
81//! ctx.declare_var("value", 1);
82//!
83//! let evaluated = "Value: 1, escaped: ${value}";
84//! assert_eq!(template.evaluate(&ctx)?, evaluated);
85//! # Ok(())
86//! # }
87//! ```
88//!
89//! Here's another example which evaluates some attribute expressions using [`from_str`] as
90//! described in the [deserialization
91//! example][crate::eval#expression-evaluation-during-de-serialization] below:
92//!
93//! ```
94//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
95//! use hcl::Body;
96//! use hcl::eval::Context;
97//!
98//! let input = r#"
99//! operation = 1 + 1
100//! conditional = cond ? "yes" : "no"
101//! for_expr = [for item in items: item if item <= 3]
102//! "#;
103//!
104//! let mut ctx = Context::new();
105//! ctx.declare_var("cond", true);
106//! ctx.declare_var("items", vec![1, 2, 3, 4, 5]);
107//!
108//! let body: Body = hcl::eval::from_str(input, &ctx)?;
109//!
110//! let expected = Body::builder()
111//! .add_attribute(("operation", 2))
112//! .add_attribute(("conditional", "yes"))
113//! .add_attribute(("for_expr", vec![1, 2, 3]))
114//! .build();
115//!
116//! assert_eq!(body, expected);
117//! # Ok(())
118//! # }
119//! ```
120//!
121//! ## Function calls in expressions
122//!
123//! To evaluate functions calls, you need to create a function definition and make it available to
124//! the evaluation context. Function definitions are created via the [`FuncDef`] type which
125//! contains more information in its [type-level documentation][FuncDef].
126//!
127//! Here's the example from above, updated to also include a function call to make the `name`
128//! uppercase:
129//!
130//! ```
131//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
132//! use hcl::Value;
133//! use hcl::eval::{Context, Evaluate, FuncArgs, FuncDef, ParamType};
134//! use hcl::expr::TemplateExpr;
135//!
136//! // A template expression which needs to be evaluated. It needs access
137//! // to the `uppercase` function and `name` variable.
138//! let expr = TemplateExpr::from("Hello ${uppercase(name)}!");
139//!
140//! // A function that is made available to expressions via the `Context` value.
141//! fn uppercase(args: FuncArgs) -> Result<Value, String> {
142//! // We know that there is one argument and it is of type `String`
143//! // because the function arguments are validated using the parameter
144//! // type information in the `FuncDef` before calling the function.
145//! Ok(Value::from(args[0].as_str().unwrap().to_uppercase()))
146//! }
147//!
148//! // Create a definition for the `uppercase` function.
149//! let uppercase_func = FuncDef::builder()
150//! .param(ParamType::String)
151//! .build(uppercase);
152//!
153//! // Create the context and add variables and functions to it.
154//! let mut ctx = Context::new();
155//! ctx.declare_var("name", "world");
156//! ctx.declare_func("uppercase", uppercase_func);
157//!
158//! // Evaluate the expression.
159//! assert_eq!(expr.evaluate(&ctx)?, Value::from("Hello WORLD!"));
160//! # Ok(())
161//! # }
162//! ```
163//!
164//! ## Expression evaluation during (de-)serialization
165//!
166//! It's possible to evaluate expressions directly when deserializing HCL into a Rust value, or
167//! when serializing a Rust value that contains HCL expressions into HCL.
168//!
169//! For these use cases the convenience functions [`hcl::eval::from_str`][from_str] and
170//! [`hcl::eval::to_string`][to_string] are provided. Their usage is similar to
171//! [`hcl::from_str`][crate::from_str] and [`hcl::to_string`][crate::to_string] but they receive a
172//! reference to a [`Context`] value as second parameter.
173//!
174//! Here's a deserialization example using `from_str`:
175//!
176//! ```
177//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
178//! use hcl::Body;
179//! use hcl::eval::Context;
180//!
181//! let input = r#"hello_world = "Hello, ${name}!""#;
182//!
183//! let mut ctx = Context::new();
184//! ctx.declare_var("name", "Rust");
185//!
186//! let body: Body = hcl::eval::from_str(input, &ctx)?;
187//!
188//! let expected = Body::builder()
189//! .add_attribute(("hello_world", "Hello, Rust!"))
190//! .build();
191//!
192//! assert_eq!(body, expected);
193//! # Ok(())
194//! # }
195//! ```
196//!
197//! And here's how expression evaluation during serialization via `to_string` works:
198//!
199//! ```
200//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
201//! use hcl::Body;
202//! use hcl::eval::Context;
203//! use hcl::expr::TemplateExpr;
204//!
205//! let expr = TemplateExpr::from("Hello, ${name}!");
206//!
207//! let body = Body::builder()
208//! .add_attribute(("hello_world", expr))
209//! .build();
210//!
211//! let mut ctx = Context::new();
212//! ctx.declare_var("name", "Rust");
213//!
214//! let string = hcl::eval::to_string(&body, &ctx)?;
215//!
216//! assert_eq!(string, "hello_world = \"Hello, Rust!\"\n");
217//! # Ok(())
218//! # }
219//! ```
220
221mod error;
222mod expr;
223mod func;
224mod impls;
225mod template;
226
227pub use self::error::{Error, ErrorKind, Errors, EvalResult};
228pub use self::func::{
229 Func, FuncArgs, FuncDef, FuncDefBuilder, ParamType, PositionalArgs, VariadicArgs,
230};
231use crate::expr::{
232 BinaryOp, BinaryOperator, Conditional, Expression, ForExpr, FuncCall, FuncName, Object,
233 ObjectKey, Operation, TemplateExpr, Traversal, TraversalOperator, UnaryOp, UnaryOperator,
234};
235use crate::parser;
236use crate::structure::{Attribute, Block, Body, Structure};
237use crate::template::{
238 Directive, Element, ForDirective, IfDirective, Interpolation, Strip, Template,
239};
240use crate::{Identifier, Map, Result, Value};
241use serde::{de, ser};
242use vecmap::VecMap;
243
244mod private {
245 pub trait Sealed {}
246}
247
248/// A trait for evaluating the HCL template and expression sub-languages.
249///
250/// The types implementing this trait must recursively evaluate all HCL templates and expressions
251/// in their fields.
252///
253/// This trait is sealed to prevent implementation outside of this crate.
254pub trait Evaluate: private::Sealed {
255 /// The type that is returned by [`evaluate`][Evaluate::evaluate] on success.
256 type Output;
257
258 /// Recursively evaluates all HCL templates and expressions in the implementing type using the
259 /// variables and functions declared in the `Context`.
260 ///
261 /// See the [module-level documentation][crate::eval] for usage examples.
262 ///
263 /// # Errors
264 ///
265 /// This function fails with an error if:
266 ///
267 /// - an expression evaluates to a value that is not allowed in a given context, e.g. a string
268 /// occures where a boolean value is expected.
269 /// - an operation is performed on values that it's not applicable to.
270 /// - an undefined variable or function is encountered.
271 /// - a defined function is called with unexpected arguments.
272 fn evaluate(&self, ctx: &Context) -> EvalResult<Self::Output>;
273
274 /// Recursively tries to evaluate all nested expressions in place.
275 ///
276 /// This function does not stop at the first error but continues to evaluate expressions as far
277 /// as it can.
278 ///
279 /// The default implementation does nothing and always returns `Ok(())`.
280 ///
281 /// # Errors
282 ///
283 /// Returns an [`Errors`] value containing one of more [`Error`]s if the evaluation of any
284 /// (potentially nested) expression fails.
285 ///
286 /// See the errors section of [`evaluate`][Evaluate::evaluate] for a list of failure modes.
287 fn evaluate_in_place(&mut self, ctx: &Context) -> EvalResult<(), Errors> {
288 _ = ctx;
289 Ok(())
290 }
291}
292
293/// A type holding the evaluation context.
294///
295/// The `Context` is used to declare variables and functions that are evaluated when evaluating a
296/// template or expression.
297#[derive(Debug, Clone)]
298pub struct Context<'a> {
299 vars: Map<Identifier, Value>,
300 funcs: VecMap<FuncName, FuncDef>,
301 parent: Option<&'a Context<'a>>,
302 expr: Option<&'a Expression>,
303}
304
305impl Default for Context<'_> {
306 fn default() -> Self {
307 Context {
308 vars: Map::new(),
309 funcs: VecMap::new(),
310 parent: None,
311 expr: None,
312 }
313 }
314}
315
316impl<'a> Context<'a> {
317 /// Creates an empty `Context`.
318 pub fn new() -> Self {
319 Context::default()
320 }
321
322 // Create a new child `Context` which has the current one as parent.
323 fn child(&self) -> Context<'_> {
324 let mut ctx = Context::new();
325 ctx.parent = Some(self);
326 ctx
327 }
328
329 // Create a new child `Context` which has the current one as parent and also contains context
330 // about the expression that is currently evaluated.
331 fn child_with_expr(&self, expr: &'a Expression) -> Context<'_> {
332 let mut ctx = self.child();
333 ctx.expr = Some(expr);
334 ctx
335 }
336
337 /// Declare a variable from a name and a value.
338 ///
339 /// # Example
340 ///
341 /// ```
342 /// # use hcl::eval::Context;
343 /// let mut ctx = Context::new();
344 /// ctx.declare_var("some_number", 42);
345 /// ```
346 pub fn declare_var<I, T>(&mut self, name: I, value: T)
347 where
348 I: Into<Identifier>,
349 T: Into<Value>,
350 {
351 self.vars.insert(name.into(), value.into());
352 }
353
354 /// Declare a function from a name and a function definition.
355 ///
356 /// See the documentation of the [`FuncDef`] type to learn about all available options for
357 /// constructing a function definition.
358 ///
359 /// # Example
360 ///
361 /// ```
362 /// # use hcl::eval::Context;
363 /// use hcl::Value;
364 /// use hcl::eval::{FuncArgs, FuncDef, ParamType};
365 ///
366 /// fn strlen(args: FuncArgs) -> Result<Value, String> {
367 /// // The arguments are already validated against the function
368 /// // definition's parameters, so we know that there is exactly
369 /// // one arg of type string.
370 /// Ok(Value::from(args[0].as_str().unwrap().len()))
371 /// }
372 ///
373 /// let func_def = FuncDef::builder()
374 /// .param(ParamType::String)
375 /// .build(strlen);
376 ///
377 /// let mut ctx = Context::new();
378 /// ctx.declare_func("strlen", func_def);
379 /// ```
380 pub fn declare_func<I>(&mut self, name: I, func: FuncDef)
381 where
382 I: Into<FuncName>,
383 {
384 self.funcs.insert(name.into(), func);
385 }
386
387 /// Lookup a variable's value.
388 ///
389 /// When the variable is declared in multiple parent scopes, the innermost variable's value is
390 /// returned.
391 fn lookup_var(&self, name: &Identifier) -> EvalResult<&Value> {
392 self.var(name)
393 .ok_or_else(|| self.error(ErrorKind::UndefinedVar(name.clone())))
394 }
395
396 /// Lookup a function definition.
397 ///
398 /// When the function is declared in multiple parent scopes, the innermost definition is
399 /// returned.
400 fn lookup_func(&self, name: &FuncName) -> EvalResult<&FuncDef> {
401 self.func(name)
402 .ok_or_else(|| self.error(ErrorKind::UndefinedFunc(name.clone())))
403 }
404
405 /// Creates an error enriched with expression information, if available.
406 fn error<T>(&self, inner: T) -> Error
407 where
408 T: Into<ErrorKind>,
409 {
410 // The parent expression gives better context about the potential error location. Use it if
411 // available.
412 match self.parent_expr().or(self.expr) {
413 Some(expr) => Error::new_with_expr(inner, Some(expr.clone())),
414 None => Error::new(inner),
415 }
416 }
417
418 fn var(&self, name: &Identifier) -> Option<&Value> {
419 self.vars
420 .get(name)
421 .or_else(|| self.parent.and_then(|parent| parent.var(name)))
422 }
423
424 fn func(&self, name: &FuncName) -> Option<&FuncDef> {
425 self.funcs
426 .get(name)
427 .or_else(|| self.parent.and_then(|parent| parent.func(name)))
428 }
429
430 fn expr(&self) -> Option<&Expression> {
431 self.expr.or_else(|| self.parent_expr())
432 }
433
434 fn parent_expr(&self) -> Option<&Expression> {
435 self.parent.and_then(Context::expr)
436 }
437}
438
439/// Deserialize an instance of type `T` from a string of HCL text and evaluate all expressions
440/// using the given context.
441///
442/// See the [module level documentation][crate::eval#expression-evaluation-during-de-serialization]
443/// for a usage example.
444///
445/// # Errors
446///
447/// This function fails with an error if:
448///
449/// - the string `s` cannot be parsed as HCL.
450/// - any condition described in the error section of the [`evaluate` method
451/// documentation][Evaluate::evaluate] meets.
452/// - the evaluated value cannot be deserialized as a `T`.
453pub fn from_str<T>(s: &str, ctx: &Context) -> Result<T>
454where
455 T: de::DeserializeOwned,
456{
457 let body = parser::parse(s)?;
458 let evaluated = body.evaluate(ctx)?;
459 super::from_body(evaluated)
460}
461
462/// Serialize the given value as an HCL string after evaluating all expressions using the given
463/// context.
464///
465/// See the [module level documentation][crate::eval#expression-evaluation-during-de-serialization]
466/// for a usage example.
467///
468/// # Errors
469///
470/// This function fails with an error if any condition described in the error section of the
471/// [`evaluate` method documentation][Evaluate::evaluate] meets.
472pub fn to_string<T>(value: &T, ctx: &Context) -> Result<String>
473where
474 T: ?Sized + Evaluate,
475 <T as Evaluate>::Output: ser::Serialize,
476{
477 let evaluated = value.evaluate(ctx)?;
478 super::to_string(&evaluated)
479}