confx

package module
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Nov 4, 2026 License: MIT Imports: 10 Imported by: 0

README

confx

Go Reference CI Release

Explicit confmaker registration for Uber Fx. confx provides typed configs to Fx consumers. confmaker owns defaults, configuration engines, validation and diagnostics; the adapter owns wiring them into Fx.

Install · Quick start · App options · Modules · Engines · Diagnostics · Manifest · Testing

Installation

Requires Go 1.27.0 or later. The command below installs the latest stable release. This README describes the current branch; for a pinned release, use its tagged documentation. The App API uses confcli.Describe from confmaker/v2 v2.1.0 or later.

go get github.com/uchaloop/confx@latest
import "github.com/uchaloop/confx"

Import github.com/uchaloop/confmaker/v2 when using core options such as WithEnv, WithPrefix or WithDiagnostics.

Quick start

Choose the entry point that matches your application:

Need Entry point
Ordinary Fx injection, no built-in help/describe fx.New(confx.Module(), server.Module, store.Module).Run()
Built-in help and configuration description confx.New(modules...).Run()
Custom CLI or manual Fx lifecycle App.Manifest / App.FX, or MakeModules
Caller-owned loader and registrations FromLoader / FromHandle

Both variants below use these shared declarations. Create a main.go with this block, then append one of the two module/startup blocks:

package main

import (
	"fmt"

	"github.com/uchaloop/confx"
	"go.uber.org/fx"
)

type ServerConfig struct {
	Port int `env:"PORT"`
}

func (c *ServerConfig) SetDefaults() { c.Port = 8080 }

type StoreConfig struct {
	Host string `env:"HOST"`
}

func (c *StoreConfig) SetDefaults() { c.Host = "localhost:5432" }

func showServer(cfg ServerConfig) { fmt.Println("server port:", cfg.Port) }
func showStore(cfg StoreConfig) { fmt.Println("store host:", cfg.Host) }
Ordinary Fx without describe

Use ordinary fx.Module values and install confx.Module() once at the root. Neither MakeModule nor Runtime is required:

var ServerModule = fx.Module("server",
	confx.Provide[ServerConfig]("server"),
	fx.Invoke(showServer),
)

var StoreModule = fx.Module("store",
	confx.Provide[StoreConfig]("store"),
	fx.Invoke(showStore),
)

func main() {
	fx.New(confx.Module(), ServerModule, StoreModule).Run()
}

This is a supported API for applications that do not need offline description. To configure loading, pass core options to confx.Module(opts...), for example confx.Module(confmaker.WithDiagnosticHandler(handleReport)).

App with help and describe

Use MakeModule for configuration-bearing branches and Runtime for service providers, invocations and decorators that description should skip:

var ServerModule = confx.MakeModule("server",
	confx.Provide[ServerConfig]("server"),
	confx.Runtime(fx.Invoke(showServer)),
)

var StoreModule = confx.MakeModule("store",
	confx.Provide[StoreConfig]("store"),
	confx.Runtime(fx.Invoke(showStore)),
)

func main() {
	confx.New(ServerModule, StoreModule).Run()
}

In a larger application these modules live in domain packages, so main can be just confx.New(server.Module, store.Module, orders.Module).Run(). App installs the adapter itself; do not add confx.Module() or FromLoader. Service constructors registered with fx.Provide inside Runtime consume the same typed configs normally.

Both examples read SERVER_PORT and STORE_HOST, with defaults 8080 and localhost:5432. The invocations only demonstrate injection; they do not start an HTTP server or database. Normal execution waits for Fx shutdown signals:

go build -o service .
SERVER_PORT=9090 STORE_HOST=db:5432 ./service

Only the App variant handles the following CLI commands.

New is lazy: it captures the module list without constructing Fx, reading ENV, or evaluating defaults. Run reads the current process arguments and supports:

./service -help
./service -describe=env
./service -describe=markdown
./service -describe=json

Help and description return without constructing Fx. For configurable defaults, custom flags and manual startup, see the following sections.

App options and lifecycle

WithOptions appends loader options and returns a new composition:

confx.New(ServerModule, StoreModule).WithOptions(
	confmaker.WithDiagnosticHandler(handleReport),
).Run()

WithManifestOptions replaces description options and also returns a copy. App descriptions include defaults unless configured otherwise. To skip default evaluation without changing runtime loading or disabling CLI:

confx.New(ServerModule, StoreModule).WithManifestOptions().Run()

App.Manifest(opts...) applies saved options followed by the call's options. See Manifest and describe for the inspection boundary. The zero App is equivalent to New().

Without a CLI action, Run delegates to fx.New(app.FX()).Run(). Fx owns signals, timeouts, startup rollback and shutdown exit codes. Help and descriptions go to stdout and return. CLI/description/output errors print to stderr and exit with code 1; unknown flags and positional arguments are rejected. User panics are not recovered. Concurrent Run calls are unsupported.

Process exits skip caller defers. For custom CLI flags use App.Manifest with confcli.MakeDescribeFlag; for manual lifecycle control construct fx.New(app.FX(), ...) and use Fx's Start/Stop. confx.App itself has no Err, Start, Stop or Wait methods. See Testing for fx.Populate.

