github.com/opentofu/opentofu@v1.7.1/internal/command/jsonformat/differ/object.go (about)

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