// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 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 NodeName not node_name. 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 :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": "", // }) // // Deprecated: Use lib/decode.HookTranslateKeys instead. 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 } }