github.com/opentofu/opentofu@v1.7.1/internal/command/jsonformat/computed/renderers/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 renderers 7 8 import ( 9 "bytes" 10 "fmt" 11 "sort" 12 13 "github.com/opentofu/opentofu/internal/command/jsonformat/computed" 14 "github.com/opentofu/opentofu/internal/plans" 15 ) 16 17 var _ computed.DiffRenderer = (*objectRenderer)(nil) 18 19 func Object(attributes map[string]computed.Diff) computed.DiffRenderer { 20 return &objectRenderer{ 21 attributes: attributes, 22 overrideNullSuffix: true, 23 } 24 } 25 26 func NestedObject(attributes map[string]computed.Diff) computed.DiffRenderer { 27 return &objectRenderer{ 28 attributes: attributes, 29 overrideNullSuffix: false, 30 } 31 } 32 33 type objectRenderer struct { 34 NoWarningsRenderer 35 36 attributes map[string]computed.Diff 37 overrideNullSuffix bool 38 } 39 40 func (renderer objectRenderer) RenderHuman(diff computed.Diff, indent int, opts computed.RenderHumanOpts) string { 41 if len(renderer.attributes) == 0 { 42 return fmt.Sprintf("{}%s%s", nullSuffix(diff.Action, opts), forcesReplacement(diff.Replace, opts)) 43 } 44 45 attributeOpts := opts.Clone() 46 attributeOpts.OverrideNullSuffix = renderer.overrideNullSuffix 47 48 // We need to keep track of our keys in two ways. The first is the order in 49 // which we will display them. The second is a mapping to their safely 50 // escaped equivalent. 51 52 maximumKeyLen := 0 53 var keys []string 54 escapedKeys := make(map[string]string) 55 for key := range renderer.attributes { 56 keys = append(keys, key) 57 escapedKey := EnsureValidAttributeName(key) 58 escapedKeys[key] = escapedKey 59 if maximumKeyLen < len(escapedKey) { 60 maximumKeyLen = len(escapedKey) 61 } 62 } 63 sort.Strings(keys) 64 65 unchangedAttributes := 0 66 var buf bytes.Buffer 67 buf.WriteString(fmt.Sprintf("{%s\n", forcesReplacement(diff.Replace, opts))) 68 for _, key := range keys { 69 attribute := renderer.attributes[key] 70 71 if importantAttribute(key) { 72 importantAttributeOpts := attributeOpts.Clone() 73 importantAttributeOpts.ShowUnchangedChildren = true 74 75 for _, warning := range attribute.WarningsHuman(indent+1, importantAttributeOpts) { 76 buf.WriteString(fmt.Sprintf("%s%s\n", formatIndent(indent+1), warning)) 77 } 78 buf.WriteString(fmt.Sprintf("%s%s%-*s = %s\n", formatIndent(indent+1), writeDiffActionSymbol(attribute.Action, importantAttributeOpts), maximumKeyLen, escapedKeys[key], attribute.RenderHuman(indent+1, importantAttributeOpts))) 79 continue 80 } 81 82 if attribute.Action == plans.NoOp && !opts.ShowUnchangedChildren { 83 // Don't render NoOp operations when we are compact display. 84 unchangedAttributes++ 85 continue 86 } 87 88 for _, warning := range attribute.WarningsHuman(indent+1, opts) { 89 buf.WriteString(fmt.Sprintf("%s%s\n", formatIndent(indent+1), warning)) 90 } 91 buf.WriteString(fmt.Sprintf("%s%s%-*s = %s\n", formatIndent(indent+1), writeDiffActionSymbol(attribute.Action, attributeOpts), maximumKeyLen, escapedKeys[key], attribute.RenderHuman(indent+1, attributeOpts))) 92 } 93 94 if unchangedAttributes > 0 { 95 buf.WriteString(fmt.Sprintf("%s%s%s\n", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts), unchanged("attribute", unchangedAttributes, opts))) 96 } 97 98 buf.WriteString(fmt.Sprintf("%s%s}%s", formatIndent(indent), writeDiffActionSymbol(plans.NoOp, opts), nullSuffix(diff.Action, opts))) 99 return buf.String() 100 }