Skip to content

Commit

Permalink
Add mvc.Application.EnableStructDependents() and app.ConfigureContain…
Browse files Browse the repository at this point in the history
…er().EnableStructDependents()

relative to: #2158
  • Loading branch information
kataras committed Jul 17, 2023
1 parent 6add1ba commit d254d48
Show file tree
Hide file tree
Showing 11 changed files with 85 additions and 34 deletions.
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene

Changes apply to `master` branch.

- Add `mvc.Application.EnableStructDependents()` method to handle [#2158](https://github.com/kataras/iris/issues/2158).

- Fix [iris-premium#17](https://github.com/kataras/iris-premium/issues/17).

- 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).
Expand Down
8 changes: 8 additions & 0 deletions core/router/api_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ func (api *APIContainer) EnableStrictMode(strictMode bool) *APIContainer {
return api
}

// EnableStructDependents sets the container's EnableStructDependents to true.
// It's used to automatically fill the dependencies of a struct's fields
// based on the previous registered dependencies, just like function inputs.
func (api *APIContainer) EnableStructDependents() *APIContainer {
api.Container.EnableStructDependents = true
return api
}

// SetDependencyMatcher replaces the function that compares equality between
// a dependency and an input (struct field or function parameter).
//
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/fatih/structs v1.1.0
github.com/flosch/pongo2/v4 v4.0.2
github.com/golang/snappy v0.0.4
github.com/gomarkdown/markdown v0.0.0-20230313173142-2ced44d5b584
github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12
github.com/google/uuid v1.3.0
github.com/gorilla/securecookie v1.1.1
github.com/iris-contrib/httpexpect/v2 v2.12.1
Expand Down Expand Up @@ -72,13 +72,13 @@ require (
github.com/iris-contrib/go.uuid v2.0.0+incompatible // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mediocregopher/radix/v3 v3.8.1 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nats-io/jwt/v2 v2.4.0 // indirect
github.com/nats-io/jwt/v2 v2.4.1 // indirect
github.com/nats-io/nats.go v1.23.0 // indirect
github.com/nats-io/nkeys v0.4.4 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
Expand Down
13 changes: 6 additions & 7 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions hero/binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ func getBindingsForFunc(fn reflect.Value, dependencies []*Dependency, disablePay
return bindings
}

func getBindingsForStruct(v reflect.Value, dependencies []*Dependency, markExportedFieldsAsRequired bool, disablePayloadAutoBinding bool, matchDependency DependencyMatcher, paramsCount int, sorter Sorter) (bindings []*binding) {
func getBindingsForStruct(v reflect.Value, dependencies []*Dependency, markExportedFieldsAsRequired bool, disablePayloadAutoBinding, enableStructDependents bool, matchDependency DependencyMatcher, paramsCount int, sorter Sorter) (bindings []*binding) {
typ := indirectType(v.Type())
if typ.Kind() != reflect.Struct {
panic(fmt.Sprintf("bindings: unresolved: not a struct type: %#+v", v))
Expand All @@ -303,7 +303,7 @@ func getBindingsForStruct(v reflect.Value, dependencies []*Dependency, markExpor
for _, f := range nonZero {
// fmt.Printf("Controller [%s] | NonZero | Field Index: %v | Field Type: %s\n", typ, f.Index, f.Type)
bindings = append(bindings, &binding{
Dependency: newDependency(elem.FieldByIndex(f.Index).Interface(), disablePayloadAutoBinding, nil),
Dependency: newDependency(elem.FieldByIndex(f.Index).Interface(), disablePayloadAutoBinding, enableStructDependents, nil),
Input: newStructFieldInput(f),
})
}
Expand Down
4 changes: 2 additions & 2 deletions hero/binding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ func TestBindingsForStruct(t *testing.T) {
}

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

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

// should panic if fail.
_ = getBindingsForStruct(reflect.ValueOf(new(controller)), dependencies, true, true, DefaultDependencyMatcher, 0, nil)
_ = getBindingsForStruct(reflect.ValueOf(new(controller)), dependencies, true, true, false, DefaultDependencyMatcher, 0, nil)
}
34 changes: 21 additions & 13 deletions hero/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ type Container struct {
// if at least one input binding depends on the request and not in a static structure.
DisableStructDynamicBindings bool

// StructDependents if true then the Container will try to resolve
// the fields of a struct value, if any, when it's a dependent struct value
// based on the previous registered dependencies.
//
// Defaults to false.
EnableStructDependents bool // this can be renamed to IndirectDependencies?.

// DependencyMatcher holds the function that compares equality between
// a dependency with an input. Defaults to DefaultMatchDependencyFunc.
DependencyMatcher DependencyMatcher
Expand Down Expand Up @@ -146,11 +153,11 @@ func (c *Container) fillReport(fullName string, bindings []*binding) {
// Contains the iris context, standard context, iris sessions and time dependencies.
var BuiltinDependencies = []*Dependency{
// iris context dependency.
newDependency(func(ctx *context.Context) *context.Context { return ctx }, true, nil).Explicitly(),
newDependency(func(ctx *context.Context) *context.Context { return ctx }, true, false, nil).Explicitly(),
// standard context dependency.
newDependency(func(ctx *context.Context) stdContext.Context {
return ctx.Request().Context()
}, true, nil).Explicitly(),
}, true, false, nil).Explicitly(),
// iris session dependency.
newDependency(func(ctx *context.Context) *sessions.Session {
session := sessions.Get(ctx)
Expand All @@ -163,43 +170,43 @@ var BuiltinDependencies = []*Dependency{
}

return session
}, true, nil).Explicitly(),
}, true, false, nil).Explicitly(),
// application's logger.
newDependency(func(ctx *context.Context) *golog.Logger {
return ctx.Application().Logger()
}, true, nil).Explicitly(),
}, true, false, nil).Explicitly(),
// time.Time to time.Now dependency.
newDependency(func(ctx *context.Context) time.Time {
return time.Now()
}, true, nil).Explicitly(),
}, true, false, nil).Explicitly(),
// standard http Request dependency.
newDependency(func(ctx *context.Context) *http.Request {
return ctx.Request()
}, true, nil).Explicitly(),
}, true, false, nil).Explicitly(),
// standard http ResponseWriter dependency.
newDependency(func(ctx *context.Context) http.ResponseWriter {
return ctx.ResponseWriter()
}, true, nil).Explicitly(),
}, true, false, nil).Explicitly(),
// http headers dependency.
newDependency(func(ctx *context.Context) http.Header {
return ctx.Request().Header
}, true, nil).Explicitly(),
}, true, false, nil).Explicitly(),
// Client IP.
newDependency(func(ctx *context.Context) net.IP {
return net.ParseIP(ctx.RemoteAddr())
}, true, nil).Explicitly(),
}, true, false, nil).Explicitly(),
// Status Code (special type for MVC HTTP Error handler to not conflict with path parameters)
newDependency(func(ctx *context.Context) Code {
return Code(ctx.GetStatusCode())
}, true, nil).Explicitly(),
}, true, false, nil).Explicitly(),
// Context Error. May be nil
newDependency(func(ctx *context.Context) Err {
err := ctx.GetErr()
if err == nil {
return nil
}
return err
}, true, nil).Explicitly(),
}, true, false, nil).Explicitly(),
// Context User, e.g. from basic authentication.
newDependency(func(ctx *context.Context) context.User {
u := ctx.User()
Expand All @@ -208,7 +215,7 @@ var BuiltinDependencies = []*Dependency{
}

return u
}, true, nil),
}, true, false, nil),
// payload and param bindings are dynamically allocated and declared at the end of the `binding` source file.
}

