-
Notifications
You must be signed in to change notification settings - Fork 0
/
method_handlers.go
90 lines (81 loc) · 2.96 KB
/
method_handlers.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
package chioas
import (
"fmt"
"net/http"
"reflect"
"runtime"
"strings"
)
// GetHandler is a function that can be set on Method.Handler - and is called to obtain the http.HandlerFunc
type GetHandler func(path string, method string, thisApi any) (http.HandlerFunc, error)
type MethodHandlerBuilder interface {
BuildHandler(path string, method string, mdef Method, thisApi any) (http.HandlerFunc, error)
}
var defaultMethodHandlerBuilder MethodHandlerBuilder = &methodHandlerBuilder{}
func getMethodHandlerBuilder(builder MethodHandlerBuilder) MethodHandlerBuilder {
if builder != nil {
return builder
}
return defaultMethodHandlerBuilder
}
type methodHandlerBuilder struct {
}
func (m *methodHandlerBuilder) BuildHandler(path string, method string, mdef Method, thisApi any) (http.HandlerFunc, error) {
if mdef.Handler == nil {
return nil, fmt.Errorf("handler not set (path: %s, method: %s)", path, method)
}
switch hf := mdef.Handler.(type) {
case http.HandlerFunc:
return hf, nil
case func(http.ResponseWriter, *http.Request):
return hf, nil
case func(string, string, any) (http.HandlerFunc, error):
return hf(path, method, thisApi)
case string:
if thisApi == nil {
return nil, fmt.Errorf("method by name '%s' can only be used when 'thisApi' arg is passed to Definition.SetupRoutes (path: %s, method: %s)", hf, path, method)
}
mfn := reflect.ValueOf(thisApi).MethodByName(hf)
if !mfn.IsValid() {
return nil, fmt.Errorf("method name '%s' does not exist (path: %s, method: %s)", hf, path, method)
}
if hf, ok := mfn.Interface().(func(http.ResponseWriter, *http.Request)); ok {
return hf, nil
}
return nil, fmt.Errorf("method name '%s' is not http.HandlerFunc (path: %s, method: %s)", hf, path, method)
default:
if mf, ok := isMethodExpression(mdef.Handler, thisApi); ok {
return mf, nil
}
}
return nil, fmt.Errorf("invalid handler type (path: %s, method: %s)", path, method)
}
func isMethodExpression(m any, thisApi any) (http.HandlerFunc, bool) {
if thisApi != nil {
mv := reflect.ValueOf(m)
mt := mv.Type()
// check it's a func with 3 in args and no return...
if mt.Kind() == reflect.Func && mt.NumIn() == 3 && mt.NumOut() == 0 {
apiv := reflect.ValueOf(thisApi)
// for it to be a potential method expression, the first in arg must be receiver type...
if mt.In(0) == apiv.Type() {
// check that the function name indicates it is actually a method expression...
// (i.e. rather than just a regular function whose first arg type happens to be receiver type)
mn := runtime.FuncForPC(mv.Pointer()).Name()
if strings.Contains(mn, ".(") && strings.Contains(mn, ").") {
mn = parseMethodName(mn)
if mfn := apiv.MethodByName(mn); mfn.IsValid() {
if hf, ok := mfn.Interface().(func(http.ResponseWriter, *http.Request)); ok {
return hf, true
}
}
}
}
}
}
return nil, false
}
func parseMethodName(methodName string) string {
parts := strings.Split(methodName, ".")
return parts[len(parts)-1]
}