Skip to content

Commit 39cf796

Browse files
committed
A bit of refactoring and more documentation
1 parent 39e7e94 commit 39cf796

File tree

11 files changed

+179
-139
lines changed

11 files changed

+179
-139
lines changed

client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,20 @@ import (
77
"net/url"
88
)
99

10+
// Client is responsible for making calls to RPC services with help of underlying rpc.Client.
1011
type Client struct {
1112
*rpc.Client
1213
}
1314

15+
// NewClient creates a Client with http.DefaultClient.
16+
// If provided endpoint is not valid, an error is returned.
1417
func NewClient(endpoint string) (*Client, error) {
1518

1619
return NewClientWithHttpClient(endpoint, http.DefaultClient)
1720
}
1821

22+
// NewClientWithHttpClient allows customization of http.Client used to make RPC calls.
23+
// If provided endpoint is not valid, an error is returned.
1924
func NewClientWithHttpClient(endpoint string, httpClient *http.Client) (*Client, error) {
2025

2126
// Parse Endpoint URL

client_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ func TestClient_Call(t *testing.T) {
1717
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1818

1919
m := &struct {
20-
Name string `xml:"methodName"`
21-
Params []*respParam `xml:"params>param"`
20+
Name string `xml:"methodName"`
21+
Params []*ResponseParam `xml:"params>param"`
2222
}{}
2323
body, err := ioutil.ReadAll(r.Body)
2424
assert.NoError(t, err, "test server: read body")

codec.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"sync"
1212
)
1313

14+
// Codec implements methods required by rpc.ClientCodec
15+
// In this implementation Codec is the one performing actual RPC requests with http.Client.
1416
type Codec struct {
1517
endpoint *url.URL
1618
httpClient *http.Client
@@ -20,7 +22,9 @@ type Codec struct {
2022
pending map[uint64]*rpcCall
2123

2224
// Current in-flight response
23-
response *decodableResponse
25+
response *Response
26+
encoder Encoder
27+
decoder Decoder
2428

2529
// presents completed requests by sequence ID
2630
ready chan uint64
@@ -32,11 +36,16 @@ type rpcCall struct {
3236
httpResponse *http.Response
3337
}
3438

39+
// NewCodec creates a new Codec bound to provided endpoint.
40+
// Provided client will be used to perform RPC requests.
3541
func NewCodec(endpoint *url.URL, httpClient *http.Client) *Codec {
3642
return &Codec{
3743
endpoint: endpoint,
3844
httpClient: httpClient,
3945

46+
encoder: &StdEncoder{},
47+
decoder: &StdDecoder{},
48+
4049
pending: make(map[uint64]*rpcCall),
4150
response: nil,
4251
ready: make(chan uint64),
@@ -46,7 +55,7 @@ func NewCodec(endpoint *url.URL, httpClient *http.Client) *Codec {
4655
func (c *Codec) WriteRequest(req *rpc.Request, args interface{}) error {
4756

4857
bodyBuffer := new(bytes.Buffer)
49-
err := EncodeMethodCall(bodyBuffer, req.ServiceMethod, args)
58+
err := c.encoder.Encode(bodyBuffer, req.ServiceMethod, args)
5059
if err != nil {
5160
return err
5261
}
@@ -105,14 +114,14 @@ func (c *Codec) ReadResponseHeader(resp *rpc.Response) error {
105114
return nil
106115
}
107116

108-
decodableResponse, err := newDecodableResponse(body)
117+
decodableResponse, err := NewResponse(body)
109118
if err != nil {
110119
resp.Error = err.Error()
111120
return nil
112121
}
113122

114123
// Return response Fault already a this stage
115-
if err := decodableResponse.Fault(); err != nil {
124+
if err := c.decoder.DecodeFault(decodableResponse); err != nil {
116125
resp.Error = err.Error()
117126
return nil
118127
}
@@ -131,7 +140,7 @@ func (c *Codec) ReadResponseBody(v interface{}) error {
131140
return errors.New("no in-flight response found")
132141
}
133142

134-
return c.response.Decode(v)
143+
return c.decoder.Decode(c.response, v)
135144
}
136145

137146
func (c *Codec) Close() error {

codec_response.go

Lines changed: 0 additions & 34 deletions
This file was deleted.

decode.go

Lines changed: 35 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package xmlrpc
22

33
import (
44
"encoding/base64"
5-
"encoding/xml"
65
"fmt"
76
"reflect"
87
"strconv"
@@ -14,81 +13,59 @@ const (
1413
errFormatInvalidFieldType = "invalid field type: expected '%s', got '%s'"
1514
)
1615

17-
type respWrapper struct {
18-
Params []respParam `xml:"params>param"`
19-
Fault *respFault `xml:"fault,omitempty"`
16+
// Decoder implementations provide mechanisms for parsing of XML-RPC responses to native data-types.
17+
type Decoder interface {
18+
DecodeRaw(body []byte, v interface{}) error
19+
Decode(response *Response, v interface{}) error
20+
DecodeFault(response *Response) *Fault
2021
}
2122

22-
type respParam struct {
23-
Value respValue `xml:"value"`
24-
}
25-
26-
type respValue struct {
27-
Array []*respValue `xml:"array>data>value"`
28-
Struct []*respStructMember `xml:"struct>member"`
29-
String string `xml:"string"`
30-
Int string `xml:"int"`
31-
Int4 string `xml:"i4"`
32-
Double string `xml:"double"`
33-
Boolean string `xml:"boolean"`
34-
DateTime string `xml:"dateTime.iso8601"`
35-
Base64 string `xml:"base64"`
36-
37-
Raw string `xml:",innerxml"` // the value can be default string
38-
}
39-
40-
type respStructMember struct {
41-
Name string `xml:"name"`
42-
Value respValue `xml:"value"`
43-
}
44-
45-
type respFault struct {
46-
Value respValue `xml:"value"`
47-
}
23+
// StdDecoder is the default implementation of the Decoder interface.
24+
type StdDecoder struct{}
4825

49-
func DecodeResponse(body []byte, v interface{}) error {
26+
func (d *StdDecoder) DecodeRaw(body []byte, v interface{}) error {
5027

51-
wrapper, err := toRespWrapper(body)
28+
response, err := NewResponse(body)
5229
if err != nil {
5330
return err
5431
}
5532

56-
if wrapper.Fault != nil {
57-
return decodeFault(wrapper.Fault)
58-
}
59-
60-
return decodeWrapper(wrapper, v)
61-
}
62-
63-
func toRespWrapper(body []byte) (*respWrapper, error) {
64-
wrapper := &respWrapper{}
65-
if err := xml.Unmarshal(body, wrapper); err != nil {
66-
return nil, err
33+
if response.Fault != nil {
34+
return d.decodeFault(response.Fault)
6735
}
6836

69-
return wrapper, nil
37+
return d.Decode(response, v)
7038
}
7139

72-
func decodeWrapper(wrapper *respWrapper, v interface{}) error {
40+
func (d *StdDecoder) Decode(response *Response, v interface{}) error {
7341

7442
// Validate that v has same number of public fields as response params
75-
if err := fieldsMustEqual(v, len(wrapper.Params)); err != nil {
43+
if err := fieldsMustEqual(v, len(response.Params)); err != nil {
7644
return err
7745
}
7846

7947
vElem := reflect.Indirect(reflect.ValueOf(v))
80-
for i, param := range wrapper.Params {
48+
for i, param := range response.Params {
8149
field := vElem.Field(i)
8250

83-
if err := decodeValue(&param.Value, &field); err != nil {
51+
if err := d.decodeValue(&param.Value, &field); err != nil {
8452
return err
8553
}
8654
}
8755

8856
return nil
8957
}
9058

91-
func decodeFault(fault *respFault) *Fault {
59+
func (d *StdDecoder) DecodeFault(response *Response) *Fault {
60+
61+
if response.Fault == nil {
62+
return nil
63+
}
64+
65+
return d.decodeFault(response.Fault)
66+
}
67+
68+
func (d *StdDecoder) decodeFault(fault *ResponseFault) *Fault {
9269

9370
f := &Fault{}
9471
for _, m := range fault.Value.Struct {
@@ -107,7 +84,7 @@ func decodeFault(fault *respFault) *Fault {
10784
return f
10885
}
10986

110-
func decodeValue(value *respValue, field *reflect.Value) error {
87+
func (d *StdDecoder) decodeValue(value *ResponseValue, field *reflect.Value) error {
11188

11289
var val interface{}
11390
var err error
@@ -124,16 +101,16 @@ func decodeValue(value *respValue, field *reflect.Value) error {
124101
val, err = strconv.ParseFloat(value.Double, 64)
125102

126103
case value.Boolean != "":
127-
val, err = decodeBoolean(value.Boolean)
104+
val, err = d.decodeBoolean(value.Boolean)
128105

129106
case value.String != "":
130107
val, err = value.String, nil
131108

132109
case value.Base64 != "":
133-
val, err = decodeBase64(value.Base64)
110+
val, err = d.decodeBase64(value.Base64)
134111

135112
case value.DateTime != "":
136-
val, err = decodeDateTime(value.DateTime)
113+
val, err = d.decodeDateTime(value.DateTime)
137114

138115
// Array decoding
139116
case len(value.Array) > 0:
@@ -145,7 +122,7 @@ func decodeValue(value *respValue, field *reflect.Value) error {
145122
slice := reflect.MakeSlice(reflect.TypeOf(field.Interface()), len(value.Array), len(value.Array))
146123
for i, v := range value.Array {
147124
item := slice.Index(i)
148-
if err := decodeValue(v, &item); err != nil {
125+
if err := d.decodeValue(v, &item); err != nil {
149126
return fmt.Errorf("failed decoding array item at index %d: %w", i, err)
150127
}
151128
}
@@ -168,7 +145,7 @@ func decodeValue(value *respValue, field *reflect.Value) error {
168145
return fmt.Errorf("cannot find field '%s' on struct", fName)
169146
}
170147

171-
if err := decodeValue(&m.Value, &f); err != nil {
148+
if err := d.decodeValue(&m.Value, &f); err != nil {
172149
return fmt.Errorf("failed decoding struct member '%s': %w", m.Name, err)
173150
}
174151
}
@@ -188,7 +165,7 @@ func decodeValue(value *respValue, field *reflect.Value) error {
188165
return nil
189166
}
190167

191-
func decodeBoolean(value string) (bool, error) {
168+
func (d *StdDecoder) decodeBoolean(value string) (bool, error) {
192169

193170
switch value {
194171
case "1", "true", "TRUE", "True":
@@ -199,12 +176,12 @@ func decodeBoolean(value string) (bool, error) {
199176
return false, fmt.Errorf("unrecognized value '%s' for boolean", value)
200177
}
201178

202-
func decodeBase64(value string) ([]byte, error) {
179+
func (d *StdDecoder) decodeBase64(value string) ([]byte, error) {
203180

204181
return base64.StdEncoding.DecodeString(value)
205182
}
206183

207-
func decodeDateTime(value string) (time.Time, error) {
184+
func (d *StdDecoder) decodeDateTime(value string) (time.Time, error) {
208185

209186
return time.Parse(time.RFC3339, value)
210187
}

decode_response.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package xmlrpc
2+
3+
import "encoding/xml"
4+
5+
// Response is the basic parsed object of the XML-RPC response body.
6+
// While it's not convenient to use this object directly - it contains all the information needed to unmarshal into other data-types.
7+
type Response struct {
8+
Params []ResponseParam `xml:"params>param"`
9+
Fault *ResponseFault `xml:"fault,omitempty"`
10+
}
11+
12+
// NewResponse creates a Response object from XML body.
13+
// It relies on XML Unmarshaler and if it fails - error is returned.
14+
func NewResponse(body []byte) (*Response, error) {
15+
16+
response := &Response{}
17+
if err := xml.Unmarshal(body, response); err != nil {
18+
return nil, err
19+
}
20+
21+
return response, nil
22+
}
23+
24+
type ResponseParam struct {
25+
Value ResponseValue `xml:"value"`
26+
}
27+
28+
type ResponseValue struct {
29+
Array []*ResponseValue `xml:"array>data>value"`
30+
Struct []*ResponseStructMember `xml:"struct>member"`
31+
String string `xml:"string"`
32+
Int string `xml:"int"`
33+
Int4 string `xml:"i4"`
34+
Double string `xml:"double"`
35+
Boolean string `xml:"boolean"`
36+
DateTime string `xml:"dateTime.iso8601"`
37+
Base64 string `xml:"base64"`
38+
}
39+
40+
type ResponseStructMember struct {
41+
Name string `xml:"name"`
42+
Value ResponseValue `xml:"value"`
43+
}
44+
45+
type ResponseFault struct {
46+
Value ResponseValue `xml:"value"`
47+
}

0 commit comments

Comments
 (0)