Impossible to inject struct that is part of a group by itself #1184
-
Describe the bug Both implementations are provided as part of the func AsFetcher(f any, fetcherType any) any {
return fx.Annotate(
f,
fx.As(new(Fetcher)),
fx.As(fetcherType),
fx.ResultTags(`group:"fetchers"`),
)
}
...
fx.Provide(
AsFetcher(newGameFetcher, new(GameFetcher)),
AsFetcher(newUserFetcher, new(UserFetcher)),
) I am not sure whether this is a bug or whether I'm doing things wrong, but I'd appreciate some guidance. To Reproduce package main
import (
"go.uber.org/fx"
)
type Fetcher interface{}
type GameFetcher interface {
Fetcher
}
type UserFetcher interface {
Fetcher
}
type GameFetcherImpl struct{}
type UserFetcherImpl struct{}
func newUserFetcher() *UserFetcherImpl {
return &UserFetcherImpl{}
}
func newGameFetcher() *GameFetcherImpl {
return &GameFetcherImpl{}
}
type StructUsingAllFetchers struct {
fetchers []Fetcher
}
func NewStructUsingAllFetchers(fetchers []Fetcher) *StructUsingAllFetchers {
return &StructUsingAllFetchers{
fetchers: fetchers,
}
}
func AsFetcher(f any, fetcherType any) any {
return fx.Annotate(
f,
fx.As(new(Fetcher)),
fx.As(fetcherType),
fx.ResultTags(`group:"fetchers"`),
)
}
func main() {
fx.New(
fx.Provide(
AsFetcher(newGameFetcher, new(GameFetcher)),
AsFetcher(newUserFetcher, new(UserFetcher)),
fx.Annotate(
NewStructUsingAllFetchers,
fx.ParamTags(`group:"fetchers"`),
),
),
fx.Invoke(func(s *StructUsingAllFetchers) {}),
fx.Invoke(func(gameFetcher GameFetcher) {}),
).Run()
} Output:
Expected behavior
Additional context package main
import (
"go.uber.org/fx"
)
type Fetcher interface {
Fetch()
}
type GameFetcher interface {
Fetcher
}
type UserFetcher interface {
Fetcher
}
type GameFetcherImpl struct{}
type UserFetcherImpl struct{}
func (g *GameFetcherImpl) Fetch() {}
func (u *UserFetcherImpl) Fetch() {}
func newUserFetcher() *UserFetcherImpl {
return &UserFetcherImpl{}
}
func newGameFetcher() *GameFetcherImpl {
return &GameFetcherImpl{}
}
type StructUsingAllFetchers struct {
fetchers []Fetcher
}
func NewStructUsingAllFetchers(fetchers []Fetcher) *StructUsingAllFetchers {
return &StructUsingAllFetchers{
fetchers: fetchers,
}
}
type StructUsingSpecificFetcher struct {
gameFetcher GameFetcher
}
func NewStructUsingSpecificFetcher(f []GameFetcher) *StructUsingSpecificFetcher {
if len(f) != 1 {
panic("expected 1 fetcher")
}
return &StructUsingSpecificFetcher{
gameFetcher: f[0],
}
}
func AsFetcher(f any, fetcherType any) any {
return fx.Annotate(
f,
fx.As(new(Fetcher)),
fx.As(fetcherType),
fx.ResultTags(`group:"fetchers"`),
)
}
func main() {
fx.New(
fx.Provide(
AsFetcher(newGameFetcher, new(GameFetcher)),
AsFetcher(newUserFetcher, new(UserFetcher)),
fx.Annotate(
NewStructUsingAllFetchers,
fx.ParamTags(`group:"fetchers"`),
),
fx.Annotate(
NewStructUsingSpecificFetcher,
fx.ParamTags(`group:"fetchers"`),
),
),
fx.Invoke(func(s *StructUsingAllFetchers) {}),
fx.Invoke(func(s *StructUsingSpecificFetcher) {}),
).Run()
} However, I find this undesirable because I know there will always be one element in the slice, even though I am forced to provide a slice of |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments
-
Hey @giovannizotta, thanks for the issue! Other maintainers please correct me if I'm wrong but: I think the problem is that from Fx's perspective, types annotated with group or name tags are considered their own distinct types. Looking at the logs from the example you gave,
The current code is providing the result of The way to get around this right now is to provide the same instance but both annotated w/ the group tag and unannotated. For example, by returning a result struct from the fetcher constructors like: type GameFetcherResult struct {
fx.Out
GameFetcher GameFetcher
Fetcher Fetcher `group:"fetchers"`
}
func newGameFetcher() GameFetcherResult {
res := &GameFetcherImpl{}
return GameFetcherResult{
GameFetcher: res,
Fetcher: res,
}
} (runnable/error-free example: https://go.dev/play/p/2Mh7-kVqP10) As a side note, this is somewhat related to these issues - #998, #1036 - in that Fx does not support a clean way to both name an instance and place it in a group, and be able to depend elsewhere on either the individual object or the group. There is discussion/WIP to allow annotating a type w/ both a name and a group tag, and be able to retrieve either the entire group, or one specific item (uber-go/dig#381). With this, you could annotate the result of newGameFetcher w/ something like:
and then be able to get both the group and the individual game fetcher. |
Beta Was this translation helpful? Give feedback.
-
Thank you @JacobOaks the quick response and for the explanation!
This is indeed what I was missing. The rest all makes sense, thanks for the information, I'll stay on the lookout for a better way to achieve this in the future! |
Beta Was this translation helpful? Give feedback.
-
Hey @giovannizotta - converting the issue into a discussion since my reply resolved your issue and in case anybody else has thoughts to give on the topic. |
Beta Was this translation helpful? Give feedback.
Hey @giovannizotta, thanks for the issue!
Other maintainers please correct me if I'm wrong but: I think the problem is that from Fx's perspective, types annotated with group or name tags are considered their own distinct types. Looking at the logs from the example you gave,
The current code is providing the result of
newGameFetcher
into the group ofFetcher
s…