Skip to content

Commit fb2babd

Browse files
authored
[collection] Add utilities to serialising maps into comma separated strings (#616)
<!-- Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved. SPDX-License-Identifier: Apache-2.0 --> ### Description Added a few utilities to convert maps into slices or strings so that they can be serialised easily ### Test Coverage <!-- Please put an `x` in the correct box e.g. `[x]` to indicate the testing coverage of this change. --> - [x] This change is covered by existing or additional automated tests. - [ ] Manual testing has been performed (and evidence provided) as automated testing was not feasible. - [ ] Additional tests are not required for this change (e.g. documentation update).
1 parent d9731ae commit fb2babd

File tree

3 files changed

+176
-32
lines changed

3 files changed

+176
-32
lines changed

changes/20250516124618.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:sparkles: `[collection]` Add utilities to serialising maps into comma separated strings

utils/collection/parseLists.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package collection
66

77
import (
8+
"fmt"
89
"strings"
910
"unicode"
1011

@@ -73,3 +74,73 @@ func ParseCommaSeparatedListToMap(input string) (pairs map[string]string, err er
7374

7475
return
7576
}
77+
78+
// ParseCommaSeparatedListOfPairsToMap returns a map of key value pairs from a string containing a comma separated list of pairs using pairSeparator to separate between keys and values e.g. key1=value1,key2=value2
79+
func ParseCommaSeparatedListOfPairsToMap(input, pairSeparator string) (pairs map[string]string, err error) {
80+
if pairSeparator == "," {
81+
pairs, err = ParseCommaSeparatedListToMap(input)
82+
return
83+
}
84+
inputSplit := ParseCommaSeparatedList(input)
85+
pairs = make(map[string]string, len(inputSplit))
86+
for i := range inputSplit {
87+
pair := ParseListWithCleanup(inputSplit[i], pairSeparator)
88+
switch len(pair) {
89+
case 0:
90+
continue
91+
case 2:
92+
pairs[pair[0]] = pair[1]
93+
default:
94+
err = commonerrors.Newf(commonerrors.ErrInvalid, "could not parse key value pair '%v'", inputSplit[i])
95+
return
96+
}
97+
}
98+
return
99+
}
100+
101+
// ConvertSliceToCommaSeparatedList converts a slice into a string containing a coma separated list
102+
func ConvertSliceToCommaSeparatedList[T any](slice []T) string {
103+
if len(slice) == 0 {
104+
return ""
105+
}
106+
sliceOfStrings := make([]string, 0, len(slice))
107+
for i := range slice {
108+
sliceOfStrings = append(sliceOfStrings, fmt.Sprintf("%v", slice[i]))
109+
}
110+
111+
return strings.Join(sliceOfStrings, ",")
112+
}
113+
114+
// ConvertMapToSlice converts a map to list of keys and values listed sequentially e.g. [key1, value1, key2, value2]
115+
func ConvertMapToSlice[K comparable, V any](pairs map[K]V) []string {
116+
if len(pairs) == 0 {
117+
return nil
118+
}
119+
slice := make([]string, 0, len(pairs)*2)
120+
for key, value := range pairs {
121+
slice = append(slice, fmt.Sprintf("%v", key), fmt.Sprintf("%v", value))
122+
}
123+
return slice
124+
}
125+
126+
// ConvertMapToPairSlice converts a map to list of key value pairs e.g. ["key1=value1", "key2=value2"]
127+
func ConvertMapToPairSlice[K comparable, V any](pairs map[K]V, pairSeparator string) []string {
128+
if len(pairs) == 0 {
129+
return nil
130+
}
131+
slice := make([]string, 0, len(pairs)*2)
132+
for key, value := range pairs {
133+
slice = append(slice, fmt.Sprintf("%v%v%v", key, pairSeparator, value))
134+
}
135+
return slice
136+
}
137+
138+
// ConvertMapToCommaSeparatedList converts a map to a string of comma separated list of keys and values defined sequentially
139+
func ConvertMapToCommaSeparatedList[K comparable, V any](pairs map[K]V) string {
140+
return ConvertSliceToCommaSeparatedList[string](ConvertMapToSlice[K, V](pairs))
141+
}
142+
143+
// ConvertMapToCommaSeparatedPairsList converts a map to a string of comma separated list of key, value pairs.
144+
func ConvertMapToCommaSeparatedPairsList[K comparable, V any](pairs map[K]V, pairSeparator string) string {
145+
return ConvertSliceToCommaSeparatedList[string](ConvertMapToPairSlice[K, V](pairs, pairSeparator))
146+
}

utils/collection/parseLists_test.go

Lines changed: 104 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
package collection
66

77
import (
8+
"maps"
89
"math/rand"
910
"testing"
1011
"time"
1112

1213
"github.com/go-faker/faker/v4"
1314
"github.com/stretchr/testify/assert"
1415
"github.com/stretchr/testify/require"
15-
"golang.org/x/exp/maps"
1616

1717
"github.com/ARM-software/golang-utils/utils/commonerrors"
1818
"github.com/ARM-software/golang-utils/utils/commonerrors/errortest"
@@ -23,47 +23,59 @@ var (
2323
)
2424

2525
func TestParseCommaSeparatedListWordsOnly(t *testing.T) {
26-
stringList := ""
27-
stringArray := []string{}
28-
// we don't need cryptographically secure random numbers for generating a number of elements in a list
29-
lengthOfList := random.Intn(10) //nolint:gosec
30-
for i := 0; i < lengthOfList; i++ {
31-
word := faker.Word()
32-
stringList += word
33-
stringArray = append(stringArray, word)
34-
numSpacesToAdd := random.Intn(5) //nolint:gosec
35-
for j := 0; j < numSpacesToAdd; j++ {
36-
stringList += " "
26+
t.Run("simple", func(t *testing.T) {
27+
stringArray := []string{faker.Word(), faker.Word(), faker.Word(), faker.UUIDDigit(), faker.URL(), faker.Username(), faker.DomainName()}
28+
require.Equal(t, stringArray, ParseCommaSeparatedList(ConvertSliceToCommaSeparatedList[string](stringArray)))
29+
})
30+
t.Run("with whitespaces", func(t *testing.T) {
31+
stringList := ""
32+
var stringArray []string
33+
// we don't need cryptographically secure random numbers for generating a number of elements in a list
34+
lengthOfList := random.Intn(10) //nolint:gosec
35+
for i := 0; i < lengthOfList; i++ {
36+
word := faker.Word()
37+
stringList += word
38+
stringArray = append(stringArray, word)
39+
numSpacesToAdd := random.Intn(5) //nolint:gosec
40+
for j := 0; j < numSpacesToAdd; j++ {
41+
stringList += " "
42+
}
43+
stringList += ","
3744
}
38-
stringList += ","
39-
}
40-
finalList := ParseCommaSeparatedList(stringList)
41-
require.Equal(t, stringArray, finalList)
45+
finalList := ParseCommaSeparatedList(stringList)
46+
require.Equal(t, stringArray, finalList)
47+
})
4248
}
4349

4450
// Test to make sure that spaces that show up within the words aren't removed
4551
func TestParseCommaSeparatedListWithSpacesBetweenWords(t *testing.T) {
46-
stringList := ""
47-
stringArray := []string{}
48-
// we don't need cryptographically secure random numbers for generating a number of elements in a list
49-
lengthOfList := random.Intn(10) //nolint:gosec
50-
for i := 0; i < lengthOfList; i++ {
51-
word := faker.Sentence()
52-
stringList += word
53-
stringArray = append(stringArray, word)
54-
numSpacesToAdd := random.Intn(5) //nolint:gosec
55-
for j := 0; j < numSpacesToAdd; j++ {
56-
stringList += " "
52+
t.Run("simple", func(t *testing.T) {
53+
stringArray := []string{faker.Paragraph(), faker.Word(), faker.Sentence(), faker.UUIDDigit(), faker.Name(), faker.Username(), faker.DomainName()}
54+
require.Equal(t, stringArray, ParseCommaSeparatedList(ConvertSliceToCommaSeparatedList[string](stringArray)))
55+
})
56+
t.Run("with whitespaces", func(t *testing.T) {
57+
stringList := ""
58+
var stringArray []string
59+
// we don't need cryptographically secure random numbers for generating a number of elements in a list
60+
lengthOfList := random.Intn(10) //nolint:gosec
61+
for i := 0; i < lengthOfList; i++ {
62+
word := faker.Sentence()
63+
stringList += word
64+
stringArray = append(stringArray, word)
65+
numSpacesToAdd := random.Intn(5) //nolint:gosec
66+
for j := 0; j < numSpacesToAdd; j++ {
67+
stringList += " "
68+
}
69+
stringList += ","
5770
}
58-
stringList += ","
59-
}
60-
finalList := ParseCommaSeparatedList(stringList)
61-
require.Equal(t, stringArray, finalList)
71+
finalList := ParseCommaSeparatedList(stringList)
72+
require.Equal(t, stringArray, finalList)
73+
})
6274
}
6375

6476
func TestParseCommaSeparatedListWithSpacesBetweenWordsKeepBlanks(t *testing.T) {
6577
stringList := ""
66-
stringArray := []string{}
78+
var stringArray []string
6779
// we don't need cryptographically secure random numbers for generating a number of elements in a list
6880
lengthOfList := random.Intn(10) + 8 //nolint:gosec
6981
for i := 0; i < lengthOfList; i++ {
@@ -94,6 +106,17 @@ func TestParseCommaSeparatedListWithSpacesBetweenWordsKeepBlanks(t *testing.T) {
94106
}
95107

96108
func TestParseCommaSeparatedListToMap(t *testing.T) {
109+
randomMap := map[string]string{
110+
faker.Sentence(): faker.Sentence(),
111+
faker.Word(): faker.Paragraph(),
112+
faker.Name(): faker.Sentence(),
113+
faker.Sentence(): faker.Sentence(),
114+
faker.Word(): faker.Paragraph(),
115+
faker.Name(): faker.Sentence(),
116+
faker.Sentence(): faker.Sentence(),
117+
faker.Word(): faker.Paragraph(),
118+
faker.Name(): faker.Sentence(),
119+
}
97120
for _, test := range []struct {
98121
Name string
99122
Input string
@@ -111,6 +134,7 @@ func TestParseCommaSeparatedListToMap(t *testing.T) {
111134
{"Normal 8", ",", map[string]string{}, nil},
112135
{"Normal 9", ",,,,,", map[string]string{}, nil},
113136
{"Normal 10", ",, ,, ,", map[string]string{}, nil},
137+
{"Normal 11", ConvertMapToCommaSeparatedList[string, string](randomMap), randomMap, nil},
114138
{"Bad 1", "one", nil, commonerrors.ErrInvalid},
115139
{"Bad 1", "one, two, three", nil, commonerrors.ErrInvalid},
116140
{"Bad 2", "one element with spaces", nil, commonerrors.ErrInvalid},
@@ -125,3 +149,51 @@ func TestParseCommaSeparatedListToMap(t *testing.T) {
125149
})
126150
}
127151
}
152+
153+
func TestParseCommaSeparatedPairListToMap(t *testing.T) {
154+
randomMap := map[string]string{
155+
faker.Sentence(): faker.Sentence(),
156+
faker.Word(): faker.Paragraph(),
157+
faker.Name(): faker.Sentence(),
158+
faker.Sentence(): faker.Sentence(),
159+
faker.Word(): faker.Paragraph(),
160+
faker.Name(): faker.Sentence(),
161+
faker.Sentence(): faker.Sentence(),
162+
faker.Word(): faker.Paragraph(),
163+
faker.Name(): faker.Sentence(),
164+
}
165+
for _, test := range []struct {
166+
Name string
167+
Input string
168+
Expected map[string]string
169+
Err error
170+
PairSeparator string
171+
}{
172+
{"Normal 1", "hello=world", map[string]string{"hello": "world"}, nil, "="},
173+
{"Normal 2", "hello+world,adrien+cabarbaye", map[string]string{"hello": "world", "adrien": "cabarbaye"}, nil, "+"},
174+
{"Normal 2", "hello, world, adrien, cabarbaye", map[string]string{"hello": "world", "adrien": "cabarbaye"}, nil, ","},
175+
{"Normal 2.5", "hello= world, adrien = cabarbaye", map[string]string{"hello": "world", "adrien": "cabarbaye"}, nil, "="},
176+
{"Normal 3", "hello&world,adrien&cabarbaye,", map[string]string{"hello": "world", "adrien": "cabarbaye"}, nil, "&"},
177+
{"Normal 4", "hello%%world,,,,adrien%%cabarbaye,,,", map[string]string{"hello": "world", "adrien": "cabarbaye"}, nil, "%%"},
178+
{"Normal 5", "hello$$$world,this$$$value has spaces", map[string]string{"hello": "world", "this": "value has spaces"}, nil, "$$$"},
179+
{"Normal 6", "hello__world,,,this__value has spaces,,,", map[string]string{"hello": "world", "this": "value has spaces"}, nil, "__"},
180+
{"Normal 7", "", map[string]string{}, nil, "+"},
181+
{"Normal 8", ",", map[string]string{}, nil, "+"},
182+
{"Normal 9", ",,,,,", map[string]string{}, nil, "^"},
183+
{"Normal 10", ",, ,, ,", map[string]string{}, nil, "+"},
184+
{"Normal 11", ConvertMapToCommaSeparatedPairsList[string, string](randomMap, "/"), randomMap, nil, "/"},
185+
{"Normal 12", ConvertMapToCommaSeparatedPairsList[string, string](randomMap, " "), randomMap, nil, " "},
186+
{"Bad 1", "one", nil, commonerrors.ErrInvalid, "+"},
187+
{"Bad 1", "one, two, three", nil, commonerrors.ErrInvalid, "+"},
188+
{"Bad 2", "one element with spaces", nil, commonerrors.ErrInvalid, "+"},
189+
{"Bad 3", "one element with spaces and end comma,", nil, commonerrors.ErrInvalid, "+"},
190+
{"Bad 4", "one element with spaces and multiple end commas,,,", nil, commonerrors.ErrInvalid, "+"},
191+
{"Bad 5", ",,,one element with spaces and multiple end/beginning commas,,,", nil, commonerrors.ErrInvalid, "="},
192+
} {
193+
t.Run(test.Name, func(t *testing.T) {
194+
pairs, err := ParseCommaSeparatedListOfPairsToMap(test.Input, test.PairSeparator)
195+
errortest.AssertError(t, err, test.Err)
196+
assert.True(t, maps.Equal(test.Expected, pairs))
197+
})
198+
}
199+
}

0 commit comments

Comments
 (0)