-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
/
Copy pathstruct.go
189 lines (155 loc) · 5.62 KB
/
struct.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
package hero
import (
"fmt"
"reflect"
"github.com/kataras/iris/v12/context"
)
// Sorter is the type for sort customization of a struct's fields
// and its available bindable values.
//
// Sorting applies only when a field can accept more than one registered value.
type Sorter func(t1 reflect.Type, t2 reflect.Type) bool
// sortByNumMethods is a builtin sorter to sort fields and values
// based on their type and its number of methods, highest number of methods goes first.
//
// It is the default sorter on struct injector of `hero.Struct` method.
var sortByNumMethods Sorter = func(t1 reflect.Type, t2 reflect.Type) bool {
if t1.Kind() != t2.Kind() {
return true
}
if k := t1.Kind(); k == reflect.Interface || k == reflect.Struct {
return t1.NumMethod() > t2.NumMethod()
} else if k != reflect.Struct {
return false // non-structs goes last.
}
return true
}
// Struct keeps a record of a particular struct value injection.
// See `Container.Struct` and `mvc#Application.Handle` methods.
type Struct struct {
ptrType reflect.Type
ptrValue reflect.Value // the original ptr struct value.
elementType reflect.Type // the original struct type.
bindings []*binding // struct field bindings.
Container *Container
Singleton bool
}
type singletonStruct interface {
Singleton() bool
}
func isMarkedAsSingleton(structPtr any) bool {
if sing, ok := structPtr.(singletonStruct); ok && sing.Singleton() {
return true
}
return false
}
func makeStruct(structPtr interface{}, c *Container, partyParamsCount int) *Struct {
v := valueOf(structPtr)
typ := v.Type()
if typ.Kind() != reflect.Ptr || indirectType(typ).Kind() != reflect.Struct {
panic("binder: struct: should be a pointer to a struct value")
}
isSingleton := isMarkedAsSingleton(structPtr)
disablePayloadAutoBinding := c.DisablePayloadAutoBinding
enableStructDependents := c.EnableStructDependents
disableStructDynamicBindings := c.DisableStructDynamicBindings
if isSingleton {
disablePayloadAutoBinding = true
enableStructDependents = false
disableStructDynamicBindings = true
}
// get struct's fields bindings.
bindings := getBindingsForStruct(v, c.Dependencies, c.MarkExportedFieldsAsRequired, disablePayloadAutoBinding, enableStructDependents, c.DependencyMatcher, partyParamsCount, c.Sorter)
// length bindings of 0, means that it has no fields or all mapped deps are static.
// If static then Struct.Acquire will return the same "value" instance, otherwise it will create a new one.
singleton := true
elem := v.Elem()
// fmt.Printf("Service: %s, Bindings(%d):\n", typ, len(bindings))
for _, b := range bindings {
// fmt.Printf("* " + b.String() + "\n")
if b.Dependency.Static {
// Fill now.
input, err := b.Dependency.Handle(nil, b.Input)
if err != nil {
if err == ErrSeeOther {
continue
}
panic(err)
}
elem.FieldByIndex(b.Input.StructFieldIndex).Set(input)
} else if !b.Dependency.Static {
if disableStructDynamicBindings {
panic(fmt.Sprintf("binder: DisableStructDynamicBindings setting is set to true: dynamic binding found: %s", b.String()))
}
singleton = false
}
}
if isSingleton && !singleton {
panic(fmt.Sprintf("binder: Singleton setting is set to true but struct has dynamic bindings: %s", typ))
}
s := &Struct{
ptrValue: v,
ptrType: typ,
elementType: elem.Type(),
bindings: bindings,
Singleton: singleton,
}
isErrHandler := isErrorHandler(typ)
newContainer := c.Clone()
newContainer.fillReport(typ.String(), bindings)
// Add the controller dependency itself as func dependency but with a known type which should be explicit binding
// in order to keep its maximum priority.
newContainer.Register(s.Acquire).Explicitly().DestType = typ
newContainer.GetErrorHandler = func(ctx *context.Context) ErrorHandler {
if isErrHandler {
return ctx.Controller().Interface().(ErrorHandler)
}
return c.GetErrorHandler(ctx)
}
s.Container = newContainer
return s
}
// Acquire returns a struct value based on the request.
// If the dependencies are all static then these are already set-ed at the initialization of this Struct
// and the same struct value instance will be returned, ignoring the Context. Otherwise
// a new struct value with filled fields by its pre-calculated bindings will be returned instead.
func (s *Struct) Acquire(ctx *context.Context) (reflect.Value, error) {
if s.Singleton {
ctx.Values().Set(context.ControllerContextKey, s.ptrValue)
return s.ptrValue, nil
}
ctrl := ctx.Controller()
if ctrl.Kind() == reflect.Invalid ||
ctrl.Type() != s.ptrType /* in case of changing controller in the same request (see RouteOverlap feature) */ {
ctrl = reflect.New(s.elementType)
ctx.Values().Set(context.ControllerContextKey, ctrl)
elem := ctrl.Elem()
for _, b := range s.bindings {
input, err := b.Dependency.Handle(ctx, b.Input)
if err != nil {
if err == ErrSeeOther {
continue
}
s.Container.GetErrorHandler(ctx).HandleError(ctx, err)
if ctx.IsStopped() {
// return emptyValue, err
return ctrl, err
} // #1629
}
elem.FieldByIndex(b.Input.StructFieldIndex).Set(input)
}
}
return ctrl, nil
}
// MethodHandler accepts a "methodName" that should be a valid an exported
// method of the struct and returns its converted Handler.
//
// Second input is optional,
// even zero is a valid value and can resolve path parameters correctly if from root party.
func (s *Struct) MethodHandler(methodName string, paramsCount int) context.Handler {
m, ok := s.ptrValue.Type().MethodByName(methodName)
if !ok {
panic(fmt.Sprintf("struct: method: %s does not exist", methodName))
}
return makeHandler(m.Func, s.Container, paramsCount)
}