inject

package
v1.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 31 Imported by: 0

README

inject

inject generates fx modules from comment annotations. You annotate constructors with @Provide and entry points with @Invoke; the generator reads them with the annotation package, builds the dependency graph, checks it and writes one fx module per function plus a root Module() that wires the whole application.

Annotations

Annotation On Meaning
@Provide(index=N) constructor result N is provided to the graph
@Provide(name=X, index=N) constructor result N is provided as the named value X (name:"X")
@Provide(group=X, index=N) constructor result N is added to the value group X (group:"X")
@Inject(index=N) @Provide/@Invoke function parameter N must have a provider in the graph
@Inject(name=X, index=N) @Provide/@Invoke function parameter N receives the named value X
@Inject(group=X, index=N) @Provide/@Invoke function parameter N (a slice []T) receives every T of group X
@Inject(name=X, index=N, plugins=[a, b]) @Provide/@Invoke function parameter N, a factory client (*resty.Client, *nats.Conn, *gocql.Session, ...), is built by the generator with the plugins a and b (see below)
@Invoke function the function runs when the application starts (fx.Invoke)
@Default @Provide constructor default implementation of a value with two or more providers; config picks one (see below)
@Config struct the struct's fields are config keys; the generator registers them and provides *Struct (see below)

index is the zero-based position of the result or parameter and is always required. A parameter without @Inject is linked to a default provider of the same type when one is annotated; otherwise the application must provide it (for example with fx.Supply or another fx module).

package greeting

// NewGreeter greets informally.
// @Provide(index=0)
func NewGreeter() *Greeter { return &Greeter{Prefix: "Hello"} }

// NewFormalGreeter greets formally.
// @Provide(name=formal, index=0)
func NewFormalGreeter() (*Greeter, error) { return &Greeter{Prefix: "Good morning"}, nil }
package app

// Announce runs on start.
// @Inject(name=formal, index=0)
// @Inject(group=names, index=2)
// @Invoke
func Announce(formal, casual *greeting.Greeter, names []string, board *Board) { ... }

Generating

Add a go:generate line to the application and run go generate:

//go:generate go run github.com/xgodev/boost/inject/cmd/inject --boost.inject.output=gen
go generate ./...

The command starts with boost.Start(), so its settings are boost config keys (flags, env vars or config files):

Key Env Default Description
boost.inject.path BOOST_INJECT_PATH . directory scanned for annotations
boost.inject.output BOOST_INJECT_OUTPUT gen/inject directory that receives the generated modules; must be inside the Go module
boost.inject.graphviz BOOST_INJECT_GRAPHVIZ (empty) optional file that receives the dependency graph in Graphviz DOT format

The output mirrors the project, the directory that holds it: each annotated function's module sits at the path of its package in the project. A package of another module mirrors its whole import path.

gen/
  module.go                                   # package gen: func Module() fx.Option
  greeting/newgreeter_module.go               # source in github.com/acme/app/greeting
  greeting/options_module.go                  # @Config struct Options
  internal/stock/new_module.go                # source in github.com/acme/app/internal/stock
  github.com/acme/lib/clock/new_module.go     # source in another module

Two renames keep the generated packages importable and apart (GenPath):

  • an internal element below the first is written internal_. The generated packages import each other across gen/, and Go lets only the tree above an internal/ directory import what is in it; a leading internal/ sits right under gen/, so it stays. Under gen/implementation/ and gen/builder/middleware/ the leading one is written internal_ too (GenPathUnder).
  • the first element of a package of the project gets a trailing _ when it is a directory the generators own (builder, client, implementation) or has a dot, as the first element of an import path has.

