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  }