github.com/sl1pm4t/consul@v1.4.5-0.20190325224627-74c31c540f9c/agent/config/translate.go (about) 1 package config 2 3 import ( 4 "strings" 5 ) 6 7 // TranslateKeys recursively translates all keys from m in-place to their 8 // canonical form as defined in dict which maps an alias name to the canonical 9 // name. If m already has a value for the canonical name then that one is used 10 // and the value for the alias name is discarded. Alias names are matched 11 // case-insensitive. 12 // 13 // Example: 14 // 15 // m = TranslateKeys(m, map[string]string{"snake_case": "CamelCase"}) 16 // 17 // If the canonical string provided is the empty string, the effect is to stop 18 // recursing into any key matching the left hand side. In this case the left 19 // hand side must use periods to specify a full path e.g. 20 // `connect.proxy.config`. The path must be the canonical key names (i.e. 21 // CamelCase) AFTER translation so ExecMode not exec_mode. These are still match 22 // in a case-insensitive way. 23 // 24 // This is needed for example because parts of the Service Definition are 25 // "opaque" maps of metadata or config passed to another process or component. 26 // If we allow translation to recurse we might mangle the "opaque" keys given 27 // where the clash with key names in other parts of the definition (and they do 28 // in practice with deprecated managed proxy upstreams) :sob: 29 // 30 // Example: 31 // m - TranslateKeys(m, map[string]string{ 32 // "foo_bar": "FooBar", 33 // "widget.config": "", 34 // // Assume widgets is an array, this will prevent recursing into any 35 // // item's config field 36 // "widgets.config": "", 37 // }) 38 func TranslateKeys(v map[string]interface{}, dict map[string]string) { 39 // Convert all dict keys for exclusions to lower. so we can match against them 40 // unambiguously with a single lookup. 41 for k, v := range dict { 42 if v == "" { 43 dict[strings.ToLower(k)] = "" 44 } 45 } 46 ck(v, dict, "") 47 } 48 49 func ck(v interface{}, dict map[string]string, pathPfx string) interface{} { 50 // In array case we don't add a path segment for the item as they are all 51 // assumed to be same which is why we check the prefix doesn't already end in 52 // a . 53 if pathPfx != "" && !strings.HasSuffix(pathPfx, ".") { 54 pathPfx += "." 55 } 56 switch x := v.(type) { 57 case map[string]interface{}: 58 for k, v := range x { 59 lowerK := strings.ToLower(k) 60 61 // Check if this path has been excluded 62 val, ok := dict[pathPfx+lowerK] 63 if ok && val == "" { 64 // Don't recurse into this key 65 continue 66 } 67 68 canonKey, ok := dict[lowerK] 69 70 // no canonical key? -> use this key 71 if !ok { 72 x[k] = ck(v, dict, pathPfx+lowerK) 73 continue 74 } 75 76 // delete the alias 77 delete(x, k) 78 79 // if there is a value for the canonical key then keep it 80 if _, ok := x[canonKey]; ok { 81 continue 82 } 83 84 // otherwise translate to the canonical key 85 x[canonKey] = ck(v, dict, pathPfx+strings.ToLower(canonKey)) 86 } 87 return x 88 89 case []interface{}: 90 var a []interface{} 91 for _, xv := range x { 92 a = append(a, ck(xv, dict, pathPfx)) 93 } 94 return a 95 96 default: 97 return v 98 } 99 }