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) }