martinohmann / hcl-rs

HCL parsing and encoding libraries for rust with serde support
Apache License 2.0
121 stars 14 forks source link

Using jsonencode #283

Closed tobimai closed 1 year ago

tobimai commented 1 year ago

I am currently trying to write something which relies on using terraforms jsonencode.

From what I can tell this is currently not supported/possible, is this correct?

So what I was trying to do was basically wrapping a block into a jsonecode block and therefore be able to write everything in rust/hcl-rs without having to resort to raw string inputs in attributes (which also mess up escaping and intendation)

martinohmann commented 1 year ago

Hi there!

Reading your description I'm still not entirely sure what your use case is.

Let's take a simple example as a starting point. Given the following HCL:

resource "aws_sqs_queue" "queue" {
  name = "my-queue"

  redrive_policy = jsonencode({
    deadLetterTargetArn = aws_sqs_queue.dead_letter_queue.arn
    maxReceiveCount     = 5
  })
}

The first two cases are supported by hcl-rs and I can provide examples for them.

Can you further elaborate? Maybe an example of what you're trying to achieve in pseudo code could help.

tobimai commented 1 year ago

Sorry I was not clear.

It's the second one, I want to output data I have in a struct in rust as HCL.

Example:

I have this:

struct Demo {
foo: String
}

Demo{foo: "bar".into()}

And want to get this:

resource "demo" "demo" {
   model = jsonencode({
     foo = "bar"
    })
}
martinohmann commented 1 year ago

This is certainly possible. Here's one way to achieve this (I added comments to the most relevant bits):

use hcl::expr::FuncCall;
use hcl::structure::{Attribute, Block, Body};

#[derive(serde::Serialize)]
struct Demo {
    foo: String,
}

let demo = Demo { foo: "bar".into() };

// Convert the custom struct to an `hcl::expr::Expression`.
let expr = hcl::to_expression(&demo).unwrap();

let body = Body::builder()
    .add_block(
        Block::builder("resource")
            .add_labels(["demo", "demo"])
            .add_attribute(Attribute::new(
                "model",
                // Construct the `jsonencode` function call.
                FuncCall::builder("jsonencode").arg(expr).build(),
            ))
            .build(),
    )
    .build();

assert_eq!(
    hcl::to_string(&body).unwrap(),
    indoc! {r#"
        resource "demo" "demo" {
          model = jsonencode({ "foo" = "bar" })
        }
    "#}
);

Please note that the final formatting is a little bit different than in your example:

use hcl::format::{Format, Formatter};

let mut fmt = Formatter::builder().prefer_ident_keys(true).build_vec();

let formatted = body.format_string(&mut fmt).unwrap();

assert_eq!(
    formatted,
    indoc! {r#"
        resource "demo" "demo" {
          model = jsonencode({ foo = "bar" })
        }
    "#}
);

I hope this more or less covers your use case. Let me know if something is missing.

tobimai commented 1 year ago

Thanks, thats exactly what I needed. (well, multiline would be nicer but doesn't really change anything, the code will be auto-generated and not touched 99% of the time)

One short thing, I noticed that when you have an empty list in a struct you serialze using to_expression it will not generate a comma after it. Is that a know error/Intended? If not I would create a proper bug report once I have time.

martinohmann commented 1 year ago

Could you provide an example of the actual output and how it should look like in your opinion? In general the formatter avoids to emit commas that are not significant.