Repository: martinohmann/hcl-rs
Pull request #62 (2022-09-07 18:43:46)

feat: add `Expression::VariableExpr`
This adds supports for parsing and serializing variable expressions. Right now only bare identifiers like `var` are supported. Element access like `var.foo` will come later since this is a different type of expression.


Patch:
diff --git a/src/format/impls.rs b/src/format/impls.rs
index 695b74e..7c17f1f 100644
--- a/src/format/impls.rs
+++ b/src/format/impls.rs
@@ -105,6 +105,7 @@ impl Format for Expression {
             Expression::Object(object) => format_object(fmt, object),
             Expression::Raw(raw) => raw.format(fmt),
             Expression::TemplateExpr(expr) => expr.format(fmt),
+            Expression::VariableExpr(ident) => ident.format(fmt),
         }
     }
 }
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 45d6d95..d764033 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -176,7 +176,8 @@ fn parse_expr_term(pair: Pair<Rule>) -> Result<Expression> {
             }
             Rule::Tuple => parse_expressions(pair).map(Expression::Array),
             Rule::Object => parse_object(pair).map(Expression::Object),
-            // @TODO(mohmann): Process ForExpr, VariableExpr etc.
+            Rule::VariableExpr => Ok(Expression::VariableExpr(parse_ident(pair).into())),
+            // @TODO(mohmann): Process ForExpr, etc.
             _ => Ok(Expression::Raw(raw_expression(pair.as_str()))),
         }
     }
diff --git a/src/structure/de.rs b/src/structure/de.rs
index e361282..b67a9df 100644
--- a/src/structure/de.rs
+++ b/src/structure/de.rs
@@ -260,6 +260,7 @@ impl<'de> de::Deserializer<'de> for Expression {
             Expression::Object(object) => visitor.visit_map(object.into_deserializer()),
             Expression::Raw(expr) => expr.into_deserializer().deserialize_any(visitor),
             Expression::TemplateExpr(expr) => expr.into_deserializer().deserialize_any(visitor),
+            Expression::VariableExpr(expr) => expr.into_deserializer().deserialize_any(visitor),
         }
     }
 
@@ -287,6 +288,7 @@ impl VariantName for Expression {
             Expression::Object(_) => "Object",
             Expression::Raw(_) => "Raw",
             Expression::TemplateExpr(_) => "TemplateExpr",
+            Expression::VariableExpr(_) => "VariableExpr",
         }
     }
 }
diff --git a/src/structure/expression.rs b/src/structure/expression.rs
index b29131a..fcaac3d 100644
--- a/src/structure/expression.rs
+++ b/src/structure/expression.rs
@@ -30,6 +30,8 @@ pub enum Expression {
     Object(Object<ObjectKey, Expression>),
     /// A quoted string or heredoc that embeds a program written in the template sub-language.
     TemplateExpr(Box<TemplateExpr>),
+    /// Represents a variable name identifier.
+    VariableExpr(Identifier),
     /// Represents a raw HCL expression. This includes any expression kind that does match any of
     /// the enum variants above. See [`RawExpression`] for more details.
     Raw(RawExpression),
@@ -45,6 +47,7 @@ impl From<Expression> for Value {
             Expression::Array(array) => array.into_iter().collect(),
             Expression::Object(object) => object.into_iter().collect(),
             Expression::TemplateExpr(expr) => Value::String(expr.to_string()),
+            Expression::VariableExpr(ident) => Value::String(RawExpression(ident.0).into()),
             Expression::Raw(raw) => Value::String(raw.into()),
         }
     }
@@ -166,6 +169,12 @@ impl From<TemplateExpr> for Expression {
     }
 }
 
+impl From<Identifier> for Expression {
+    fn from(ident: Identifier) -> Self {
+        Expression::VariableExpr(ident)
+    }
+}
+
 /// Represents an object key.
 #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
 #[serde(rename = "$hcl::object_key")]
diff --git a/src/structure/ser/expression.rs b/src/structure/ser/expression.rs
index 2fe8684..ae7eaee 100644
--- a/src/structure/ser/expression.rs
+++ b/src/structure/ser/expression.rs
@@ -1,5 +1,7 @@
 use super::{template::TemplateExprSerializer, StringSerializer};
