-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapi.go
164 lines (148 loc) · 4.21 KB
/
api.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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package dispatch
import (
"context"
"encoding/json"
"log"
"reflect"
"runtime/debug"
)
// API is an object that holds all API methods and can dispatch them.
type API struct {
Endpoints []*Endpoint
}
// MatchEndpoint matches a request to an endpoint, creating a map of path
// variables in the process.
func (api *API) MatchEndpoint(method, path string) (*Endpoint, PathVars) {
for _, endpt := range api.Endpoints {
pathVars, match := endpt.pathMatcher.Match(method, path)
if match {
return endpt, pathVars
}
}
return nil, nil
}
// GetMethodsForPath returns the list of valid methods for a specified path
// (for use in OPTIONS requests).
func (api *API) GetMethodsForPath(path string) []string {
methods := make([]string, 0)
for _, endpt := range api.Endpoints {
match := endpt.pathMatcher.MatchPath(path)
if match {
methods = append(methods, endpt.pathMatcher.Method)
}
}
return methods
}
// Call sends the input to the endpoint and returns the result.
func (api *API) Call(ctx context.Context, method, path string, input json.RawMessage) (out interface{}, err error) {
// Recover from any panics, and return an internal error in that case
defer func() {
if r := recover(); r != nil {
log.Printf("API.Call panic: %v\n", r)
debug.PrintStack()
out = nil
err = ErrInternal
}
}()
endpoint, pathVars := api.MatchEndpoint(method, path)
if endpoint == nil {
return nil, ErrNotFound
}
ctx = SetContextPathVars(ctx, pathVars)
for _, hook := range endpoint.PreRequestHooks {
originalInput := &EndpointInput{method, path, ctx, input}
modifiedInput, err := hook(originalInput)
if err != nil {
return nil, err
}
method = modifiedInput.Method
path = modifiedInput.Path
ctx = modifiedInput.Ctx
input = modifiedInput.Input
}
handlerType := reflect.TypeOf(endpoint.Handler)
if handlerType.Kind() != reflect.Func {
log.Printf("Bad handler type for %s: %s\n", endpoint.Path, handlerType.Kind())
return nil, ErrInternal
}
// Handler functions can take a custom value type and/or a context input
if handlerType.NumIn() > 2 {
log.Printf("Handler %s takes too many args\n", endpoint.Path)
return nil, ErrInternal
}
var inputType reflect.Type
var takesContext, takesCustom bool
var ctxIndex, customIndex int
for i := 0; i < handlerType.NumIn(); i++ {
inType := handlerType.In(i)
ctxType := reflect.TypeOf((*context.Context)(nil)).Elem()
if inType.Implements(ctxType) {
if takesContext {
log.Printf("Handler %s takes multiple context inputs", endpoint.Path)
return nil, ErrInternal
}
takesContext = true
ctxIndex = i
} else {
if takesCustom {
log.Printf("Handler %s takes multiple inputs", endpoint.Path)
return nil, ErrInternal
}
takesCustom = true
customIndex = i
inputType = handlerType.In(i)
}
}
handlerValue := reflect.ValueOf(endpoint.Handler)
var resultValues []reflect.Value
if takesCustom || takesContext {
// Can return any interface and/or an error
inputList := make([]reflect.Value, handlerType.NumIn())
if takesContext {
inputList[ctxIndex] = reflect.ValueOf(ctx)
}
if takesCustom {
inputVal := reflect.New(inputType)
inputInterface := inputVal.Interface()
err = json.Unmarshal(input, inputInterface)
if err != nil {
return nil, err
}
directInput := reflect.Indirect(reflect.ValueOf(inputInterface))
inputList[customIndex] = directInput
}
resultValues = handlerValue.Call(inputList)
} else {
resultValues = handlerValue.Call(nil)
}
switch len(resultValues) {
case 0:
return nil, nil
case 1:
// Function may return _either_ an error or a value
retval := resultValues[0].Interface()
// If nil, it doesn't matter
if retval == nil {
return nil, nil
}
// Otherwise, check if it can be asserted as an error
returnErr, ok := retval.(error)
if ok {
return nil, returnErr
}
// Otherwise, assume it's data
return retval, nil
case 2:
// If a value and error are returned, they must be in the order (out, error)
out = resultValues[0].Interface()
if errVal := resultValues[1].Interface(); errVal == nil {
err = nil
} else {
err = errVal.(error)
}
return out, err
default:
log.Printf("Handler %s returned too many values\n", endpoint.Path)
return nil, ErrInternal
}
}