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 }