-
Notifications
You must be signed in to change notification settings - Fork 333
Description
Describe the bug
When trying to inject a struct that is part of a group (tagged via ResultTags) singularily, fx cannot find the requested dependency. Let's say we have two interfaces: GameFetcher and UserFetcher, both embedding the Fetcher interface.
Both implementations are provided as part of the fetchers group, annotated with both interfaces:
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
https://gist.github.com/giovannizotta/d74a5ccfa28208ad2f582590d10b605c
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:
[Fx] PROVIDE main.Fetcher[group = "fetchers"] <= fx.Annotate(main.newGameFetcher(), fx.ResultTags(["group:\"fetchers\""]), fx.As([[main.Fetcher] [main.GameFetcher]])
[Fx] PROVIDE main.GameFetcher[group = "fetchers"] <= fx.Annotate(main.newGameFetcher(), fx.ResultTags(["group:\"fetchers\""]), fx.As([[main.Fetcher] [main.GameFetcher]])
[Fx] PROVIDE main.Fetcher[group = "fetchers"] <= fx.Annotate(main.newUserFetcher(), fx.ResultTags(["group:\"fetchers\""]), fx.As([[main.Fetcher] [main.UserFetcher]])
[Fx] PROVIDE main.UserFetcher[group = "fetchers"] <= fx.Annotate(main.newUserFetcher(), fx.ResultTags(["group:\"fetchers\""]), fx.As([[main.Fetcher] [main.UserFetcher]])
[Fx] PROVIDE *main.StructUsingAllFetchers <= fx.Annotate(main.NewStructUsingAllFetchers(), fx.ParamTags(["group:\"fetchers\""])
[Fx] PROVIDE fx.Lifecycle <= go.uber.org/fx.New.func1()
[Fx] PROVIDE fx.Shutdowner <= go.uber.org/fx.(*App).shutdowner-fm()
[Fx] PROVIDE fx.DotGraph <= go.uber.org/fx.(*App).dotGraph-fm()
[Fx] INVOKE main.main.func1()
[Fx] RUN provide: fx.Annotate(main.newGameFetcher(), fx.ResultTags(["group:\"fetchers\""]), fx.As([[main.Fetcher] [main.GameFetcher]])
[Fx] RUN provide: fx.Annotate(main.newUserFetcher(), fx.ResultTags(["group:\"fetchers\""]), fx.As([[main.Fetcher] [main.UserFetcher]])
[Fx] RUN provide: fx.Annotate(main.NewStructUsingAllFetchers(), fx.ParamTags(["group:\"fetchers\""])
[Fx] INVOKE main.main.func2()
[Fx] ERROR fx.Invoke(main.main.func2()) called from:
main.main
main.go:59
runtime.main
/opt/homebrew/Cellar/go/1.21.5/libexec/src/runtime/proc.go:267
Failed: missing dependencies for function "main".main.func2
main.go:59:
missing type:
- main.GameFetcher (did you mean to use one of *main.StructUsingAllFetchers, fx.DotGraph, fx.Lifecycle, or fx.Shutdowner?)
[Fx] ERROR Failed to start: missing dependencies for function "main".main.func2
main.go:59:
missing type:
- main.GameFetcher (did you mean to use one of *main.StructUsingAllFetchers, fx.DotGraph, fx.Lifecycle, or fx.Shutdowner?)
exit status 1
Expected behavior
I would expect the second invoked function to be injected with the GameFetcher, since fx provided it earlier:
[Fx] PROVIDE main.GameFetcher[group = "fetchers"] <= fx.Annotate(main.newGameFetcher(), fx.ResultTags(["group:\"fetchers\""]), fx.As([[main.Fetcher] [main.GameFetcher]])
Additional context
I found it possible to achieve what I describe in another way, by injecting a slice of the specific elements []GameFetcher:
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 GameFetcher. It could very well be that I'm doing things wrong and I'm missing something, please let me know if there is a better way to do this!