Skip to content

Commit d254d48

Browse files
committed
Add mvc.Application.EnableStructDependents() and app.ConfigureContainer().EnableStructDependents()
relative to: #2158
1 parent 6add1ba commit d254d48

11 files changed

+85
-34
lines changed

HISTORY.md

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
2323

2424
Changes apply to `master` branch.
2525

26+
- Add `mvc.Application.EnableStructDependents()` method to handle [#2158](https://github.com/kataras/iris/issues/2158).
27+
2628
- Fix [iris-premium#17](https://github.com/kataras/iris-premium/issues/17).
2729

2830
- Replace [russross/blackfriday](github.com/russross/blackfriday/v2) with [gomarkdown](https://github.com/gomarkdown/markdown) as requested at [#2098](https://github.com/kataras/iris/issues/2098).

core/router/api_container.go

+8
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,14 @@ func (api *APIContainer) EnableStrictMode(strictMode bool) *APIContainer {
9595
return api
9696
}
9797

98+
// EnableStructDependents sets the container's EnableStructDependents to true.
99+
// It's used to automatically fill the dependencies of a struct's fields
100+
// based on the previous registered dependencies, just like function inputs.
101+
func (api *APIContainer) EnableStructDependents() *APIContainer {
102+
api.Container.EnableStructDependents = true
103+
return api
104+
}
105+
98106
// SetDependencyMatcher replaces the function that compares equality between
99107
// a dependency and an input (struct field or function parameter).
100108
//

go.mod

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ require (
1515
github.com/fatih/structs v1.1.0
1616
github.com/flosch/pongo2/v4 v4.0.2
1717
github.com/golang/snappy v0.0.4
18-
github.com/gomarkdown/markdown v0.0.0-20230313173142-2ced44d5b584
18+
github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12
1919
github.com/google/uuid v1.3.0
2020
github.com/gorilla/securecookie v1.1.1
2121
github.com/iris-contrib/httpexpect/v2 v2.12.1
@@ -72,13 +72,13 @@ require (
7272
github.com/iris-contrib/go.uuid v2.0.0+incompatible // indirect
7373
github.com/josharian/intern v1.0.0 // indirect
7474
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
75-
github.com/mattn/go-isatty v0.0.17 // indirect
75+
github.com/mattn/go-isatty v0.0.19 // indirect
7676
github.com/mediocregopher/radix/v3 v3.8.1 // indirect
7777
github.com/minio/highwayhash v1.0.2 // indirect
7878
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
7979
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
8080
github.com/modern-go/reflect2 v1.0.2 // indirect
81-
github.com/nats-io/jwt/v2 v2.4.0 // indirect
81+
github.com/nats-io/jwt/v2 v2.4.1 // indirect
8282
github.com/nats-io/nats.go v1.23.0 // indirect
8383
github.com/nats-io/nkeys v0.4.4 // indirect
8484
github.com/nats-io/nuid v1.0.1 // indirect

go.sum

+6-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

hero/binding.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ func getBindingsForFunc(fn reflect.Value, dependencies []*Dependency, disablePay
291291
return bindings
292292
}
293293

294-
func getBindingsForStruct(v reflect.Value, dependencies []*Dependency, markExportedFieldsAsRequired bool, disablePayloadAutoBinding bool, matchDependency DependencyMatcher, paramsCount int, sorter Sorter) (bindings []*binding) {
294+
func getBindingsForStruct(v reflect.Value, dependencies []*Dependency, markExportedFieldsAsRequired bool, disablePayloadAutoBinding, enableStructDependents bool, matchDependency DependencyMatcher, paramsCount int, sorter Sorter) (bindings []*binding) {
295295
typ := indirectType(v.Type())
296296
if typ.Kind() != reflect.Struct {
297297
panic(fmt.Sprintf("bindings: unresolved: not a struct type: %#+v", v))
@@ -303,7 +303,7 @@ func getBindingsForStruct(v reflect.Value, dependencies []*Dependency, markExpor
303303
for _, f := range nonZero {
304304
// fmt.Printf("Controller [%s] | NonZero | Field Index: %v | Field Type: %s\n", typ, f.Index, f.Type)
305305
bindings = append(bindings, &binding{
306-
Dependency: newDependency(elem.FieldByIndex(f.Index).Interface(), disablePayloadAutoBinding, nil),
306+
Dependency: newDependency(elem.FieldByIndex(f.Index).Interface(), disablePayloadAutoBinding, enableStructDependents, nil),
307307
Input: newStructFieldInput(f),
308308
})
309309
}

hero/binding_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ func TestBindingsForStruct(t *testing.T) {
524524
}
525525

526526
for i, tt := range tests {
527-
bindings := getBindingsForStruct(reflect.ValueOf(tt.Value), tt.Registered, false, false, DefaultDependencyMatcher, 0, nil)
527+
bindings := getBindingsForStruct(reflect.ValueOf(tt.Value), tt.Registered, false, false, false, DefaultDependencyMatcher, 0, nil)
528528

529529
if expected, got := len(tt.Expected), len(bindings); expected != got {
530530
t.Logf("[%d] expected bindings length to be: %d but got: %d:\n", i, expected, got)
@@ -565,5 +565,5 @@ func TestBindingsForStructMarkExportedFieldsAsRequred(t *testing.T) {
565565
}
566566

567567
// should panic if fail.
568-
_ = getBindingsForStruct(reflect.ValueOf(new(controller)), dependencies, true, true, DefaultDependencyMatcher, 0, nil)
568+
_ = getBindingsForStruct(reflect.ValueOf(new(controller)), dependencies, true, true, false, DefaultDependencyMatcher, 0, nil)
569569
}

hero/container.go

+21-13
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ type Container struct {
5757
// if at least one input binding depends on the request and not in a static structure.
5858
DisableStructDynamicBindings bool
5959

60+
// StructDependents if true then the Container will try to resolve
61+
// the fields of a struct value, if any, when it's a dependent struct value
62+
// based on the previous registered dependencies.
63+
//
64+
// Defaults to false.
65+
EnableStructDependents bool // this can be renamed to IndirectDependencies?.
66+
6067
// DependencyMatcher holds the function that compares equality between
6168
// a dependency with an input. Defaults to DefaultMatchDependencyFunc.
6269
DependencyMatcher DependencyMatcher
@@ -146,11 +153,11 @@ func (c *Container) fillReport(fullName string, bindings []*binding) {
146153
// Contains the iris context, standard context, iris sessions and time dependencies.
147154
var BuiltinDependencies = []*Dependency{
148155
// iris context dependency.
149-
newDependency(func(ctx *context.Context) *context.Context { return ctx }, true, nil).Explicitly(),
156+
newDependency(func(ctx *context.Context) *context.Context { return ctx }, true, false, nil).Explicitly(),
150157
// standard context dependency.
151158
newDependency(func(ctx *context.Context) stdContext.Context {
152159
return ctx.Request().Context()
153-
}, true, nil).Explicitly(),
160+
}, true, false, nil).Explicitly(),
154161
// iris session dependency.
155162
newDependency(func(ctx *context.Context) *sessions.Session {
156163
session := sessions.Get(ctx)
@@ -163,43 +170,43 @@ var BuiltinDependencies = []*Dependency{
163170
}
164171

165172
return session
166-
}, true, nil).Explicitly(),
173+
}, true, false, nil).Explicitly(),
167174
// application's logger.
168175
newDependency(func(ctx *context.Context) *golog.Logger {
169176
return ctx.Application().Logger()
170-
}, true, nil).Explicitly(),
177+
}, true, false, nil).Explicitly(),
171178
// time.Time to time.Now dependency.
172179
newDependency(func(ctx *context.Context) time.Time {
173180
return time.Now()
174-
}, true, nil).Explicitly(),
181+
}, true, false, nil).Explicitly(),
175182
// standard http Request dependency.
176183
newDependency(func(ctx *context.Context) *http.Request {
177184
return ctx.Request()
178-
}, true, nil).Explicitly(),
185+
}, true, false, nil).Explicitly(),
179186
// standard http ResponseWriter dependency.
180187
newDependency(func(ctx *context.Context) http.ResponseWriter {
181188
return ctx.ResponseWriter()
182-
}, true, nil).Explicitly(),
189+
}, true, false, nil).Explicitly(),
183190
// http headers dependency.
184191
newDependency(func(ctx *context.Context) http.Header {
185192
return ctx.Request().Header
186-
}, true, nil).Explicitly(),
193+
}, true, false, nil).Explicitly(),
187194
// Client IP.
188195
newDependency(func(ctx *context.Context) net.IP {
189196
return net.ParseIP(ctx.RemoteAddr())
190-
}, true, nil).Explicitly(),
197+
}, true, false, nil).Explicitly(),
191198
// Status Code (special type for MVC HTTP Error handler to not conflict with path parameters)
192199
newDependency(func(ctx *context.Context) Code {
193200
return Code(ctx.GetStatusCode())
194-
}, true, nil).Explicitly(),
201+
}, true, false, nil).Explicitly(),
195202
// Context Error. May be nil
196203
newDependency(func(ctx *context.Context) Err {
197204
err := ctx.GetErr()
198205
if err == nil {
199206
return nil
200207
}
201208
return err
202-
}, true, nil).Explicitly(),
209+
}, true, false, nil).Explicitly(),
203210
// Context User, e.g. from basic authentication.
204211
newDependency(func(ctx *context.Context) context.User {
205212
u := ctx.User()
@@ -208,7 +215,7 @@ var BuiltinDependencies = []*Dependency{
208215
}
209216

210217
return u
211-
}, true, nil),
218+
}, true, false, nil),
212219
// payload and param bindings are dynamically allocated and declared at the end of the `binding` source file.
213220
}
214221

