forked from hashicorp/terraform-json
-
Notifications
You must be signed in to change notification settings - Fork 0
/
expression.go
127 lines (104 loc) · 3.72 KB
/
expression.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package tfjson
import "encoding/json"
type unknownConstantValue struct{}
// UnknownConstantValue is a singleton type that denotes that a
// constant value is explicitly unknown. This is set during an
// unmarshal when references are found in an expression to help more
// explicitly differentiate between an explicit null and unknown
// value.
var UnknownConstantValue = &unknownConstantValue{}
// Expression describes the format for an individual key in a
// Terraform configuration.
//
// This struct wraps ExpressionData to support custom JSON parsing.
type Expression struct {
*ExpressionData
}
// ExpressionData describes the format for an individual key in a
// Terraform configuration.
type ExpressionData struct {
// If the *entire* expression is a constant-defined value, this
// will contain the Go representation of the expression's data.
//
// Note that a nil here denotes and explicit null. When a value is
// unknown on part of the value coming from an expression that
// cannot be resolved at parse time, this field will contain
// UnknownConstantValue.
ConstantValue interface{} `json:"constant_value,omitempty"`
// If any part of the expression contained values that were not
// able to be resolved at parse-time, this will contain a list of
// the referenced identifiers that caused the value to be unknown.
References []string `json:"references,omitempty"`
// A list of complex objects that were nested in this expression.
// If this value is a nested block in configuration, sometimes
// referred to as a "sub-resource", this field will contain those
// values, and ConstantValue and References will be blank.
NestedBlocks []map[string]*Expression `json:"-"`
}
// UnmarshalJSON implements json.Unmarshaler for Expression.
func (e *Expression) UnmarshalJSON(b []byte) error {
result := new(ExpressionData)
// Check to see if this is an array first. If it is, this is more
// than likely a list of nested blocks.
var rawNested []map[string]json.RawMessage
if err := json.Unmarshal(b, &rawNested); err == nil {
result.NestedBlocks, err = unmarshalExpressionBlocks(rawNested)
if err != nil {
return err
}
} else {
// It's a non-nested expression block, parse normally
if err := json.Unmarshal(b, &result); err != nil {
return err
}
// If References is non-zero, then ConstantValue is unknown. Set
// this explicitly.
if len(result.References) > 0 {
result.ConstantValue = UnknownConstantValue
}
}
e.ExpressionData = result
return nil
}
func unmarshalExpressionBlocks(raw []map[string]json.RawMessage) ([]map[string]*Expression, error) {
var result []map[string]*Expression
for _, rawBlock := range raw {
block := make(map[string]*Expression)
for k, rawExpr := range rawBlock {
var expr *Expression
if err := json.Unmarshal(rawExpr, &expr); err != nil {
return nil, err
}
block[k] = expr
}
result = append(result, block)
}
return result, nil
}
// MarshalJSON implements json.Marshaler for Expression.
func (e *Expression) MarshalJSON() ([]byte, error) {
switch {
case len(e.ExpressionData.NestedBlocks) > 0:
return marshalExpressionBlocks(e.ExpressionData.NestedBlocks)
case e.ExpressionData.ConstantValue == UnknownConstantValue:
return json.Marshal(&ExpressionData{
References: e.ExpressionData.References,
})
}
return json.Marshal(e.ExpressionData)
}
func marshalExpressionBlocks(nested []map[string]*Expression) ([]byte, error) {
var rawNested []map[string]json.RawMessage
for _, block := range nested {
rawBlock := make(map[string]json.RawMessage)
for k, expr := range block {
raw, err := json.Marshal(expr)
if err != nil {
return nil, err
}
rawBlock[k] = raw
}
rawNested = append(rawNested, rawBlock)
}
return json.Marshal(rawNested)
}