hcl/ser/mod.rs
1//! Serialize a Rust data structure into HCL data.
2//!
3//! This module provides the [`Serializer`] type and the convienince functions [`to_string`],
4//! [`to_vec`] and [`to_writer`] for serializing data to HCL.
5//!
6//! Furthermore, the [`Block`] and [`LabeledBlock`] wrapper types, and the
7//! [`block`][crate::ser::block()], [`labeled_block`] and [`doubly_labeled_block`] functions can be
8//! used to construct HCL block structures from custom types. See the type and function level
9//! documentation for usage examples.
10//!
11//! If you want to serialize the data structures provided by this crate (e.g. [`Body`]) consider
12//! using the functionality in the [`format`](crate::format) module instead because it is more
13//! efficient.
14//!
15//! ## Supported top-level types
16//!
17//! The [`Serializer`] supports serialization to HCL for types that are either structured like
18//! maps or sequences of maps. For example, at the top level a struct with one or more named
19//! fields is supported, while a newtype struct wrapping a primitive type like `u8` is not.
20//!
21//! Other example of supported top-level types:
22//!
23//! - tuple or newtype structs wrapping a map-like type
24//! - enums with newtype or tuple variants wrapping map-like types, or struct variants
25//!
26//! Please note that these restrictions only apply to the top-level type that is serialized.
27//! Nested fields can have any type that is serializable.
28//!
29//! ## Serializing a custom type
30//!
31//! The following example will serialize the data as a deeply nested HCL attribute.
32//!
33//! ```
34//! # use std::error::Error;
35//! #
36//! # fn main() -> Result<(), Box<dyn Error>> {
37//! use serde::Serialize;
38//!
39//! #[derive(Serialize)]
40//! struct User {
41//! age: u8,
42//! username: &'static str,
43//! email: &'static str,
44//! }
45//!
46//! #[derive(Serialize)]
47//! struct Data {
48//! users: Vec<User>,
49//! }
50//!
51//! let data = Data {
52//! users: vec![
53//! User {
54//! age: 34,
55//! username: "johndoe",
56//! email: "johndoe@example.com",
57//! },
58//! User {
59//! age: 27,
60//! username: "janedoe",
61//! email: "janedoe@example.com",
62//! },
63//! ],
64//! };
65//!
66//! let expected = r#"
67//! users = [
68//! {
69//! "age" = 34
70//! "username" = "johndoe"
71//! "email" = "johndoe@example.com"
72//! },
73//! {
74//! "age" = 27
75//! "username" = "janedoe"
76//! "email" = "janedoe@example.com"
77//! }
78//! ]
79//! "#.trim_start();
80//!
81//! let serialized = hcl::to_string(&data)?;
82//!
83//! assert_eq!(serialized, expected);
84//! # Ok(())
85//! # }
86//! ```
87//!
88//! ## Serializing context-aware HCL
89//!
90//! If you need full control over the way data is serialized to HCL, you can make use of the [`Body`][Body] type which can be constructed using the builder pattern.
91//!
92//! The following example uses HCL blocks to format the same data from above in a different way.
93//!
94//! [Body]: ../struct.Body.html
95//!
96//! ```
97//! # use std::error::Error;
98//! #
99//! # fn main() -> Result<(), Box<dyn Error>> {
100//! use hcl::{Block, Body};
101//!
102//! let body = Body::builder()
103//! .add_block(
104//! Block::builder("user")
105//! .add_label("johndoe")
106//! .add_attribute(("age", 34))
107//! .add_attribute(("email", "johndoe@example.com"))
108//! .build(),
109//! )
110//! .add_block(
111//! Block::builder("user")
112//! .add_label("janedoe")
113//! .add_attribute(("age", 27))
114//! .add_attribute(("email", "janedoe@example.com"))
115//! .build(),
116//! )
117//! .build();
118//!
119//! let expected = r#"
120//! user "johndoe" {
121//! age = 34
122//! email = "johndoe@example.com"
123//! }
124//!
125//! user "janedoe" {
126//! age = 27
127//! email = "janedoe@example.com"
128//! }
129//! "#.trim_start();
130//!
131//! let serialized = hcl::to_string(&body)?;
132//!
133//! assert_eq!(serialized, expected);
134//! # Ok(())
135//! # }
136//! ```
137//!
138//! The same result could be acheived using the [`block!`] macro:
139//!
140//! ```
141//! # use std::error::Error;
142//! #
143//! # fn main() -> Result<(), Box<dyn Error>> {
144//! use serde::Serialize;
145//!
146//! #[derive(Serialize)]
147//! struct User {
148//! age: u8,
149//! username: &'static str,
150//! email: &'static str,
151//! }
152//!
153//! let users = vec![
154//! User {
155//! age: 34,
156//! username: "johndoe",
157//! email: "johndoe@example.com",
158//! },
159//! User {
160//! age: 27,
161//! username: "janedoe",
162//! email: "janedoe@example.com",
163//! },
164//! ];
165//!
166//! let body: hcl::Body = users
167//! .into_iter()
168//! .map(|user| {
169//! hcl::block! {
170//! user (user.username) {
171//! age = (user.age)
172//! email = (user.email)
173//! }
174//! }
175//! })
176//! .collect();
177//!
178//! let expected = r#"
179//! user "johndoe" {
180//! age = 34
181//! email = "johndoe@example.com"
182//! }
183//!
184//! user "janedoe" {
185//! age = 27
186//! email = "janedoe@example.com"
187//! }
188//! "#
189//! .trim_start();
190//!
191//! let serialized = hcl::to_string(&body)?;
192//!
193//! assert_eq!(serialized, expected);
194//! # Ok(())
195//! # }
196//! ```
197//! ## Serializing HCL blocks using a custom type
198//!
199//! An example to serialize a terraform configuration block using a custom type and the
200//! [`LabeledBlock`] and [`Block`] marker types from this module:
201//!
202//! ```
203//! use hcl::expr::{Expression, Traversal, Variable};
204//! use indexmap::{indexmap, IndexMap};
205//! use serde::Serialize;
206//!
207//! #[derive(Serialize)]
208//! struct Config {
209//! #[serde(
210//! rename = "resource",
211//! serialize_with = "hcl::ser::labeled_block"
212//! )]
213//! resources: Resources,
214//! }
215//!
216//! #[derive(Serialize)]
217//! struct Resources {
218//! #[serde(
219//! rename = "aws_sns_topic_subscription",
220//! serialize_with = "hcl::ser::labeled_block"
221//! )]
222//! aws_sns_topic_subscriptions: IndexMap<String, AwsSnsTopicSubscription>,
223//! }
224//!
225//! #[derive(Serialize)]
226//! struct AwsSnsTopicSubscription {
227//! topic_arn: Traversal,
228//! protocol: Expression,
229//! endpoint: Traversal,
230//! }
231//!
232//! let subscription = AwsSnsTopicSubscription {
233//! topic_arn: Traversal::builder(Variable::new("aws_sns_topic").unwrap())
234//! .attr("my-topic")
235//! .attr("arn")
236//! .build(),
237//! protocol: "sqs".into(),
238//! endpoint: Traversal::builder(Variable::new("aws_sqs_queue").unwrap())
239//! .attr("my-queue")
240//! .attr("arn")
241//! .build()
242//! };
243//!
244//! let config = Config {
245//! resources: Resources {
246//! aws_sns_topic_subscriptions: indexmap! {
247//! "my-subscription".into() => subscription,
248//! },
249//! },
250//! };
251//!
252//! let expected = r#"
253//! resource "aws_sns_topic_subscription" "my-subscription" {
254//! topic_arn = aws_sns_topic.my-topic.arn
255//! protocol = "sqs"
256//! endpoint = aws_sqs_queue.my-queue.arn
257//! }
258//! "#.trim_start();
259//!
260//! let serialized = hcl::to_string(&config).unwrap();
261//!
262//! assert_eq!(serialized, expected);
263//! ```
264
265pub(crate) mod blocks;
266
267pub use self::blocks::{block, doubly_labeled_block, labeled_block, Block, LabeledBlock};
268use crate::format::{Format, Formatter};
269use crate::structure::Body;
270use crate::{Error, Identifier, Result};
271use serde::ser::{self, Impossible, Serialize, SerializeStruct};
272use std::cell::RefCell;
273use std::collections::BTreeMap;
274use std::fmt;
275use std::io;
276use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
277
278// Deprecated, this re-export will be removed in a future release.
279#[doc(hidden)]
280pub use crate::expr::to_expression;
281
282thread_local! {
283 static INTERNAL_SERIALIZATION: AtomicBool = const { AtomicBool::new(false) };
284}
285
286pub(crate) fn in_internal_serialization() -> bool {
287 INTERNAL_SERIALIZATION.with(|flag| flag.load(Ordering::Relaxed))
288}
289
290pub(crate) fn with_internal_serialization<R, F: FnOnce() -> R>(f: F) -> R {
291 INTERNAL_SERIALIZATION.with(|flag| {
292 let old = flag.load(Ordering::Relaxed);
293 flag.store(true, Ordering::Relaxed);
294 let _on_drop = OnDrop::new(|| {
295 flag.store(old, Ordering::Relaxed);
296 });
297 f()
298 })
299}
300
301/// A structure for serializing Rust values into HCL.
302pub struct Serializer<'a, W> {
303 formatter: Formatter<'a, W>,
304}
305
306impl<'a, W> Serializer<'a, W>
307where
308 W: io::Write,
309{
310 /// Creates a new `Serializer` which serializes to the provides writer using the default
311 /// formatter.
312 pub fn new(writer: W) -> Serializer<'a, W> {
313 Serializer::with_formatter(Formatter::new(writer))
314 }
315
316 /// Creates a new `Serializer` which uses the provides formatter to format the serialized HCL.
317 pub fn with_formatter(formatter: Formatter<'a, W>) -> Serializer<'a, W> {
318 Serializer { formatter }
319 }
320
321 /// Consumes `self` and returns the wrapped writer.
322 pub fn into_inner(self) -> W {
323 self.formatter.into_inner()
324 }
325
326 /// Serialize the given value as HCL via the serializer's `Formatter` to the underlying writer.
327 ///
328 /// # Errors
329 ///
330 /// Serialization fails if the type cannot be represented as HCL.
331 pub fn serialize<T>(&mut self, value: &T) -> Result<()>
332 where
333 T: ?Sized + Serialize,
334 {
335 let serialized = Body::from_serializable(value)?;
336 serialized.format(&mut self.formatter)
337 }
338}
339
340impl<W> Serializer<'_, W>
341where
342 W: io::Write + AsMut<Vec<u8>>,
343{
344 /// Serialize the given value as HCL and returns the result as a `String`.
345 ///
346 /// # Errors
347 ///
348 /// Serialization fails if the type cannot be represented as HCL.
349 pub fn serialize_string<T>(&mut self, value: &T) -> Result<String>
350 where
351 T: ?Sized + Serialize,
352 {
353 let serialized = Body::from_serializable(value)?;
354 serialized.format_string(&mut self.formatter)
355 }
356
357 /// Serialize the given value as HCL and returns the result as a `Vec<u8>`.
358 ///
359 /// # Errors
360 ///
361 /// Serialization fails if the type cannot be represented as HCL.
362 pub fn serialize_vec<T>(&mut self, value: &T) -> Result<Vec<u8>>
363 where
364 T: ?Sized + Serialize,
365 {
366 let serialized = Body::from_serializable(value)?;
367 serialized.format_vec(&mut self.formatter)
368 }
369}
370
371/// Serialize the given value as an HCL byte vector.
372///
373/// If you want to serialize the data structures provided by this crate (e.g. [`Body`]) consider
374/// using [`hcl::format::to_vec`](crate::format::to_vec) instead because it is more efficient.
375///
376/// # Errors
377///
378/// Serialization fails if the type cannot be represented as HCL.
379pub fn to_vec<T>(value: &T) -> Result<Vec<u8>>
380where
381 T: ?Sized + Serialize,
382{
383 let mut serializer = Serializer::with_formatter(Formatter::default());
384 serializer.serialize_vec(value)
385}
386
387/// Serialize the given value as an HCL string.
388///
389/// If you want to serialize the data structures provided by this crate (e.g. [`Body`]) consider
390/// using [`hcl::format::to_string`](crate::format::to_string) instead because it is more
391/// efficient.
392///
393/// # Errors
394///
395/// Serialization fails if the type cannot be represented as HCL.
396pub fn to_string<T>(value: &T) -> Result<String>
397where
398 T: ?Sized + Serialize,
399{
400 let mut serializer = Serializer::with_formatter(Formatter::default());
401 serializer.serialize_string(value)
402}
403
404/// Serialize the given value as HCL into the IO stream.
405///
406/// If you want to serialize the data structures provided by this crate (e.g. [`Body`]) consider
407/// using [`hcl::format::to_writer`](crate::format::to_writer) instead because it is more
408/// efficient.
409///
410/// # Errors
411///
412/// Serialization fails if any operation on the writer fails or if the type cannot be represented
413/// as HCL.
414pub fn to_writer<W, T>(writer: W, value: &T) -> Result<()>
415where
416 W: io::Write,
417 T: ?Sized + Serialize,
418{
419 let mut serializer = Serializer::new(writer);
420 serializer.serialize(value)
421}
422
423pub(crate) struct StringSerializer;
424
425impl ser::Serializer for StringSerializer {
426 type Ok = String;
427 type Error = Error;
428
429 type SerializeSeq = Impossible<String, Error>;
430 type SerializeTuple = Impossible<String, Error>;
431 type SerializeTupleStruct = Impossible<String, Error>;
432 type SerializeTupleVariant = Impossible<String, Error>;
433 type SerializeMap = Impossible<String, Error>;
434 type SerializeStruct = Impossible<String, Error>;
435 type SerializeStructVariant = Impossible<String, Error>;
436
437 serialize_unsupported! {
438 i8 i16 i32 i64 u8 u16 u32 u64
439 bool f32 f64 bytes unit unit_struct newtype_variant none
440 seq tuple tuple_struct tuple_variant map struct struct_variant
441 }
442 serialize_self! { some newtype_struct }
443
444 fn serialize_char(self, value: char) -> Result<Self::Ok> {
445 Ok(value.to_string())
446 }
447
448 fn serialize_str(self, value: &str) -> Result<Self::Ok> {
449 Ok(value.to_owned())
450 }
451
452 fn serialize_unit_variant(
453 self,
454 _name: &'static str,
455 _variant_index: u32,
456 variant: &'static str,
457 ) -> Result<Self::Ok> {
458 Ok(variant.to_owned())
459 }
460
461 fn collect_str<T>(self, value: &T) -> Result<Self::Ok>
462 where
463 T: ?Sized + fmt::Display,
464 {
465 Ok(value.to_string())
466 }
467}
468
469pub(crate) struct IdentifierSerializer;
470
471impl ser::Serializer for IdentifierSerializer {
472 type Ok = Identifier;
473 type Error = Error;
474
475 type SerializeSeq = Impossible<Identifier, Error>;
476 type SerializeTuple = Impossible<Identifier, Error>;
477 type SerializeTupleStruct = Impossible<Identifier, Error>;
478 type SerializeTupleVariant = Impossible<Identifier, Error>;
479 type SerializeMap = Impossible<Identifier, Error>;
480 type SerializeStruct = Impossible<Identifier, Error>;
481 type SerializeStructVariant = Impossible<Identifier, Error>;
482
483 serialize_unsupported! {
484 i8 i16 i32 i64 u8 u16 u32 u64
485 bool f32 f64 bytes unit unit_struct newtype_variant none
486 seq tuple tuple_struct tuple_variant map struct struct_variant
487 }
488 serialize_self! { some newtype_struct }
489
490 fn serialize_char(self, value: char) -> Result<Self::Ok> {
491 self.serialize_str(&value.to_string())
492 }
493
494 fn serialize_str(self, value: &str) -> Result<Self::Ok> {
495 Identifier::new(value)
496 }
497
498 fn serialize_unit_variant(
499 self,
500 _name: &'static str,
501 _variant_index: u32,
502 variant: &'static str,
503 ) -> Result<Self::Ok> {
504 self.serialize_str(variant)
505 }
506}
507
508struct U64Serializer;
509
510impl ser::Serializer for U64Serializer {
511 type Ok = u64;
512 type Error = Error;
513
514 type SerializeSeq = Impossible<u64, Error>;
515 type SerializeTuple = Impossible<u64, Error>;
516 type SerializeTupleStruct = Impossible<u64, Error>;
517 type SerializeTupleVariant = Impossible<u64, Error>;
518 type SerializeMap = Impossible<u64, Error>;
519 type SerializeStruct = Impossible<u64, Error>;
520 type SerializeStructVariant = Impossible<u64, Error>;
521
522 serialize_unsupported! {
523 i8 i16 i32 i64 u8 u16 u32 f32 f64 char str bool bytes
524 unit unit_variant unit_struct newtype_struct newtype_variant
525 some none seq tuple tuple_struct tuple_variant map struct struct_variant
526 }
527
528 fn serialize_u64(self, value: u64) -> Result<Self::Ok> {
529 Ok(value)
530 }
531}
532
533pub(crate) struct SerializeInternalHandleStruct {
534 handle: Option<u64>,
535}
536
537impl SerializeInternalHandleStruct {
538 pub(crate) fn new() -> Self {
539 SerializeInternalHandleStruct { handle: None }
540 }
541}
542
543impl ser::SerializeStruct for SerializeInternalHandleStruct {
544 type Ok = usize;
545 type Error = Error;
546
547 fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
548 where
549 T: ?Sized + ser::Serialize,
550 {
551 assert_eq!(key, "handle", "bad handle struct");
552 self.handle = Some(value.serialize(U64Serializer)?);
553 Ok(())
554 }
555
556 fn end(self) -> Result<Self::Ok> {
557 let handle = self.handle.expect("bad handle reference in roundtrip");
558 Ok(handle as usize)
559 }
560}
561
562pub(crate) struct InternalHandles<T> {
563 marker: &'static str,
564 last_handle: AtomicUsize,
565 handles: RefCell<BTreeMap<usize, T>>,
566}
567
568impl<T> InternalHandles<T> {
569 pub(crate) fn new(marker: &'static str) -> InternalHandles<T> {
570 InternalHandles {
571 marker,
572 last_handle: AtomicUsize::new(0),
573 handles: RefCell::new(BTreeMap::new()),
574 }
575 }
576
577 pub(crate) fn remove(&self, handle: usize) -> T {
578 self.handles
579 .borrow_mut()
580 .remove(&handle)
581 .expect("handle not in registry")
582 }
583
584 pub(crate) fn serialize<V, S>(&self, value: V, serializer: S) -> Result<S::Ok, S::Error>
585 where
586 S: serde::Serializer,
587 V: Into<T>,
588 {
589 let handle = self.last_handle.fetch_add(1, Ordering::Relaxed);
590 self.handles.borrow_mut().insert(handle, value.into());
591 let mut s = serializer.serialize_struct(self.marker, 1)?;
592 s.serialize_field("handle", &handle)?;
593 s.end()
594 }
595}
596
597struct OnDrop<F: FnOnce()>(Option<F>);
598
599impl<F: FnOnce()> OnDrop<F> {
600 fn new(f: F) -> Self {
601 Self(Some(f))
602 }
603}
604
605impl<F: FnOnce()> Drop for OnDrop<F> {
606 fn drop(&mut self) {
607 self.0.take().unwrap()();
608 }
609}