github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/jsonformat/differ/object.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package differ
     5  
     6  import (
     7  	"github.com/zclconf/go-cty/cty"
     8  
     9  	"github.com/terramate-io/tf/command/jsonformat/collections"
    10  	"github.com/terramate-io/tf/command/jsonformat/computed"
    11  	"github.com/terramate-io/tf/command/jsonformat/computed/renderers"
    12  	"github.com/terramate-io/tf/command/jsonformat/structured"
    13  	"github.com/terramate-io/tf/command/jsonprovider"
    14  	"github.com/terramate-io/tf/plans"
    15  )
    16  
    17  func computeAttributeDiffAsObject(change structured.Change, attributes map[string]cty.Type) computed.Diff {
    18  	attributeDiffs, action := processObject(change, attributes, func(value structured.Change, ctype cty.Type) computed.Diff {
    19  		return ComputeDiffForType(value, ctype)
    20  	})
    21  	return computed.NewDiff(renderers.Object(attributeDiffs), action, change.ReplacePaths.Matches())
    22  }
    23  
    24  func computeAttributeDiffAsNestedObject(change structured.Change, attributes map[string]*jsonprovider.Attribute) computed.Diff {
    25  	attributeDiffs, action := processObject(change, attributes, func(value structured.Change, attribute *jsonprovider.Attribute) computed.Diff {
    26  		return ComputeDiffForAttribute(value, attribute)
    27  	})
    28  	return computed.NewDiff(renderers.NestedObject(attributeDiffs), action, change.ReplacePaths.Matches())
    29  }
    30  
    31  // processObject steps through the children of value as if it is an object and
    32  // calls out to the provided computeDiff function once it has collated the
    33  // diffs for each child attribute.
    34  //
    35  // We have to make this generic as attributes and nested objects process either
    36  // cty.Type or jsonprovider.Attribute children respectively. And we want to
    37  // reuse as much code as possible.
    38  //
    39  // Also, as it generic we cannot make this function a method on Change as you
    40  // can't create generic methods on structs. Instead, we make this a generic
    41  // function that receives the value as an argument.
    42  func processObject[T any](v structured.Change, attributes map[string]T, computeDiff func(structured.Change, T) computed.Diff) (map[string]computed.Diff, plans.Action) {
    43  	attributeDiffs := make(map[string]computed.Diff)
    44  	mapValue := v.AsMap()
    45  
    46  	currentAction := v.GetDefaultActionForIteration()
    47  	for key, attribute := range attributes {
    48  		attributeValue := mapValue.GetChild(key)
    49  
    50  		if !attributeValue.RelevantAttributes.MatchesPartial() {
    51  			// Mark non-relevant attributes as unchanged.
    52  			attributeValue = attributeValue.AsNoOp()
    53  		}
    54  
    55  		// We always assume changes to object are implicit.
    56  		attributeValue.BeforeExplicit = false
    57  		attributeValue.AfterExplicit = false
    58  
    59  		attributeDiff := computeDiff(attributeValue, attribute)
    60  		if attributeDiff.Action == plans.NoOp && attributeValue.Before == nil && attributeValue.After == nil {
    61  			// We skip attributes of objects that are null both before and
    62  			// after. We don't even count these as unchanged attributes.
    63  			continue
    64  		}
    65  		attributeDiffs[key] = attributeDiff
    66  		currentAction = collections.CompareActions(currentAction, attributeDiff.Action)
    67  	}
    68  
    69  	return attributeDiffs, currentAction
    70  }