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::structure::{Attribute, Block, Body, Structure};
236use crate::template::{
237 Directive, Element, ForDirective, IfDirective, Interpolation, Strip, Template,
238};
239use crate::{Identifier, Map, Result, Value};
240use serde::{de, ser};
241use vecmap::VecMap;
242
243mod private {
244 pub trait Sealed {}
245}
246
247/// A trait for evaluating the HCL template and expression sub-languages.
248///
249/// The types implementing this trait must recursively evaluate all HCL templates and expressions
250/// in their fields.
251///
252/// This trait is sealed to prevent implementation outside of this crate.
253pub trait Evaluate: private::Sealed {
254 /// The type that is returned by [`evaluate`][Evaluate::evaluate] on success.
255 type Output;
256
257 /// Recursively evaluates all HCL templates and expressions in the implementing type using the
258 /// variables and functions declared in the `Context`.
259 ///
260 /// See the [module-level documentation][crate::eval] for usage examples.
261 ///
262 /// # Errors
263 ///
264 /// This function fails with an error if:
265 ///
266 /// - an expression evaluates to a value that is not allowed in a given context, e.g. a string
267 /// occures where a boolean value is expected.
268 /// - an operation is performed on values that it's not applicable to.
269 /// - an undefined variable or function is encountered.
270 /// - a defined function is called with unexpected arguments.
271 fn evaluate(&self, ctx: &Context) -> EvalResult<Self::Output>;
272
273 /// Recursively tries to evaluate all nested expressions in place.
274 ///
275 /// This function does not stop at the first error but continues to evaluate expressions as far
276 /// as it can.
277 ///
278 /// The default implementation does nothing and always returns `Ok(())`.
279 ///
280 /// # Errors
281 ///
282 /// Returns an [`Errors`] value containing one of more [`Error`]s if the evaluation of any
283 /// (potentially nested) expression fails.
284 ///
285 /// See the errors section of [`evaluate`][Evaluate::evaluate] for a list of failure modes.
286 fn evaluate_in_place(&mut self, ctx: &Context) -> EvalResult<(), Errors> {
287 _ = ctx;
288 Ok(())
289 }
290}
291
292/// A type holding the evaluation context.
293///
294/// The `Context` is used to declare variables and functions that are evaluated when evaluating a
295/// template or expression.
296#[derive(Debug, Clone)]
297pub struct Context<'a> {
298 vars: Map<Identifier, Value>,
299 funcs: VecMap<FuncName, FuncDef>,
300 parent: Option<&'a Context<'a>>,
301 expr: Option<&'a Expression>,
302}
303
304impl Default for Context<'_> {
305 fn default() -> Self {
306 Context {
307 vars: Map::new(),
308 funcs: VecMap::new(),
309 parent: None,
310 expr: None,
311 }
312 }
313}
314
315impl<'a> Context<'a> {
316 /// Creates an empty `Context`.
317 pub fn new() -> Self {
318 Context::default()
319 }
320
321 // Create a new child `Context` which has the current one as parent.
322 fn child(&self) -> Context<'_> {
323 let mut ctx = Context::new();
324 ctx.parent = Some(self);
325 ctx
326 }
327
328 // Create a new child `Context` which has the current one as parent and also contains context
329 // about the expression that is currently evaluated.
330 fn child_with_expr(&self, expr: &'a Expression) -> Context<'_> {
331 let mut ctx = self.child();
332 ctx.expr = Some(expr);
333 ctx
334 }
335
336 /// Declare a variable from a name and a value.
337 ///
338 /// # Example
339 ///
340 /// ```
341 /// # use hcl::eval::Context;
342 /// let mut ctx = Context::new();
343 /// ctx.declare_var("some_number", 42);
344 /// ```
345 pub fn declare_var<I, T>(&mut self, name: I, value: T)
346 where
347 I: Into<Identifier>,
348 T: Into<Value>,
349 {
350 self.vars.insert(name.into(), value.into());
351 }
352
353 /// Declare a function from a name and a function definition.
354 ///
355 /// See the documentation of the [`FuncDef`] type to learn about all available options for
356 /// constructing a function definition.
357 ///
358 /// # Example
359 ///
360 /// ```
361 /// # use hcl::eval::Context;
362 /// use hcl::Value;
363 /// use hcl::eval::{FuncArgs, FuncDef, ParamType};
364 ///
365 /// fn strlen(args: FuncArgs) -> Result<Value, String> {
366 /// // The arguments are already validated against the function
367 /// // definition's parameters, so we know that there is exactly
368 /// // one arg of type string.
369 /// Ok(Value::from(args[0].as_str().unwrap().len()))
370 /// }
371 ///
372 /// let func_def = FuncDef::builder()
373 /// .param(ParamType::String)
374 /// .build(strlen);
375 ///
376 /// let mut ctx = Context::new();
377 /// ctx.declare_func("strlen", func_def);
378 /// ```
379 pub fn declare_func<I>(&mut self, name: I, func: FuncDef)
380 where
381 I: Into<FuncName>,
382 {
383 self.funcs.insert(name.into(), func);
384 }
385
386 /// Lookup a variable's value.
387 ///
388 /// When the variable is declared in multiple parent scopes, the innermost variable's value is
389 /// returned.
390 fn lookup_var(&self, name: &Identifier) -> EvalResult<&Value> {
391 self.var(name)
392 .ok_or_else(|| self.error(ErrorKind::UndefinedVar(name.clone())))
393 }
394
395 /// Lookup a function definition.
396 ///
397 /// When the function is declared in multiple parent scopes, the innermost definition is
398 /// returned.
399 fn lookup_func(&self, name: &FuncName) -> EvalResult<&FuncDef> {
400 self.func(name)
401 .ok_or_else(|| self.error(ErrorKind::UndefinedFunc(name.clone())))
402 }
403
404 /// Creates an error enriched with expression information, if available.
405 fn error<T>(&self, inner: T) -> Error
406 where
407 T: Into<ErrorKind>,
408 {
409 // The parent expression gives better context about the potential error location. Use it if
410 // available.
411 match self.parent_expr().or(self.expr) {
412 Some(expr) => Error::new_with_expr(inner, Some(expr.clone())),
413 None => Error::new(inner),
414 }
415 }
416
417 fn var(&self, name: &Identifier) -> Option<&Value> {
418 self.vars
419 .get(name)
420 .or_else(|| self.parent.and_then(|parent| parent.var(name)))
421 }
422
423 fn func(&self, name: &FuncName) -> Option<&FuncDef> {
424 self.funcs
425 .get(name)
426 .or_else(|| self.parent.and_then(|parent| parent.func(name)))
427 }
428
429 fn expr(&self) -> Option<&Expression> {
430 self.expr.or_else(|| self.parent_expr())
431 }
432
433 fn parent_expr(&self) -> Option<&Expression> {
434 self.parent.and_then(Context::expr)
435 }
436}
437
438/// Deserialize an instance of type `T` from a string of HCL text and evaluate all expressions
439/// using the given context.
440///
441/// See the [module level documentation][crate::eval#expression-evaluation-during-de-serialization]
442/// for a usage example.
443///
444/// # Errors
445///
446/// This function fails with an error if:
447///
448/// - the string `s` cannot be parsed as HCL.
449/// - any condition described in the error section of the [`evaluate` method
450/// documentation][Evaluate::evaluate] meets.
451/// - the evaluated value cannot be deserialized as a `T`.
452pub fn from_str<T>(s: &str, ctx: &Context) -> Result<T>
453where
454 T: de::DeserializeOwned,
455{
456 let body: Body = s.parse()?;
457 let evaluated = body.evaluate(ctx)?;
458 super::from_body(evaluated)
459}
460
461/// Serialize the given value as an HCL string after evaluating all expressions using the given
462/// context.
463///
464/// See the [module level documentation][crate::eval#expression-evaluation-during-de-serialization]
465/// for a usage example.
466///
467/// # Errors
468///
469/// This function fails with an error if any condition described in the error section of the
470/// [`evaluate` method documentation][Evaluate::evaluate] meets.
471pub fn to_string<T>(value: &T, ctx: &Context) -> Result<String>
472where
473 T: ?Sized + Evaluate,
474 <T as Evaluate>::Output: ser::Serialize,
475{
476 let evaluated = value.evaluate(ctx)?;
477 super::to_string(&evaluated)
478}