-use crate::{serialize_unsupported, Error, Expression, Object, ObjectKey, RawExpression, Result};
+use crate::{
+    serialize_unsupported, Error, Expression, Identifier, Object, ObjectKey, RawExpression, Result,
+};
 use serde::ser::{self, Impossible};
 use std::fmt::Display;
 
@@ -105,6 +107,10 @@ impl ser::Serializer for ExpressionSerializer {
             Ok(Expression::Raw(RawExpression::from(
                 value.serialize(StringSerializer)?,
             )))
+        } else if name == "$hcl::identifier" {
+            Ok(Expression::VariableExpr(Identifier::from(
+                value.serialize(StringSerializer)?,
+            )))
         } else {
             value.serialize(self)
         }
diff --git a/src/template.rs b/src/template.rs
index 32cbf5a..d4efdd6 100644
--- a/src/template.rs
+++ b/src/template.rs
@@ -20,15 +20,15 @@
 //! # use std::error::Error;
 //! #
 //! # fn main() -> Result<(), Box<dyn Error>> {
-//! use hcl::{template::{Interpolation, Template}};
-//! use hcl::{RawExpression, TemplateExpr};
+//! use hcl::template::Template;
+//! use hcl::{Expression, Identifier, TemplateExpr};
 //!
 //! let expr = TemplateExpr::QuotedString(String::from("Hello ${name}!"));
 //! let template = Template::from_expr(&expr)?;
 //!
 //! let expected = Template::new()
 //!     .add_literal("Hello ")
-//!     .add_interpolation(Interpolation::new(RawExpression::new("name")))
+//!     .add_interpolation(Expression::VariableExpr(Identifier::new("name")))
 //!     .add_literal("!");
 //!
 //! assert_eq!(expected, template);
@@ -43,8 +43,8 @@
 //! # use std::error::Error;
 //! #
 //! # fn main() -> Result<(), Box<dyn Error>> {
-//! use hcl::{template::{ForDirective, ForExpr, Interpolation, StripMode, Template}};
-//! use hcl::{Identifier, RawExpression};
+//! use hcl::{template::{ForDirective, ForExpr, StripMode, Template}};
+//! use hcl::{Expression, Identifier};
 //! use std::str::FromStr;
 //!
 //! let raw = r#"
@@ -62,11 +62,11 @@
 //!         ForDirective::new(
 //!             ForExpr::new(
 //!                 Identifier::new("item"),
-//!                 RawExpression::new("items"),
+//!                 Expression::VariableExpr(Identifier::new("items")),
 //!                 Template::new()
 //!                     .add_literal("- ")
 //!                     .add_interpolation(
-//!                         Interpolation::new(RawExpression::new("item"))
+//!                         Expression::VariableExpr(Identifier::new("item"))
 //!                     )
 //!                     .add_literal("\n")
 //!             )


Test patch:
diff --git a/src/parser/tests.rs b/src/parser/tests.rs
index 1e05794..5fcad72 100644
--- a/src/parser/tests.rs
+++ b/src/parser/tests.rs
@@ -670,12 +670,12 @@ fn template_expr() {
 
             let expected_template = Template::new()
                 .add_literal("bar ")
-                .add_interpolation(Expression::Raw("baz".into()))
+                .add_interpolation(Expression::VariableExpr(Identifier::new("baz")))
                 .add_literal(" ")
                 .add_directive(
                     IfDirective::new(
                         IfExpr::new(
-                            Expression::Raw("cond".into()),
+                            Expression::VariableExpr(Identifier::new("cond")),
                             Template::new().add_literal("qux"),
                         )
                         .with_strip_mode(StripMode::Start),
diff --git a/src/ser/tests.rs b/src/ser/tests.rs
index e6f6f1a..6b02fbc 100644
--- a/src/ser/tests.rs
+++ b/src/ser/tests.rs
@@ -333,6 +333,10 @@ fn roundtrip() {
                                 "${var.team}".into(),
                             ))),
                         ),
+                        (
+                            ObjectKey::Identifier("environment".into()),
+                            Expression::VariableExpr("environment".into()),
+                        ),
                     ]),
                 ))
                 .build(),