blob: 769a63a18949865f88efefe581fe9802f63111d1 [file] [log] [blame]
package testhelpers
import (
"crypto/sha256"
"fmt"
"reflect"
"github.com/mitchellh/go-testing-interface"
"github.com/mitchellh/mapstructure"
)
// ToMap renders an input value of any type as a map. This is intended for
// logging human-readable data dumps in test logs, so it uses the `json`
// tags on struct fields: this makes it easy to exclude `"-"` values that
// are typically not interesting, respect omitempty, etc.
//
// We also replace any []byte fields with a hash of their value.
// This is usually sufficient for test log purposes, and is a lot more readable
// than a big array of individual byte values like Go would normally stringify a
// byte slice.
func ToMap(in any) (map[string]any, error) {
temp := make(map[string]any)
cfg := &mapstructure.DecoderConfig{
TagName: "json",
IgnoreUntaggedFields: true,
Result: &temp,
}
md, err := mapstructure.NewDecoder(cfg)
if err != nil {
return nil, err
}
err = md.Decode(in)
if err != nil {
return nil, err
}
// mapstructure doesn't call the DecodeHook for each field when doing
// struct->map conversions, but it does for map->map, so call it a second
// time to convert each []byte field.
out := make(map[string]any)
md2, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: &out,
DecodeHook: func(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) {
if from.Kind() != reflect.Slice || from.Elem().Kind() != reflect.Uint8 {
return data, nil
}
b := data.([]byte)
return fmt.Sprintf("%x", sha256.Sum256(b)), nil
},
})
if err != nil {
return nil, err
}
err = md2.Decode(temp)
if err != nil {
return nil, err
}
return out, nil
}
// ToString renders its input using ToMap, and returns a string containing the
// result or an error if that fails.
func ToString(in any) string {
m, err := ToMap(in)
if err != nil {
return err.Error()
}
return fmt.Sprintf("%v", m)
}
// StringOrDie renders its input using ToMap, and returns a string containing the
// result. If rendering yields an error, calls t.Fatal.
func StringOrDie(t testing.T, in any) string {
t.Helper()
m, err := ToMap(in)
if err != nil {
t.Fatal(err)
}
return fmt.Sprintf("%v", m)
}