From 7932a4b7e7944921c350985c69e112bd790cbc2b Mon Sep 17 00:00:00 2001 From: Johannes Aubart Date: Tue, 29 Jul 2025 16:41:39 +0200 Subject: [PATCH] add map containment utility functions --- pkg/collections/maps/utils.go | 32 ++++++++++++++ pkg/collections/maps/utils_test.go | 67 ++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/pkg/collections/maps/utils.go b/pkg/collections/maps/utils.go index 77a5153..acb6b4e 100644 --- a/pkg/collections/maps/utils.go +++ b/pkg/collections/maps/utils.go @@ -1,6 +1,8 @@ package maps import ( + "reflect" + "k8s.io/utils/ptr" "github.com/openmcp-project/controller-utils/pkg/collections/filters" @@ -56,6 +58,36 @@ func Intersect[K comparable, V any](source map[K]V, maps ...map[K]V) map[K]V { return res } +// ContainsKeysWithValues checks if 'super' is a superset of 'sub', meaning that all keys of 'sub' are also present in 'super' with the same values. +// Uses reflect.DeepEqual to compare the values, use ContainsMapFunc if you want to use a custom equality function. +func ContainsKeysWithValues[K comparable, V any](super, sub map[K]V) bool { + return ContainsKeysWithValuesFunc(super, sub, func(a, b V) bool { + return reflect.DeepEqual(a, b) + }) +} + +// ContainsKeysWithValuesFunc checks if 'super' is a superset of 'sub', meaning that all keys of 'sub' are also present in 'super' with the same values. +// The values are compared using the provided equality function. +// If the equality function returns false for any key-value pair, it returns false. +func ContainsKeysWithValuesFunc[K comparable, V any](super, sub map[K]V, equal func(a, b V) bool) bool { + for k, bv := range sub { + if av, ok := super[k]; !ok || !equal(av, bv) { + return false + } + } + return true +} + +// ContainsKeys returns true if all given keys are present in the map. +func ContainsKeys[K comparable, V any](super map[K]V, keys ...K) bool { + for _, k := range keys { + if _, ok := super[k]; !ok { + return false + } + } + return true +} + // GetAny returns an arbitrary key-value pair from the map as a pointer to a pairs.Pair. // If the map is empty, it returns nil. func GetAny[K comparable, V any](m map[K]V) *pairs.Pair[K, V] { diff --git a/pkg/collections/maps/utils_test.go b/pkg/collections/maps/utils_test.go index f27efbb..3bcc9c7 100644 --- a/pkg/collections/maps/utils_test.go +++ b/pkg/collections/maps/utils_test.go @@ -78,4 +78,71 @@ var _ = Describe("Map Utils Tests", func() { }) + Context("ContainsKeysWithValues", func() { + + It("should return true if the second map is empty or nil", func() { + m1 := map[string]string{"foo": "bar", "bar": "baz"} + Expect(maps.ContainsKeysWithValues(m1, nil)).To(BeTrue()) + Expect(maps.ContainsKeysWithValues(m1, map[string]string{})).To(BeTrue()) + Expect(maps.ContainsKeysWithValues[string, string](nil, nil)).To(BeTrue()) + }) + + It("should return true if both maps are identical", func() { + m1 := map[string]string{"foo": "bar", "bar": "baz"} + Expect(maps.ContainsKeysWithValues(m1, m1)).To(BeTrue()) + }) + + It("should return true if the first map contains all keys and values of the second map", func() { + m1 := map[string]string{"foo": "bar", "bar": "baz", "baz": "asdf"} + m2 := map[string]string{"foo": "bar", "baz": "asdf"} + Expect(maps.ContainsKeysWithValues(m1, m2)).To(BeTrue()) + + m3 := map[string]string{"bar": "baz"} + Expect(maps.ContainsKeysWithValues(m1, m3)).To(BeTrue()) + }) + + It("should return false if the first map contains all keys but has different values", func() { + m1 := map[string]string{"foo": "bar", "bar": "baz"} + m2 := map[string]string{"foo": "baz", "bar": "baz"} + Expect(maps.ContainsKeysWithValues(m1, m2)).To(BeFalse()) + }) + + It("should return false if the first map does not contain all keys of the second map", func() { + m1 := map[string]string{"foo": "bar"} + m2 := map[string]string{"foo": "bar", "bar": "baz"} + Expect(maps.ContainsKeysWithValues(m1, m2)).To(BeFalse()) + }) + + It("should work with a custom equality function", func() { + m1 := map[string]string{"foo": "bar", "bar": "baz"} + m2 := map[string]string{"foo": "xyz", "bar": "mno"} + cmp := func(a, b string) bool { + return len(a) == len(b) + } + Expect(maps.ContainsKeysWithValuesFunc(m1, m2, cmp)).To(BeTrue()) + }) + + }) + + Context("ContainsKeys", func() { + + It("should return true if all keys are present", func() { + m1 := map[string]string{"foo": "bar", "bar": "baz", "baz": "asdf"} + Expect(maps.ContainsKeys(m1, "foo", "bar")).To(BeTrue()) + }) + + It("should return true if no keys are provided", func() { + m1 := map[string]string{"foo": "bar", "bar": "baz", "baz": "asdf"} + Expect(maps.ContainsKeys(m1)).To(BeTrue()) + Expect(maps.ContainsKeys[string, string](nil)).To(BeTrue()) + }) + + It("should return false if any key is missing", func() { + m1 := map[string]string{"foo": "bar", "bar": "baz", "baz": "asdf"} + Expect(maps.ContainsKeys(m1, "foo", "missing")).To(BeFalse()) + Expect(maps.ContainsKeys(m1, "missing")).To(BeFalse()) + }) + + }) + })