From 8958b6ecd7d29efe3a87dd6b3de0af1746d4ff8c Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 11 Aug 2024 21:49:34 +0200 Subject: [PATCH] Fix case-insensitively merging template maps (#15351) --- util/templates/merge.go | 55 ++++++++++++++++++++++++++++++++++++ util/templates/merge_test.go | 32 +++++++++++++++++++++ util/templates/template.go | 5 ++-- 3 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 util/templates/merge.go create mode 100644 util/templates/merge_test.go diff --git a/util/templates/merge.go b/util/templates/merge.go new file mode 100644 index 000000000..fa9efdfc4 --- /dev/null +++ b/util/templates/merge.go @@ -0,0 +1,55 @@ +package templates + +import ( + "reflect" + "strings" +) + +// https://github.com/peterbourgon/mergemap + +const mergeMaxDepth = 100 + +var matchKey = strings.EqualFold + +// mergeMaps recursively merges other into target using matchKey for key comparison +func mergeMaps(other map[string]any, target map[string]any) error { + // return mergo.Map(&target, other, mergo.WithOverride) + // return util.DecodeOther(other, target) + merge(target, other, 0) + return nil +} + +func merge(dst, src map[string]any, depth int) map[string]any { + if depth > mergeMaxDepth { + panic("too deep!") + } + for key, srcVal := range src { + for k := range dst { + if matchKey(k, key) { + // overwrite key + key = k + + srcMap, srcMapOk := mapify(srcVal) + dstMap, dstMapOk := mapify(dst[k]) + if srcMapOk && dstMapOk { + srcVal = merge(dstMap, srcMap, depth+1) + } + break + } + } + dst[key] = srcVal + } + return dst +} + +func mapify(i any) (map[string]any, bool) { + value := reflect.ValueOf(i) + if value.Kind() == reflect.Map { + m := map[string]any{} + for _, k := range value.MapKeys() { + m[k.String()] = value.MapIndex(k).Interface() + } + return m, true + } + return map[string]any{}, false +} diff --git a/util/templates/merge_test.go b/util/templates/merge_test.go new file mode 100644 index 000000000..b8e878872 --- /dev/null +++ b/util/templates/merge_test.go @@ -0,0 +1,32 @@ +package templates + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMergeMaps(t *testing.T) { + target := map[string]any{ + "foo": "bar", + "nested": map[string]any{ + "bar": "baz", + }, + } + other := map[string]any{ + "Foo": 1, + "Nested": map[string]any{ + "Bar": 2, + }, + "baz": 3, + } + + require.NoError(t, mergeMaps(other, target)) + require.Equal(t, map[string]any{ + "foo": 1, + "nested": map[string]any{ + "bar": 2, + }, + "baz": 3, + }, target) +} diff --git a/util/templates/template.go b/util/templates/template.go index b982a8f29..c2e1d09b9 100644 --- a/util/templates/template.go +++ b/util/templates/template.go @@ -9,7 +9,6 @@ import ( "strings" "text/template" - "github.com/evcc-io/evcc/util" "github.com/go-sprout/sprout" ) @@ -267,9 +266,9 @@ func (t *Template) RenderProxyWithValues(values map[string]interface{}, lang str } // RenderResult renders the result template to instantiate the proxy -func (t *Template) RenderResult(renderMode int, other map[string]interface{}) ([]byte, map[string]interface{}, error) { +func (t *Template) RenderResult(renderMode int, other map[string]any) ([]byte, map[string]any, error) { values := t.Defaults(renderMode) - if err := util.DecodeOther(other, &values); err != nil { + if err := mergeMaps(other, values); err != nil { return nil, values, err }