Files
opentf/internal/repl/format.go
Brandon Croft bd744ad4e9 Unify all sensitive value plan output as "(sensitive value)"
Previously, there was mixed usage of "(sensitive)" and "(sensitive value)" and even though it was more common to see "(sensitive)", the thought is that it's a value we are hiding rather than describing something already shown.
2022-10-24 12:50:46 -06:00

174 lines
4.2 KiB
Go

package repl
import (
"fmt"
"strconv"
"strings"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/zclconf/go-cty/cty"
)
// FormatValue formats a value in a way that resembles Terraform language syntax
// and uses the type conversion functions where necessary to indicate exactly
// what type it is given, so that equality test failures can be quickly
// understood.
func FormatValue(v cty.Value, indent int) string {
if !v.IsKnown() {
return "(known after apply)"
}
if v.HasMark(marks.Sensitive) {
return "(sensitive value)"
}
if v.IsNull() {
ty := v.Type()
switch {
case ty == cty.DynamicPseudoType:
return "null"
case ty == cty.String:
return "tostring(null)"
case ty == cty.Number:
return "tonumber(null)"
case ty == cty.Bool:
return "tobool(null)"
case ty.IsListType():
return fmt.Sprintf("tolist(null) /* of %s */", ty.ElementType().FriendlyName())
case ty.IsSetType():
return fmt.Sprintf("toset(null) /* of %s */", ty.ElementType().FriendlyName())
case ty.IsMapType():
return fmt.Sprintf("tomap(null) /* of %s */", ty.ElementType().FriendlyName())
default:
return fmt.Sprintf("null /* %s */", ty.FriendlyName())
}
}
ty := v.Type()
switch {
case ty.IsPrimitiveType():
switch ty {
case cty.String:
if formatted, isMultiline := formatMultilineString(v, indent); isMultiline {
return formatted
}
return strconv.Quote(v.AsString())
case cty.Number:
bf := v.AsBigFloat()
return bf.Text('f', -1)
case cty.Bool:
if v.True() {
return "true"
} else {
return "false"
}
}
case ty.IsObjectType():
return formatMappingValue(v, indent)
case ty.IsTupleType():
return formatSequenceValue(v, indent)
case ty.IsListType():
return fmt.Sprintf("tolist(%s)", formatSequenceValue(v, indent))
case ty.IsSetType():
return fmt.Sprintf("toset(%s)", formatSequenceValue(v, indent))
case ty.IsMapType():
return fmt.Sprintf("tomap(%s)", formatMappingValue(v, indent))
}
// Should never get here because there are no other types
return fmt.Sprintf("%#v", v)
}
func formatMultilineString(v cty.Value, indent int) (string, bool) {
str := v.AsString()
lines := strings.Split(str, "\n")
if len(lines) < 2 {
return "", false
}
// If the value is indented, we use the indented form of heredoc for readability.
operator := "<<"
if indent > 0 {
operator = "<<-"
}
// Default delimiter is "End Of Text" by convention
delimiter := "EOT"
OUTER:
for {
// Check if any of the lines are in conflict with the delimiter. The
// parser allows leading and trailing whitespace, so we must remove it
// before comparison.
for _, line := range lines {
// If the delimiter matches a line, extend it and start again
if strings.TrimSpace(line) == delimiter {
delimiter = delimiter + "_"
continue OUTER
}
}
// None of the lines match the delimiter, so we're ready
break
}
// Write the heredoc, with indentation as appropriate.
var buf strings.Builder
buf.WriteString(operator)
buf.WriteString(delimiter)
for _, line := range lines {
buf.WriteByte('\n')
buf.WriteString(strings.Repeat(" ", indent))
buf.WriteString(line)
}
buf.WriteByte('\n')
buf.WriteString(strings.Repeat(" ", indent))
buf.WriteString(delimiter)
return buf.String(), true
}
func formatMappingValue(v cty.Value, indent int) string {
var buf strings.Builder
count := 0
buf.WriteByte('{')
indent += 2
for it := v.ElementIterator(); it.Next(); {
count++
k, v := it.Element()
buf.WriteByte('\n')
buf.WriteString(strings.Repeat(" ", indent))
buf.WriteString(FormatValue(k, indent))
buf.WriteString(" = ")
buf.WriteString(FormatValue(v, indent))
}
indent -= 2
if count > 0 {
buf.WriteByte('\n')
buf.WriteString(strings.Repeat(" ", indent))
}
buf.WriteByte('}')
return buf.String()
}
func formatSequenceValue(v cty.Value, indent int) string {
var buf strings.Builder
count := 0
buf.WriteByte('[')
indent += 2
for it := v.ElementIterator(); it.Next(); {
count++
_, v := it.Element()
buf.WriteByte('\n')
buf.WriteString(strings.Repeat(" ", indent))
buf.WriteString(FormatValue(v, indent))
buf.WriteByte(',')
}
indent -= 2
if count > 0 {
buf.WriteByte('\n')
buf.WriteString(strings.Repeat(" ", indent))
}
buf.WriteByte(']')
return buf.String()
}