From 7cf96ade221e91ca4ea2d5c09b51b79e2f2f633d Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 15 Jun 2020 14:48:33 -0400 Subject: [PATCH] Merge pull request #8106 from hashicorp/dnephin/hook-translate-keys-make-a-copy decode: do not modify the source data in HookTranslateKeys --- lib/decode/decode.go | 15 +++++++++++---- lib/decode/decode_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/lib/decode/decode.go b/lib/decode/decode.go index 6730d47f85..7a6534e64d 100644 --- a/lib/decode/decode.go +++ b/lib/decode/decode.go @@ -44,21 +44,28 @@ func HookTranslateKeys(_, to reflect.Type, data interface{}) (interface{}, error } rules := translationsForType(to) + // Avoid making a copy if there are no translation rules + if len(rules) == 0 { + return data, nil + } + result := make(map[string]interface{}, len(source)) for k, v := range source { lowerK := strings.ToLower(k) canonKey, ok := rules[lowerK] if !ok { + result[k] = v continue } - delete(source, k) // if there is a value for the canonical key then keep it - if _, ok := source[canonKey]; ok { + if canonValue, ok := source[canonKey]; ok { + // Assign the value for the case where canonKey == k + result[canonKey] = canonValue continue } - source[canonKey] = v + result[canonKey] = v } - return source, nil + return result, nil } // TODO: could be cached if it is too slow diff --git a/lib/decode/decode_test.go b/lib/decode/decode_test.go index 72e012b39c..1f2b5d3529 100644 --- a/lib/decode/decode_test.go +++ b/lib/decode/decode_test.go @@ -178,6 +178,33 @@ func TestHookTranslateKeys_TargetStructHasPointerReceiver(t *testing.T) { require.Equal(t, expected, target, "decode metadata: %#v", md) } +func TestHookTranslateKeys_DoesNotModifySourceData(t *testing.T) { + raw := map[string]interface{}{ + "S": map[string]interface{}{ + "None": "no translation", + "OldOne": "value1", + "oldtwo": "value2", + }, + } + + cfg := Config{} + decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + DecodeHook: HookTranslateKeys, + Result: &cfg, + }) + require.NoError(t, err) + require.NoError(t, decoder.Decode(raw)) + + expected := map[string]interface{}{ + "S": map[string]interface{}{ + "None": "no translation", + "OldOne": "value1", + "oldtwo": "value2", + }, + } + require.Equal(t, raw, expected) +} + type translateExample struct { FieldDefaultCanonical string `alias:"first"` FieldWithMapstructureTag string `alias:"second" mapstructure:"field_with_mapstruct_tag"`