All registered configurations, including unused ones, load during fx.New, before lifecycle OnStart. Ordinary constructors and invocations can also run at construction time; Runtime does not defer them until Start. Loading errors prevent startup. The adapter adds no cancellation or engine resource lifecycle to Fx construction.

Module, App.FX and Modules.FX create a fresh loader per Fx application. App/Modules install the adapter automatically; do not combine their FX() with Module or FromLoader. Options can be reused; supplied engines and callbacks remain shared. Treat loaded configuration values, including maps and slices, as read-only. Caller-owned loaders follow the external loader contract instead.

Modules and instances

Nest MakeModule values to preserve Fx private-provider visibility and decorator scopes. A plain fx.Module hides declarations from offline inspection; use MakeModule on configuration-bearing branches and Runtime on service-only branches.

An instance name, an ENV prefix and an Fx tag serve different purposes:

Concept Source Purpose
Instance name First argument to Provide / ProvideNamed Core registration identity and diagnostics
ENV prefix (built-in engine) Derived from the name, or WithPrefix Variable lookup
Fx name tag ProvideNamed only Select a named dependency

Using ServerConfig from the quick start, replace its registration with:

confx.Provide[ServerConfig]("server"),
confx.ProvideNamed[ServerConfig]("replica", confmaker.WithPrefix("READ_SERVER_")),

The first reads SERVER_PORT, the second READ_SERVER_PORT. Consume them with:

fx.Invoke(func(params struct {
	fx.In

	Primary ServerConfig
	Replica ServerConfig `name:"replica"`
}) {
	fmt.Println(params.Primary.Port, params.Replica.Port)
})

The same parameter shape can be used by a constructor. Two untagged providers of the same type conflict in Fx even if their instance names differ. WithPrefix never changes the Fx tag.

Configuration engines

The default engine reads ENV with confmaker tags and rules. Configure it through Module(opts...) or App.WithOptions / Modules.WithOptions.

A custom engine is selected once for the same composition:

modules := confx.MakeModules(
	ServerModule,
	StoreModule,
).WithOptions(
	confmaker.WithEngine(engine),
	confmaker.WithDiagnosticHandler(handleReport),
)

The custom engine owns its sources, tags and decoding rules. confmaker still applies root defaults before decoding and validates afterward. Built-in options such as WithEnv and WithPrefix cannot be combined with a custom engine.

Runnable example What it demonstrates
env/v11 Native ENV tags and envDefault, isolated input maps, two named configs
koanf + TOML Embedded TOML files, overlay settings, native koanf tags, two named configs

Both examples build a domain module and inject configurations into a service. Their dependencies live in a separate Go module, not in the adapter's main module. The package GoDoc also includes a runnable standard-library JSON engine example. For Viper and direct YAML/TOML decoders, see confmaker's engine examples.

Engines and callbacks must support concurrent calls if reused concurrently. The application owns their resources. Backend errors may contain input values; the diagnostic report contains no configuration values. Custom-engine reports have config-level detail. Custom engines do not support manifest/describe; they return confmaker.ErrManifestUnsupported. App help remains available.

Errors and diagnostics

Inspect construction errors on the fx.App returned by fx.New.

With the App modules above (and imports for log and confmaker), core structured errors remain reachable through Fx's wrappers:

definition := confx.New(ServerModule, StoreModule)
fxApp := fx.New(definition.FX())
if err := fxApp.Err(); err != nil {
	for _, problem := range confmaker.ConfigErrors(err) {
		fmt.Println(problem.Kind, problem.InstanceName, problem.VariableName)
	}
	log.Fatal(err)
}

Always handle the original error: missing Module and Fx dependency conflicts are not core ConfigError diagnostics. Nil adapter arguments can be recognized with errors.Is(err, confx.ErrLoaderIsNil) or errors.Is(err, confx.ErrHandleIsNil); this also works through fxApp.Err().

Use confmaker.WithDiagnosticHandler(func(report confmaker.LoadReport) { ... }) through App.WithOptions / Modules.WithOptions (or the standalone Module) for a value-free report during app construction, including load errors. With an external loader, WithDiagnostics() enables loader.Report() snapshots. The loader owns the report; there is no separate receiver. Callbacks run after publication and do not run for loading panics.

If Fx rejects the graph before loading, the diagnostic callback does not run. An enabled external loader remains not_started. Inspect fxApp.Err() for graph errors; reports describe configuration loading, not the entire Fx application.

Manifest and describe

Use the App modules from the quick start as the declaration tree. Manifest does not build Fx, load ENV, invoke services or call load diagnostics. It does not validate the Fx graph or all load-only options.

App.Manifest includes defaults by default; Modules.Manifest requires confmaker.IncludeDefaults() explicitly. Defaults and renderers are application code and may perform I/O, including reading ENV.

To avoid incomplete output, manifest rejects opaque options outside Runtime, including ordinary fx.Options, fx.Module and fx.Provide, with confx.ErrOpaqueOption. The error identifies the module path and option index. The entire tree is checked before defaults run; errors return no partial result. These ordinary Fx options remain usable on the runtime FX() path.

