Documentation
¶
Overview ¶
Package confx adapts github.com/uchaloop/confmaker to Uber Fx. The application explicitly registers its package configs; confmaker owns ENV parsing, defaults, validation and diagnostics, while confx provides the resulting typed values. This adapter uses confmaker v1.0.0 and retains independent v0.x versioning.
Import github.com/uchaloop/confx for registration and github.com/uchaloop/confmaker when using core options such as WithEnv or WithPrefix.
Registration ¶
fx.New(
confx.Module(),
confx.Provide[postgres.Config]("postgres"),
confx.ProvideNamed[postgres.Config]("replica", confmaker.WithPrefix("REPLICA_POSTGRES_")),
)
Module creates one confmaker.Loader per application and is required once when configs are registered, unless FromLoader supplies an existing loader instead. Provide passes the explicit instance name to the core and provides the result without an Fx tag. ProvideNamed additionally uses that name as the Fx tag name:"<name>". confmaker validates names, derives ENV prefixes and loads the configurations; the adapter handles their registration in Fx.
Lifecycle ¶
Every provided config is loaded during fx.New, including configs with no consumer. Loading errors are available from fx.App.Err and prevent startup. Options returned by Provide and ProvideNamed may be reused across applications; each application has its own loader and config values.
Multiple instances and errors ¶
Use Provide for an untagged value. Two values of the same Go type need distinct Fx identities, even when their ENV prefixes differ. ProvideNamed uses the instance name as that identity:
type Params struct {
fx.In
Primary postgres.Config
Replica postgres.Config `name:"replica"`
}
Inspect construction errors with app.Err(). Core ConfigError diagnostics remain reachable through confmaker.ConfigErrors and errors.As. Missing Module or conflicting Fx providers are adapter or Fx errors, not core config diagnostics. Loaded configs should be treated as read-only, including their maps and slices.
External registrations ¶
FromLoader uses a caller-owned confmaker.Loader. FromHandle and FromHandleNamed provide existing handles without registering them again. A named handle uses Handle.Name as its Fx tag. All handles must belong to the same loader. Exactly one Module or FromLoader may be installed.
For offline documentation, register configs externally and generate the manifest before fx.New. Normal startup can then use Run or explicit Start/Stop. Module accepts only Provide/ProvideNamed, while FromLoader accepts only FromHandle/FromHandleNamed. Mixing modes or supplying a foreign handle fails before registrations or loading and leaves the external loader unchanged. Previously loaded loaders retain their results. Reusing an external loader across apps shares its load state and values. The adapter does not interpret command-line flags or exit the process.
Load diagnostics ¶
Pass confmaker.WithDiagnosticHandler to Module to process a value-free report before a loading error reaches Fx, preserving fx.New(...).Run(). For explicit inspection, pass confmaker.WithDiagnostics and read the receiver after fx.New, before Run. External loaders accept the same core options. If Fx rejects the graph before loading, the handler is not called and the receiver stays not started.
Core library ¶
The core also works without Fx:
cfg, err := confmaker.Load[postgres.Config]("postgres")
Declaring configs, options, the manifest and loading rules are documented in package github.com/uchaloop/confmaker.
Example ¶
Provide makes the config available to constructors. WithEnv stands in for the process environment here; an application calls confx.Module() without it.
package main
import (
"fmt"
"time"
"github.com/uchaloop/confmaker"
"github.com/uchaloop/confx"
"go.uber.org/fx"
)
// StoreConfig is the kind of config a library declares: plain fields, env tags,
// and no knowledge of where the values come from.
type StoreConfig struct {
Host string `env:"HOST,notEmpty"`
Timeout time.Duration `env:"TIMEOUT"`
}
func (c *StoreConfig) SetDefaults() { c.Timeout = 30 * time.Second }
func main() {
env := confmaker.WithEnv(map[string]string{"STORE_HOST": "db:5432"})
app := fx.New(
fx.NopLogger,
confx.Module(env),
confx.Provide[StoreConfig]("store"),
fx.Invoke(func(cfg StoreConfig) {
fmt.Println(cfg.Host, cfg.Timeout)
}),
)
if err := app.Err(); err != nil {
fmt.Println(err)
}
}
Output: db:5432 30s
Index ¶
- func FromHandle[T any](handle *confmaker.Handle[T]) fx.Option
- func FromHandleNamed[T any](handle *confmaker.Handle[T]) fx.Option
- func FromLoader(loader *confmaker.Loader) fx.Option
- func Module(opts ...confmaker.EnvOption) fx.Option
- func Provide[T any](name string, opts ...confmaker.ConfigOption) fx.Option
- func ProvideNamed[T any](name string, opts ...confmaker.ConfigOption) fx.Option
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func FromHandle ¶ added in v0.3.0
FromHandle provides an existing registration as an untagged T. FromLoader must reference its owner. No second registration is made. Nil, zero and foreign handles fail app construction before loading the application's configs.
func FromHandleNamed ¶ added in v0.3.0
FromHandleNamed provides an existing registration with its registration name as the Fx name tag. WithPrefix does not affect this name. FromLoader is required.
func FromLoader ¶ added in v0.3.0
FromLoader uses an existing loader instead of Module. Use only FromHandle or FromHandleNamed with it; Provide registrations are rejected before loading. Load runs during fx.New, even without config consumers. A previously loaded loader retains its result and refuses new registrations. Include exactly one Module or FromLoader per app. Sharing an external loader across apps shares its values and one-shot load state.
func Module ¶
Module loads the configs registered by Provide and ProvideNamed through one confmaker.Loader per application, configured by opts. Loading happens once during fx.New, when a constructor or invocation requests a config. Every registration is checked, including unused configs. Loading errors reach fx.App.Err and prevent startup, regardless of registration order.
Include exactly one Module or FromLoader. Module also checks its options when no configs are registered.
func Provide ¶
Provide registers a config with the required instance name and provides it without an Fx tag. confmaker validates the name and derives its default ENV prefix; confmaker.WithPrefix overrides only the prefix. Module is required; FromLoader cannot be mixed with Provide. The config is loaded during fx.New even when no constructor consumes it.
func ProvideNamed ¶
ProvideNamed registers a config with the required instance name and provides it with the Fx tag name:"<name>". The name also determines the default prefix; confmaker.WithPrefix can override the prefix. Module is required, and loading happens during fx.New as for Provide.
Example ¶
ProvideNamed provides a second instance of the same type under an Fx tag, with its own prefix.
package main
import (
"fmt"
"time"
"github.com/uchaloop/confmaker"
"github.com/uchaloop/confx"
"go.uber.org/fx"
)
// StoreConfig is the kind of config a library declares: plain fields, env tags,
// and no knowledge of where the values come from.
type StoreConfig struct {
Host string `env:"HOST,notEmpty"`
Timeout time.Duration `env:"TIMEOUT"`
}
func (c *StoreConfig) SetDefaults() { c.Timeout = 30 * time.Second }
func main() {
env := confmaker.WithEnv(map[string]string{
"STORE_HOST": "db:5432",
"REPLICA_STORE_HOST": "replica:5432",
})
type params struct {
fx.In
Primary StoreConfig
Replica StoreConfig `name:"replica"`
}
app := fx.New(
fx.NopLogger,
confx.Module(env),
confx.Provide[StoreConfig]("store"),
confx.ProvideNamed[StoreConfig]("replica", confmaker.WithPrefix("REPLICA_STORE_")),
fx.Invoke(func(p params) {
fmt.Println(p.Primary.Host, p.Replica.Host)
}),
)
if err := app.Err(); err != nil {
fmt.Println(err)
}
}
Output: db:5432 replica:5432
Types ¶
This section is empty.