// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package lib
import (
"fmt"
"reflect"
"github.com/mitchellh/copystructure"
"github.com/mitchellh/mapstructure"
"github.com/mitchellh/reflectwalk"
)
// MapWalk will traverse through the supplied input which should be a
// map[string]interface{} (or something compatible that we can coerce
// to a map[string]interface{}) and from it create a new map[string]interface{}
// with all internal values coerced to JSON compatible types. i.e. a []uint8
// can be converted (in most cases) to a string so it will not be base64 encoded
// when output in JSON
func MapWalk ( input interface { } ) ( map [ string ] interface { } , error ) {
mapCopyRaw , err := copystructure . Copy ( input )
if err != nil {
return nil , err
}
mapCopy , ok := mapCopyRaw . ( map [ string ] interface { } )
if ! ok {
return nil , fmt . Errorf ( "internal error: input to MapWalk is not a map[string]interface{}" )
}
if err := reflectwalk . Walk ( mapCopy , & mapWalker { } ) ; err != nil {
return nil , err
}
return mapCopy , nil
}
var typMapIfaceIface = reflect . TypeOf ( map [ interface { } ] interface { } { } )
var typByteSlice = reflect . TypeOf ( [ ] byte { } )
// mapWalker implements interfaces for the reflectwalk package
// (github.com/mitchellh/reflectwalk) that can be used to automatically
// make a JSON compatible map safe for JSON usage. This is currently
// targeted at the map[string]interface{}
//
// Most of the implementation here is just keeping track of where we are
// in the reflectwalk process, so that we can replace values. The key logic
// is in Slice() and SliceElem().
//
// In particular we're looking to replace two cases the msgpack codec causes:
//
// 1.) String values get turned into byte slices. JSON will base64-encode
// this and we don't want that, so we convert them back to strings.
//
// 2.) Nested maps turn into map[interface{}]interface{}. JSON cannot
// encode this, so we need to turn it back into map[string]interface{}.
type mapWalker struct {
lastValue reflect . Value // lastValue of map, required for replacement
loc , lastLoc reflectwalk . Location // locations
cs [ ] reflect . Value // container stack
csKey [ ] reflect . Value // container keys (maps) stack
csData interface { } // current container data
sliceIndex [ ] int // slice index stack (one for each slice in cs)
}
func ( w * mapWalker ) Enter ( loc reflectwalk . Location ) error {
w . lastLoc = w . loc
w . loc = loc
return nil
}
func ( w * mapWalker ) Exit ( loc reflectwalk . Location ) error {
w . loc = reflectwalk . None
w . lastLoc = reflectwalk . None
switch loc {
case reflectwalk . Map :
w . cs = w . cs [ : len ( w . cs ) - 1 ]
case reflectwalk . MapValue :
w . csKey = w . csKey [ : len ( w . csKey ) - 1 ]
case reflectwalk . Slice :
// Split any values that need to be split
w . cs = w . cs [ : len ( w . cs ) - 1 ]
case reflectwalk . SliceElem :
w . csKey = w . csKey [ : len ( w . csKey ) - 1 ]
w . sliceIndex = w . sliceIndex [ : len ( w . sliceIndex ) - 1 ]
}
return nil
}
func ( w * mapWalker ) Map ( m reflect . Value ) error {
w . cs = append ( w . cs , m )
return nil
}
func ( w * mapWalker ) MapElem ( m , k , v reflect . Value ) error {
w . csData = k
w . csKey = append ( w . csKey , k )
w . lastValue = v
// We're looking specifically for map[interface{}]interface{}, but the
// values in a map could be wrapped up in interface{} so we need to unwrap
// that first. Therefore, we do three checks: 1.) is it valid? so we
// don't panic, 2.) is it an interface{}? so we can unwrap it and 3.)
// after unwrapping the interface do we have the map we expect?
if ! v . IsValid ( ) {
return nil
}
if v . Kind ( ) != reflect . Interface {
return nil
}
if inner := v . Elem ( ) ; inner . IsValid ( ) && inner . Type ( ) == typMapIfaceIface {
// map[interface{}]interface{}, attempt to weakly decode into string keys
var target map [ string ] interface { }
if err := mapstructure . WeakDecode ( v . Interface ( ) , & target ) ; err != nil {
return err
}
m . SetMapIndex ( k , reflect . ValueOf ( target ) )
}
return nil
}
func ( w * mapWalker ) Slice ( v reflect . Value ) error {
// If we find a []byte slice, it is an HCL-string converted to []byte.
// Convert it back to a Go string and replace the value so that JSON
// doesn't base64-encode it.
if v . Type ( ) == typByteSlice {
resultVal := reflect . ValueOf ( string ( v . Interface ( ) . ( [ ] byte ) ) )
switch w . lastLoc {
case reflectwalk . MapKey :
m := w . cs [ len ( w . cs ) - 1 ]
// Delete the old value
var zero reflect . Value
m . SetMapIndex ( w . csData . ( reflect . Value ) , zero )
// Set the new key with the existing value
m . SetMapIndex ( resultVal , w . lastValue )
// Set the key to be the new key
w . csData = resultVal
case reflectwalk . MapValue :
// If we're in a map, then the only way to set a map value is
// to set it directly.
m := w . cs [ len ( w . cs ) - 1 ]
mk := w . csData . ( reflect . Value )
m . SetMapIndex ( mk , resultVal )
case reflectwalk . Slice :
s := w . cs [ len ( w . cs ) - 1 ]
s . Index ( w . sliceIndex [ len ( w . sliceIndex ) - 1 ] ) . Set ( resultVal )
default :
return fmt . Errorf ( "cannot convert []byte" )
}
}
w . cs = append ( w . cs , v )
return nil
}
func ( w * mapWalker ) SliceElem ( i int , elem reflect . Value ) error {
w . csKey = append ( w . csKey , reflect . ValueOf ( i ) )
w . sliceIndex = append ( w . sliceIndex , i )
// We're looking specifically for map[interface{}]interface{}, but the
// values in a slice are wrapped up in interface{} so we need to unwrap
// that first. Therefore, we do three checks: 1.) is it valid? so we
// don't panic, 2.) is it an interface{}? so we can unwrap it and 3.)
// after unwrapping the interface do we have the map we expect?
if ! elem . IsValid ( ) {
return nil
}
if elem . Kind ( ) != reflect . Interface {
return nil
}
if inner := elem . Elem ( ) ; inner . Type ( ) == typMapIfaceIface {
// map[interface{}]interface{}, attempt to weakly decode into string keys
var target map [ string ] interface { }
if err := mapstructure . WeakDecode ( inner . Interface ( ) , & target ) ; err != nil {
return err
}
elem . Set ( reflect . ValueOf ( target ) )
} else if inner := elem . Elem ( ) ; inner . Type ( ) == typByteSlice {
elem . Set ( reflect . ValueOf ( string ( inner . Interface ( ) . ( [ ] byte ) ) ) )
}
return nil
}