Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion map.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,21 @@ func toStringMapE[T any](i any, fn func(any) T) (map[string]T, error) {

// ToStringMapStringE casts any value to a map[string]string type.
func ToStringMapStringE(i any) (map[string]string, error) {
return toStringMapE(i, ToString)
var firstErr error
toString := func(value any) string {
result, err := ToStringE(value)
if firstErr == nil {
firstErr = err
}
return result
}

result, err := toMapE(i, toString, toString)
if err != nil {
return result, err
}

return result, firstErr
}

// ToStringMapStringSliceE casts any value to a map[string][]string type.
Expand Down
124 changes: 124 additions & 0 deletions map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,3 +288,127 @@ func TestStringMapString(t *testing.T) {

runMapTests(t, testCases, cast.ToStringMapString, cast.ToStringMapStringE)
}

func TestStringMapStringConversionErrors(t *testing.T) {
type namedMap map[string]any

testCases := []struct {
name string
input any
expected map[string]string
expectError bool
}{
{
name: "string keys with multiple invalid values",
input: map[string]any{"slice": []any{1}, "struct": struct{}{}, "number": 123, "nil": nil},
expected: map[string]string{"slice": "", "struct": "", "number": "123", "nil": ""},
expectError: true,
},
{
name: "interface keys with multiple invalid values",
input: map[any]any{"slice": []any{1}, "struct": struct{}{}, 123: true, "nil": nil},
expected: map[string]string{"slice": "", "struct": "", "123": "true", "nil": ""},
expectError: true,
},
{
name: "invalid interface key with string values",
input: map[any]string{struct{}{}: "value", "good": "kept"},
expected: map[string]string{"": "value", "good": "kept"},
expectError: true,
},
{
name: "invalid interface key and value",
input: map[any]any{struct{}{}: "value", "bad": []any{1}, "good": 123},
expected: map[string]string{"": "value", "bad": "", "good": "123"},
expectError: true,
},
{
name: "successful scalars",
input: map[string]any{"string": "value", "bool": true, "int": 123, "float": 1.5, "bytes": []byte("bytes"), "nil": nil},
expected: map[string]string{"string": "value", "bool": "true", "int": "123", "float": "1.5", "bytes": "bytes", "nil": ""},
},
{
name: "string map",
input: map[string]string{"key": "value"},
expected: map[string]string{"key": "value"},
},
{
name: "nil string map",
input: map[string]string(nil),
},
{
name: "nil interface map",
input: map[string]any(nil),
expected: map[string]string{},
},
{
name: "JSON string",
input: `{"key":"value"}`,
expected: map[string]string{"key": "value"},
},
{
name: "JSON null",
input: "null",
},
{
name: "nil input",
expected: map[string]string{},
expectError: true,
},
{
name: "invalid input",
input: struct{}{},
expected: map[string]string{},
expectError: true,
},
{
name: "invalid JSON",
input: `{"key":"value"`,
expected: map[string]string{},
expectError: true,
},
{
name: "unsupported named map",
input: namedMap{"key": "value"},
expected: map[string]string{},
expectError: true,
},
}

for _, tc := range testCases {
tc := tc

t.Run(tc.name, func(t *testing.T) {
t.Parallel()

c := qt.New(t)
result, err := cast.ToStringMapStringE(tc.input)
c.Assert(result, qt.DeepEquals, tc.expected)
c.Assert(cast.ToStringMapString(tc.input), qt.DeepEquals, tc.expected)
c.Assert(err != nil, qt.Equals, tc.expectError)
})
}
}

type countingMapStringer struct {
value string
calls int
}

func (s *countingMapStringer) String() string {
s.calls++
return s.value
}

func TestStringMapStringConvertsOnce(t *testing.T) {
c := qt.New(t)
key := &countingMapStringer{value: "key"}
value := &countingMapStringer{value: "value"}
input := map[any]any{key: value, "invalid": []any{1}, "good": "kept"}

result, err := cast.ToStringMapStringE(input)
c.Assert(result, qt.DeepEquals, map[string]string{"key": "value", "invalid": "", "good": "kept"})
c.Assert(err, qt.IsNotNil)
c.Assert(key.calls, qt.Equals, 1)
c.Assert(value.calls, qt.Equals, 1)
}