From bf8c0da179c692935996695005160d8f2d701ab4 Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Wed, 6 Nov 2024 09:40:50 -0600 Subject: [PATCH] feat(depinject): silently ignore private fields on In structs (#22438) Co-authored-by: Julien Robert --- depinject/CHANGELOG.md | 4 ++++ depinject/binding_test.go | 13 +++++++++++++ depinject/container.go | 3 +++ depinject/container_test.go | 3 +-- depinject/provider_desc.go | 1 + depinject/struct_args.go | 11 +++++++---- 6 files changed, 29 insertions(+), 6 deletions(-) diff --git a/depinject/CHANGELOG.md b/depinject/CHANGELOG.md index 4b308e63a3e9..dfbafdd21103 100644 --- a/depinject/CHANGELOG.md +++ b/depinject/CHANGELOG.md @@ -22,6 +22,10 @@ Each entry must include the Github issue reference in the following format: ## [Unreleased] +## 1.1.0 + +* [#22438](https://github.com/cosmos/cosmos-sdk/pull/22438) Unexported fields on `In` structs are now silently ignored instead of failing. + ## 1.0.0 * [#20540](https://github.com/cosmos/cosmos-sdk/pull/20540) Add support for defining `appconfig` module configuration types using `github.com/cosmos/gogoproto/proto` in addition to `google.golang.org/protobuf` so that users can use gogo proto across their stack. diff --git a/depinject/binding_test.go b/depinject/binding_test.go index 19decc989c56..3c90ec5e2e7b 100644 --- a/depinject/binding_test.go +++ b/depinject/binding_test.go @@ -300,3 +300,16 @@ func TestBindingInterfaceTwoModuleScopedAndGlobalBinding(t *testing.T) { IsResolvedModuleScope(t, pond, moduleC, "Marbled") IsResolvedInGlobalScope(t, pond, "Marbled") } + +func TestIgnoredField(t *testing.T) { + t.Parallel() + cfg := struct { + depinject.In + TheDuck Duck + privateField bool + AnotherDuck Duck + }{} + + err := depinject.Inject(depinject.Provide(ProvideMallard), &cfg) + require.NoError(t, err) +} diff --git a/depinject/container.go b/depinject/container.go index 67772acdbca2..948a4b545cac 100644 --- a/depinject/container.go +++ b/depinject/container.go @@ -71,6 +71,9 @@ func (c *container) call(provider *providerDescriptor, moduleKey *moduleKey) ([] c.indentLogger() inVals := make([]reflect.Value, len(provider.Inputs)) for i, in := range provider.Inputs { + if in.Ignored { + continue + } val, err := c.resolve(in, moduleKey, loc) if err != nil { return nil, err diff --git a/depinject/container_test.go b/depinject/container_test.go index dc4a9291e370..925ae44e3048 100644 --- a/depinject/container_test.go +++ b/depinject/container_test.go @@ -213,7 +213,7 @@ func TestUnexportedField(t *testing.T) { "depinject.Out struct", ) - require.ErrorContains(t, + require.NoError(t, depinject.Inject( scenarioConfigDependency, &handlers, @@ -221,7 +221,6 @@ func TestUnexportedField(t *testing.T) { &a, &c, ), - "depinject.In struct", ) require.ErrorContains(t, diff --git a/depinject/provider_desc.go b/depinject/provider_desc.go index f17537ccb521..2e6c612eeffe 100644 --- a/depinject/provider_desc.go +++ b/depinject/provider_desc.go @@ -32,6 +32,7 @@ type providerDescriptor struct { type providerInput struct { Type reflect.Type Optional bool + Ignored bool } type providerOutput struct { diff --git a/depinject/struct_args.go b/depinject/struct_args.go index 16e4c7cc5a9c..312070948079 100644 --- a/depinject/struct_args.go +++ b/depinject/struct_args.go @@ -8,7 +8,7 @@ import ( // In can be embedded in another struct to inform the container that the // fields of the struct should be treated as dependency inputs. // This allows a struct to be used to specify dependencies rather than -// positional parameters. +// positional parameters. Unexpected fields will be ignored. // // Fields of the struct may support the following tags: // @@ -126,6 +126,7 @@ func structArgsInTypes(typ reflect.Type) ([]providerInput, error) { res = append(res, providerInput{ Type: f.Type, Optional: optional, + Ignored: !f.IsExported(), }) } return res, nil @@ -166,13 +167,15 @@ func buildIn(typ reflect.Type, values []reflect.Value) (reflect.Value, int, erro j := 0 res := reflect.New(typ) for i := 0; i < numFields; i++ { + if !res.Elem().Field(i).CanSet() { + // private field, skip + j++ + continue + } f := typ.Field(i) if f.Type.AssignableTo(isInType) { continue } - if !res.Elem().Field(i).CanSet() { - return reflect.Value{}, 0, fmt.Errorf("depinject.In struct %s on package %s can't have unexported field", res.Elem().String(), f.PkgPath) - } if !values[j].CanInterface() { return reflect.Value{}, 0, fmt.Errorf("depinject.Out struct %s on package %s can't have unexported field", res.Elem().String(), f.PkgPath) }