forked from cloudfoundry/bosh-cpi-go
/
json_caller.go
143 lines (113 loc) · 3.41 KB
/
json_caller.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
package rpc
import (
"encoding/json"
"reflect"
bosherr "github.com/cloudfoundry/bosh-utils/errors"
)
// JSONCaller unmarshals call arguments with json package and calls action.Run
type JSONCaller struct{}
func NewJSONCaller() JSONCaller {
return JSONCaller{}
}
func (r JSONCaller) Call(action interface{}, args []interface{}) (value interface{}, err error) {
actionValue := reflect.ValueOf(action)
var runMethodValue reflect.Value
if actionValue.Kind() == reflect.Func {
runMethodValue = actionValue
} else {
runMethodValue = actionValue.MethodByName("Run")
if runMethodValue.Kind() != reflect.Func {
err = bosherr.Error("Run method not found")
return
}
}
runMethodType := runMethodValue.Type()
if r.invalidReturnTypes(runMethodType) {
err = bosherr.Error("Run method should return a value and an error")
return
}
methodArgs, err := r.extractMethodArgs(runMethodType, args)
if err != nil {
err = bosherr.WrapError(err, "Extracting method arguments from payload")
return
}
values := runMethodValue.Call(methodArgs)
return r.extractReturns(runMethodType, values)
}
func (r JSONCaller) invalidReturnTypes(methodType reflect.Type) (valid bool) {
if methodType.NumOut() == 0 {
return true
}
lastReturnType := methodType.Out(methodType.NumOut() - 1)
if lastReturnType.Kind() != reflect.Interface {
return true
}
errorType := reflect.TypeOf(bosherr.Error(""))
secondReturnIsError := errorType.Implements(lastReturnType)
if !secondReturnIsError {
return true
}
return false
}
func (r JSONCaller) extractMethodArgs(runMethodType reflect.Type, args []interface{}) (methodArgs []reflect.Value, err error) {
numberOfArgs := runMethodType.NumIn()
numberOfReqArgs := numberOfArgs
if runMethodType.IsVariadic() {
numberOfReqArgs--
}
if len(args) < numberOfReqArgs {
err = bosherr.Errorf("Not enough arguments, expected %d, got %d", numberOfReqArgs, len(args))
return
}
for i, argFromPayload := range args {
var rawArgBytes []byte
rawArgBytes, err = json.Marshal(argFromPayload)
if err != nil {
err = bosherr.WrapError(err, "Marshalling action argument")
return
}
argType, typeFound := r.getMethodArgType(runMethodType, i)
if !typeFound {
continue
}
argValuePtr := reflect.New(argType)
err = json.Unmarshal(rawArgBytes, argValuePtr.Interface())
if err != nil {
err = bosherr.WrapError(err, "Unmarshalling action argument")
return
}
methodArgs = append(methodArgs, reflect.Indirect(argValuePtr))
}
return
}
func (r JSONCaller) getMethodArgType(methodType reflect.Type, index int) (argType reflect.Type, found bool) {
numberOfArgs := methodType.NumIn()
switch {
case !methodType.IsVariadic() && index >= numberOfArgs:
return nil, false
case methodType.IsVariadic() && index >= numberOfArgs-1:
sliceType := methodType.In(numberOfArgs - 1)
return sliceType.Elem(), true
default:
return methodType.In(index), true
}
}
func (r JSONCaller) extractReturns(methodType reflect.Type, values []reflect.Value) (interface{}, error) {
var err error
errValue := values[methodType.NumOut()-1]
if !errValue.IsNil() {
err = errValue.Interface().(error)
}
switch {
case methodType.NumOut() == 1:
return nil, err
case methodType.NumOut() == 2:
return values[0].Interface(), err
default:
returnValues := []interface{}{}
for i := 0; i < methodType.NumOut()-1; i++ {
returnValues = append(returnValues, values[i].Interface())
}
return returnValues, err
}
}