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}