/
handler.go
205 lines (171 loc) · 6.43 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
package hero
import (
"fmt"
"reflect"
"github.com/kataras/iris/v12/context"
)
type (
// ErrorHandler describes an interface to handle errors per hero handler and its dependencies.
//
// Handles non-nil errors return from a hero handler or a controller's method (see `getBindingsFor` and `Handler`)
// the error may return from a request-scoped dependency too (see `Handler`).
ErrorHandler interface {
HandleError(*context.Context, error)
}
// ErrorHandlerFunc implements the `ErrorHandler`.
// It describes the type defnition for an error function handler.
ErrorHandlerFunc func(*context.Context, error)
// Code is a special type for status code.
// It's used for a builtin dependency to map the status code given by a previous
// method or middleware.
// Use a type like that in order to not conflict with any developer-registered
// dependencies.
// Alternatively: ctx.GetStatusCode().
Code int
// Err is a special type for error stored in mvc responses or context.
// It's used for a builtin dependency to map the error given by a previous
// method or middleware.
// Use a type like that in order to not conflict with any developer-registered
// dependencies.
// Alternatively: ctx.GetErr().
Err error
)
// HandleError fires when a non-nil error returns from a request-scoped dependency at serve-time or the handler itself.
func (fn ErrorHandlerFunc) HandleError(ctx *context.Context, err error) {
fn(ctx, err)
}
// String implements the fmt.Stringer interface.
// Returns the text corresponding to this status code, e.g. "Not Found".
// Same as iris.StatusText(int(code)).
func (code Code) String() string {
return context.StatusText(int(code))
}
// Value returns the underline int value.
// Same as int(code).
func (code Code) Value() int {
return int(code)
}
var (
// ErrSeeOther may be returned from a dependency handler to skip a specific dependency
// based on custom logic.
ErrSeeOther = fmt.Errorf("see other")
// ErrStopExecution may be returned from a dependency handler to stop
// and return the execution of the function without error (it calls ctx.StopExecution() too).
// It may be occurred from request-scoped dependencies as well.
ErrStopExecution = fmt.Errorf("stop execution")
)
var (
// DefaultErrStatusCode is the default error status code (400)
// when the response contains a non-nil error or a request-scoped binding error occur.
DefaultErrStatusCode = 400
// DefaultErrorHandler is the default error handler which is fired
// when a function returns a non-nil error or a request-scoped dependency failed to binded.
DefaultErrorHandler = ErrorHandlerFunc(func(ctx *context.Context, err error) {
if err != ErrStopExecution {
if status := ctx.GetStatusCode(); status == 0 || !context.StatusCodeNotSuccessful(status) {
ctx.StatusCode(DefaultErrStatusCode)
}
_, _ = ctx.WriteString(err.Error())
}
ctx.StopExecution()
})
)
var (
irisHandlerType = reflect.TypeOf((*context.Handler)(nil)).Elem()
irisHandlerFuncType = reflect.TypeOf(func(*context.Context) {})
)
func isIrisHandlerType(typ reflect.Type) bool {
return typ == irisHandlerType || typ == irisHandlerFuncType
}
func makeHandler(fn interface{}, c *Container, paramsCount int) context.Handler {
if fn == nil {
panic("makeHandler: function is nil")
}
// 0. A normal handler.
if handler, ok := isHandler(fn); ok {
return handler
}
// 1. A handler which returns just an error, handle it faster.
if handlerWithErr, ok := isHandlerWithError(fn); ok {
return func(ctx *context.Context) {
if err := handlerWithErr(ctx); err != nil {
c.GetErrorHandler(ctx).HandleError(ctx, err)
}
}
}
v := valueOf(fn)
typ := v.Type()
numIn := typ.NumIn()
bindings := getBindingsForFunc(v, c.Dependencies, c.DisablePayloadAutoBinding, paramsCount)
c.fillReport(context.HandlerName(fn), bindings)
// Check if it's a function that accept zero or more dependencies
// and returns an Iris Handler.
if paramsCount <= 0 {
// println(irisHandlerType.String())
if typ.NumOut() == 1 && isIrisHandlerType(typ.Out(0)) {
inputs := getStaticInputs(bindings, numIn)
if len(inputs) != numIn {
panic(fmt.Sprintf("makeHandler: func(...<T>) iris.Handler: expected %d function input parameters but fewer static dependencies matched (%d)", numIn, len(inputs)))
}
handler := v.Call(inputs)[0].Interface().(context.Handler)
return handler
}
}
resultHandler := defaultResultHandler
for i, lidx := 0, len(c.resultHandlers)-1; i <= lidx; i++ {
resultHandler = c.resultHandlers[lidx-i](resultHandler)
}
return func(ctx *context.Context) {
inputs := make([]reflect.Value, numIn)
for _, binding := range bindings {
input, err := binding.Dependency.Handle(ctx, binding.Input)
if err != nil {
if err == ErrSeeOther {
continue
}
// handled inside ErrorHandler.
// else if err == ErrStopExecution {
// ctx.StopExecution()
// return // return without error.
// }
c.GetErrorHandler(ctx).HandleError(ctx, err)
// return [13 Sep 2020, commented that in order to be able to
// give end-developer the option not only to handle the error
// but to skip it if necessary, example:
// read form, unknown field, continue without StopWith,
// the binder should bind the method's input argument and continue
// without errors. See `mvc.TestErrorHandlerContinue` test.]
}
// If ~an error status code is set or~ execution has stopped
// from within the dependency (something went wrong while validating the request),
// then stop everything and let handler fire that status code.
if ctx.IsStopped() /* || context.StatusCodeNotSuccessful(ctx.GetStatusCode())*/ {
return
}
inputs[binding.Input.Index] = input
}
// fmt.Printf("For func: %s | valid input deps length(%d)\n", typ.String(), len(inputs))
// for idx, in := range inputs {
// fmt.Printf("[%d] (%s) %#+v\n", idx, in.Type().String(), in.Interface())
// }
outputs := v.Call(inputs)
if err := dispatchFuncResult(ctx, outputs, resultHandler); err != nil {
c.GetErrorHandler(ctx).HandleError(ctx, err)
}
}
}
func isHandler(fn interface{}) (context.Handler, bool) {
if handler, ok := fn.(context.Handler); ok {
return handler, ok
}
if handler, ok := fn.(func(*context.Context)); ok {
return handler, ok
}
return nil, false
}
func isHandlerWithError(fn interface{}) (func(*context.Context) error, bool) {
if handlerWithErr, ok := fn.(func(*context.Context) error); ok {
return handlerWithErr, true
}
return nil, false
}