mirror of
https://github.com/aykhans/go-utils.git
synced 2025-10-15 18:25:57 +00:00
first commit
This commit is contained in:
38
maps/base.go
Normal file
38
maps/base.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package maps
|
||||
|
||||
import "maps"
|
||||
|
||||
// InitMap initializes a map pointer if it is nil.
|
||||
// If the map is already initialized, this function does nothing.
|
||||
//
|
||||
// This is useful for ensuring a map is ready to use before adding entries,
|
||||
// preventing nil pointer dereferences.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var m map[string]int
|
||||
// InitMap(&m)
|
||||
// m["key"] = 42 // safe to use
|
||||
func InitMap[K comparable, V any, T ~map[K]V](m *T) {
|
||||
if *m == nil {
|
||||
*m = make(T)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateMap merges entries from the new map into the old map.
|
||||
// If the old map is nil, it will be initialized first.
|
||||
// Existing keys in the old map will be overwritten with values from the new map.
|
||||
//
|
||||
// This function modifies the old map in place by copying all key-value pairs
|
||||
// from the new map into it.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// old := map[string]int{"a": 1, "b": 2}
|
||||
// new := map[string]int{"b": 3, "c": 4}
|
||||
// UpdateMap(&old, new)
|
||||
// // old is now: {"a": 1, "b": 3, "c": 4}
|
||||
func UpdateMap[K comparable, V any, T ~map[K]V](oldMap *T, newMap T) {
|
||||
InitMap(oldMap)
|
||||
maps.Copy(*oldMap, newMap)
|
||||
}
|
317
maps/base_test.go
Normal file
317
maps/base_test.go
Normal file
@@ -0,0 +1,317 @@
|
||||
package maps
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInitMap(t *testing.T) {
|
||||
t.Run("initializes nil map", func(t *testing.T) {
|
||||
var m map[string]int
|
||||
require.Nil(t, m)
|
||||
|
||||
InitMap(&m)
|
||||
|
||||
assert.NotNil(t, m)
|
||||
assert.Empty(t, m)
|
||||
assert.Empty(t, m)
|
||||
})
|
||||
|
||||
t.Run("does not reinitialize existing map", func(t *testing.T) {
|
||||
m := map[string]int{"key": 42}
|
||||
originalLen := len(m)
|
||||
|
||||
InitMap(&m)
|
||||
|
||||
assert.Len(t, m, originalLen)
|
||||
assert.Equal(t, 42, m["key"])
|
||||
})
|
||||
|
||||
t.Run("allows adding entries after initialization", func(t *testing.T) {
|
||||
var m map[string]int
|
||||
InitMap(&m)
|
||||
|
||||
m["test"] = 100
|
||||
assert.Equal(t, 100, m["test"])
|
||||
assert.Len(t, m, 1)
|
||||
})
|
||||
|
||||
t.Run("works with empty initialized map", func(t *testing.T) {
|
||||
m := make(map[string]int)
|
||||
InitMap(&m)
|
||||
|
||||
assert.NotNil(t, m)
|
||||
assert.Empty(t, m)
|
||||
})
|
||||
|
||||
t.Run("works with different key types", func(t *testing.T) {
|
||||
t.Run("int keys", func(t *testing.T) {
|
||||
var m map[int]string
|
||||
InitMap(&m)
|
||||
|
||||
assert.NotNil(t, m)
|
||||
m[1] = "one"
|
||||
assert.Equal(t, "one", m[1])
|
||||
})
|
||||
|
||||
t.Run("struct keys", func(t *testing.T) {
|
||||
type Key struct {
|
||||
ID int
|
||||
Name string
|
||||
}
|
||||
var m map[Key]bool
|
||||
InitMap(&m)
|
||||
|
||||
assert.NotNil(t, m)
|
||||
k := Key{ID: 1, Name: "test"}
|
||||
m[k] = true
|
||||
assert.True(t, m[k])
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("works with different value types", func(t *testing.T) {
|
||||
t.Run("string values", func(t *testing.T) {
|
||||
var m map[string]string
|
||||
InitMap(&m)
|
||||
|
||||
assert.NotNil(t, m)
|
||||
m["key"] = "value"
|
||||
assert.Equal(t, "value", m["key"])
|
||||
})
|
||||
|
||||
t.Run("struct values", func(t *testing.T) {
|
||||
type Value struct {
|
||||
Count int
|
||||
Name string
|
||||
}
|
||||
var m map[string]Value
|
||||
InitMap(&m)
|
||||
|
||||
assert.NotNil(t, m)
|
||||
m["test"] = Value{Count: 5, Name: "foo"}
|
||||
assert.Equal(t, 5, m["test"].Count)
|
||||
})
|
||||
|
||||
t.Run("slice values", func(t *testing.T) {
|
||||
var m map[string][]int
|
||||
InitMap(&m)
|
||||
|
||||
assert.NotNil(t, m)
|
||||
m["numbers"] = []int{1, 2, 3}
|
||||
assert.Equal(t, []int{1, 2, 3}, m["numbers"])
|
||||
})
|
||||
|
||||
t.Run("pointer values", func(t *testing.T) {
|
||||
var m map[string]*int
|
||||
InitMap(&m)
|
||||
|
||||
assert.NotNil(t, m)
|
||||
val := 42
|
||||
m["ptr"] = &val
|
||||
assert.Equal(t, 42, *m["ptr"])
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("works with custom map types", func(t *testing.T) {
|
||||
type CustomMap map[string]int
|
||||
var m CustomMap
|
||||
InitMap(&m)
|
||||
|
||||
assert.NotNil(t, m)
|
||||
m["custom"] = 99
|
||||
assert.Equal(t, 99, m["custom"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateMap(t *testing.T) {
|
||||
t.Run("merges new entries into existing map", func(t *testing.T) {
|
||||
oldMap := map[string]int{"a": 1, "b": 2}
|
||||
newMap := map[string]int{"c": 3, "d": 4}
|
||||
|
||||
UpdateMap(&oldMap, newMap)
|
||||
|
||||
assert.Len(t, oldMap, 4)
|
||||
assert.Equal(t, 1, oldMap["a"])
|
||||
assert.Equal(t, 2, oldMap["b"])
|
||||
assert.Equal(t, 3, oldMap["c"])
|
||||
assert.Equal(t, 4, oldMap["d"])
|
||||
})
|
||||
|
||||
t.Run("overwrites existing keys", func(t *testing.T) {
|
||||
oldMap := map[string]int{"a": 1, "b": 2}
|
||||
newMap := map[string]int{"b": 3, "c": 4}
|
||||
|
||||
UpdateMap(&oldMap, newMap)
|
||||
|
||||
assert.Len(t, oldMap, 3)
|
||||
assert.Equal(t, 1, oldMap["a"])
|
||||
assert.Equal(t, 3, oldMap["b"], "existing key 'b' should be overwritten")
|
||||
assert.Equal(t, 4, oldMap["c"])
|
||||
})
|
||||
|
||||
t.Run("initializes nil map before merging", func(t *testing.T) {
|
||||
var oldMap map[string]int
|
||||
newMap := map[string]int{"a": 1, "b": 2}
|
||||
|
||||
require.Nil(t, oldMap)
|
||||
UpdateMap(&oldMap, newMap)
|
||||
|
||||
assert.NotNil(t, oldMap)
|
||||
assert.Len(t, oldMap, 2)
|
||||
assert.Equal(t, 1, oldMap["a"])
|
||||
assert.Equal(t, 2, oldMap["b"])
|
||||
})
|
||||
|
||||
t.Run("handles empty new map", func(t *testing.T) {
|
||||
oldMap := map[string]int{"a": 1}
|
||||
newMap := map[string]int{}
|
||||
|
||||
UpdateMap(&oldMap, newMap)
|
||||
|
||||
assert.Len(t, oldMap, 1)
|
||||
assert.Equal(t, 1, oldMap["a"])
|
||||
})
|
||||
|
||||
t.Run("handles empty old map", func(t *testing.T) {
|
||||
oldMap := map[string]int{}
|
||||
newMap := map[string]int{"a": 1, "b": 2}
|
||||
|
||||
UpdateMap(&oldMap, newMap)
|
||||
|
||||
assert.Len(t, oldMap, 2)
|
||||
assert.Equal(t, 1, oldMap["a"])
|
||||
assert.Equal(t, 2, oldMap["b"])
|
||||
})
|
||||
|
||||
t.Run("handles both empty maps", func(t *testing.T) {
|
||||
oldMap := map[string]int{}
|
||||
newMap := map[string]int{}
|
||||
|
||||
UpdateMap(&oldMap, newMap)
|
||||
|
||||
assert.NotNil(t, oldMap)
|
||||
assert.Empty(t, oldMap)
|
||||
})
|
||||
|
||||
t.Run("works with different types", func(t *testing.T) {
|
||||
t.Run("string values", func(t *testing.T) {
|
||||
oldMap := map[string]string{"key1": "value1"}
|
||||
newMap := map[string]string{"key2": "value2"}
|
||||
|
||||
UpdateMap(&oldMap, newMap)
|
||||
|
||||
assert.Len(t, oldMap, 2)
|
||||
assert.Equal(t, "value1", oldMap["key1"])
|
||||
assert.Equal(t, "value2", oldMap["key2"])
|
||||
})
|
||||
|
||||
t.Run("struct values", func(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
oldMap := map[string]Person{
|
||||
"john": {Name: "John", Age: 30},
|
||||
}
|
||||
newMap := map[string]Person{
|
||||
"jane": {Name: "Jane", Age: 25},
|
||||
}
|
||||
|
||||
UpdateMap(&oldMap, newMap)
|
||||
|
||||
assert.Len(t, oldMap, 2)
|
||||
assert.Equal(t, "John", oldMap["john"].Name)
|
||||
assert.Equal(t, "Jane", oldMap["jane"].Name)
|
||||
})
|
||||
|
||||
t.Run("int keys", func(t *testing.T) {
|
||||
oldMap := map[int]string{1: "one"}
|
||||
newMap := map[int]string{2: "two"}
|
||||
|
||||
UpdateMap(&oldMap, newMap)
|
||||
|
||||
assert.Len(t, oldMap, 2)
|
||||
assert.Equal(t, "one", oldMap[1])
|
||||
assert.Equal(t, "two", oldMap[2])
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("modifies map in place", func(t *testing.T) {
|
||||
oldMap := map[string]int{"a": 1}
|
||||
originalMap := oldMap
|
||||
newMap := map[string]int{"b": 2}
|
||||
|
||||
UpdateMap(&oldMap, newMap)
|
||||
|
||||
// Verify it's the same map (modified in place)
|
||||
assert.Equal(t, 2, oldMap["b"])
|
||||
// Note: originalMap is pointing to the same underlying map
|
||||
// so it should also have the new value
|
||||
assert.Equal(t, 2, originalMap["b"])
|
||||
})
|
||||
|
||||
t.Run("does not modify new map", func(t *testing.T) {
|
||||
oldMap := map[string]int{"a": 1}
|
||||
newMap := map[string]int{"b": 2, "c": 3}
|
||||
newLen := len(newMap)
|
||||
|
||||
UpdateMap(&oldMap, newMap)
|
||||
|
||||
assert.Len(t, newMap, newLen, "new map should not be modified")
|
||||
assert.Equal(t, 2, newMap["b"])
|
||||
assert.Equal(t, 3, newMap["c"])
|
||||
_, hasA := newMap["a"]
|
||||
assert.False(t, hasA, "new map should not contain keys from old map")
|
||||
})
|
||||
|
||||
t.Run("works with custom map types", func(t *testing.T) {
|
||||
type CustomMap map[string]int
|
||||
oldMap := CustomMap{"a": 1}
|
||||
newMap := CustomMap{"b": 2}
|
||||
|
||||
UpdateMap(&oldMap, newMap)
|
||||
|
||||
assert.Len(t, oldMap, 2)
|
||||
assert.Equal(t, 1, oldMap["a"])
|
||||
assert.Equal(t, 2, oldMap["b"])
|
||||
})
|
||||
|
||||
t.Run("overwrites with zero values", func(t *testing.T) {
|
||||
oldMap := map[string]int{"a": 10, "b": 20}
|
||||
newMap := map[string]int{"a": 0}
|
||||
|
||||
UpdateMap(&oldMap, newMap)
|
||||
|
||||
assert.Len(t, oldMap, 2)
|
||||
assert.Equal(t, 0, oldMap["a"], "should overwrite with zero value")
|
||||
assert.Equal(t, 20, oldMap["b"])
|
||||
})
|
||||
|
||||
t.Run("complex merge scenario", func(t *testing.T) {
|
||||
oldMap := map[string]int{
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": 3,
|
||||
}
|
||||
newMap := map[string]int{
|
||||
"b": 20, // overwrite
|
||||
"c": 30, // overwrite
|
||||
"d": 40, // new
|
||||
"e": 50, // new
|
||||
}
|
||||
|
||||
UpdateMap(&oldMap, newMap)
|
||||
|
||||
expected := map[string]int{
|
||||
"a": 1, // preserved
|
||||
"b": 20, // overwritten
|
||||
"c": 30, // overwritten
|
||||
"d": 40, // new
|
||||
"e": 50, // new
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, oldMap)
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user