diff --git a/changes/20251015131907.feature b/changes/20251015131907.feature new file mode 100644 index 0000000000..a895160321 --- /dev/null +++ b/changes/20251015131907.feature @@ -0,0 +1 @@ +:sparkles: `collection` Add functions for converting maps to slices in a stable/ordered way diff --git a/utils/collection/parseLists.go b/utils/collection/parseLists.go index 9c3b47b959..24e74ed80c 100644 --- a/utils/collection/parseLists.go +++ b/utils/collection/parseLists.go @@ -5,7 +5,9 @@ package collection import ( + "cmp" "fmt" + "maps" "slices" "strings" "unicode" @@ -170,3 +172,20 @@ func ConvertMapToCommaSeparatedList[K comparable, V any](pairs map[K]V) string { func ConvertMapToCommaSeparatedPairsList[K comparable, V any](pairs map[K]V, pairSeparator string) string { return ConvertSliceToCommaSeparatedList[string](ConvertMapToPairSlice[K, V](pairs, pairSeparator)) } + +// ConvertMapToOrderedSlice converts a map to a slice and ensures that the ordering will always be the same +func ConvertMapToOrderedSlice[K cmp.Ordered, V any](pairs map[K]V) []string { + if len(pairs) == 0 { + return nil + } + slice := make([]string, 0, len(pairs)*2) + for _, key := range slices.Sorted(maps.Keys(pairs)) { + slice = append(slice, fmt.Sprintf("%v", key), fmt.Sprintf("%v", pairs[key])) + } + return slice +} + +// ConvertMapToOrderedCommaSeparatedList converts a map to a commar-separated list and ensures that the ordering will always be the same +func ConvertMapToOrderedCommaSeparatedList[K cmp.Ordered, V any](pairs map[K]V) string { + return ConvertSliceToCommaSeparatedList[string](ConvertMapToOrderedSlice[K, V](pairs)) +} diff --git a/utils/collection/parseLists_test.go b/utils/collection/parseLists_test.go index 7e78d990de..17b3eee64a 100644 --- a/utils/collection/parseLists_test.go +++ b/utils/collection/parseLists_test.go @@ -181,6 +181,7 @@ func TestParseCommaSeparatedPairListToMap(t *testing.T) { {"Normal 10", ",, ,, ,", map[string]string{}, nil, "+"}, {"Normal 11", ConvertMapToCommaSeparatedPairsList[string, string](randomMap, "/"), randomMap, nil, "/"}, {"Normal 12", ConvertMapToCommaSeparatedPairsList[string, string](randomMap, " "), randomMap, nil, " "}, + {"Normal 13", ConvertMapToOrderedCommaSeparatedList[string, string](randomMap), randomMap, nil, ","}, {"Bad 1", "one", nil, commonerrors.ErrInvalid, "+"}, {"Bad 1", "one, two, three", nil, commonerrors.ErrInvalid, "+"}, {"Bad 2", "one element with spaces", nil, commonerrors.ErrInvalid, "+"}, @@ -195,3 +196,11 @@ func TestParseCommaSeparatedPairListToMap(t *testing.T) { }) } } + +func TestConvertMapToCommaSeparatedListStable(t *testing.T) { + testMap := map[string]string{"hello": "world", "adrien": "cabarbaye", "fish": "cake", "apple": "pie"} + expected := "adrien,cabarbaye,apple,pie,fish,cake,hello,world" + for range 100 { + assert.Equal(t, expected, ConvertMapToOrderedCommaSeparatedList(testMap)) + } +}