Runtime asserts that its contents have no configuration declarations. Direct registrations, module specifications and standalone loader/handle options are rejected inside it. Registrations hidden deeper inside an opaque Fx wrapper cannot be detected. Go argument evaluation and package init run normally, even in description mode. See Configuration engines for the custom-engine limitation.

Custom CLI with Modules

For a custom CLI, compose the modules once in your application. describe below is a confcli.MakeDescribeFlag already parsed by your own flag parser. Only this manual path leaves flag parsing with the application; App.Run handles it itself. Use App.Manifest() and App.FX() instead if you prefer the App composition.

modules := confx.MakeModules(
	ServerModule,
	StoreModule,
).WithOptions(
	confmaker.WithDiagnosticHandler(handleReport),
)

if describe.Requested() {
	manifest, err := modules.Manifest(confmaker.IncludeDefaults())
	if err != nil {
		log.Fatal(err)
	}

	if err := describe.Write(os.Stdout, manifest); err != nil {
		log.Fatal(err)
	}

	return
}

fx.New(modules.FX()).Run()

External loader

Use an external Loader when your application already owns configuration registration, loading or inspection. FromLoader and the handle adapters bridge those registrations into Fx without registering them again.

Adapter Purpose
FromLoader(loader) Use an existing loader instead of Module
FromHandle(handle) Provide an existing registration as an untagged value
FromHandleNamed(handle) Provide it with its registration name as the Fx tag

All registrations load, even when no handle has a consumer. Handles must belong to the supplied loader; nil, zero and foreign handles fail construction. An already-loaded loader reuses its result. Reusing an external loader across apps also shares its values and load state; create separate loaders for isolation.

The two modes cannot be mixed: Module accepts only Provide/ProvideNamed; FromLoader accepts only FromHandle/FromHandleNamed. Mode and handle ownership checks finish before any registration or loading. Invalid connections leave the external loader unchanged, so a corrected connection can reuse it. Once Load has run, its result remains final as usual.

Register every config outside Fx in the extended mode. The manifest generated before fx.New then describes the complete registration set.

For export formats and guarantees, see the core manifest guide. External loaders also provide LoadContext and report snapshots for callers that need explicit loading control. These adapters do not parse CLI flags or terminate the process.

Testing

Pass an isolated ENV map to Module. This setup uses ServerConfig above and imports testing, confmaker, confx and Fx:

func TestConfig(t *testing.T) {
	t.Parallel()

	var got ServerConfig
	app := fx.New(
		fx.NopLogger,
		confx.Module(confmaker.WithEnv(map[string]string{"SERVER_PORT": "9090"})),
		confx.Provide[ServerConfig]("server"),
		fx.Populate(&got),
	)
	if err := app.Err(); err != nil {
		t.Fatal(err)
	}
	if got.Port != 9090 {
		t.Fatalf("unexpected port: %d", got.Port)
	}
}

These tests exercise Fx construction; Start is needed only for lifecycle hooks.

For the App variant, keep CLI parsing out of the test and use FX() with fx.Populate. This fragment belongs inside a test using the App modules above:

definition := confx.New(ServerModule, StoreModule).WithOptions(
	confmaker.WithEnv(map[string]string{"SERVER_PORT": "9090"}),
)
var got ServerConfig
fxApp := fx.New(fx.NopLogger, definition.FX(), fx.Populate(&got))
if err := fxApp.Err(); err != nil {
	t.Fatal(err)
}
if got.Port != 9090 {
	t.Fatalf("unexpected port: %d", got.Port)
}

For repository checks, run each module explicitly with GOWORK=off: the root, integration/secret and examples/engines. In each directory run:

GOWORK=off go mod tidy -diff
GOWORK=off go build ./...
GOWORK=off go vet ./...
GOWORK=off go test -race ./...

Real secret compatibility lives in integration/secret. The root module does not depend on secret or the example engines. See CONTRIBUTING.md for code style and contribution requirements.

Reference

Acknowledgements

Thanks to the authors and maintainers of Uber Fx for its dependency injection and lifecycle primitives.

License

MIT

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

Examples

Constants

This section is empty.

Variables

View Source
var ErrHandleIsNil = errors.New("handle is nil")

ErrHandleIsNil means a handle adapter was called without a handle.

View Source
var ErrLoaderIsNil = errors.New("loader is nil")

ErrLoaderIsNil means FromLoader was called without a loader.

View Source
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

func FromHandle[T any](handle *confmaker.Handle[T]) fx.Option

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

func FromHandleNamed[T any](handle *confmaker.Handle[T]) fx.Option

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

func FromLoader(loader *confmaker.Loader) fx.Option

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

func Provide[T any](name string, opts ...confmaker.ConfigOption) fx.Option

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

func ProvideNamed[T any](name string, opts ...confmaker.ConfigOption) fx.Option

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

func Runtime(opts ...fx.Option) fx.Option

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

func New(modules ...fx.Option) *App

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

func (a *App) FX() fx.Option

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

func MakeModules(modules ...fx.Option) *Modules

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

func (m *Modules) FX() fx.Option

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

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.

Jump to

Keyboard shortcuts

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