consul/lib/translate.go

100 lines
2.9 KiB
Go

package lib
import (
"strings"
)
// TranslateKeys recursively translates all keys from m in-place to their
// canonical form as defined in dict which maps an alias name to the canonical
// name. If m already has a value for the canonical name then that one is used
// and the value for the alias name is discarded. Alias names are matched
// case-insensitive.
//
// Example:
//
// m = TranslateKeys(m, map[string]string{"snake_case": "CamelCase"})
//
// If the canonical string provided is the empty string, the effect is to stop
// recursing into any key matching the left hand side. In this case the left
// hand side must use periods to specify a full path e.g.
// `connect.proxy.config`. The path must be the canonical key names (i.e.
// CamelCase) AFTER translation so ExecMode not exec_mode. These are still match
// in a case-insensitive way.
//
// This is needed for example because parts of the Service Definition are
// "opaque" maps of metadata or config passed to another process or component.
// If we allow translation to recurse we might mangle the "opaque" keys given
// where the clash with key names in other parts of the definition (and they do
// in practice with deprecated managed proxy upstreams) :sob:
//
// Example:
// m - TranslateKeys(m, map[string]string{
// "foo_bar": "FooBar",
// "widget.config": "",
// // Assume widgets is an array, this will prevent recursing into any
// // item's config field
// "widgets.config": "",
// })
func TranslateKeys(v map[string]interface{}, dict map[string]string) {
// Convert all dict keys for exclusions to lower. so we can match against them
// unambiguously with a single lookup.
for k, v := range dict {
if v == "" {
dict[strings.ToLower(k)] = ""
}
}
ck(v, dict, "")
}
func ck(v interface{}, dict map[string]string, pathPfx string) interface{} {
// In array case we don't add a path segment for the item as they are all
// assumed to be same which is why we check the prefix doesn't already end in
// a .
if pathPfx != "" && !strings.HasSuffix(pathPfx, ".") {
pathPfx += "."
}
switch x := v.(type) {
case map[string]interface{}:
for k, v := range x {
lowerK := strings.ToLower(k)
// Check if this path has been excluded
val, ok := dict[pathPfx+lowerK]
if ok && val == "" {
// Don't recurse into this key
continue
}
canonKey, ok := dict[lowerK]
// no canonical key? -> use this key
if !ok {
x[k] = ck(v, dict, pathPfx+lowerK)
continue
}
// delete the alias
delete(x, k)
// if there is a value for the canonical key then keep it
if _, ok := x[canonKey]; ok {
continue
}
// otherwise translate to the canonical key
x[canonKey] = ck(v, dict, pathPfx+strings.ToLower(canonKey))
}
return x
case []interface{}:
var a []interface{}
for _, xv := range x {
a = append(a, ck(xv, dict, pathPfx))
}
return a
default:
return v
}
}