forked from loopfz/gadgeto
/
handler.go
396 lines (372 loc) · 10.6 KB
/
handler.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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
package tonic
import (
"fmt"
"reflect"
"runtime"
"strconv"
"strings"
"sync"
"github.com/gin-gonic/gin"
validator "github.com/go-playground/validator/v10"
"github.com/google/uuid"
)
var (
validatorObj *validator.Validate
validatorOnce sync.Once
)
// Handler returns a Gin HandlerFunc that wraps the handler passed
// in parameters.
// The handler may use the following signature:
//
// func(*gin.Context, [input object ptr]) ([output object], error)
//
// Input and output objects are both optional.
// As such, the minimal accepted signature is:
//
// func(*gin.Context) error
//
// The wrapping gin-handler will bind the parameters from the query-string,
// path, body and headers, and handle the errors.
//
// Handler will panic if the tonic handler or its input/output values
// are of incompatible type.
func Handler(h interface{}, status int, options ...func(*Route)) gin.HandlerFunc {
hv := reflect.ValueOf(h)
if hv.Kind() != reflect.Func {
panic(fmt.Sprintf("handler parameters must be a function, got %T", h))
}
ht := hv.Type()
fname := fmt.Sprintf("%s_%s", runtime.FuncForPC(hv.Pointer()).Name(), uuid.Must(uuid.NewRandom()).String())
in := input(ht, fname)
out := output(ht, fname)
// Wrap Gin handler.
f := func(c *gin.Context) {
_, ok := c.Get(tonicWantRouteInfos)
r := &Route{}
for _, opt := range options {
opt(r)
}
if ok {
r.defaultStatusCode = status
r.handler = hv
r.handlerType = ht
r.inputType = in
r.outputType = out
c.Set(tonicRoutesInfos, r)
c.Abort()
return
}
// funcIn contains the input parameters of the
// tonic handler call.
args := []reflect.Value{reflect.ValueOf(c)}
// Tonic handler has custom input, handle
// binding.
if in != nil {
input := reflect.New(in)
routeBindHook := r.GetBindHook()
if routeBindHook == nil {
// use the default bindHook if the route
// does not have a custom one
routeBindHook = bindHook
}
// Bind the body with the hook.
if err := routeBindHook(c, input.Interface()); err != nil {
handleError(c, BindError{message: err.Error(), typ: in})
return
}
// Bind query-parameters.
if err := bind(c, input, QueryTag, extractQuery); err != nil {
handleError(c, err)
return
}
// Bind path arguments.
if err := bind(c, input, PathTag, extractPath); err != nil {
handleError(c, err)
return
}
// Bind headers.
if err := bind(c, input, HeaderTag, extractHeader); err != nil {
handleError(c, err)
return
}
// validating query and path inputs if they have a validate tag
initValidator()
args = append(args, input)
if err := validatorObj.Struct(input.Interface()); err != nil {
handleError(c, BindError{message: err.Error(), validationErr: err})
return
}
}
// Call tonic handler with the arguments
// and extract the returned values.
var err, val interface{}
ret := hv.Call(args)
if out != nil {
val = ret[0].Interface()
err = ret[1].Interface()
} else {
err = ret[0].Interface()
}
// Handle the error returned by the
// handler invocation, if any.
if err != nil {
handleError(c, err.(error))
return
}
routeRenderHook := r.GetRenderHook()
if routeRenderHook == nil {
routeRenderHook = renderHook
}
routeRenderHook(c, status, val)
}
// Register route in tonic-enabled routes map
route := &Route{
defaultStatusCode: status,
handler: hv,
handlerType: ht,
inputType: in,
outputType: out,
}
for _, opt := range options {
opt(route)
}
routesMu.Lock()
routes[fname] = route
routesMu.Unlock()
ret := func(c *gin.Context) { execHook(c, f, fname) }
funcsMu.Lock()
defer funcsMu.Unlock()
funcs[runtime.FuncForPC(reflect.ValueOf(ret).Pointer()).Name()] = struct{}{}
return ret
}
// RegisterValidation registers a custom validation on the validator.Validate instance of the package
// NOTE: calling this function may instantiate the validator itself.
// NOTE: this function is not thread safe, since the validator validation registration isn't
func RegisterValidation(tagName string, validationFunc validator.Func) error {
initValidator()
return validatorObj.RegisterValidation(tagName, validationFunc)
}
// RegisterTagNameFunc registers a function to get alternate names for StructFields.
//
// eg. to use the names which have been specified for JSON representations of structs, rather than normal Go field names:
//
// validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
// name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
// if name == "-" {
// return ""
// }
// return name
// }
func RegisterTagNameFunc(registerTagFunc validator.TagNameFunc) {
validatorObj.RegisterTagNameFunc(registerTagFunc)
}
func initValidator() {
validatorOnce.Do(func() {
validatorObj = validator.New()
validatorObj.SetTagName(ValidationTag)
})
}
// bind binds the fields the fields of the input object in with
// the values of the parameters extracted from the Gin context.
// It reads tag to know what to extract using the extractor func.
func bind(c *gin.Context, v reflect.Value, tag string, extract extractor) error {
t := v.Type()
if t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
for i := 0; i < t.NumField(); i++ {
ft := t.Field(i)
field := v.Field(i)
// Handle embedded fields with a recursive call.
// If the field is a pointer, but is nil, we
// create a new value of the same type, or we
// take the existing memory address.
if ft.Anonymous {
if field.Kind() == reflect.Ptr {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
}
} else {
if field.CanAddr() {
field = field.Addr()
}
}
err := bind(c, field, tag, extract)
if err != nil {
return err
}
continue
}
tagValue := ft.Tag.Get(tag)
if tagValue == "" {
continue
}
// Set-up context for extractors.
// Query.
c.Set(ExplodeTag, true) // default
if explodeVal, ok := ft.Tag.Lookup(ExplodeTag); ok {
if explode, err := strconv.ParseBool(explodeVal); err == nil && !explode {
c.Set(ExplodeTag, false)
}
}
_, fieldValues, err := extract(c, tagValue)
if err != nil {
return BindError{field: ft.Name, typ: t, message: err.Error()}
}
// Extract default value and use it in place
// if no values were returned.
def, ok := ft.Tag.Lookup(DefaultTag)
if ok && len(fieldValues) == 0 {
if c.GetBool(ExplodeTag) {
fieldValues = append(fieldValues, strings.Split(def, ",")...)
} else {
fieldValues = append(fieldValues, def)
}
}
if len(fieldValues) == 0 {
continue
}
// If the field is a nil pointer to a concrete type,
// create a new addressable value for this type.
if field.Kind() == reflect.Ptr && field.IsNil() {
f := reflect.New(field.Type().Elem())
field.Set(f)
}
// Dereference pointer.
if field.Kind() == reflect.Ptr {
field = field.Elem()
}
kind := field.Kind()
// Multiple values can only be filled to types
// Slice and Array.
if len(fieldValues) > 1 && (kind != reflect.Slice && kind != reflect.Array) {
return BindError{field: ft.Name, typ: t, message: "multiple values not supported"}
}
// Ensure that the number of values to fill does
// not exceed the length of a field of type Array.
if kind == reflect.Array {
if field.Len() != len(fieldValues) {
return BindError{field: ft.Name, typ: t, message: fmt.Sprintf(
"parameter expect %d values, got %d", field.Len(), len(fieldValues)),
}
}
}
if kind == reflect.Slice || kind == reflect.Array {
// Create a new slice with an adequate
// length to set all the values.
if kind == reflect.Slice {
field.Set(reflect.MakeSlice(field.Type(), 0, len(fieldValues)))
}
for i, val := range fieldValues {
v := reflect.New(field.Type().Elem()).Elem()
err = bindStringValue(val, v)
if err != nil {
return BindError{field: ft.Name, typ: t, message: err.Error()}
}
if kind == reflect.Slice {
field.Set(reflect.Append(field, v))
}
if kind == reflect.Array {
field.Index(i).Set(v)
}
}
continue
}
// Handle enum values.
enum := ft.Tag.Get(EnumTag)
if enum != "" {
enumValues := strings.Split(strings.TrimSpace(enum), ",")
if len(enumValues) != 0 {
if !contains(enumValues, fieldValues[0]) {
return BindError{field: ft.Name, typ: t, message: fmt.Sprintf(
"parameter has not an acceptable value, %s=%v", EnumTag, enumValues),
}
}
}
}
// Fill string value into input field.
err = bindStringValue(fieldValues[0], field)
if err != nil {
return BindError{field: ft.Name, typ: t, message: err.Error()}
}
}
return nil
}
// input checks the input parameters of a tonic handler
// and return the type of the second parameter, if any.
func input(ht reflect.Type, name string) reflect.Type {
n := ht.NumIn()
if n < 1 || n > 2 {
panic(fmt.Sprintf(
"incorrect number of input parameters for handler %s, expected 1 or 2, got %d",
name, n,
))
}
// First parameter of tonic handler must be
// a pointer to a Gin context.
if !ht.In(0).ConvertibleTo(reflect.TypeOf(&gin.Context{})) {
panic(fmt.Sprintf(
"invalid first parameter for handler %s, expected *gin.Context, got %v",
name, ht.In(0),
))
}
if n == 2 {
// Check the type of the second parameter
// of the handler. Must be a pointer to a struct.
if ht.In(1).Kind() != reflect.Ptr || ht.In(1).Elem().Kind() != reflect.Struct {
panic(fmt.Sprintf(
"invalid second parameter for handler %s, expected pointer to struct, got %v",
name, ht.In(1),
))
} else {
return ht.In(1).Elem()
}
}
return nil
}
// output checks the output parameters of a tonic handler
// and return the type of the return type, if any.
func output(ht reflect.Type, name string) reflect.Type {
n := ht.NumOut()
if n < 1 || n > 2 {
panic(fmt.Sprintf(
"incorrect number of output parameters for handler %s, expected 1 or 2, got %d",
name, n,
))
}
// Check the type of the error parameter, which
// should always come last.
if !ht.Out(n - 1).Implements(reflect.TypeOf((*error)(nil)).Elem()) {
panic(fmt.Sprintf(
"unsupported type for handler %s output parameter: expected error interface, got %v",
name, ht.Out(n-1),
))
}
if n == 2 {
t := ht.Out(0)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t
}
return nil
}
// handleError handles any error raised during the execution
// of the wrapping gin-handler.
func handleError(c *gin.Context, err error) {
if len(c.Errors) == 0 {
c.Error(err)
}
code, resp := errorHook(c, err)
renderHook(c, code, resp)
}
// contains returns whether in contain s.
func contains(in []string, s string) bool {
for _, v := range in {
if v == s {
return true
}
}
return false
}