Documentation
¶
Overview ¶
Package confx provides confmaker configuration values to Uber Fx. Confmaker owns defaults, decoding, validation and diagnostics; confx owns registration and typed injection. Import github.com/uchaloop/confmaker/v2 for core loader/config options.
Ordinary Fx ¶
Install Module once and register configs with Provide or ProvideNamed. Ordinary Fx modules need no description-specific wrappers:
server := fx.Module("server", confx.Provide[ServerConfig]("server"))
store := fx.Module("store", confx.Provide[StoreConfig]("store"))
fx.New(confx.Module(), server, store).Run()
ServerConfig and StoreConfig are application types. The package example shows both modules with consumers and an isolated ENV map. For a required field, see the Provide example; for multiple instances, see the ProvideNamed example.
App with help and describe ¶
For configuration description, preserve declarations with MakeModule and wrap service constructors, invocations and decorators in Runtime:
server := confx.MakeModule("server",
confx.Provide[ServerConfig]("server"),
confx.Runtime(fx.Invoke(showServer)),
)
store := confx.MakeModule("store",
confx.Provide[StoreConfig]("store"),
confx.Runtime(fx.Invoke(showStore)),
)
confx.New(server, store).Run()
The New example shows both modules with consumers. New captures the composition lazily. App.Run handles help and describe before constructing Fx, then delegates normal startup to Fx. It may exit the process, skipping caller defers. For custom CLI handling or manual lifecycle control, use App.Manifest and App.FX; the FX example demonstrates injection through fx.Populate. App has no Err/Start/Stop methods. Its method documentation defines CLI behavior and option precedence.
Loading and ownership ¶
All registered configs, including unused ones, load during fx.New, before Start. Errors reach fx.App.Err and prevent startup. Runtime does not defer invocations until Start. Module, App.FX and Modules.FX create a fresh loader per Fx app; the FX methods install Module automatically. Do not add another adapter. Treat loaded config values, including maps and slices, as read-only.
Loader options may supply custom engines and diagnostic callbacks. Reused engines and callbacks remain shared and must support concurrent use when applicable. Engine resources remain caller-owned; confx adds no cancellation or resource lifecycle to Fx construction. Custom engines do not support manifest/describe.
Registration, errors and diagnostics ¶
Provide supplies an untagged value; ProvideNamed uses the registration name as its Fx name tag. With the built-in engine, the name also determines the ENV prefix; confmaker.WithPrefix changes only that prefix. Multiple values of one Go type need distinct Fx identities. A custom engine owns source lookup and tags.
Use errors.Is with ErrLoaderIsNil, ErrHandleIsNil and ErrOpaqueOption. Core diagnostics remain accessible through confmaker.ConfigErrors and errors.As. Graph errors need not be configuration errors: always handle fx.App.Err itself. confmaker.WithDiagnosticHandler provides a value-free load report; it does not run if Fx rejects the graph before loading. Backend errors may contain values.
External loaders and offline description ¶
FromLoader uses a caller-owned loader with FromHandle / FromHandleNamed. Do not mix that mode with Module/Provide. Reusing an external loader shares its load state and values. Its LoadContext and Report methods remain available; the adapter documentation specifies ownership checks and nil-handle behavior.
MakeModules supports composition without App. Manifest inspects MakeModule declarations and skips Runtime; opaque options outside Runtime are rejected before defaults run. App.Manifest includes defaults by default, while Modules.Manifest requires confmaker.IncludeDefaults. User defaults/renderers can perform I/O; manifest does not validate the Fx graph or all load-only options. See Runtime for the boundary of offline inspection and the MakeModules example for explicit description followed by Fx construction.
Example ¶
Provide makes the config available to constructors. WithEnv stands in for the process environment here; an application calls confx.Module() without it.
env := confmaker.WithEnv(map[string]string{"STORE_HOST": "db:5432"})
server := fx.Module("server",
confx.Provide[ServerConfig]("server"),
fx.Invoke(func(cfg ServerConfig) {
fmt.Println("server port:", cfg.Port)
}),
)
store := fx.Module("store",
confx.Provide[StoreConfig]("store"),
fx.Invoke(func(cfg StoreConfig) {
fmt.Println("store host:", cfg.Host)
}),
)
fxApp := fx.New(
fx.NopLogger,
confx.Module(env),
server,
store,
)
if err := fxApp.Err(); err != nil {
fmt.Println(err)
}
Output: server port: 8080 store host: db:5432
Index ¶
- Variables
- 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.LoaderOption) fx.Option
- func Provide[T any](name string, opts ...confmaker.ConfigOption) fx.Option
- func ProvideNamed[T any](name string, opts ...confmaker.ConfigOption) fx.Option
- func Runtime(opts ...fx.Option) fx.Option
- type App
- type ModuleSpec
- type Modules
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrHandleIsNil = errors.New("handle is nil")
ErrHandleIsNil means a handle adapter was called without a handle.
var ErrLoaderIsNil = errors.New("loader is nil")
ErrLoaderIsNil means FromLoader was called without a loader.
var ErrOpaqueOption = errors.New("opaque option")
ErrOpaqueOption means Manifest cannot inspect an option. Use MakeModule for configuration modules and Runtime for options containing no registrations.
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. A nil handle produces ErrHandleIsNil in fx.App.Err.
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. A nil handle produces ErrHandleIsNil in fx.App.Err.
func FromLoader ¶ added in v0.3.0
FromLoader uses an existing loader instead of Module. A nil loader produces ErrLoaderIsNil in fx.App.Err. 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 ¶
func Module(opts ...confmaker.LoaderOption) fx.Option
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.
Example (CustomEngine) ¶
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/uchaloop/confmaker/v2"
"github.com/uchaloop/confx"
"go.uber.org/fx"
)
func main() {
type config struct {
Port int `json:"port"`
}
engine := confmaker.EngineFunc(func(ctx context.Context, req confmaker.LoadRequest) error {
if err := ctx.Err(); err != nil {
return err
}
if req.Name != "server" {
return fmt.Errorf("unknown configuration %q", req.Name)
}
return json.Unmarshal([]byte(`{"port":9090}`), req.Target)
})
app := fx.New(
fx.NopLogger,
confx.Module(confmaker.WithEngine(engine)),
confx.Provide[config]("server"),
fx.Invoke(func(cfg config) {
fmt.Println(cfg.Port)
}),
)
if err := app.Err(); err != nil {
fmt.Println(err)
}
}
Output: 9090
func Provide ¶
Provide registers a config with the required instance name and provides it without an Fx tag. With the built-in engine, confmaker validates the name and derives its ENV prefix; confmaker.WithPrefix overrides only the prefix. Install Module once with direct fx.New; App.FX and Modules.FX install it automatically. FromLoader cannot be mixed with Provide. The config is loaded during fx.New even when no constructor consumes it.
Example ¶
A config without a default can require an explicit nonempty value.
package main
import (
"fmt"
"github.com/uchaloop/confmaker/v2"
"github.com/uchaloop/confx"
"go.uber.org/fx"
)
func main() {
type requiredConfig struct {
Host string `env:"HOST,notEmpty"`
}
fxApp := fx.New(
fx.NopLogger,
confx.Module(confmaker.WithEnv(map[string]string{})),
confx.Provide[requiredConfig]("store"),
)
fmt.Println("missing host:", fxApp.Err() != nil)
}
Output: missing host: true
func ProvideNamed ¶
ProvideNamed registers a config with the required instance name and provides it with the Fx tag name:"<name>". With the built-in engine, the name determines the default prefix; confmaker.WithPrefix can override the prefix. Install Module once with direct fx.New; App.FX and Modules.FX install it automatically. 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"
"github.com/uchaloop/confmaker/v2"
"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"`
}
func (c *StoreConfig) SetDefaults() { c.Host = "localhost:5432" }
func main() {
env := confmaker.WithEnv(map[string]string{
"STORE_HOST": "db:5432",
"REPLICA_STORE_HOST": "replica:5432",
})
app := fx.New(
fx.NopLogger,
confx.Module(env),
confx.Provide[StoreConfig]("store"),
confx.ProvideNamed[StoreConfig]("replica", confmaker.WithPrefix("REPLICA_STORE_")),
fx.Invoke(func(p struct {
fx.In
Primary StoreConfig
Replica StoreConfig `name:"replica"`
}) {
fmt.Println(p.Primary.Host, p.Replica.Host)
}),
)
if err := app.Err(); err != nil {
fmt.Println(err)
}
}
Output: db:5432 replica:5432
func Runtime ¶ added in v0.5.1
Runtime marks options as containing no configuration declarations. Manifest skips them; Fx applies them normally in their original module scope. Known direct configuration options and nil options are rejected. Registrations hidden inside opaque fx.Options or fx.Module cannot be detected: callers must not hide them here. Argument evaluation and package init run normally in Go.
Types ¶
type App ¶ added in v0.6.0
type App struct {
// contains filtered or unexported fields
}
App is an immutable application composition with configuration description and process-level startup. Its zero value is equivalent to New(). Use Manifest and FX for custom command-line handling or a manual Fx lifecycle.
func New ¶ added in v0.6.0
New captures modules without constructing an Fx app, loading configuration, or evaluating defaults. Go argument evaluation and package init still run normally. Use MakeModule for configuration branches and Runtime for ordinary Fx options that offline description should skip.
Example ¶
server := confx.MakeModule("server",
confx.Provide[ServerConfig]("server"),
confx.Runtime(fx.Invoke(func(cfg ServerConfig) {
fmt.Println("server port:", cfg.Port)
})),
)
store := confx.MakeModule("store",
confx.Provide[StoreConfig]("store"),
confx.Runtime(fx.Invoke(func(cfg StoreConfig) {
fmt.Println("store host:", cfg.Host)
})),
)
// Defaults match the README: SERVER_PORT=8080, STORE_HOST=localhost:5432.
// In main, Run handles -help and -describe before starting Fx.
confx.New(server, store).Run()
func (*App) FX ¶ added in v0.6.0
FX returns the composition's Fx option without constructing an application or parsing command-line arguments. Each fx.New gets a fresh loader. Do not combine it with Module or FromLoader. Use fx.New(a.FX(), ...) for manual lifecycle control or additional runtime-only options such as fx.Populate.
Example ¶
FX exposes the composition for testing and manual Fx lifecycle control. Unlike Run, it does not parse the process arguments.
server := confx.MakeModule("server", confx.Provide[ServerConfig]("server"))
store := confx.MakeModule("store", confx.Provide[StoreConfig]("store"))
definition := confx.New(server, store).WithOptions(
confmaker.WithEnv(map[string]string{
"SERVER_PORT": "9090",
"STORE_HOST": "db:5432",
}),
)
var serverCfg ServerConfig
var storeCfg StoreConfig
fxApp := fx.New(
fx.NopLogger,
definition.FX(),
fx.Populate(&serverCfg, &storeCfg),
)
if err := fxApp.Err(); err != nil {
fmt.Println(err)
return
}
// Loading happens during fx.New; no Start is needed to inspect values.
fmt.Println(serverCfg.Port, storeCfg.Host)
Output: 9090 db:5432
func (*App) Manifest ¶ added in v0.6.0
func (a *App) Manifest(opts ...confmaker.ManifestOption) (confmaker.ManifestResult, error)
Manifest describes the composition without constructing Fx or loading ENV. Unlike Modules.Manifest, it includes defaults by default, executing user defaults and renderers. Use WithManifestOptions() to disable that behavior. Additional opts follow the saved options. Custom engines do not support manifests; opaque options outside Runtime are rejected before defaults run.
Example ¶
server := confx.MakeModule("server", confx.Provide[ServerConfig]("server"))
store := confx.MakeModule("store", confx.Provide[StoreConfig]("store"))
definition := confx.New(server, store)
manifest, err := definition.Manifest()
if err != nil {
fmt.Println(err)
return
}
variable := manifest.Configs[0].Variables[0]
fmt.Println(variable.Name, variable.Default.Text)
Output: SERVER_PORT 8080
func (*App) Run ¶ added in v0.6.0
func (a *App) Run()
Run handles -help and -describe=env|markdown|json before constructing Fx. It reads the current process arguments and writes help/descriptions to stdout. Unknown flags, positional arguments, and description/output errors are printed to stderr and exit with code 1. Successful help or description returns normally. Otherwise Run delegates to fx.New(a.FX()).Run(), including Fx's signal, timeout, shutdown and exit-code behavior. Each call creates a fresh Fx app; concurrent Run calls are unsupported. Use Manifest and FX for custom CLI or lifecycle control. Run does not recover user panics. Process exits skip caller defers; use Manifest and FX when the caller must retain control of cleanup and exit decisions.
func (*App) WithManifestOptions ¶ added in v0.6.0
func (a *App) WithManifestOptions(opts ...confmaker.ManifestOption) *App
WithManifestOptions returns a clone with replacement description options. App includes defaults unless this method is called. Calling it with no arguments disables default evaluation; it does not affect runtime loading.
func (*App) WithOptions ¶ added in v0.6.0
func (a *App) WithOptions(opts ...confmaker.LoaderOption) *App
WithOptions returns a clone with additional confmaker loader options. Each Fx application receives a fresh loader; supplied engines and callbacks are shared.
type ModuleSpec ¶ added in v0.5.1
type ModuleSpec struct {
// contains filtered or unexported fields
}
ModuleSpec preserves configuration declarations alongside a real Fx module. Create it with MakeModule. Its zero value is invalid.
func MakeModule ¶ added in v0.5.1
func MakeModule(name string, opts ...fx.Option) *ModuleSpec
MakeModule groups declarations and runtime options, preserving Fx scopes. Manifest rejects opaque options unless explicitly enclosed in Runtime.
type Modules ¶ added in v0.5.1
type Modules struct {
// contains filtered or unexported fields
}
Modules is an immutable application composition. Use MakeModules to create it. A zero Modules describes an empty application.
func MakeModules ¶ added in v0.5.1
MakeModules captures one module list for Manifest and FX.
Example ¶
One module tree is used for offline description and normal Fx construction.
package main
import (
"fmt"
"github.com/uchaloop/confmaker/v2"
"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"`
}
func (c *StoreConfig) SetDefaults() { c.Host = "localhost:5432" }
func main() {
orders := confx.MakeModule("orders",
confx.Provide[StoreConfig]("store"),
confx.Runtime(
fx.Invoke(func(cfg StoreConfig) {
fmt.Println("loaded:", cfg.Host)
}),
),
)
modules := confx.MakeModules(orders).WithOptions(
confmaker.WithEnv(map[string]string{"STORE_HOST": "db:5432"}),
)
manifest, err := modules.Manifest()
if err != nil {
fmt.Println(err)
return
}
fmt.Println("described:", manifest.Configs[0].InstanceName)
app := fx.New(fx.NopLogger, modules.FX())
if err := app.Err(); err != nil {
fmt.Println(err)
}
}
Output: described: store loaded: db:5432
func (*Modules) FX ¶ added in v0.5.1
FX builds an Fx option with one fresh loader per application. Do not combine it with Module or FromLoader. Ordinary opaque options work in this path.
func (*Modules) Manifest ¶ added in v0.5.1
func (m *Modules) Manifest(opts ...confmaker.ManifestOption) (confmaker.ManifestResult, error)
Manifest describes all visible declarations without constructing an Fx app. It first rejects opaque branches, then delegates to the core. IncludeDefaults executes user defaults and renderers. This does not validate the Fx graph or all load-only options. A custom engine returns ErrManifestUnsupported.
func (*Modules) WithOptions ¶ added in v0.5.1
func (m *Modules) WithOptions(opts ...confmaker.LoaderOption) *Modules
WithOptions returns a copy with additional shared core options. Engine objects and callback closures are shared; the loader itself is never shared.