1package lib
2
3import (
4	"fmt"
5	"reflect"
6
7	"github.com/mitchellh/copystructure"
8	"github.com/mitchellh/mapstructure"
9	"github.com/mitchellh/reflectwalk"
10)
11
12// MapWalk will traverse through the supplied input which should be a
13// map[string]interface{} (or something compatible that we can coerce
14// to a map[string]interface{}) and from it create a new map[string]interface{}
15// with all internal values coerced to JSON compatible types. i.e. a []uint8
16// can be converted (in most cases) to a string so it will not be base64 encoded
17// when output in JSON
18func MapWalk(input interface{}) (map[string]interface{}, error) {
19	mapCopyRaw, err := copystructure.Copy(input)
20	if err != nil {
21		return nil, err
22	}
23
24	mapCopy, ok := mapCopyRaw.(map[string]interface{})
25	if !ok {
26		return nil, fmt.Errorf("internal error: input to MapWalk is not a map[string]interface{}")
27	}
28
29	if err := reflectwalk.Walk(mapCopy, &mapWalker{}); err != nil {
30		return nil, err
31	}
32
33	return mapCopy, nil
34}
35
36var typMapIfaceIface = reflect.TypeOf(map[interface{}]interface{}{})
37var typByteSlice = reflect.TypeOf([]byte{})
38
39// mapWalker implements interfaces for the reflectwalk package
40// (github.com/mitchellh/reflectwalk) that can be used to automatically
41// make a JSON compatible map safe for JSON usage. This is currently
42// targeted at the map[string]interface{}
43//
44// Most of the implementation here is just keeping track of where we are
45// in the reflectwalk process, so that we can replace values. The key logic
46// is in Slice() and SliceElem().
47//
48// In particular we're looking to replace two cases the msgpack codec causes:
49//
50//   1.) String values get turned into byte slices. JSON will base64-encode
51//       this and we don't want that, so we convert them back to strings.
52//
53//   2.) Nested maps turn into map[interface{}]interface{}. JSON cannot
54//       encode this, so we need to turn it back into map[string]interface{}.
55//
56type mapWalker struct {
57	lastValue    reflect.Value        // lastValue of map, required for replacement
58	loc, lastLoc reflectwalk.Location // locations
59	cs           []reflect.Value      // container stack
60	csKey        []reflect.Value      // container keys (maps) stack
61	csData       interface{}          // current container data
62	sliceIndex   []int                // slice index stack (one for each slice in cs)
63}
64
65func (w *mapWalker) Enter(loc reflectwalk.Location) error {
66	w.lastLoc = w.loc
67	w.loc = loc
68	return nil
69}
70
71func (w *mapWalker) Exit(loc reflectwalk.Location) error {
72	w.loc = reflectwalk.None
73	w.lastLoc = reflectwalk.None
74
75	switch loc {
76	case reflectwalk.Map:
77		w.cs = w.cs[:len(w.cs)-1]
78	case reflectwalk.MapValue:
79		w.csKey = w.csKey[:len(w.csKey)-1]
80	case reflectwalk.Slice:
81		// Split any values that need to be split
82		w.cs = w.cs[:len(w.cs)-1]
83	case reflectwalk.SliceElem:
84		w.csKey = w.csKey[:len(w.csKey)-1]
85		w.sliceIndex = w.sliceIndex[:len(w.sliceIndex)-1]
86	}
87
88	return nil
89}
90
91func (w *mapWalker) Map(m reflect.Value) error {
92	w.cs = append(w.cs, m)
93	return nil
94}
95
96func (w *mapWalker) MapElem(m, k, v reflect.Value) error {
97	w.csData = k
98	w.csKey = append(w.csKey, k)
99	w.lastValue = v
100
101	// We're looking specifically for map[interface{}]interface{}, but the
102	// values in a map could be wrapped up in interface{} so we need to unwrap
103	// that first. Therefore, we do three checks: 1.) is it valid? so we
104	// don't panic, 2.) is it an interface{}? so we can unwrap it and 3.)
105	// after unwrapping the interface do we have the map we expect?
106	if !v.IsValid() {
107		return nil
108	}
109
110	if v.Kind() != reflect.Interface {
111		return nil
112	}
113
114	if inner := v.Elem(); inner.Type() == typMapIfaceIface {
115		// map[interface{}]interface{}, attempt to weakly decode into string keys
116		var target map[string]interface{}
117		if err := mapstructure.WeakDecode(v.Interface(), &target); err != nil {
118			return err
119		}
120
121		m.SetMapIndex(k, reflect.ValueOf(target))
122	}
123
124	return nil
125}
126
127func (w *mapWalker) Slice(v reflect.Value) error {
128	// If we find a []byte slice, it is an HCL-string converted to []byte.
129	// Convert it back to a Go string and replace the value so that JSON
130	// doesn't base64-encode it.
131	if v.Type() == typByteSlice {
132		resultVal := reflect.ValueOf(string(v.Interface().([]byte)))
133		switch w.lastLoc {
134		case reflectwalk.MapKey:
135			m := w.cs[len(w.cs)-1]
136
137			// Delete the old value
138			var zero reflect.Value
139			m.SetMapIndex(w.csData.(reflect.Value), zero)
140
141			// Set the new key with the existing value
142			m.SetMapIndex(resultVal, w.lastValue)
143
144			// Set the key to be the new key
145			w.csData = resultVal
146		case reflectwalk.MapValue:
147			// If we're in a map, then the only way to set a map value is
148			// to set it directly.
149			m := w.cs[len(w.cs)-1]
150			mk := w.csData.(reflect.Value)
151			m.SetMapIndex(mk, resultVal)
152		case reflectwalk.Slice:
153			s := w.cs[len(w.cs)-1]
154			s.Index(w.sliceIndex[len(w.sliceIndex)-1]).Set(resultVal)
155		default:
156			return fmt.Errorf("cannot convert []byte")
157		}
158	}
159
160	w.cs = append(w.cs, v)
161	return nil
162}
163
164func (w *mapWalker) SliceElem(i int, elem reflect.Value) error {
165	w.csKey = append(w.csKey, reflect.ValueOf(i))
166	w.sliceIndex = append(w.sliceIndex, i)
167
168	// We're looking specifically for map[interface{}]interface{}, but the
169	// values in a slice are wrapped up in interface{} so we need to unwrap
170	// that first. Therefore, we do three checks: 1.) is it valid? so we
171	// don't panic, 2.) is it an interface{}? so we can unwrap it and 3.)
172	// after unwrapping the interface do we have the map we expect?
173	if !elem.IsValid() {
174		return nil
175	}
176
177	if elem.Kind() != reflect.Interface {
178		return nil
179	}
180
181	if inner := elem.Elem(); inner.Type() == typMapIfaceIface {
182		// map[interface{}]interface{}, attempt to weakly decode into string keys
183		var target map[string]interface{}
184		if err := mapstructure.WeakDecode(inner.Interface(), &target); err != nil {
185			return err
186		}
187
188		elem.Set(reflect.ValueOf(target))
189	} else if inner := elem.Elem(); inner.Type() == typByteSlice {
190		elem.Set(reflect.ValueOf(string(inner.Interface().([]byte))))
191	}
192
193	return nil
194}
195