Repository: martinohmann/hcl-rs
Pull request #32 (2022-06-04 10:21:20)

fix(serializer): do not emit newline if block body is empty
Previously, empty blocks were rendered as:

    empty {
    }

With this change they are rendered as:

    empty {}


Patch:
diff --git a/src/ser/format.rs b/src/ser/format.rs
index 98db6e4..3b23b39 100644
--- a/src/ser/format.rs
+++ b/src/ser/format.rs
@@ -243,6 +243,7 @@ enum FormatState {
     AttributeEnd,
     BlockStart,
     BlockEnd,
+    BlockBodyStart,
 }
 
 /// A pretty printing HCL formatter.
@@ -409,11 +410,7 @@ impl<'a> Format for PrettyFormatter<'a> {
     where
         W: ?Sized + io::Write,
     {
-        if !self.dense && self.state == FormatState::BlockEnd {
-            writer.write_all(b"\n")?;
-        }
-
-        self.state = FormatState::AttributeStart;
+        self.maybe_write_newline(writer, FormatState::AttributeStart)?;
         indent(writer, self.current_indent, self.indent)
     }
 
@@ -429,16 +426,7 @@ impl<'a> Format for PrettyFormatter<'a> {
     where
         W: ?Sized + io::Write,
     {
-        if !self.dense
-            && matches!(
-                self.state,
-                FormatState::AttributeEnd | FormatState::BlockEnd
-            )
-        {
-            writer.write_all(b"\n")?;
-        }
-
-        self.state = FormatState::BlockStart;
+        self.maybe_write_newline(writer, FormatState::BlockStart)?;
         indent(writer, self.current_indent, self.indent)
     }
 
@@ -447,7 +435,8 @@ impl<'a> Format for PrettyFormatter<'a> {
         W: ?Sized + io::Write,
     {
         self.current_indent += 1;
-        writer.write_all(b" {\n")
+        self.state = FormatState::BlockBodyStart;
+        writer.write_all(b" {")
     }
 
     fn end_block<W>(&mut self, writer: &mut W) -> io::Result<()>
@@ -461,6 +450,35 @@ impl<'a> Format for PrettyFormatter<'a> {
     }
 }
 
+impl<'a> PrettyFormatter<'a> {
+    // Conditionally writes a newline character depending on the formatter configuration and the
+    // current and next state. Updates the state to `next_state`.
+    fn maybe_write_newline<W>(&mut self, writer: &mut W, next_state: FormatState) -> io::Result<()>
+    where
+        W: ?Sized + io::Write,
+    {
+        let newline = match &self.state {
+            FormatState::AttributeEnd if !self.dense => {
+                matches!(next_state, FormatState::BlockStart)
+            }
+            FormatState::BlockEnd if !self.dense => {
+                matches!(
+                    next_state,
+                    FormatState::BlockStart | FormatState::AttributeStart
+                )
+            }
+            other => matches!(other, FormatState::BlockBodyStart),
+        };
+
+        if newline {
+            writer.write_all(b"\n")?;
+        }
+
+        self.state = next_state;
+        Ok(())
+    }
+}
+
 fn indent<W>(writer: &mut W, n: usize, s: &[u8]) -> io::Result<()>
 where
     W: ?Sized + io::Write,


Test patch:
diff --git a/src/ser/tests.rs b/src/ser/tests.rs
index b340853..dab8d6f 100644
--- a/src/ser/tests.rs
+++ b/src/ser/tests.rs
@@ -181,6 +181,15 @@ qux = {
     assert_eq!(to_string(&value).unwrap(), expected);
 }
 
+#[test]
+fn serialize_empty_block() {
+    let body = Body::builder()
+        .add_block(Block::builder("empty").build())
+        .build();
+
+    assert_eq!(to_string(&body).unwrap(), "empty {}\n");
+}
+
 #[test]
 fn serialize_errors() {
     assert!(to_string(&true).is_err());