@@ -254,6 +261,7 @@ func (c *Container) Clone() *Container {
254261
cloned.Dependencies = clonedDeps
255262
cloned.DisablePayloadAutoBinding = c.DisablePayloadAutoBinding
256263
cloned.DisableStructDynamicBindings = c.DisableStructDynamicBindings
264+
cloned.EnableStructDependents = c.EnableStructDependents
257265
cloned.MarkExportedFieldsAsRequired = c.MarkExportedFieldsAsRequired
258266
cloned.resultHandlers = c.resultHandlers
259267
// Reports are not cloned.
@@ -291,7 +299,7 @@ func Register(dependency interface{}) *Dependency {
291299
// - Register(func(ctx iris.Context) User {...})
292300
// - Register(func(User) OtherResponse {...})
293301
func (c *Container) Register(dependency interface{}) *Dependency {
294-
d := newDependency(dependency, c.DisablePayloadAutoBinding, c.DependencyMatcher, c.Dependencies...)
302+
d := newDependency(dependency, c.DisablePayloadAutoBinding, c.EnableStructDependents, c.DependencyMatcher, c.Dependencies...)
295303
if d.DestType == nil {
296304
// prepend the dynamic dependency so it will be tried at the end
297305
// (we don't care about performance here, design-time)

hero/dependency.go

+28-6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ type (
3838

3939
// Match holds the matcher. Defaults to the Container's one.
4040
Match DependencyMatchFunc
41+
42+
// StructDependents if true then the Container will try to resolve
43+
// the fields of a struct value, if any, when it's a dependent struct value
44+
// based on the previous registered dependencies.
45+
//
46+
// Defaults to false.
47+
StructDependents bool
4148
}
4249
)
4350

@@ -50,6 +57,12 @@ func (d *Dependency) Explicitly() *Dependency {
5057
return d
5158
}
5259

60+
// EnableStructDependents sets StructDependents to true.
61+
func (d *Dependency) EnableStructDependents() *Dependency {
62+
d.StructDependents = true
63+
return d
64+
}
65+
5366
func (d *Dependency) String() string {
5467
sourceLine := d.Source.String()
5568
val := d.OriginalValue
@@ -63,10 +76,16 @@ func (d *Dependency) String() string {
6376
//
6477
// See `Container.Handler` for more.
6578
func NewDependency(dependency interface{}, funcDependencies ...*Dependency) *Dependency { // used only on tests.
66-
return newDependency(dependency, false, nil, funcDependencies...)
79+
return newDependency(dependency, false, false, nil, funcDependencies...)
6780
}
6881

69-
func newDependency(dependency interface{}, disablePayloadAutoBinding bool, matchDependency DependencyMatcher, funcDependencies ...*Dependency) *Dependency {
82+
func newDependency(
83+
dependency interface{},
84+
disablePayloadAutoBinding bool,
85+
enableStructDependents bool,
86+
matchDependency DependencyMatcher,
87+
funcDependencies ...*Dependency,
88+
) *Dependency {
7089
if dependency == nil {
7190
panic(fmt.Sprintf("bad value: nil: %T", dependency))
7291
}
@@ -86,8 +105,9 @@ func newDependency(dependency interface{}, disablePayloadAutoBinding bool, match
86105
}
87106

88107
dest := &Dependency{
89-
Source: newSource(v),
90-
OriginalValue: dependency,
108+
Source: newSource(v),
109+
OriginalValue: dependency,
110+
StructDependents: enableStructDependents,
91111
}
92112
dest.Match = ToDependencyMatchFunc(dest, matchDependency)
93113

@@ -171,7 +191,7 @@ func fromStructValueOrDependentStructValue(v reflect.Value, disablePayloadAutoBi
171191
return false
172192
}
173193

174-
if len(prevDependencies) == 0 { // As a non depedent struct.
194+
if len(prevDependencies) == 0 || !dest.StructDependents { // As a non depedent struct.
175195
// We must make this check so we can avoid the auto-filling of
176196
// the dependencies from Iris builtin dependencies.
177197
return fromStructValue(v, dest)
@@ -180,11 +200,13 @@ func fromStructValueOrDependentStructValue(v reflect.Value, disablePayloadAutoBi
180200
// Check if it's a builtin dependency (e.g an MVC Application (see mvc.go#newApp)),
181201
// if it's and registered without a Dependency wrapper, like the rest builtin dependencies,
182202
// then do NOT try to resolve its fields.
203+
//
204+
// Although EnableStructDependents is false by default, we must check if it's a builtin dependency for any case.
183205
if strings.HasPrefix(indirectType(v.Type()).PkgPath(), "github.com/kataras/iris/v12") {
184206
return fromStructValue(v, dest)
185207
}
186208

187-
bindings := getBindingsForStruct(v, prevDependencies, false, disablePayloadAutoBinding, DefaultDependencyMatcher, -1, nil)
209+
bindings := getBindingsForStruct(v, prevDependencies, false, disablePayloadAutoBinding, dest.StructDependents, DefaultDependencyMatcher, -1, nil)
188210
if len(bindings) == 0 {
189211
return fromStructValue(v, dest) // same as above.
190212
}

hero/reflect.go

+4
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ func lookupFields(elem reflect.Value, skipUnexported bool, onlyZeros bool, paren
154154
// Note: embedded pointers are not supported.
155155
// elem = reflect.Indirect(elem)
156156
elemTyp := elem.Type()
157+
if elemTyp.Kind() == reflect.Pointer {
158+
return
159+
}
160+
157161
for i, n := 0, elem.NumField(); i < n; i++ {
158162
field := elemTyp.Field(i)
159163
fieldValue := elem.Field(i)

hero/struct.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func makeStruct(structPtr interface{}, c *Container, partyParamsCount int) *Stru
5151
}
5252

5353
// get struct's fields bindings.
54-
bindings := getBindingsForStruct(v, c.Dependencies, c.MarkExportedFieldsAsRequired, c.DisablePayloadAutoBinding, c.DependencyMatcher, partyParamsCount, c.Sorter)
54+
bindings := getBindingsForStruct(v, c.Dependencies, c.MarkExportedFieldsAsRequired, c.DisablePayloadAutoBinding, c.EnableStructDependents, c.DependencyMatcher, partyParamsCount, c.Sorter)
5555

5656
// length bindings of 0, means that it has no fields or all mapped deps are static.
5757
// If static then Struct.Acquire will return the same "value" instance, otherwise it will create a new one.

mvc/mvc.go

+8
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ func (app *Application) SetControllersNoLog(disable bool) *Application {
142142
return app
143143
}
144144

145+
// EnableStructDependents will try to resolve
146+
// the fields of a struct value, if any, when it's a dependent struct value
147+
// based on the previous registered dependencies.
148+
func (app *Application) EnableStructDependents() *Application {
149+
app.container.EnableStructDependents = true
150+
return app
151+
}
152+
145153
// Register appends one or more values as dependencies.
146154
// The value can be a single struct value-instance or a function
147155
// which has one input and one output, the input should be

0 commit comments

Comments
 (0)