This repository has been archived by the owner on Jan 19, 2022. It is now read-only.
/
json_caller.go
129 lines (100 loc) · 3.09 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
package dispatcher
import (
"encoding/json"
"reflect"
bosherr "github.com/cloudfoundry/bosh-utils/errors"
bgcaction "github.com/oracle/bosh-oracle-cpi/action"
)
type Caller interface {
Call(bgcaction.Action, []interface{}) (interface{}, error)
}
// JSONCaller unmarshals call arguments with json package and calls action.Run
type JSONCaller struct{}
func NewJSONCaller() JSONCaller {
return JSONCaller{}
}
func (r JSONCaller) Call(action bgcaction.Action, args []interface{}) (value interface{}, err error) {
actionValue := reflect.ValueOf(action)
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(values)
}
func (r JSONCaller) invalidReturnTypes(methodType reflect.Type) (valid bool) {
if methodType.NumOut() != 2 {
return true
}
secondReturnType := methodType.Out(1)
if secondReturnType.Kind() != reflect.Interface {
return true
}
errorType := reflect.TypeOf(bosherr.Error(""))
secondReturnIsError := errorType.Implements(secondReturnType)
if !secondReturnIsError {
return true
}
return
}
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)
if err = json.Unmarshal(rawArgBytes, argValuePtr.Interface()); 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(values []reflect.Value) (value interface{}, err error) {
errValue := values[1]
if !errValue.IsNil() {
err = errValue.Interface().(error)
}
value = values[0].Interface()
return
}