hcl/structure/
json_spec.rs

1use super::{Block, Body, Structure};
2use crate::{Expression, Identifier, Map, Value};
3use indexmap::map::Entry;
4
5/// A trait to convert an HCL structure into its [JSON representation][json-spec].
6///
7/// This is used internally by the `Body` and `Block` types to convert into an `Expression`.
8///
9/// [json-spec]: https://github.com/hashicorp/hcl/blob/main/json/spec.md#blocks
10pub(crate) trait IntoJsonSpec: Sized {
11    /// Converts a value to an expression that conforms to the HCL JSON specification.
12    ///
13    /// Provides a default implementation which converts the result of `into_nodes` into an
14    /// `Expression` and unsually does not need to be overridden.
15    fn into_json_spec(self) -> Expression {
16        Expression::from_iter(self.into_json_nodes())
17    }
18
19    /// Converts the value into a map of nodes.
20    ///
21    /// The detour over a map of nodes is necessary as HCL blocks with the same identifier and
22    /// labels need to be merged so that the `Expression` resulting from `into_json_spec` conforms
23    /// to the HCL JSON specification.
24    fn into_json_nodes(self) -> Map<String, JsonNode>;
25}
26
27impl IntoJsonSpec for Body {
28    fn into_json_nodes(self) -> Map<String, JsonNode> {
29        self.into_iter().fold(Map::new(), |mut map, structure| {
30            match structure {
31                Structure::Attribute(attr) => {
32                    map.insert(attr.key.into_inner(), JsonNode::Expr(attr.expr));
33                }
34                Structure::Block(block) => {
35                    for (key, node) in block.into_json_nodes() {
36                        node.deep_merge_into(&mut map, key);
37                    }
38                }
39            }
40
41            map
42        })
43    }
44}
45
46impl IntoJsonSpec for Block {
47    fn into_json_nodes(self) -> Map<String, JsonNode> {
48        let mut labels = self.labels.into_iter();
49
50        let node = match labels.next() {
51            Some(label) => {
52                let block = Block {
53                    identifier: Identifier::unchecked(label.into_inner()),
54                    labels: labels.collect(),
55                    body: self.body,
56                };
57
58                JsonNode::Map(block.into_json_nodes())
59            }
60            None => JsonNode::Body(vec![self.body]),
61        };
62
63        std::iter::once((self.identifier.into_inner(), node)).collect()
64    }
65}
66
67pub(crate) enum JsonNode {
68    Map(Map<String, JsonNode>),
69    Body(Vec<Body>),
70    Expr(Expression),
71}
72
73impl From<JsonNode> for Expression {
74    fn from(node: JsonNode) -> Self {
75        match node {
76            JsonNode::Map(map) => Expression::from_iter(map),
77            JsonNode::Body(mut vec) => {
78                // Flatten as per the [HCL JSON spec][json-spec].
79                //
80                // > After any labelling levels, the next nested value is either a JSON
81                // > object representing a single block body, or a JSON array of JSON
82                // > objects that each represent a single block body.
83                //
84                // [json-spec]: https://github.com/hashicorp/hcl/blob/main/json/spec.md#blocks
85                if vec.len() == 1 {
86                    vec.remove(0).into()
87                } else {
88                    vec.into()
89                }
90            }
91            JsonNode::Expr(expr) => expr,
92        }
93    }
94}
95
96impl<T> From<T> for Expression
97where
98    T: IntoJsonSpec,
99{
100    fn from(value: T) -> Expression {
101        value.into_json_spec()
102    }
103}
104
105impl<T> From<T> for Value
106where
107    T: IntoJsonSpec,
108{
109    fn from(value: T) -> Value {
110        Value::from(value.into_json_spec())
111    }
112}
113
114impl JsonNode {
115    fn deep_merge_into(self, map: &mut Map<String, JsonNode>, key: String) {
116        match map.entry(key) {
117            Entry::Occupied(o) => o.into_mut().deep_merge(self),
118            Entry::Vacant(v) => {
119                v.insert(self);
120            }
121        }
122    }
123
124    fn deep_merge(&mut self, other: JsonNode) {
125        match (self, other) {
126            (JsonNode::Map(lhs), JsonNode::Map(rhs)) => {
127                for (key, node) in rhs {
128                    node.deep_merge_into(lhs, key);
129                }
130            }
131            (JsonNode::Body(lhs), JsonNode::Body(mut rhs)) => {
132                lhs.append(&mut rhs);
133            }
134            (lhs, rhs) => *lhs = rhs,
135        }
136    }
137}