1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
|
package ctydebug
import (
"fmt"
"reflect"
"strings"
"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty"
)
// DiffValues returns a human-oriented description of the differences between
// the two given values. It's guaranteed to return an empty string if the
// two values are RawEqual.
//
// Don't depend on the exact formatting of the result. It is likely to change
// in future releases.
func DiffValues(want, got cty.Value) string {
// want and got are in the order they are here because that's how cmp
// seems to treat them, and we'd like to be consistent with cmp to
// minimize confusion in codebases that are using both cmp directly and
// indirectly via DiffValues.
if got.RawEquals(want) {
return "" // just to make sure
}
r := &diffValuesReporter{}
cmp.Equal(want, got, CmpOptions, cmp.Reporter(r))
return r.Result()
}
// This is a very simple reporter for now. Hopefully one day it can become
// more sophisticated and produce output that looks more like the result
// of ValueString.
type diffValuesReporter struct {
path cmp.Path
sb strings.Builder
}
func (r *diffValuesReporter) PushStep(step cmp.PathStep) {
r.path = append(r.path, step)
}
func (r *diffValuesReporter) PopStep() {
r.path = r.path[:len(r.path)-1]
}
func (r *diffValuesReporter) Report(result cmp.Result) {
if result.Equal() {
return
}
r.sb.WriteString(cmpPathString(r.path))
r.sb.WriteString("\n")
want, got := r.path.Last().Values()
fmt.Fprintf(&r.sb, " got: %s\n", resultValueString(got))
fmt.Fprintf(&r.sb, " want: %s\n", resultValueString(want))
r.sb.WriteString("\n")
}
func (r *diffValuesReporter) Result() string {
return r.sb.String()
}
func resultValueString(rv reflect.Value) string {
if !rv.IsValid() {
return "(no value)"
}
if v, ok := rv.Interface().(cty.Value); ok && v == cty.NilVal {
return "cty.NilVal"
}
if ty, ok := rv.Interface().(cty.Type); ok && ty == cty.NilType {
return "cty.NilType"
}
return fmt.Sprintf("%#v", rv)
}
// cmpPathString returns the given path serialized using a compact syntax
// that isn't in any language exactly but is hopefully intuitive.
func cmpPathString(path cmp.Path) string {
var b strings.Builder
for _, step := range path {
switch step := step.(type) {
case cmp.Transform:
if step.Option() == transformValueOp || step.Option() == transformTypeOp {
continue // ignore; it's an implementation detail
}
b.WriteString(step.String())
case cmp.TypeAssertion:
// These show up on the results of the transforms we do to trick
// cmp into walking into our structural/collection values, but
// that's an implementation detail so we'll skip it.
continue
case cmp.Indirect:
continue
case cmp.MapIndex:
fmt.Fprintf(&b, "[%q]", step.Key())
case cmp.SliceIndex:
fmt.Fprintf(&b, "[%d]", step.Key())
case cmp.StructField:
// We don't expect to see any struct field traversals in our
// work because of our transformations, but if one shows up then
// we'll handle it somewhat gracefully...
fmt.Fprintf(&b, ".%s", step.Name())
default:
b.WriteString(step.String())
}
}
return b.String()
}
|