-
Notifications
You must be signed in to change notification settings - Fork 0
/
api.go
183 lines (161 loc) · 4.61 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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package dispatch
import (
"encoding/json"
"errors"
"log"
"net/http"
"reflect"
"runtime/debug"
"github.com/dgrijalva/jwt-go"
)
// Claims stores the set of user claims for JWTs.
type Claims struct {
jwt.StandardClaims
}
// ErrorBadRequest represents an error from a malformed request.
var ErrorBadRequest = errors.New("Bad request")
// ErrorNotFound represents a 404 error.
var ErrorNotFound = errors.New("Path not found")
// ErrorInternal represents some unexpected internal error.
var ErrorInternal = errors.New("Internal error")
// Context represents data about the endpoint call, such as path variables, the
// calling user, and so on.
type Context struct {
// Request is the original http request.
Request *http.Request
// Writer is the original response writer.
Writer http.ResponseWriter
// PathVars is the map of path variable names to values.
PathVars PathVars
Claims *Claims
}
// 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
}
// Call sends the input to the endpoint and returns the result.
func (api *API) Call(method, path string, ctx *Context, 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 = errors.New("Internal error")
}
}()
if ctx == nil {
ctx = &Context{}
}
endpoint, pathVars := api.MatchEndpoint(method, path)
if endpoint == nil {
return nil, ErrorNotFound
}
ctx.PathVars = 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, ErrorInternal
}
// 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, ErrorInternal
}
var inputType reflect.Type
var takesContext, takesCustom, ctxPointer bool
var ctxIndex, customIndex int
for i := 0; i < handlerType.NumIn(); i++ {
inType := handlerType.In(i)
if inType == reflect.TypeOf(Context{}) {
takesContext = true
ctxIndex = i
} else if inType == reflect.TypeOf(&Context{}) {
takesContext = true
ctxPointer = true
ctxIndex = i
} else {
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 {
if ctxPointer {
inputList[ctxIndex] = reflect.ValueOf(ctx)
} else {
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)
}
if len(resultValues) > 2 {
log.Printf("Handler %s returned too many values\n", endpoint.Path)
return nil, ErrorInternal
}
if len(resultValues) == 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
}
if len(resultValues) == 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
}
return nil, nil
}