forked from kolo/xmlrpc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
multicall.go
144 lines (123 loc) · 3.78 KB
/
multicall.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package xmlrpc
import (
"bytes"
"encoding/xml"
"fmt"
"reflect"
"strconv"
)
// Types used for unmarshalling
// main fault should be already checked
type response struct {
Name xml.Name `xml:"methodResponse"`
Params []param `xml:"params>param"`
}
type param struct {
Value value `xml:"value"`
}
type member struct {
Name string `xml:"name"`
Value value `xml:"value"`
}
type value struct {
Array []value `xml:"array>data>value"` // used for returns
Struct []member `xml:"struct>member"` // used for fault
String string `xml:"string"` // used for faults
Int string `xml:"int"` // used for faults
Raw []byte `xml:",innerxml"`
}
// getFaultResponse converts faultValue to Fault.
func getFaultResponse(fault []member) FaultError {
var (
code int
str string
)
for _, field := range fault {
if field.Name == "faultCode" {
code, _ = strconv.Atoi(field.Value.Int)
} else if field.Name == "faultString" {
str = field.Value.String
if str == "" {
str = string(field.Value.Raw)
}
}
}
return FaultError{Code: code, String: str}
}
// MulticallFault tracks the position of the fault.
type MulticallFault struct {
FaultError
Index int // 0 based
methodName string // for better message
}
func (m MulticallFault) Error() string {
return fmt.Sprintf("fault in call %d (%s) : %s", m.Index, m.methodName, m.FaultError.Error())
}
func (r Response) unmarshalMulticall(out multicallOut) error {
switch ki := reflect.TypeOf(out.datas).Kind(); ki {
case reflect.Array, reflect.Slice: // OK
default:
return fmt.Errorf("destination for multicall must be Array or Slice, got %s", ki)
}
outSlice := reflect.ValueOf(out.datas)
parts, err := splitMulticall(r)
if multicallErr, ok := err.(MulticallFault); ok {
multicallErr.methodName = out.calls[multicallErr.Index].MethodName
return multicallErr
} else if err != nil {
return err
}
if outSlice.Len() != len(parts) {
return fmt.Errorf("invalid number of return destinations : response needs %d, got %d", len(parts), outSlice.Len())
}
for i, xmlReturn := range parts {
// pointer to one call's destination
elem := outSlice.Index(i).Interface()
// unmarshal expect a wrapping <value> tag
xmlReturn = append(append([]byte("<value>"), xmlReturn...), "</value>"...)
if err := unmarshal(xmlReturn, elem); err != nil {
return fmt.Errorf("unmarshall number %d failed : %s", i, err.Error())
}
}
return nil
}
// returns xml encoded chunks, one for each multicall response
// if there is (at least) one fault, returns the first one
// as error
func splitMulticall(xmlraw []byte) ([][]byte, error) {
// Unmarshal raw XML into the temporal structure
var ret response
dec := xml.NewDecoder(bytes.NewReader(xmlraw))
if CharsetReader != nil {
dec.CharsetReader = CharsetReader
}
if err := dec.Decode(&ret); err != nil {
return nil, err
}
if L := len(ret.Params); L != 1 {
return nil, fmt.Errorf("unexpected number of arguments : got %d", L)
}
// multicall returns one array of values
returns := ret.Params[0].Value.Array
out := make([][]byte, len(returns))
for i, oneReturn := range returns {
// multicall return are always wrapped in one-sized array
// otherwise, it's a fault
if len(oneReturn.Array) != 1 {
fault := getFaultResponse(oneReturn.Struct)
return nil, MulticallFault{Index: i, FaultError: fault}
}
// unwrap the value and store raw xml
// to further process
out[i] = oneReturn.Array[0].Raw
}
return out, nil
}
// MulticallArg stores one call
type MulticallArg struct {
MethodName string `xmlrpc:"methodName"`
Params []interface{} `xmlrpc:"params"` // 1-sized list containing the real arguments
}
func NewMulticallArg(method string, args interface{}) MulticallArg {
return MulticallArg{MethodName: method, Params: []interface{}{args}}
}