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 }