diff --git a/.golangci.yml b/.golangci.yml index 8fd343a..0c25dde 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -10,7 +10,10 @@ linters-settings: - $gostd - github.com/stretchr/testify gci: - local-prefixes: github.com/golangci/golangci-lint + sections: + - standard # Standard section: captures all standard packages. + - default + - prefix(alexejk.io/go-xmlrpc) goconst: min-len: 2 min-occurrences: 2 @@ -29,7 +32,7 @@ linters-settings: - wrapperFunc goimports: local-prefixes: alexejk.io/go-xmlrpc - gomnd: + mnd: settings: mnd: # don't include the "operation" and "assign" @@ -63,7 +66,7 @@ linters: - gocritic - gofmt - goimports - - gomnd + - mnd - goprintffuncname - gosec - gosimple diff --git a/CHANGELOG.md b/CHANGELOG.md index 9da5e4a..292f9fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.5.3 + +Bugfixes: +* Decoding an `` of mixed types that contains another set of nested `` (with equally mixed types) (#86). + Outer slice would need to be defined as `[]any` and it's up to the user to cast the inner values (including nested slices) to the desired/expected type. + + ## 0.5.2 Bugfixes: diff --git a/client_test.go b/client_test.go index 68102e0..eb7d743 100644 --- a/client_test.go +++ b/client_test.go @@ -68,6 +68,88 @@ func TestClient_Call(t *testing.T) { require.Equal(t, 12345, resp.Index) } +func TestClient_Github_86(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + m := &struct { + Name string `xml:"methodName"` + Params []*ResponseParam `xml:"params>param"` + }{} + body, err := io.ReadAll(r.Body) + require.NoError(t, err, "test server: read body") + + err = xml.Unmarshal(body, m) + require.NoError(t, err, "test server: unmarshal body") + + require.Equal(t, "di", m.Name) + require.Equal(t, 3, len(m.Params)) + + expected := []string{"abc", "def", "hij"} + for i, p := range m.Params { + require.Equal(t, expected[i], *p.Value.String) + } + + respBody := ` + + + + OK + + + 200 + + + + + + 200 + Some String + Server: Sip Express Media Server (5.1.0 (x86_64/Linux)) calls: 0 active/0 total/0 connect/0 min + + + + + +` + _, _ = fmt.Fprintln(w, respBody) + })) + defer ts.Close() + + c, err := NewClient(ts.URL) + require.NoError(t, err) + require.NotNil(t, c) + + type DiRequest struct { + First string + Second string + Third string + } + + type DiResponse struct { + Status string + Code int + Data []any + } + + req := &DiRequest{ + First: "abc", + Second: "def", + Third: "hij", + } + resp := &DiResponse{} + + err = c.Call("di", req, resp) + require.NoError(t, err) + require.Equal(t, "OK", resp.Status) + require.Equal(t, 200, resp.Code) + + // Data array decoding validation + require.NotEmpty(t, resp.Data) + require.Len(t, resp.Data, 3) + require.Equal(t, 200, resp.Data[0].(int)) + require.Equal(t, []any{"Some String"}, resp.Data[1].([]any)) + require.Equal(t, "Server: Sip Express Media Server (5.1.0 (x86_64/Linux)) calls: 0 active/0 total/0 connect/0 min", resp.Data[2].(string)) +} + func TestClient_Fault(t *testing.T) { ts := mockupServer(t, "response_fault.xml") defer ts.Close() diff --git a/decode.go b/decode.go index 0d93769..3e26949 100644 --- a/decode.go +++ b/decode.go @@ -119,7 +119,18 @@ func (d *StdDecoder) decodeValue(value *ResponseValue, field reflect.Value) erro // Array decoding case value.Array != nil: - if field.Kind() != reflect.Slice { + fieldKind := field.Kind() + + // When dealing with nested arrays of []any type, initial kind for the nested array will be reflect.Interface + if fieldKind == reflect.Interface { + // Create a new []interface{} and assign it to field + fieldType := reflect.SliceOf(reflect.TypeOf((*interface{})(nil)).Elem()) + fieldKind = reflect.Slice + + field.Set(reflect.MakeSlice(fieldType, 0, 0)) + } + + if fieldKind != reflect.Slice { return fmt.Errorf(errFormatInvalidFieldType, reflect.Slice.String(), field.Kind().String()) } diff --git a/decode_test.go b/decode_test.go index 8717ac6..ba3d661 100644 --- a/decode_test.go +++ b/decode_test.go @@ -77,6 +77,7 @@ func TestStdDecoder_DecodeRaw(t *testing.T) { WoBleBobble bool WoBleBobble2 int Field2 int `xmlrpc:"2"` + Array []any } }{}, expect: &struct { @@ -86,6 +87,7 @@ func TestStdDecoder_DecodeRaw(t *testing.T) { WoBleBobble bool WoBleBobble2 int Field2 int `xmlrpc:"2"` + Array []any } }{ Struct: struct { @@ -94,12 +96,14 @@ func TestStdDecoder_DecodeRaw(t *testing.T) { WoBleBobble bool WoBleBobble2 int Field2 int `xmlrpc:"2"` + Array []any }{ Foo: "bar", Baz: 2, WoBleBobble: true, WoBleBobble2: 34, Field2: 3, + Array: []any{200, "Some String", []any{"Nested String", 10, true}}, }, }, }, @@ -274,6 +278,7 @@ func TestStdDecoder_DecodeRaw_StructFields(t *testing.T) { WoBleBobble bool WoBleBobble2 int Field2 int `xmlrpc:"2"` + Array []any } }{}, expect: &struct { @@ -283,6 +288,7 @@ func TestStdDecoder_DecodeRaw_StructFields(t *testing.T) { WoBleBobble bool WoBleBobble2 int Field2 int `xmlrpc:"2"` + Array []any } }{ Struct: struct { @@ -291,12 +297,14 @@ func TestStdDecoder_DecodeRaw_StructFields(t *testing.T) { WoBleBobble bool WoBleBobble2 int Field2 int `xmlrpc:"2"` + Array []any }{ Foo: "bar", Baz: 2, WoBleBobble: true, WoBleBobble2: 34, Field2: 3, + Array: []any{200, "Some String", []any{"Nested String", 10, true}}, }, }, }, @@ -310,6 +318,7 @@ func TestStdDecoder_DecodeRaw_StructFields(t *testing.T) { WoBleBobble bool WoBleBobble2 *int Field2 *int `xmlrpc:"2"` + Array []any } }{}, expect: &struct { @@ -319,6 +328,7 @@ func TestStdDecoder_DecodeRaw_StructFields(t *testing.T) { WoBleBobble bool WoBleBobble2 *int Field2 *int `xmlrpc:"2"` + Array []any } }{ Struct: &struct { @@ -327,12 +337,14 @@ func TestStdDecoder_DecodeRaw_StructFields(t *testing.T) { WoBleBobble bool WoBleBobble2 *int Field2 *int `xmlrpc:"2"` + Array []any }{ Foo: sPtr("bar"), Baz: 2, WoBleBobble: true, WoBleBobble2: iPtr(34), Field2: iPtr(3), + Array: []any{200, "Some String", []any{"Nested String", 10, true}}, }, }, }, @@ -407,6 +419,13 @@ func TestStdDecoder_DecodeRaw_Arrays(t *testing.T) { Array: []any{0, "4099", "O3D217AC", "123"}, }, }, + // Related to issue #86: https://github.com/alexejk/go-xmlrpc/issues/86 + "Basic mixed array - with nested mixed array (Github #86)": { + testFile: "response_array_mixed_with_array.xml", + expect: &TestStruct{ + Array: []any{10, "s11", true, []any{"Some String", []any{"Nested String", 10, true}}}, + }, + }, } for name, tt := range tests { @@ -456,6 +475,7 @@ func TestStdDecoder_DecodeRaw_Struct_Map(t *testing.T) { "woBleBobble": true, "WoBleBobble2": 34, "2": 3, + "array": []any{200, "Some String", []any{"Nested String", 10, true}}, }, }, }, @@ -622,7 +642,8 @@ func Test_structMemberToFieldName(t *testing.T) { } } -func Test_github(t *testing.T) { +// Issue: https://github.com/alexejk/go-xmlrpc/issues/84 +func Test_github_84(t *testing.T) { dec := &StdDecoder{} decodeTarget := struct { Array []any diff --git a/hack/linter.sh b/hack/linter.sh index e47348a..39fb56e 100644 --- a/hack/linter.sh +++ b/hack/linter.sh @@ -6,7 +6,7 @@ GREEN="\033[32m" YELLOW="\033[33m" NORMAL="\033[39m" -LINTER_VERSION=1.56.1 +LINTER_VERSION=1.58.1 LINTER_BINDIR=$(go env GOPATH)/bin LINTER_NAME=golangci-lint diff --git a/testdata/response_array_mixed_with_array.xml b/testdata/response_array_mixed_with_array.xml new file mode 100644 index 0000000..273fb9f --- /dev/null +++ b/testdata/response_array_mixed_with_array.xml @@ -0,0 +1,36 @@ + + + + + + + + + 10 + s11 + 1 + + + + Some String + + + + Nested String + 10 + 1 + + + + + + + + + + + + diff --git a/testdata/response_struct.xml b/testdata/response_struct.xml index 76f40cb..7fcc632 100644 --- a/testdata/response_struct.xml +++ b/testdata/response_struct.xml @@ -24,6 +24,26 @@ 2 3 + + array + + + + 200 + Some String + + + + Nested String + 10 + 1 + + + + + + +