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)
}
|