Expand Down Expand Up @@ -254,6 +261,7 @@ func (c *Container) Clone() *Container {
cloned.Dependencies = clonedDeps
cloned.DisablePayloadAutoBinding = c.DisablePayloadAutoBinding
cloned.DisableStructDynamicBindings = c.DisableStructDynamicBindings
cloned.EnableStructDependents = c.EnableStructDependents
cloned.MarkExportedFieldsAsRequired = c.MarkExportedFieldsAsRequired
cloned.resultHandlers = c.resultHandlers
// Reports are not cloned.
Expand Down Expand Up @@ -291,7 +299,7 @@ func Register(dependency interface{}) *Dependency {
// - Register(func(ctx iris.Context) User {...})
// - Register(func(User) OtherResponse {...})
func (c *Container) Register(dependency interface{}) *Dependency {
d := newDependency(dependency, c.DisablePayloadAutoBinding, c.DependencyMatcher, c.Dependencies...)
d := newDependency(dependency, c.DisablePayloadAutoBinding, c.EnableStructDependents, c.DependencyMatcher, c.Dependencies...)
if d.DestType == nil {
// prepend the dynamic dependency so it will be tried at the end
// (we don't care about performance here, design-time)
Expand Down
34 changes: 28 additions & 6 deletions hero/dependency.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ type (

// Match holds the matcher. Defaults to the Container's one.
Match DependencyMatchFunc

// StructDependents if true then the Container will try to resolve
// the fields of a struct value, if any, when it's a dependent struct value
// based on the previous registered dependencies.
//
// Defaults to false.
StructDependents bool
}
)

Expand All @@ -50,6 +57,12 @@ func (d *Dependency) Explicitly() *Dependency {
return d
}

// EnableStructDependents sets StructDependents to true.
func (d *Dependency) EnableStructDependents() *Dependency {
d.StructDependents = true
return d
}

func (d *Dependency) String() string {
sourceLine := d.Source.String()
val := d.OriginalValue
Expand All @@ -63,10 +76,16 @@ func (d *Dependency) String() string {
//
// See `Container.Handler` for more.
func NewDependency(dependency interface{}, funcDependencies ...*Dependency) *Dependency { // used only on tests.
return newDependency(dependency, false, nil, funcDependencies...)
return newDependency(dependency, false, false, nil, funcDependencies...)
}

func newDependency(dependency interface{}, disablePayloadAutoBinding bool, matchDependency DependencyMatcher, funcDependencies ...*Dependency) *Dependency {
func newDependency(
dependency interface{},
disablePayloadAutoBinding bool,
enableStructDependents bool,
matchDependency DependencyMatcher,
funcDependencies ...*Dependency,
) *Dependency {
if dependency == nil {
panic(fmt.Sprintf("bad value: nil: %T", dependency))
}
Expand All @@ -86,8 +105,9 @@ func newDependency(dependency interface{}, disablePayloadAutoBinding bool, match
}

dest := &Dependency{
Source: newSource(v),
OriginalValue: dependency,
Source: newSource(v),
OriginalValue: dependency,
StructDependents: enableStructDependents,
}
dest.Match = ToDependencyMatchFunc(dest, matchDependency)

Expand Down Expand Up @@ -171,7 +191,7 @@ func fromStructValueOrDependentStructValue(v reflect.Value, disablePayloadAutoBi
return false
}

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

bindings := getBindingsForStruct(v, prevDependencies, false, disablePayloadAutoBinding, DefaultDependencyMatcher, -1, nil)
bindings := getBindingsForStruct(v, prevDependencies, false, disablePayloadAutoBinding, dest.StructDependents, DefaultDependencyMatcher, -1, nil)
if len(bindings) == 0 {
return fromStructValue(v, dest) // same as above.
}
Expand Down
4 changes: 4 additions & 0 deletions hero/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ func lookupFields(elem reflect.Value, skipUnexported bool, onlyZeros bool, paren
// Note: embedded pointers are not supported.
// elem = reflect.Indirect(elem)
elemTyp := elem.Type()
if elemTyp.Kind() == reflect.Pointer {
return
}

for i, n := 0, elem.NumField(); i < n; i++ {
field := elemTyp.Field(i)
fieldValue := elem.Field(i)
Expand Down
2 changes: 1 addition & 1 deletion hero/struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func makeStruct(structPtr interface{}, c *Container, partyParamsCount int) *Stru
}

// get struct's fields bindings.
bindings := getBindingsForStruct(v, c.Dependencies, c.MarkExportedFieldsAsRequired, c.DisablePayloadAutoBinding, c.DependencyMatcher, partyParamsCount, c.Sorter)
bindings := getBindingsForStruct(v, c.Dependencies, c.MarkExportedFieldsAsRequired, c.DisablePayloadAutoBinding, c.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.
Expand Down
8 changes: 8 additions & 0 deletions mvc/mvc.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@ func (app *Application) SetControllersNoLog(disable bool) *Application {
return app
}

// EnableStructDependents will try to resolve
// the fields of a struct value, if any, when it's a dependent struct value
// based on the previous registered dependencies.
func (app *Application) EnableStructDependents() *Application {
app.container.EnableStructDependents = true
return app
}

// Register appends one or more values as dependencies.
// The value can be a single struct value-instance or a function
// which has one input and one output, the input should be
Expand Down

0 comments on commit d254d48

Please sign in to comment.