diff --git a/HISTORY.md b/HISTORY.md index 9627f716..602c44a2 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -23,6 +23,12 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene Changes apply to `main` branch. +- Add optional `Singleton() bool` method to controllers to mark them as singleton, will panic with a specific error if a controller expects dynamic dependencies. This behavior is idendical to the app-driven `app.EnsureStaticBindings()`. + +- Non-zero fields of a controller that are marked as ignored, with `ignore:"true"` field tag, they are not included in the dependencies at all now. + +- Re-add error log on context rich write (e.g. JSON) failures when the application is running under debug mode (with `app.Logger().SetLevel("debug")`) and there is no a registered context error handler at place. + - `master` branch finally renamed to `main`. Don't worry GitHub will still navigate any `master` request to `main` automatically. Examples, Documentation and other Pages are refactored too. # Sat, 12 Aug 2023 | v12.2.4 diff --git a/context/context.go b/context/context.go index 97bdd91e..df9ccf20 100644 --- a/context/context.go +++ b/context/context.go @@ -4213,6 +4213,9 @@ func (ctx *Context) handleContextError(err error) { if errHandler := ctx.app.GetContextErrorHandler(); errHandler != nil { errHandler.HandleContextError(ctx, err) } else { + if ctx.IsDebug() { + ctx.app.Logger().Error(err) + } ctx.StatusCode(http.StatusInternalServerError) } @@ -4258,8 +4261,8 @@ func (ctx *Context) JSON(v interface{}, opts ...JSON) (err error) { } if err = ctx.writeJSON(v, options); err != nil { - // if no options are given or OmitErrorHandler is true - // then do call the error handler (which may lead to a cycle). + // if no options are given or OmitErrorHandler is false + // then call the error handler (which may lead to a cycle if the error handler fails to write JSON too). if !options.OmitErrorHandler { ctx.handleContextError(err) } diff --git a/hero/reflect.go b/hero/reflect.go index d214f01b..c54d398f 100644 --- a/hero/reflect.go +++ b/hero/reflect.go @@ -201,6 +201,9 @@ func lookupFields(elem reflect.Value, skipUnexported bool, onlyZeros bool, paren func lookupNonZeroFieldValues(elem reflect.Value) (nonZeroFields []reflect.StructField) { fields, _ := lookupFields(elem, true, false, nil) for _, f := range fields { + if structFieldIgnored(f) { + continue // re-check here for ignored struct fields so we don't include them on dependencies. Non-zeroes fields can be static, even if they are functions. + } if fieldVal := elem.FieldByIndex(f.Index); goodVal(fieldVal) && !isZero(fieldVal) { /* && f.Type.Kind() == reflect.Ptr &&*/ nonZeroFields = append(nonZeroFields, f) diff --git a/hero/struct.go b/hero/struct.go index 30f6eba8..85a7e0de 100644 --- a/hero/struct.go +++ b/hero/struct.go @@ -43,6 +43,18 @@ type Struct struct { 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() @@ -50,8 +62,19 @@ func makeStruct(structPtr interface{}, c *Container, partyParamsCount int) *Stru 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, c.DisablePayloadAutoBinding, c.EnableStructDependents, c.DependencyMatcher, partyParamsCount, c.Sorter) + 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. @@ -73,7 +96,7 @@ func makeStruct(structPtr interface{}, c *Container, partyParamsCount int) *Stru elem.FieldByIndex(b.Input.StructFieldIndex).Set(input) } else if !b.Dependency.Static { - if c.DisableStructDynamicBindings { + if disableStructDynamicBindings { panic(fmt.Sprintf("binder: DisableStructDynamicBindings setting is set to true: dynamic binding found: %s", b.String())) } @@ -81,6 +104,10 @@ func makeStruct(structPtr interface{}, c *Container, partyParamsCount int) *Stru } } + 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,