Skip to content

Commit 70e4c86

Browse files
gustavsalexejk
authored andcommitted
encoder: Support for encoding maps into a struct value
1 parent f7a114d commit 70e4c86

File tree

2 files changed

+127
-1
lines changed

2 files changed

+127
-1
lines changed

encode.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ func (e *StdEncoder) encodeValue(w io.Writer, value interface{}) error {
130130
}
131131
}
132132

133+
case reflect.Map:
134+
if err := e.encodeMap(w, value); err != nil {
135+
return fmt.Errorf("cannot encode map value: %w", err)
136+
}
137+
133138
default:
134139
return fmt.Errorf("unsupported type %v", kind)
135140
}
@@ -225,3 +230,29 @@ func (e *StdEncoder) encodeTime(w io.Writer, val time.Time) error {
225230
_, err := fmt.Fprintf(w, "<dateTime.iso8601>%s</dateTime.iso8601>", val.Format(time.RFC3339))
226231
return err
227232
}
233+
234+
func (e *StdEncoder) encodeMap(w io.Writer, val interface{}) error {
235+
_, _ = fmt.Fprint(w, "<struct>")
236+
237+
mapValue := reflect.ValueOf(val)
238+
iter := mapValue.MapRange()
239+
240+
for iter.Next() {
241+
key := iter.Key()
242+
value := iter.Value()
243+
244+
// Convert key to string
245+
keyStr := fmt.Sprintf("%v", key.Interface())
246+
247+
_, _ = fmt.Fprintf(w, "<member><name>%s</name>", keyStr)
248+
249+
if err := e.encodeValue(w, value.Interface()); err != nil {
250+
return fmt.Errorf("cannot encode map value for key '%s': %w", keyStr, err)
251+
}
252+
253+
_, _ = fmt.Fprint(w, "</member>")
254+
}
255+
256+
_, _ = fmt.Fprint(w, "</struct>")
257+
return nil
258+
}

encode_test.go

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ func TestStdEncoder_Encode(t *testing.T) {
8383
},
8484
expect: `<methodCall><methodName>myMethod</methodName><params><param><value><boolean>1</boolean></value></param><param><value><boolean>0</boolean></value></param></params></methodCall>`,
8585
err: nil,
86-
}, {
86+
},
87+
{
8788
name: "Numerical args",
8889
methodName: "myMethod",
8990
args: &struct {
@@ -402,3 +403,97 @@ func Test_encodeTime(t *testing.T) {
402403
})
403404
}
404405
}
406+
407+
// XMLRPCValue represents a value in XML-RPC format
408+
type XMLRPCValue struct {
409+
Type string `xml:"type,attr"`
410+
Value interface{} `xml:",chardata"`
411+
}
412+
413+
// XMLRPCMember represents a member in XML-RPC struct
414+
type XMLRPCMember struct {
415+
Name string `xml:"name"`
416+
Value XMLRPCValue `xml:"value"`
417+
}
418+
419+
// XMLRPCStruct represents an XML-RPC struct
420+
type XMLRPCStruct struct {
421+
Members []XMLRPCMember `xml:"member"`
422+
}
423+
424+
func Test_encodeMap(t *testing.T) {
425+
tests := []struct {
426+
name string
427+
input interface{}
428+
expect []string // List of XML fragments that should be present
429+
err error
430+
}{
431+
{
432+
name: "empty map",
433+
input: map[string]interface{}{},
434+
expect: []string{"<struct></struct>"},
435+
err: nil,
436+
},
437+
{
438+
name: "map with basic types",
439+
input: map[string]interface{}{
440+
"string": "value",
441+
"int": 42,
442+
"bool": true,
443+
"float": 3.14,
444+
},
445+
expect: []string{
446+
"<member><name>string</name><value><string>value</string></value></member>",
447+
"<member><name>int</name><value><int>42</int></value></member>",
448+
"<member><name>bool</name><value><boolean>1</boolean></value></member>",
449+
"<member><name>float</name><value><double>3.140000</double></value></member>",
450+
},
451+
err: nil,
452+
},
453+
{
454+
name: "map with nested structures",
455+
input: map[string]interface{}{
456+
"nested": map[string]interface{}{
457+
"key": "value",
458+
},
459+
"array": []string{"a", "b", "c"},
460+
},
461+
expect: []string{
462+
"<member><name>nested</name><value><struct><member><name>key</name><value><string>value</string></value></member></struct></value></member>",
463+
"<member><name>array</name><value><array><data><value><string>a</string></value><value><string>b</string></value><value><string>c</string></value></data></array></value></member>",
464+
},
465+
err: nil,
466+
},
467+
{
468+
name: "map with non-string keys",
469+
input: map[int]string{
470+
1: "one",
471+
2: "two",
472+
},
473+
expect: []string{
474+
"<member><name>1</name><value><string>one</string></value></member>",
475+
"<member><name>2</name><value><string>two</string></value></member>",
476+
},
477+
err: nil,
478+
},
479+
}
480+
481+
for _, tt := range tests {
482+
t.Run(tt.name, func(t *testing.T) {
483+
buf := new(strings.Builder)
484+
enc := &StdEncoder{}
485+
err := enc.encodeMap(buf, tt.input)
486+
require.Equal(t, tt.err, err)
487+
488+
output := buf.String()
489+
// Verify that the output starts with <struct> and ends with </struct>
490+
require.True(t, strings.HasPrefix(output, "<struct>"))
491+
require.True(t, strings.HasSuffix(output, "</struct>"))
492+
493+
// Check that each expected XML fragment is present in the output
494+
for _, expected := range tt.expect {
495+
require.Contains(t, output, expected)
496+
}
497+
})
498+
}
499+
}

0 commit comments

Comments
 (0)