128 lines
3.7 KiB
Go
128 lines
3.7 KiB
Go
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)
|
|
}
|