uber-go/fx

Ability to fx.Populate a named value

jkanywhere opened this issue · 6 comments

Is your feature request related to a problem? Please describe.

fx.Populate makes it very easy to extract a value of a given type during testing.
It is less easy to use when the value is named.

Given a module that fx.Provides the following Result

package examplefx

type Result struct {
    fx.Out

    S string `name:"special"`
}

var Module := fx.Provide(
    func(...) Result { ... },
)

If we want to test the module by seeing what value it provides, this does not work:

package examplefx_test

var s string
...
fx.Populate(&s)

Describe the solution you'd like

I'm open to suggestions. The cleanest solution probably involves fx.Annotate.
Maybe something like

fx.Populate(
  fx.Annotate(&s, fx.ParamTags(`name:"special"`)),
)

Using fx.Annotate has the benefit it could also support extracting a value group, or an fx.From.

Describe alternatives you've considered

Passing an fx.In to fx.Populate is currently supported in Fx and achieves the desired effect at the cost of more code, i.e. defining a new type that is then just thrown away.

type wrapper struct {
    fx.In
    S string `name:"special"`
}

var w wrapper
...
        fx.Populate(&w),
...
w.S // Retrieve the desired value.

Another approach that is currently supported is to use fx.Invoke instead of fx.Populate. fx.Invoke can be used with fx.Annotate. This requires a wrapper function.

var s string
...
       fx.Invoke(
           fx.Annotate(
               func(a string) { s = a }, // Requires a wrapper function.
               fx.ParamTags(`name:"special"`),
           ),
       ),
...
s // Holds the desired value.

Is this a breaking change?

A backward compatible solution can be provided.

Additional context
n/a

fx.Populate can get a named value. The issue with the example you posted is that you're using an fx.Out struct to populate that struct. Out structs are strictly for populating result types that are returned by constructor.

Populate, on the other hand, is like extracting values from the DI container, so you should treat it like an input parameter struct. If you change that struct definition to be fx.In, it should be able to populate the named field.

For example, the following code can populate S

func main() {
	type Result struct {
		fx.In

		S string `name:"special"`
	}
	var r Result
	app := fx.New(
		fx.Provide(fx.Annotate(func() string {
			return "hello"
		}, fx.ResultTags(`name:"special"`))),
		fx.Populate(&r),
	)
	fmt.Println(r.S)
	app.Run()
}

On the other hand, fx.Annotate does not yet work with fx.Populate, and that's definitely something we can address.

fx.Annotate does not yet work with fx.Populate, and that's definitely something we can address.

Thanks!

The issue with the example you posted is that you're using an fx.Out struct to populate that struct.

Result struct containing fx.Out was meant to exemplify what a module under test would provide. I've clarified the issue.

On the other hand, fx.Annotate does not yet work with fx.Populate, and that's definitely something we can address.

That would be nice :)

cc @moisesvega in case you were interested in picking this up. (feel free to assign yourself if so)

Internal ref: GO-1951.

#1089 enables using fx.Annotate with fx.Populate.