hcl/ser/blocks.rs
1use super::in_internal_serialization;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4use std::ops;
5use vecmap::VecMap;
6
7pub(crate) const BLOCK_MARKER: &str = "$hcl::Block";
8
9/// A transparent wrapper type which hints the [`Serializer`][crate::ser::Serializer] to serialize
10/// `T` as an HCL block.
11///
12/// When passed to a serializer other than the one from this crate, a `Block<T>` serializes
13/// exactly like `T`, if `T` implements `serde::Serialize`.
14///
15/// A `Block<T>` can only be used in the *value position of a map-like structure*. For example:
16///
17/// - It can be used to wrap the *value type of a map*, e.g. `Map<K, Block<T>>`
18/// - As the value of a *struct field*, e.g. `struct S { field: Block<T> }`
19/// - Or as the value of an *enum variant*, e.g. `enum E { Variant(Block<T>) }`
20///
21/// **The serialized block's identifier will be the respective map key, struct field name or variant
22/// name.**
23///
24/// The wrapped `T` must be shaped as follows to be serialized as an HCL block:
25///
26/// - A *map-like* value (e.g. a map or struct).
27/// - A *sequence-like* value (e.g. a vector, slice or tuple) with map-like elements as described
28/// above. In this case, multiple blocks with the same identifier are produced.
29///
30/// Wrapping a type `T` that does not fulfil one of the criteria above in a `Block<T>` will result
31/// in serialization errors.
32///
33/// For more convenient usage, see the [`block`][crate::ser::block()] function.
34///
35/// # Example
36///
37/// ```
38/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
39/// use hcl::ser::Block;
40/// use serde::Serialize;
41///
42/// #[derive(Serialize)]
43/// struct Config {
44/// user: Block<Vec<User>>,
45/// }
46///
47/// #[derive(Serialize)]
48/// struct User {
49/// name: String,
50/// email: String,
51/// }
52///
53/// let users = vec![
54/// User {
55/// name: "john".into(),
56/// email: "johndoe@example.com".into(),
57/// },
58/// User {
59/// name: "jane".into(),
60/// email: "janedoe@example.com".into(),
61/// },
62/// ];
63///
64/// let config = Config {
65/// user: Block::new(users),
66/// };
67///
68/// let expected = r#"
69/// user {
70/// name = "john"
71/// email = "johndoe@example.com"
72/// }
73///
74/// user {
75/// name = "jane"
76/// email = "janedoe@example.com"
77/// }
78/// "#.trim_start();
79///
80/// assert_eq!(hcl::to_string(&config)?, expected);
81/// # Ok(())
82/// # }
83/// ```
84pub struct Block<T>(T);
85
86impl<T> Block<T> {
87 /// Create a new `Block<T>` from a `T`.
88 pub fn new(value: T) -> Block<T> {
89 Block(value)
90 }
91
92 /// Consume the `Block` and return the wrapped `T`.
93 pub fn into_inner(self) -> T {
94 self.0
95 }
96}
97
98impl<T> ops::Deref for Block<T> {
99 type Target = T;
100
101 fn deref(&self) -> &Self::Target {
102 &self.0
103 }
104}
105
106impl<T> ops::DerefMut for Block<T> {
107 fn deref_mut(&mut self) -> &mut Self::Target {
108 &mut self.0
109 }
110}
111
112impl<T> Clone for Block<T>
113where
114 T: Clone,
115{
116 fn clone(&self) -> Self {
117 Block(self.0.clone())
118 }
119}
120
121impl<T> fmt::Debug for Block<T>
122where
123 T: fmt::Debug,
124{
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 f.debug_tuple("Block").field(&self.0).finish()
127 }
128}
129
130impl<T> Serialize for Block<T>
131where
132 T: Serialize,
133{
134 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
135 where
136 S: serde::Serializer,
137 {
138 if in_internal_serialization() {
139 serializer.serialize_newtype_struct(BLOCK_MARKER, &self.0)
140 } else {
141 self.0.serialize(serializer)
142 }
143 }
144}
145
146impl<'de, T> Deserialize<'de> for Block<T>
147where
148 T: Deserialize<'de>,
149{
150 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
151 where
152 D: serde::Deserializer<'de>,
153 {
154 T::deserialize(deserializer).map(Block)
155 }
156}
157
158pub(crate) const LABELED_BLOCK_MARKER: &str = "$hcl::LabeledBlock";
159
160/// A transparent wrapper type which hints the [`Serializer`][crate::ser::Serializer] to serialize
161/// `T` as a labeled HCL block.
162///
163/// When passed to a serializer other than the one from this crate, a `LabeledBlock<T>` serializes
164/// exactly like `T`, if `T` implements `serde::Serialize`.
165///
166/// A `LabeledBlock<T>` can only be used in the *value position of a map-like structure*. For example:
167///
168/// - It can be used to wrap the *value type of a map*, e.g. `Map<K, LabeledBlock<T>>`
169/// - As the value of a *struct field*, e.g. `struct S { field: LabeledBlock<T> }`
170/// - Or as the value of an *enum variant*, e.g. `enum E { Variant(LabeledBlock<T>) }`
171///
172/// **The serialized block's identifier will be the respective map key, struct field name or variant
173/// name.**
174///
175/// The wrapped `T` must be shaped as follows to be serialized as a labeled HCL block:
176///
177/// - A *map-like* value (e.g. a map or struct) where the value may to be another
178/// `LabeledBlock<T>`, in which case a block with multiple labels is produced. Can be nested
179/// arbitrarily deep to allow for any number of block labels.
180/// - A *sequence-like* value (e.g. a vector, slice or tuple) with map-like elements as described
181/// above. In this case, multiple blocks with the same identifier and labels are produced.
182///
183/// Wrapping a type `T` that does not fulfil one of the criteria above in a [`LabeledBlock<T>`]
184/// will result in serialization errors.
185///
186/// For more convenient usage, see the [`labeled_block`] and [`doubly_labeled_block`] functions.
187///
188/// # Example
189///
190/// ```
191/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
192/// use hcl::ser::LabeledBlock;
193/// use indexmap::{indexmap, IndexMap};
194/// use serde::Serialize;
195///
196/// #[derive(Serialize)]
197/// struct Config {
198/// user: LabeledBlock<IndexMap<String, User>>,
199/// }
200///
201/// #[derive(Serialize)]
202/// struct User {
203/// email: String,
204/// }
205///
206/// let users = indexmap! {
207/// "john".into() => User {
208/// email: "johndoe@example.com".into(),
209/// },
210/// "jane".into() => User {
211/// email: "janedoe@example.com".into(),
212/// },
213/// };
214///
215/// let config = Config {
216/// user: LabeledBlock::new(users),
217/// };
218///
219/// let expected = r#"
220/// user "john" {
221/// email = "johndoe@example.com"
222/// }
223///
224/// user "jane" {
225/// email = "janedoe@example.com"
226/// }
227/// "#.trim_start();
228///
229/// assert_eq!(hcl::to_string(&config)?, expected);
230/// # Ok(())
231/// # }
232/// ```
233pub struct LabeledBlock<T>(T);
234
235impl<T> LabeledBlock<T> {
236 /// Create a new `LabeledBlock<T>` from a `T`.
237 pub fn new(value: T) -> LabeledBlock<T> {
238 LabeledBlock(value)
239 }
240
241 /// Consume the `LabeledBlock` and return the wrapped `T`.
242 pub fn into_inner(self) -> T {
243 self.0
244 }
245}
246
247impl<T> ops::Deref for LabeledBlock<T> {
248 type Target = T;
249
250 fn deref(&self) -> &Self::Target {
251 &self.0
252 }
253}
254
255impl<T> ops::DerefMut for LabeledBlock<T> {
256 fn deref_mut(&mut self) -> &mut Self::Target {
257 &mut self.0
258 }
259}
260
261impl<T> Clone for LabeledBlock<T>
262where
263 T: Clone,
264{
265 fn clone(&self) -> Self {
266 LabeledBlock(self.0.clone())
267 }
268}
269
270impl<T> fmt::Debug for LabeledBlock<T>
271where
272 T: fmt::Debug,
273{
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 f.debug_tuple("LabeledBlock").field(&self.0).finish()
276 }
277}
278
279impl<T> Serialize for LabeledBlock<T>
280where
281 T: Serialize,
282{
283 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
284 where
285 S: serde::Serializer,
286 {
287 if in_internal_serialization() {
288 serializer.serialize_newtype_struct(LABELED_BLOCK_MARKER, &self.0)
289 } else {
290 self.0.serialize(serializer)
291 }
292 }
293}
294
295impl<'de, T> Deserialize<'de> for LabeledBlock<T>
296where
297 T: Deserialize<'de>,
298{
299 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
300 where
301 D: serde::Deserializer<'de>,
302 {
303 T::deserialize(deserializer).map(LabeledBlock)
304 }
305}
306
307/// Hints the [`Serializer`][crate::ser::Serializer] to serialize `T` as an HCL block.
308///
309/// This function is intended to be used in the `#[serde(serialize_with)]` attribute and wraps `T`
310/// with a [`Block<T>`].
311///
312/// See the type-level documentation of [`Block<T>`] for more.
313///
314/// # Example
315///
316/// ```
317/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
318/// use serde::Serialize;
319///
320/// #[derive(Serialize)]
321/// struct Config {
322/// #[serde(serialize_with = "hcl::ser::block")]
323/// user: Vec<User>,
324/// }
325///
326/// #[derive(Serialize)]
327/// struct User {
328/// name: String,
329/// email: String,
330/// }
331///
332/// let config = Config {
333/// user: vec![
334/// User {
335/// name: "john".into(),
336/// email: "johndoe@example.com".into(),
337/// },
338/// User {
339/// name: "jane".into(),
340/// email: "janedoe@example.com".into(),
341/// },
342/// ],
343/// };
344///
345/// let expected = r#"
346/// user {
347/// name = "john"
348/// email = "johndoe@example.com"
349/// }
350///
351/// user {
352/// name = "jane"
353/// email = "janedoe@example.com"
354/// }
355/// "#.trim_start();
356///
357/// assert_eq!(hcl::to_string(&config)?, expected);
358/// # Ok(())
359/// # }
360/// ```
361///
362/// # Errors
363///
364/// Serialization fails if the type's shape makes it impossible to represent it as an HCL block
365/// with two labels.
366pub fn block<T, S>(value: T, serializer: S) -> Result<S::Ok, S::Error>
367where
368 T: Serialize,
369 S: serde::Serializer,
370{
371 Block::new(value).serialize(serializer)
372}
373
374/// Hints the [`Serializer`][crate::ser::Serializer] to serialize `T` as a labeled HCL block.
375///
376/// This function is intended to be used in the `#[serde(serialize_with)]` attribute and wraps `T`
377/// with a [`LabeledBlock<T>`].
378///
379/// See the type-level documentation of [`LabeledBlock<T>`] for more.
380///
381/// # Example
382///
383/// ```
384/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
385/// use indexmap::{indexmap, IndexMap};
386/// use serde::Serialize;
387///
388/// #[derive(Serialize)]
389/// struct Config {
390/// #[serde(serialize_with = "hcl::ser::labeled_block")]
391/// user: IndexMap<String, User>,
392/// }
393///
394/// #[derive(Serialize)]
395/// struct User {
396/// email: String,
397/// }
398///
399/// let config = Config {
400/// user: indexmap! {
401/// "john".into() => User {
402/// email: "johndoe@example.com".into(),
403/// },
404/// "jane".into() => User {
405/// email: "janedoe@example.com".into(),
406/// },
407/// },
408/// };
409///
410/// let expected = r#"
411/// user "john" {
412/// email = "johndoe@example.com"
413/// }
414///
415/// user "jane" {
416/// email = "janedoe@example.com"
417/// }
418/// "#.trim_start();
419///
420/// assert_eq!(hcl::to_string(&config)?, expected);
421/// # Ok(())
422/// # }
423/// ```
424///
425/// # Errors
426///
427/// Serialization fails if the type's shape makes it impossible to represent it as a labeled HCL
428/// block.
429pub fn labeled_block<T, S>(value: T, serializer: S) -> Result<S::Ok, S::Error>
430where
431 T: Serialize,
432 S: serde::Serializer,
433{
434 LabeledBlock::new(value).serialize(serializer)
435}
436
437/// Hints the [`Serializer`][crate::ser::Serializer] to serialize `T` as an HCL block with two
438/// labels.
439///
440/// This function is intended to be used in the `#[serde(serialize_with)]` attribute and wraps `T`
441/// and each value of `T` with a [`LabeledBlock<T>`]. One use case for this function is terraform
442/// configuration where blocks with two labels are common in various places.
443///
444/// See the type-level documentation of [`LabeledBlock<T>`] for more.
445///
446/// # Example
447///
448/// The following example shows a very simplified and incomplete way to serialize terraform
449/// configuration.
450///
451/// ```
452/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
453/// use indexmap::{indexmap, IndexMap};
454/// use serde::Serialize;
455///
456/// #[derive(Serialize)]
457/// struct Config {
458/// #[serde(serialize_with = "hcl::ser::doubly_labeled_block")]
459/// resource: IndexMap<String, IndexMap<String, IndexMap<String, String>>>,
460/// }
461///
462/// let config = Config {
463/// resource: indexmap! {
464/// "aws_sns_topic".into() => indexmap! {
465/// "mytopic".into() => indexmap! {
466/// "name".into() => "mytopic".into(),
467/// },
468/// },
469/// },
470/// };
471///
472/// let expected = r#"
473/// resource "aws_sns_topic" "mytopic" {
474/// name = "mytopic"
475/// }
476/// "#.trim_start();
477///
478/// assert_eq!(hcl::to_string(&config)?, expected);
479/// # Ok(())
480/// # }
481/// ```
482///
483/// # Errors
484///
485/// Serialization fails if the type's shape makes it impossible to represent it as an HCL block
486/// with two labels.
487pub fn doubly_labeled_block<T, K, V, S>(value: T, serializer: S) -> Result<S::Ok, S::Error>
488where
489 T: IntoIterator<Item = (K, V)>,
490 K: Serialize + Eq,
491 V: Serialize,
492 S: serde::Serializer,
493{
494 let value: VecMap<K, LabeledBlock<V>> = value
495 .into_iter()
496 .map(|(k, v)| (k, LabeledBlock::new(v)))
497 .collect();
498 labeled_block(value, serializer)
499}