wire icon indicating copy to clipboard operation
wire copied to clipboard

Calling a function that returns a function in wire.Build results in the unhelpful "unknown pattern" error

Open dcormier opened this issue 4 years ago • 4 comments

Describe the bug

Calling a function that returns a function in wire.Build results in the unknown pattern error.

To Reproduce

This may be in a shared package, to be used across multiple projects, for example.

func ProvideDDBClientWith(ddbEndpoint string) func(client.ConfigProvider) dynamodbiface.DynamoDBAPI {
	return func(awsSession client.ConfigProvider) dynamodbiface.DynamoDBAPI {
		return dynamodbclient.New(awsSession, ddbEndpoint)
	}
}

Elsewhere, that would be called in wire.Build like so:

func Inject(cfg Config) (Server, func(), error) (
    wire.Build(
        ...
        ProvideDDBClientWith(cfg.DDBEndpoint),
        ...
    )

    return Server{}, nil, nil
}

Expected behavior

Running wire would result in a properly generated Inject function.

Version

v0.4.0

Additional context

The package that has the Provide* function doesn't (and shouldn't) know where its arguments come from. It's up to the caller to load them from file, environment, or just hard code them. But that Provide* function will return a closure that's to be used by wire.Build.

For what it's worth, this works with fx.

dcormier avatar Mar 12 '20 12:03 dcormier

This is closely related to #184. I'm not fully sure the implications of allowing the provider function to be dynamic.

For this case, you could use a slightly different provider function in your injector that instantiates the DynamoDB API:

func ProvideDDBClient(cfg Config, awsSession client.ConfigProvider) dynamodbiface.DynamoDBAPI {
	return dynamodbclient.New(awsSession, ddbEndpoint)
}

func Inject(cfg Config) (Server, func(), error) (
    wire.Build(
        // ...
        ProvideDDBClient,
        // ...
    )

    return Server{}, nil, nil
}

zombiezen avatar Mar 14 '20 16:03 zombiezen

ddbEndpoint is not in scope in that ProvideDDBClient example. Additionally, my goal is for this function (ProvideDDBClient) to live in a shared package, while Config is specific to the consumer of that shared package.

I'm aware that something like this could be done (which maybe is what you meant?), but my goal is to reduce repeated code by putting it in shared packages.

func provideDDBClient(cfg Config, awsSession client.ConfigProvider) dynamodbiface.DynamoDBAPI {
	return dynamodbclient.New(awsSession, cfg.DDBEndpoint)
}

func Inject(cfg Config) (Server, func(), error) (
    wire.Build(
        // ...
        provideDDBClient,
        // ...
    )

    return Server{}, nil, nil
}

While I'm using a single provider function as an example, in reality I have many that I would like to make available in packages shared across a set of projects we're working on. It would significantly simplify my wire.Build setup for multiple projects.

dcormier avatar Mar 16 '20 12:03 dcormier

This is still tagged as needs info. Is there more expected from me, here?

dcormier avatar Sep 30 '20 13:09 dcormier

It'd be nice to see a solution for this. My use case is a provider that can be built with functional options. Something like this:

type Value struct {}
type ValueProvider func() (*Value, func(), error)
func ProviderWithOptions(options ...ProviderOption) ValueProvider {
    return func() (*Value, func(), error) {
        // ...
    }
}

I'd like to be able to use it inline in a wire.Build call like this:

ProviderWithOptions(WithOptionA(), WithOptionB())

My goal is to allow some customization of the provider, especially for things like migrations in a large codebase, or behavior that needs to be otherwise toggled or configured at build time. This isn't possible, currently. I need to wrap the customization in a provider function to appease wire:

func provider() (*Value, func(), error) {
    return ProviderWithOptions(WithOptionA())()
}

This gets especially cumbersome and boilerplatey when the provider has dependencies, as the type signature needs to be repeated in a number of places, and dependencies need to be manually passed in from the func to the invocation of the built provider function.

Regarding this concern:

I'm not fully sure the implications of allowing the provider function to be dynamic.

I feel like I can achieve a lot of potentially-scary dynamic behavior with the workaround, so it'd be nice to not have to write the workaround.

wyattanderson avatar Apr 19 '23 15:04 wyattanderson