uber-go/fx

Handling optional fields in fx.Decorate

mehdihadeli opened this issue · 3 comments

Hi,
Is there any way for handling optional input parameter in fx.decorate method?

Actually, I'm looking for a way if, in one of my sub module s, one dependency like *Test not provided, I can instantiate it; otherwise, I want to return an existing register object.

fx.New(
fx.Decorate(func(l TestParam) *Test {
	if l.TestData == nil {
		return &Test{Name: "Test " + l.TestData.Name}
	}
	return l.TestData
},
),
fx.Invoke(func(t *Test) {
	fmt.Print(t.Name)
}),
).Run()

type TestParam struct {
	fx.In

	TestData *Test `optional:"true"`
}

Error:

missing type:
        - *main.Test (did you mean to Provide it?)

Hi @mehdihadeli , thanks for submitting an issue.

Decorators actually allow optional dependencies with optional tag.

The issue in your code is because your Invoke needs a Test type that isn't provided any provider yet. A decorator cannot be a provider at the same time. You need to specify a provider that knows how to instantiate a Test type into the object graph.

Hi @sywhang, thanks for your quick response.

I'm looking for a way that if one struct like Test is not registered in the outer modules, my inner module can register this dependency itself. Maybe this is not possible with decorator. Is there any solution here?
For example, our inner module needed a logger, and I want the inner module to check if it is not provided and then register it.

@mehdihadeli Might be a better way to do this, but off the top of my head, you can accomplish this by using a name tag for the final logger you want to use. Something like:

type LoggerParams struct {
	fx.In

	Logger *zap.Logger `optional:"true"`
}

type TestParams struct {
	fx.In

	Logger *zap.Logger `name:"finallogger"`
}

func main() {
	fx.New(
		fx.Provide(
			fx.Annotate(
				func(p LoggerParams) (*zap.Logger, error) {
					if p.Logger != nil {
						return p.Logger, nil
					}
					return zap.NewDevelopment()
				},
				fx.ResultTags(`name:"finallogger"`),
			),
		),
		fx.Invoke(func(p TestParams) {
			p.Logger.Info("log message")
		}),
	)
}

Then if a logger is already provided it will be passed along as finallogger, otherwise a new one will be created. In this simple case you can also just remove the XParams structs and just use fx.Annotate and fx.ParamTags/fx.ResultTags, but this is assuming you have other params for the functions.