Skip to content

Commit

Permalink
feat: add MergePathAndValueIntoMap (#49)
Browse files Browse the repository at this point in the history
## Description

- included a helper for generating a nested map.

## Related Issue
defenseunicorns/zarf#2403 (comment)

---------

Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com>
Co-authored-by: Lucas Rodriguez <lucas.rodriguez@defenseunicorns.com>
Co-authored-by: Lucas Rodriguez <lucas.rodriguez9616@gmail.com>
  • Loading branch information
3 people committed Mar 29, 2024
1 parent 1d46b2f commit 5fc0d64
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 0 deletions.
26 changes: 26 additions & 0 deletions helpers/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"math"
"reflect"
"regexp"
"strings"
"time"
)

Expand Down Expand Up @@ -146,3 +147,28 @@ func MergeNonZero[T any](original T, overrides T) T {
}
return originalValue.Elem().Interface().(T)
}

// MergePathAndValueIntoMap takes a path in dot notation as a string and a value (also as a string for simplicity),
// then merges this into the provided map. The value can be any type.
func MergePathAndValueIntoMap(m map[string]any, path string, value any) error {
pathParts := strings.Split(path, ".")
current := m
for i, part := range pathParts {
if i == len(pathParts)-1 {
// Set the value at the last key in the path.
current[part] = value
} else {
if _, exists := current[part]; !exists {
// If the part does not exist, create a new map for it.
current[part] = make(map[string]any)
}

nextMap, ok := current[part].(map[string]any)
if !ok {
return fmt.Errorf("conflict at %q, expected map but got %T", strings.Join(pathParts[:i+1], "."), current[part])
}
current = nextMap
}
}
return nil
}
87 changes: 87 additions & 0 deletions helpers/misc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package helpers

import (
"errors"
"reflect"
"strings"
"testing"

Expand Down Expand Up @@ -199,3 +200,89 @@ func (suite *TestMiscSuite) TestBoolPtr() {
func TestMisc(t *testing.T) {
suite.Run(t, new(TestMiscSuite))
}

func (suite *TestMiscSuite) TestMergePathAndValueIntoMap() {
type args struct {
m map[string]interface{}
path string
value interface{}
}
tests := []struct {
name string
args args
wantErr bool
want map[string]any
}{
{
name: "nested map creation",
args: args{m: make(map[string]interface{}), path: "a.b.c", value: "hello"},
wantErr: false,
want: map[string]any{
"a": map[string]any{
"b": map[string]any{
"c": "hello",
},
},
},
},
{
name: "overwrite existing value",
args: args{m: map[string]interface{}{"a": map[string]any{"b": map[string]any{"c": "initial"}}},
path: "a.b.c", value: "updated"},
wantErr: false,
want: map[string]any{
"a": map[string]any{
"b": map[string]any{
"c": "updated",
},
},
},
},
{
name: "deeply nested map creation",
args: args{m: make(map[string]interface{}), path: "a.b.c.d.e.f", value: 42},
wantErr: false,
want: map[string]any{
"a": map[string]any{
"b": map[string]any{
"c": map[string]any{
"d": map[string]any{
"e": map[string]any{
"f": 42,
},
},
},
},
},
},
},
{
name: "empty path",
args: args{m: make(map[string]interface{}), path: "", value: "root level"},
wantErr: false,
want: map[string]any{
"": "root level",
},
},
{
name: "root level value",
args: args{m: make(map[string]interface{}), path: "root", value: "root value"},
wantErr: false,
want: map[string]any{
"root": "root value",
},
},
}
for _, tt := range tests {
suite.Run(tt.name, func() {
err := MergePathAndValueIntoMap(tt.args.m, tt.args.path, tt.args.value)
if tt.wantErr {
suite.Error(err, "Expected an error")
} else {
suite.NoError(err, "Expected no error")
}

suite.True(reflect.DeepEqual(tt.args.m, tt.want), "Map structure mismatch: got %v, want %v", tt.args.m, tt.want)
})
}
}

0 comments on commit 5fc0d64

Please sign in to comment.