hcl

Module ser

Source
Expand description

Serialize a Rust data structure into HCL data.

This module provides the Serializer type and the convienince functions to_string, to_vec and to_writer for serializing data to HCL.

Furthermore, the Block and LabeledBlock wrapper types, and the block, labeled_block and doubly_labeled_block functions can be used to construct HCL block structures from custom types. See the type and function level documentation for usage examples.

If you want to serialize the data structures provided by this crate (e.g. Body) consider using the functionality in the format module instead because it is more efficient.

§Supported top-level types

The Serializer supports serialization to HCL for types that are either structured like maps or sequences of maps. For example, at the top level a struct with one or more named fields is supported, while a newtype struct wrapping a primitive type like u8 is not.

Other example of supported top-level types:

  • tuple or newtype structs wrapping a map-like type
  • enums with newtype or tuple variants wrapping map-like types, or struct variants

Please note that these restrictions only apply to the top-level type that is serialized. Nested fields can have any type that is serializable.

§Serializing a custom type

The following example will serialize the data as a deeply nested HCL attribute.

use serde::Serialize;

#[derive(Serialize)]
struct User {
    age: u8,
    username: &'static str,
    email: &'static str,
}

#[derive(Serialize)]
struct Data {
    users: Vec<User>,
}

let data = Data {
    users: vec![
        User {
            age: 34,
            username: "johndoe",
            email: "johndoe@example.com",
        },
        User {
            age: 27,
            username: "janedoe",
            email: "janedoe@example.com",
        },
    ],
};

let expected = r#"
users = [
  {
    "age" = 34
    "username" = "johndoe"
    "email" = "johndoe@example.com"
  },
  {
    "age" = 27
    "username" = "janedoe"
    "email" = "janedoe@example.com"
  }
]
"#.trim_start();

let serialized = hcl::to_string(&data)?;

assert_eq!(serialized, expected);

§Serializing context-aware HCL

If you need full control over the way data is serialized to HCL, you can make use of the Body type which can be constructed using the builder pattern.

The following example uses HCL blocks to format the same data from above in a different way.

use hcl::{Block, Body};

let body = Body::builder()
    .add_block(
        Block::builder("user")
            .add_label("johndoe")
            .add_attribute(("age", 34))
            .add_attribute(("email", "johndoe@example.com"))
            .build(),
    )
    .add_block(
        Block::builder("user")
            .add_label("janedoe")
            .add_attribute(("age", 27))
            .add_attribute(("email", "janedoe@example.com"))
            .build(),
    )
    .build();

let expected = r#"
user "johndoe" {
  age = 34
  email = "johndoe@example.com"
}

user "janedoe" {
  age = 27
  email = "janedoe@example.com"
}
"#.trim_start();

let serialized = hcl::to_string(&body)?;

assert_eq!(serialized, expected);

The same result could be acheived using the block! macro:

use serde::Serialize;

#[derive(Serialize)]
struct User {
    age: u8,
    username: &'static str,
    email: &'static str,
}

let users = vec![
    User {
        age: 34,
        username: "johndoe",
        email: "johndoe@example.com",
    },
    User {
        age: 27,
        username: "janedoe",
        email: "janedoe@example.com",
    },
];

let body: hcl::Body = users
    .into_iter()
    .map(|user| {
        hcl::block! {
            user (user.username) {
                age = (user.age)
                email = (user.email)
            }
        }
    })
    .collect();

let expected = r#"
user "johndoe" {
  age = 34
  email = "johndoe@example.com"
}

user "janedoe" {
  age = 27
  email = "janedoe@example.com"
}
"#
.trim_start();

let serialized = hcl::to_string(&body)?;

assert_eq!(serialized, expected);

§Serializing HCL blocks using a custom type

An example to serialize a terraform configuration block using a custom type and the LabeledBlock and Block marker types from this module:

use hcl::expr::{Expression, Traversal, Variable};
use indexmap::{indexmap, IndexMap};
use serde::Serialize;

#[derive(Serialize)]
struct Config {
    #[serde(
        rename = "resource",
        serialize_with = "hcl::ser::labeled_block"
    )]
    resources: Resources,
}

#[derive(Serialize)]
struct Resources {
    #[serde(
        rename = "aws_sns_topic_subscription",
        serialize_with = "hcl::ser::labeled_block"
    )]
    aws_sns_topic_subscriptions: IndexMap<String, AwsSnsTopicSubscription>,
}

#[derive(Serialize)]
struct AwsSnsTopicSubscription {
    topic_arn: Traversal,
    protocol: Expression,
    endpoint: Traversal,
}

let subscription = AwsSnsTopicSubscription {
    topic_arn: Traversal::builder(Variable::new("aws_sns_topic").unwrap())
        .attr("my-topic")
        .attr("arn")
        .build(),
    protocol: "sqs".into(),
    endpoint: Traversal::builder(Variable::new("aws_sqs_queue").unwrap())
        .attr("my-queue")
        .attr("arn")
        .build()
};

let config = Config {
    resources: Resources {
        aws_sns_topic_subscriptions: indexmap! {
            "my-subscription".into() => subscription,
        },
    },
};

let expected = r#"
resource "aws_sns_topic_subscription" "my-subscription" {
  topic_arn = aws_sns_topic.my-topic.arn
  protocol = "sqs"
  endpoint = aws_sqs_queue.my-queue.arn
}
"#.trim_start();

let serialized = hcl::to_string(&config).unwrap();

assert_eq!(serialized, expected);

Structs§

  • A transparent wrapper type which hints the Serializer to serialize T as an HCL block.
  • A transparent wrapper type which hints the Serializer to serialize T as a labeled HCL block.
  • A structure for serializing Rust values into HCL.

Functions§