Each <func>_module.go declares <Func>Module(), an fx.Module with the modules it depends on and fx.Provide/fx.Invoke of the function, wrapped in fx.Annotate with fx.ParamTags/fx.ResultTags when names or groups are used. Every run rewrites the output and removes generated files (those starting with // Code generated by inject; DO NOT EDIT.) that are no longer produced; other files in the output directory are kept.

The application uses the root module:

func main() {
	boost.Start()

	fx.New(gen.Module()).Run()
}

Default implementation

Two or more constructors may provide the same value (same type and name, no group) when one of them is marked @Default: they form an implementation set and config chooses which one is wired. The implementation is named after its package.

package memory // example.com/app/stock/memory

// @Provide(index=0)
// @Default
func New() stock.Service { ... }
package unlimited // example.com/app/stock/unlimited

// @Provide(index=0)
func New(c *Client) stock.Service { ... }

The generator writes the switch in gen/implementation/<type import path>/<type>_implementation.go:

gen/implementation/example.com/app/stock/service_implementation.go
  ServiceImplementationModule()  // includes only the chosen implementation's module
  ServiceImplementation()        // name of the chosen implementation
  • The key is the package path of the type, relative to the Go module, plus the value name, plus implementation: stock.implementation (env STOCK_IMPLEMENTATION), stock.formal.implementation for @Provide(name=formal). _ splits segments and a-b becomes aB, so the key is always reachable from an environment variable. A type outside the module uses its full import path.
  • Unset, the key holds the @Default package name.
  • Only the chosen implementation's module is included: the others, and what only they depend on, are never built.
  • An unknown name fails the boot with an error naming the key, the value and the valid names; it never falls back to the default.
  • Every consumer and the root Module() include the switch instead of the implementations. The graph still links consumers to every implementation, so cycles are found whatever the config says.

"Default implementation" (@Default) is not "default provider" (a @Provide without name): a set of named values has a default implementation too.

Config

A package that needs settings declares a struct marked @Config; the generator writes the config.Add registration and provides *Struct to the constructors that receive it. The package never imports wrapper/config.

package resty // example.com/app/internal/pkg/client/quote/resty

// @Config
type Options struct {
	BaseURL string        `default:"http://localhost" desc:"quote API base URL"`
	Timeout time.Duration `default:"2s" desc:"call timeout"`
	Token   string        `hide:"true" desc:"API token"`
}

// @Provide(index=0)
func New(o *Options) quote.Service { ... }

The generator writes gen/<import path>/options_module.go:

const optionsRoot = "internal.pkg.client.quote.resty"

func init() {
	// env INTERNAL_PKG_CLIENT_QUOTE_RESTY_BASE__URL
	config.Add(optionsRoot+".baseUrl", "http://localhost", "quote API base URL")
	// env INTERNAL_PKG_CLIENT_QUOTE_RESTY_TIMEOUT
	config.Add(optionsRoot+".timeout", 2*time.Second, "call timeout")
	// env INTERNAL_PKG_CLIENT_QUOTE_RESTY_TOKEN
	config.Add(optionsRoot+".token", "", "API token", config.WithHide())
}

func OptionsModule() fx.Option // provides *resty.Options from config.NewOptionsWithPath
  • @Config takes no arguments and goes on an exported struct outside package main; at most one per package.
  • Every exported field is a key, unless tagged config:"-". Supported types: string, bool, int, int8..int64, uint, uint8..uint64, float32, float64, time.Duration and []string.
  • desc is required. default is optional (the zero value without it) and is parsed when generating: strconv rules for bool and numbers, time.ParseDuration for durations, a comma list for []string. hide:"true" registers a secret (config.WithHide()). config:"name" renames the key segment; it must be lowerCamel (^[a-z][a-zA-Z0-9]*$).
  • The root key is the package path relative to the Go module, with the segment rules of the implementation key (its full import path outside the module). The field segment is the field's words in lowerCamel (BaseURL -> baseUrl, HTTPTimeout -> httpTimeout), which is what the env loader builds from BASE__URL: the env var of each key is written next to its config.Add.
  • Values are read when the module is built, after boost.Start() loaded flags, env vars and config files; a []string env value is split by commas.

Factory clients

A parameter whose type is a client a boost factory builds, with @Inject, is built by the generator: one client per name, each with its own config and the plugins its @Inject lists. The app writes no factory module.

package resty // example.com/app/internal/pkg/client/quote/resty

// @Provide(index=0)
// @Inject(index=0, name=quote, plugins=[otel, prometheus])
func New(c *resty.Client) quote.Repository { ... }

The generator writes gen/client/<client type import path>/<name>_<type>.go (default_client.go for an @Inject without name):

const quoteClientRoot = "boost.factory.resty.clients.quote"

func init() {
	factory.ConfigAdd(quoteClientRoot)
	otelplugin.ConfigAdd(quoteClientRoot + ".plugins.otel")
	prometheusplugin.ConfigAdd(quoteClientRoot + ".plugins.prometheus")
}

func QuoteClientModule() fx.Option // provides *resty.Client `name:"quote"`

func newQuoteClient(ctx context.Context) (*resty.Client, error) {
	otel, err := otelplugin.NewOtelrestyWithConfigPath(quoteClientRoot + ".plugins.otel")
	...
	return factory.NewClientWithConfigPath(ctx, quoteClientRoot, otel.Register, prometheus.Register)
}
Client type Factory Plugins
*resty.Client (github.com/go-resty/resty/v2) factory/contrib/go-resty/resty/v2 log, health, otel, datadog, prometheus, retry, requestid
*redis.Client, *redis.ClusterClient (github.com/redis/go-redis/v9) factory/contrib/redis/go-redis/v9 health, otel, datadog, prometheus
*mongo.Conn (factory/contrib/go.mongodb.org/mongo-driver/v1, /v2) the same package health, otel, datadog, prometheus
*nats.Conn (github.com/nats-io/nats.go) factory/contrib/nats-io/nats.go/v1 health, log
*pubsub.Client (cloud.google.com/go/pubsub/v2) factory/contrib/cloud.google.com/pubsub/v2 log, datadog, prometheus (gRPC client plugins)
*bigtable.Client (cloud.google.com/go/bigtable) factory/contrib/cloud.google.com/bigtable/v1 (root boost.factory.bigtable) log, datadog, prometheus (gRPC client plugins)
*gocql.Session (github.com/gocql/gocql) factory/contrib/gocql/gocql/v1 health
*gocql.Session (github.com/apache/cassandra-gocql-driver/v2) factory/contrib/apache/cassandra-gocql-driver/v2 health
*sns.Client, *sqs.Client, *kinesis.Client (github.com/aws/aws-sdk-go-v2/service/...) factory/contrib/aws/aws-sdk-go-v2/v1/client/<service> (root boost.factory.aws) datadog
*kafka.Producer (github.com/confluentinc/confluent-kafka-go/v2/kafka, needs cgo) factory/contrib/confluentinc/confluent-kafka-go/v2 none

NATS, Pub/Sub, Bigtable, gocql and kafka clients hold connections, so their constructor also takes fx.Lifecycle and closes the client when the app stops:

func newEventsConn(ctx context.Context, lc fx.Lifecycle) (*nats.Conn, error) {
	...
	c, err := factory.NewConnWithConfigPath(ctx, eventsConnRoot, health.Plugin, log.Register)
	if err != nil {
		return nil, err
	}
	lc.Append(fx.StopHook(c.Close))
	return c, nil
}
  • Name. name=x is the client x; without name it is the default client of the factory. A name is lowerCamel (^[a-z][a-zA-Z0-9]*$) so an env var reaches its keys.
  • Config. The default client keeps the factory's root (boost.factory.resty.*); a named one lives under <factory root>.clients.<name> (boost.factory.resty.clients.quote.host, env BOOST_FACTORY_RESTY_CLIENTS_QUOTE_HOST) with the factory's defaults. Each plugin sits under the client (boost.factory.resty.clients.quote.plugins.otel.enabled, default true): the annotation chooses the plugins, config only turns one off.
  • Shared. Consumers with the same type and name share one client. Each may list plugins only if the lists are equal; a consumer without plugins takes the client as the others declare it; no list anywhere is a client without plugins.
  • The app wins. When a @Provide of the app provides the value (same type and name), it is used and nothing is generated. A client parameter without @Inject is left to the app.
  • Every consumer's module and the root Module() include <Name>ClientModule(). fx builds the client only when a consumer is built, so a client of an implementation that config did not choose is never built.

Publisher

A *publisher.Publisher (github.com/xgodev/boost/wrapper/publisher) parameter with @Inject is built by the generator, with the driver one config key names. The app writes no driver module.

package boost // example.com/app/publisher/ordercreated/boost

// @Provide(index=0)
// @Inject(index=0)
func New(p *publisher.Publisher) ordercreated.Publisher { ... }

The generator writes gen/client/github.com/xgodev/boost/wrapper/publisher/publisher.go with PublisherModule(), which wires the publisher and only the chosen driver and its client:

boost.wrapper.publisher.driver.name Driver Client
noop (default) wrapper/publisher/driver/extra/noop none
nats wrapper/publisher/driver/contrib/nats-io/nats.go/v1 default *nats.Conn (boost.factory.nats.*)
pubsub wrapper/publisher/driver/contrib/cloud.google.com/pubsub/v2 default *pubsub.Client (boost.factory.gcp.pubsub.*)
kafka wrapper/publisher/driver/contrib/confluentinc/confluent-kafka-go/v2 default *kafka.Producer (boost.factory.confluent.*)
  • Choice. Env BOOST_WRAPPER_PUBLISHER_DRIVER_NAME=nats. An unknown value fails the boot with NotValid, naming the key, the value and the valid names. The key is .driver.name, not .driver: .driver holds the drivers' options.
  • Clients. Each driver takes the default client of its factory, the same one an @Inject without name gets (see Factory clients): the app and the publisher share it, with the plugins the app lists. The adapter of a builder @Function shares it too (GeneratorOptions.DefaultClients).
  • cgo. The kafka client binds librdkafka through cgo, so its files build only with cgo (//go:build cgo). An app built with CGO_ENABLED=0 still builds; choosing kafka there fails the boot with NotSupported.
  • The app wins. When a @Provide of the app provides *publisher.Publisher, it is used and nothing is generated. A named publisher (name=x) is not built: provide it. plugins do not apply.
  • The hand-written driver fx modules (fx/modules/wrapper/publisher/...) and their .enabled keys are unchanged; use them or PublisherModule(), not both.

Validation

The generator fails, without touching the output directory, when:

  • index is missing, out of range or declared twice;
  • a function has both @Provide and @Invoke, or @Inject without either;
  • @Provide points to an error result, or uses both name and group;
  • @Inject(group=...) is on a parameter that is not a slice;
  • two functions provide the same value (same type and name; groups may have many providers) and none is @Default;
  • @Default takes arguments, is not on a function with exactly one @Provide, is on a group, or its value has no other provider;
  • an implementation set has two @Default, a member with more than one @Provide, two members in packages with the same name, members providing different types, or a type that is not named (T, *T, pkg.T, *pkg.T);
  • a consumer of an implementation set lives in a member's package (the switch would import itself);
  • two generated config keys (of @Config fields or implementation switches) are equal, or one is the parent of another (a.b and a.b.c);
  • @Config takes arguments, is on a function, an unexported struct or in package main, is combined with another inject annotation, or appears twice in a package;
  • a @Config field is embedded, has an unsupported type, lacks desc, has a default that does not parse for its type or a config name that is not lowerCamel, or is unexported and tagged;
  • an @Inject parameter has no provider and is not a factory client;
  • plugins is on a @Provide, on a parameter that is not a factory client or that the app provides, or with group; lists an unknown or repeated plugin; or differs between two consumers of one client;
  • a client name is not lowerCamel, or two clients would be written to the same file (name=default and the unnamed client);
  • the dependencies have a cycle, or a function depends on what it provides;
  • an annotated function is unexported or in package main (the generated code must import it);
  • the output directory is not inside the Go module.

Annotations other than @Config on structs, and annotations on methods, are ignored with a warning.

Library

The command is a thin wrapper over the package API, which can be used to build other generators or to check that the generated code is up to date:

modulePath, moduleDir, _ := inject.FindModule(".")
entries, _ := inject.CollectEntries(".")
g, _ := inject.NewGraphFromEntries(ctx, entries)
files, _ := inject.NewGenerator(g, inject.GeneratorOptions{
	ModulePath: modulePath,
	ModuleDir:  moduleDir,
	OutputDir:  outputDir, // absolute
}).Render(ctx) // or Generate(ctx) to write them

WriteGraphviz/ExportInjectGraphToGraphviz export the graph in DOT format.

Generators built on top of inject (such as builder) reuse:

Function Purpose
GeneratorOptions.BaseImport() import path of the output dir
GenPath(base, importPath) / GenPathUnder(base, sub, importPath) directory under the output dir (or its sub dir) that mirrors a package: its path in the project, the whole import path for another module
ModuleOf(base, entry) import and module call (gen_x_hash.NewXModule(), gen_x_hash.OptionsModule() for a @Config) of a component
ConfigKey(modulePath, importPath) / ConfigEnv(root, segment) root config key of a package's @Config and the env var of one of its keys
Provider(g, pkg, tp) component and result index providing the default value of a type
Alias(prefix, name, path) collision-free import alias (prefix_name_hash)
Clients(g) / Client.Module(base) factory clients the generator builds, and the import and module call of one
GeneratorOptions.DefaultClients / ClientModule(base, id) client types (github.com/nats-io/nats.go.Conn) whose default client is generated for the caller to wire, and the import and module call of one (client_nats_hash.DefaultConnModule())
DefaultClientProvider(g, id) component providing the default client of a type, with an unnamed, ungrouped @Provide
ExecuteTemplate(tmpl, name, data) executes a template and gofmt's the output
WriteGenerated(ctx, dir, files, header) writes files and removes stale ones starting with header

See examples/simple and examples/publisher: their tests run the generated application and fail when gen/ is stale. examples/clients injects a NATS conn, a Pub/Sub client, gocql v1 and v2 sessions and a Bigtable client; its test starts the app against an embedded NATS server, the Pub/Sub emulator, a fake Cassandra node and the in-process Bigtable emulator (bttest), and checks every client is closed when the app stops.

Limitations

  • Types are matched by package name and type expression, not by import path: two packages with the same name and type name are seen as the same type.
  • Each generated <Func>Module() returns its options only on the first call (sync.Once), so a constructor reached by several modules is registered once. Build the fx application once per process.
  • fx provides every non-error result of a constructor, whatever @Provide declares; @Provide only tells the generator which results to track.
  • The scan also loads the packages imported by the scanned code, which makes a run take a few seconds on large dependency trees.

Documentation

Index

Constants

View Source
const (

	// PublisherDriverKey chooses the driver of the publisher inject builds.
	// It is not boost.wrapper.publisher.driver: that key holds the options of
	// the drivers.
	PublisherDriverKey = "boost.wrapper.publisher.driver.name"
	// DefaultPublisherDriver is the driver when PublisherDriverKey is unset.
	DefaultPublisherDriver = "noop"
)
View Source
const GeneratedHeader = "// Code generated by inject; DO NOT EDIT."

GeneratedHeader marks the files owned by the generator. Generate only removes stale files that start with it.

Variables

View Source
var ErrInvalidAnnotationIDType = errors.New("not a valid AnnotationIDType")
View Source
var ErrInvalidAnnotationType = errors.New("not a valid AnnotationType")
View Source
var ErrInvalidModuleAttr = errors.New("not a valid ModuleAttr")

Functions

func Alias

func Alias(prefix, name, importPath string) string

Alias returns an import alias for the package name at importPath that does not collide with other packages of the same name: prefix_name_hash.

func CollectEntries

func CollectEntries(path string) ([]annotation.Entry, error)

CollectEntries returns the functions under path, and in the packages they import, annotated with @Provide, @Inject, @Invoke or @Default, and the structs annotated with @Config.

func ConfigEnv

func ConfigEnv(root, segment string) string

ConfigEnv returns the environment variable that sets the key segment under root: dots become "_" and the words of a camelCase segment are joined by "__", which the env loader turns back into the segment (BASE__URL -> baseUrl).

func ConfigKey

func ConfigKey(modulePath, importPath string) string

ConfigKey returns the root config key of the @Config struct of a package, given the Go module path of the application: the package path relative to the module, in dots, with the segment rules of Implementation.Key.

func ExecuteTemplate

func ExecuteTemplate(tmpl *template.Template, name string, data any) ([]byte, error)

ExecuteTemplate executes the named template and returns the result formatted with gofmt. It fails when the output is not valid Go.

func ExportInjectGraphToGraphviz

func ExportInjectGraphToGraphviz(g *graph.Graph[Component], filename string) error

ExportInjectGraphToGraphviz writes the dependency graph to filename in Graphviz DOT format. Providers are green ellipses, invokers yellow octagons, and an edge goes from a provider to each component using it.

func FindModule

func FindModule(dir string) (modulePath string, moduleDir string, err error)

FindModule walks up from dir to the nearest go.mod and returns the module path and its root directory.

func GenPath

func GenPath(base, importPath string) string

GenPath returns the directory, relative to the output dir whose import path is base, of the package generated for the package at importPath. A package of the project, the directory that holds the output dir, mirrors its path in the project (the project's base name for the project itself), so the output dir reads like the project: internal/app/..., internal/pkg/.... Any other package mirrors its whole import path. Two renames keep the packages importable and apart:

  • an internal element below the first is written internal_. Generated packages import each other across the output dir, and Go lets only the tree above an internal/ directory import what is in it; a leading internal/ sits right under the output dir, so it stays.
  • the first element of a package of the project is written with a trailing _ when it is a directory the generators own (builder, client, implementation) or has a dot, as the first element of an import path has.

func GenPathUnder

func GenPathUnder(base, sub, importPath string) string

GenPathUnder is GenPath below the directory sub of the output dir, such as implementation, with a leading internal written internal_ too: below sub no internal/ sits right under the output dir.

func NewGraphFromEntries

func NewGraphFromEntries(ctx context.Context, entries []annotation.Entry) (*graph.Graph[Component], error)

NewGraphFromEntries builds the dependency graph of the annotated functions.

Every function with @Provide or @Invoke becomes a vertex. An edge goes from a provider to each function that consumes what it provides: parameters with @Inject must have a provider in the graph, while parameters without it are linked when a default provider of the same type exists and are otherwise left for the application to provide. An @Inject client parameter (*resty.Client, ...) without a provider is a client inject builds (see Clients).

Two or more providers of the same non-group value form an implementation set when one of them is marked @Default (see Implementations).

A struct annotated @Config is a vertex too: it provides *Struct, filled from config.

It returns a NotValid error for malformed annotations, a dependency cycle, two @Config in a package, an invalid implementation set or invalid client plugins, AlreadyExists when two functions provide the same non-group value and none is @Default or two clients share a file, and NotFound when an @Inject has no provider.

func NewTemplate

func NewTemplate() (*template.Template, error)

NewTemplate parses the generator templates ("module", "root", "implementation", "config", "client", "publisher", "publisher_driver" and "publisher_unsupported").

func WriteGenerated

func WriteGenerated(ctx context.Context, dir string, files map[string][]byte, header string) error

WriteGenerated writes files, keyed by their slash-separated path relative to dir, and removes the files under dir that start with header and are not in files. Files of other generators and handwritten files are kept.

func WriteGraphviz

func WriteGraphviz(w io.Writer, g *graph.Graph[Component]) error

WriteGraphviz writes the dependency graph to w in Graphviz DOT format, in a stable order.

Types

type Annotation

type Annotation struct {
	Index   *int
	Name    string
	Group   string
	Plugins string
}

Annotation holds the arguments of a @Provide or @Inject annotation. Index points to the function result (@Provide) or parameter (@Inject); Name and Group map to fx `name:"..."` and `group:"..."` tags. Plugins, on the @Inject of a client parameter, lists the plugins of the client inject builds ("[otel, prometheus]").

func (*Annotation) ID

func (a *Annotation) ID() string

ID identifies the value kind of the annotation. Names and groups keep their case because fx matches tags case-sensitively.

func (*Annotation) IDType

func (a *Annotation) IDType() AnnotationIDType

IDType reports whether the annotation is a default, named or grouped value.

func (*Annotation) Tag

func (a *Annotation) Tag() string

Tag returns the fx struct tag for the annotation, or "" for a default value.

type AnnotationIDType

type AnnotationIDType int

ENUM(DEFAULT,NAMED,GROUPED)

const (
	// AnnotationIDTypeDEFAULT is a AnnotationIDType of type DEFAULT.
	AnnotationIDTypeDEFAULT AnnotationIDType = iota
	// AnnotationIDTypeNAMED is a AnnotationIDType of type NAMED.
	AnnotationIDTypeNAMED
	// AnnotationIDTypeGROUPED is a AnnotationIDType of type GROUPED.
	AnnotationIDTypeGROUPED
)

func ParseAnnotationIDType

func ParseAnnotationIDType(name string) (AnnotationIDType, error)

ParseAnnotationIDType attempts to convert a string to a AnnotationIDType.

func (AnnotationIDType) IsValid

func (x AnnotationIDType) IsValid() bool

IsValid provides a quick way to determine if the typed value is part of the allowed enumerated values

func (AnnotationIDType) MarshalText

func (x AnnotationIDType) MarshalText() ([]byte, error)

MarshalText implements the text marshaller method.

func (AnnotationIDType) String

func (x AnnotationIDType) String() string

String implements the Stringer interface.

func (*AnnotationIDType) UnmarshalText

func (x *AnnotationIDType) UnmarshalText(text []byte) error

UnmarshalText implements the text unmarshaller method.

type AnnotationType

type AnnotationType int

ENUM(MODULE,PROVIDE,INJECT,INVOKE,DEFAULT,CONFIG)

const (
	// AnnotationTypeMODULE is a AnnotationType of type MODULE.
	AnnotationTypeMODULE AnnotationType = iota
	// AnnotationTypePROVIDE is a AnnotationType of type PROVIDE.
	AnnotationTypePROVIDE
	// AnnotationTypeINJECT is a AnnotationType of type INJECT.
	AnnotationTypeINJECT
	// AnnotationTypeINVOKE is a AnnotationType of type INVOKE.
	AnnotationTypeINVOKE
	// AnnotationTypeDEFAULT is a AnnotationType of type DEFAULT.
	AnnotationTypeDEFAULT
	// AnnotationTypeCONFIG is a AnnotationType of type CONFIG.
	AnnotationTypeCONFIG
)

func ParseAnnotationType

func ParseAnnotationType(name string) (AnnotationType, error)

ParseAnnotationType attempts to convert a string to a AnnotationType.

func (AnnotationType) IsValid

func (x AnnotationType) IsValid() bool

IsValid provides a quick way to determine if the typed value is part of the allowed enumerated values

func (AnnotationType) MarshalText

func (x AnnotationType) MarshalText() ([]byte, error)

MarshalText implements the text marshaller method.

func (AnnotationType) String

func (x AnnotationType) String() string

String implements the Stringer interface.

func (*AnnotationType) UnmarshalText

func (x *AnnotationType) UnmarshalText(text []byte) error

UnmarshalText implements the text unmarshaller method.

type Client

type Client struct {
	Factory   ClientFactory
	Name      string   // fx name of the client, "" for the default client
	Plugins   []string // in the declared order, nil when no consumer lists them
	Consumers []string // keys of the components injecting the client, sorted
}

Client is a client inject builds through its factory: one per client type and name asked by @Inject parameters that no annotated function provides.

func Clients

func Clients(g *graph.Graph[Component]) ([]Client, error)

Clients returns the clients inject builds, sorted by type and name. A consumer without plugins takes the client as another consumer declares it. It is NotValid when plugins are listed for a client the application provides, a name is not reachable from an environment variable or two consumers of a client list different plugins, and AlreadyExists when two clients share a file (a client named "default" and the unnamed one).

func (Client) File

func (c Client) File() string

File returns the path, relative to OutputDir, of the client's file.

func (Client) Ident

func (c Client) Ident() string

Ident is the exported identifier of the client in generated code: its name plus its type ("QuoteClient", "DefaultClusterClient").

func (Client) Import

func (c Client) Import(base string) ImportData

Import returns the import of the generated package holding the client, given the import path of the output dir (BaseImport).

func (Client) Module

func (c Client) Module(base string) (ImportData, string)

Module returns the import of the client's package and the call of the module that provides the client.

func (Client) PluginRoot

func (c Client) PluginRoot(plugin string) string

PluginRoot returns the config root of a plugin of the client.

func (Client) Root

func (c Client) Root() string

Root returns the config root of the client: the factory's root for the default client, <factory root>.clients.<name> for a named one.

func (Client) String

func (c Client) String() string

String describes the client in messages: "quote client (*github.com/go-resty/resty/v2.Client)".

type ClientData

type ClientData struct {
	Package    string             // package name of the generated file
	Ident      string             // exported prefix: <Ident>Module
	Var        string             // unexported prefix of the root constant, the once guard and the constructor
	Desc       string             // the client, for the docs
	Root       string             // config root of the client
	Env        string             // environment variables that set its keys
	Module     string             // name of the fx module
	Target     string             // expression passed to fx.Provide
	Type       string             // Go type of the client
	Build      string             // ...WithConfigPath constructor of the factory
	Constraint string             // build constraint of the file, "" when it builds anywhere
	Plugins    []ClientPluginData // in the declared order
	Names      string             // plugin names, comma separated
	Close      bool               // the module closes the client when the app stops
	Imports    []ImportData       // client type, factory, context module and plugins
}

ClientData feeds the template of a client inject builds.

type ClientFactory

type ClientFactory struct {
	Path       string                  // import path of the client type's package
	Type       string                  // name of the client type, always used as a pointer
	Package    string                  // name of the client type's package ("resty", "redis", "mongo")
	Build      string                  // factory import path + "." + the ...WithConfigPath constructor
	Root       string                  // config root of the factory
	Plugins    map[string]ClientPlugin // by name: the last segment of the plugin's config root
	Constraint string                  // build constraint the client needs ("cgo"), "" when it builds anywhere
	Close      bool                    // the generated module closes the client when the app stops
}

ClientFactory is a client type inject builds through its boost factory when an @Inject parameter asks for it.

func (ClientFactory) Constructor

func (f ClientFactory) Constructor() string

Constructor returns the name of the factory's ...WithConfigPath constructor.

func (ClientFactory) FactoryPath

func (f ClientFactory) FactoryPath() string

FactoryPath returns the import path of the factory.

func (ClientFactory) PluginNames

func (f ClientFactory) PluginNames() []string

PluginNames returns the names of the factory's plugins, sorted.

type ClientPlugin

type ClientPlugin struct {
	Path     string // import path of the plugin
	New      string // ...WithConfigPath constructor of the plugin
	Register string // method passed to the factory, "" for Register
}

ClientPlugin is a plugin of a client factory. The value of its constructor registers itself through its Register method, or the method named by Register when the factory takes a plugin of another shape.

type ClientPluginData

type ClientPluginData struct {
	Name     string // plugin name, the last segment of its config root
	Alias    string // import alias of the plugin
	New      string // ...WithConfigPath constructor of the plugin
	Register string // method of the plugin passed to the factory
}

ClientPluginData is one plugin of a client.

type Component

type Component struct {
	Entry annotation.Entry
	// Type is AnnotationTypePROVIDE for constructors and AnnotationTypeINVOKE
	// for functions run when the application starts.
	Type AnnotationType
	// Provides maps a result index to its @Provide annotation.
	Provides map[int]Annotation
	// Injects maps a parameter index to its @Inject annotation. Parameters
	// without @Inject are resolved by type.
	Injects map[int]Annotation
	// Default marks the default implementation (@Default) of the value it
	// provides, among two or more providers of that value.
	Default bool
	// Config holds the keys of a @Config struct, which provides *Struct
	// filled from config.
	Config []ConfigField
}

Component is an annotated function that takes part in the injection graph.

func DefaultClientProvider added in v1.6.1

func DefaultClientProvider(g *graph.Graph[Component], id string) (Component, bool)

DefaultClientProvider returns the annotated function that provides the default client of type id ("<import path>.<Type>"), with an unnamed, ungrouped @Provide. inject builds that client only when none does.

func Provider

func Provider(g *graph.Graph[Component], pkg, tp string) (Component, int, bool)

Provider returns the component that provides the default value (no name, no group) of type tp, written as in package pkg ("*Handler" in package orders and "*orders.Handler" elsewhere are the same type), and the index of that result.

func (Component) IsConfig

func (c Component) IsConfig() bool

IsConfig reports whether the component is a @Config struct.

func (Component) Key

func (c Component) Key() string

Key uniquely identifies the component in the graph.

func (Component) Result

func (c Component) Result(idx int) string

Result returns the type of the value provided at index idx: *Struct for a @Config struct, the function result otherwise.

type ConfigData

type ConfigData struct {
	Package string          // package name of the generated file
	Struct  string          // name of the @Config struct
	Var     string          // unexported prefix of the root key constant and the once guard
	Source  string          // import path of the struct's package
	Alias   string          // import alias of the struct's package
	Root    string          // root config key of the struct
	Keys    []ConfigKeyData // in field order
	Time    bool            // a default is a time.Duration
}

ConfigData feeds the template of the module of a @Config struct.

type ConfigField

type ConfigField struct {
	Name    string // Go name of the field
	Segment string // key segment under the struct's root key
	Type    string // supported type of the field ("time.Duration" for a duration)
	Default string // Go literal of the default value, of the field type
	Desc    string // description of the key
	Hide    bool   // the value is a secret (config.WithHide)
}

ConfigField is a config key of a @Config struct.

type ConfigKeyData

type ConfigKeyData struct {
	Segment string // key segment under the root
	Value   string // Go literal of the default
	Desc    string // Go literal of the description
	Env     string // environment variable that sets the key
	Hide    bool   // the value is a secret
}

ConfigKeyData is one config key of a @Config struct.

type Generator

type Generator struct {
	// contains filtered or unexported fields
}

Generator writes one fx module per component of the graph, mirroring the import path of the annotated function under OutputDir, plus a root module.go whose Module() includes all of them.

func NewGenerator

func NewGenerator(g *graph.Graph[Component], opts GeneratorOptions) *Generator

NewGenerator returns a generator for the graph.

func (*Generator) Generate

func (p *Generator) Generate(ctx context.Context) error

Generate writes the generated files to OutputDir and removes generated files of previous runs that are no longer produced.

func (*Generator) Render

func (p *Generator) Render(ctx context.Context) (map[string][]byte, error)

Render returns the generated files keyed by their slash-separated path relative to OutputDir, without touching the disk.

type GeneratorOptions

type GeneratorOptions struct {
	// ModulePath is the Go module path of the application ("module" in go.mod).
	ModulePath string
	// ModuleDir is the absolute root directory of that module.
	ModuleDir string
	// OutputDir is the absolute directory that receives the generated code.
	// It must be inside ModuleDir so the generated packages can be imported.
	OutputDir string
	// DefaultClients are the client types ("<import path>.<Type>", like
	// github.com/nats-io/nats.go.Conn) whose default client is generated even
	// when no @Inject parameter asks for it: another generator of OutputDir
	// wires it, as builder does for the adapter of a @Function. The root
	// module leaves them out. Leave out a type the application provides.
	DefaultClients []string
}

GeneratorOptions tells the generator where the application lives and where the generated code goes.

func (GeneratorOptions) BaseImport

func (o GeneratorOptions) BaseImport() (string, error)

BaseImport returns the import path of OutputDir. It is NotValid when a field is missing or OutputDir is not a subdirectory of ModuleDir.

type Implementation

type Implementation struct {
	Type    string               // name of the provided type ("Service")
	Path    string               // import path of the package declaring the type
	Name    string               // fx name of the value, "" for the default value
	Default string               // package name of the @Default member
	Members map[string]Component // by package name: the implementation's name
}

Implementation is an implementation set: two or more functions providing the same non-group value, one of them marked @Default. The generator writes a switch module that includes only the member a config key names.

func ImplementationOf

func ImplementationOf(g *graph.Graph[Component], comp Component) (Implementation, bool)

ImplementationOf returns the implementation set comp is a member of.

func Implementations

func Implementations(g *graph.Graph[Component]) ([]Implementation, error)

Implementations returns the implementation sets of the graph, sorted by type and name. It is NotValid when a set has more than one @Default, a @Default value has no other provider, a member does not have exactly one @Provide, two members share a package name, the members do not provide the same named type or a consumer of the set lives in a member's package.

func (Implementation) Chosen

func (i Implementation) Chosen(base string) (ImportData, string)

Chosen returns the import of the switch package and the call returning the name of the chosen implementation.

func (Implementation) File

func (i Implementation) File(base string) string

File returns the path, relative to OutputDir, of the switch file, given the import path of the output dir (BaseImport).

func (Implementation) Ident

func (i Implementation) Ident() string

Ident is the exported identifier of the set in generated code: the type name, plus the value name when the value is named ("Service", "ServiceFormal").

func (Implementation) Import

func (i Implementation) Import(base string) ImportData

Import returns the import of the generated package holding the switch, given the import path of the output dir (BaseImport).

func (Implementation) Key

func (i Implementation) Key(modulePath string) string

Key returns the config key that chooses the implementation, given the Go module path of the application: the type's package path relative to the module (its full import path outside the module), plus the value name, plus "implementation", in dots. Every segment is lowercased, "_" splits segments and "a-b" becomes "aB", so the key is reachable from an environment variable (FOO_BAR__BAZ -> foo.barBaz).

func (Implementation) Module

func (i Implementation) Module(base string) (ImportData, string)

Module returns the import of the switch package and the call of the module that includes the chosen implementation.

func (Implementation) Names

func (i Implementation) Names() []string

Names returns the names of the implementations, sorted.

type ImplementationCase

type ImplementationCase struct {
	Name   string
	Module string
}

ImplementationCase is one implementation of a switch and its module call.

type ImplementationData

type ImplementationData struct {
	Package string               // package name of the generated file
	Ident   string               // exported prefix: <Ident>Implementation, <Ident>ImplementationModule
	Var     string               // unexported prefix of the key constant and the once guard
	Type    string               // path.Type of the switched value, for the docs
	Key     string               // config key
	Default string               // name of the default implementation
	Names   string               // implementation names, comma separated
	Cases   []ImplementationCase // sorted by name
	Imports []ImportData         // generated packages of the implementations
}

ImplementationData feeds the template of the switch of an implementation set.

type ImportData

type ImportData struct {
	Alias string
	Path  string
}

ImportData is an aliased import of a generated file.

func ClientModule added in v1.6.1

func ClientModule(base, id string) (ImportData, string, error)

ClientModule returns the import of the package inject generates for the default client of type id ("<import path>.<Type>") and the call of the module that provides it, given the import path of the output dir (BaseImport). It is NotValid for a type inject does not build.

func ModuleOf

func ModuleOf(base string, entry annotation.Entry) (ImportData, string)

ModuleOf returns the import of the package generated for the component declared by entry, given the import path of the output dir (BaseImport), and the call of its module function, such as gen_greet_1a2b3c4d.NewGreeterModule() (or OptionsModule() for a @Config struct Options).

type ModuleAttr

type ModuleAttr int

ENUM(MODULE,PATH,PACKAGE,FUNC)

const (
	// ModuleAttrMODULE is a ModuleAttr of type MODULE.
	ModuleAttrMODULE ModuleAttr = iota
	// ModuleAttrPATH is a ModuleAttr of type PATH.
	ModuleAttrPATH
	// ModuleAttrPACKAGE is a ModuleAttr of type PACKAGE.
	ModuleAttrPACKAGE
	// ModuleAttrFUNC is a ModuleAttr of type FUNC.
	ModuleAttrFUNC
)

func ParseModuleAttr

func ParseModuleAttr(name string) (ModuleAttr, error)

ParseModuleAttr attempts to convert a string to a ModuleAttr.

func (ModuleAttr) IsValid

func (x ModuleAttr) IsValid() bool

IsValid provides a quick way to determine if the typed value is part of the allowed enumerated values

func (ModuleAttr) MarshalText

func (x ModuleAttr) MarshalText() ([]byte, error)

MarshalText implements the text marshaller method.

func (ModuleAttr) String

func (x ModuleAttr) String() string

String implements the Stringer interface.

func (*ModuleAttr) UnmarshalText

func (x *ModuleAttr) UnmarshalText(text []byte) error

UnmarshalText implements the text unmarshaller method.

type ModuleData

type ModuleData struct {
	Package string       // package name of the generated file
	Func    string       // annotated function name
	Source  string       // import path of the annotated function
	Kind    string       // "Provide" or "Invoke"
	Target  string       // expression passed to fx.Provide/fx.Invoke
	Deps    []string     // module calls of the components it depends on
	Imports []ImportData // imports besides sync and fx
}

ModuleData feeds the module template of one component.

type Publisher added in v1.6.0

type Publisher struct {
	Consumers []string // keys of the components injecting it, sorted
}

Publisher is the *publisher.Publisher inject builds: one for the application, with the driver PublisherDriverKey names.

func PublisherOf added in v1.6.0

func PublisherOf(g *graph.Graph[Component]) Publisher

PublisherOf returns the publisher inject builds: its consumers are the components with an unnamed, ungrouped @Inject of *publisher.Publisher that no annotated function provides. It has no consumers when inject builds none.

func (Publisher) File added in v1.6.0

func (p Publisher) File() string

File returns the path, relative to OutputDir, of the publisher's file.

func (Publisher) Import added in v1.6.0

func (p Publisher) Import(base string) ImportData

Import returns the import of the generated package holding the publisher, given the import path of the output dir (BaseImport).

func (Publisher) Module added in v1.6.0

func (p Publisher) Module(base string) (ImportData, string)

Module returns the import of the publisher's package and the call of the module that provides it.

type PublisherCase added in v1.6.0

type PublisherCase struct {
	Name    string
	Options string
}

PublisherCase is one driver of the publisher and the fx options wiring it.

type PublisherData added in v1.6.0

type PublisherData struct {
	Key     string          // config key naming the driver
	Default string          // driver when the key is unset
	Names   string          // driver names, comma separated
	Cases   []PublisherCase // sorted by name
	Imports []ImportData    // packages of the drivers and clients built anywhere
}

PublisherData feeds the template of the publisher inject builds.

type PublisherDriver added in v1.6.0

type PublisherDriver struct {
	Name    string // value of PublisherDriverKey
	Path    string // import path of the driver package, whose New builds it
	Package string // package name of the driver
	Client  string // "<import path>.<Type>" of the client New takes, "" when none
}

PublisherDriver is a driver of the publisher that the generated module can wire.

type PublisherDriverData added in v1.6.0

type PublisherDriverData struct {
	Name       string       // driver name
	Constraint string       // build constraint of its client
	Options    string       // fx options wiring the driver and its client
	Imports    []ImportData // packages of the driver and its client
}

PublisherDriverData feeds the templates of a driver whose client builds only under a constraint: its file and the fallback without the constraint.

type RootData

type RootData struct {
	Package string
	Imports []ImportData
	Modules []string
}

RootData feeds the template of the root module.go.

Directories

Path Synopsis
cmd
inject command
Command inject generates fx modules from @Provide, @Inject and @Invoke annotations.
Command inject generates fx modules from @Provide, @Inject and @Invoke annotations.
examples
clients command
Command clients wires a NATS connection, a Pub/Sub client, gocql sessions of both drivers and a Bigtable client that inject builds from @Inject, and stops them with the app.
Command clients wires a NATS connection, a Pub/Sub client, gocql sessions of both drivers and a Bigtable client that inject builds from @Inject, and stops them with the app.
clients/catalog
Package catalog stores the prices of the catalog in Bigtable through the "catalog" client.
Package catalog stores the prices of the catalog in Bigtable through the "catalog" client.
clients/events
Package events publishes events over the "events" NATS connection.
Package events publishes events over the "events" NATS connection.
clients/ledger
Package ledger reads the Cassandra/ScyllaDB cluster through the "ledger" session of the Apache driver (github.com/apache/cassandra-gocql-driver/v2).
Package ledger reads the Cassandra/ScyllaDB cluster through the "ledger" session of the Apache driver (github.com/apache/cassandra-gocql-driver/v2).
clients/orders
Package orders manages the Pub/Sub topics of orders through the "orders" client.
Package orders manages the Pub/Sub topics of orders through the "orders" client.
clients/store
Package store reads the Cassandra/ScyllaDB cluster through the "store" session.
Package store reads the Cassandra/ScyllaDB cluster through the "store" session.
publisher command
Command publisher announces a created order through the publisher inject builds, on the driver boost.wrapper.publisher.driver.name chooses (noop by default).
Command publisher announces a created order through the publisher inject builds, on the driver boost.wrapper.publisher.driver.name chooses (noop by default).
publisher/domain/model/entity
Package entity holds the entities of the example.
Package entity holds the entities of the example.
publisher/domain/publisher/ordercreated
Package ordercreated announces that an order was created.
Package ordercreated announces that an order was created.
publisher/publisher/ordercreated/boost
Package boost implements ordercreated.Publisher on boost's wrapper/publisher, whose driver config chooses.
Package boost implements ordercreated.Publisher on boost's wrapper/publisher, whose driver config chooses.
simple command
Command simple wires an fx application from inject annotations.
Command simple wires an fx application from inject annotations.
simple/app
Package app consumes the values provided by the other packages.
Package app consumes the values provided by the other packages.
simple/greeting
Package greeting provides the greeters used by the example.
Package greeting provides the greeters used by the example.
simple/names
Package names contributes values to the "names" group.
Package names contributes values to the "names" group.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL