| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| 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) |
| } |