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