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}