confx

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 1, 2026 License: MIT Imports: 6 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, ENV parsing, validation and diagnostics; the adapter owns wiring them into Fx.

Install · Quick start · Instances · Lifecycle · Testing · Reference

Installation

Requires Go 1.27 or later and confmaker v0.8.0. This README describes the v0.2.0 API.

go get github.com/uchaloop/confx@v0.2.0
import "github.com/uchaloop/confx"

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

Quick start

package main

import (
	"fmt"
	"log"

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

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

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

func main() {
	app := fx.New(
		confx.Module(),
		confx.Provide[ServerConfig]("server"),
		fx.Invoke(func(cfg ServerConfig) {
			fmt.Println(cfg.Port) // Pass the config to your server constructor.
		}),
	)
	if err := app.Err(); err != nil {
		log.Fatal(err)
	}
	app.Run()
}

Run with no ENV for the default 8080, or set SERVER_PORT in your IDE or shell. The config type needs no dependency on either confmaker or Fx.

flowchart LR
    A["Provide / ProvideNamed"] --> B["Module: one core loader"]
    B --> C["Register and load all configs during fx.New"]
    C -->|Success| D["Typed values for consumers"]
    C -->|Error| E["app.Err; startup prevented"]

Multiple instances

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 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:

type Params struct {
	fx.In
	Primary ServerConfig
	Replica ServerConfig `name:"replica"`
}

Use fx.Invoke(func(p Params) { /* use p.Primary and p.Replica */ }) or take Params in a constructor. Two untagged providers of the same type conflict in Fx even if their instance names differ. WithPrefix never changes the Fx tag.

Lifecycle and errors

Include confx.Module() exactly once when using Provide or ProvideNamed. All provided configs load during fx.New, including those with no consumers. A core loading error is available through app.Err() and prevents startup.

Core structured errors remain reachable through Fx's wrappers:

if err := app.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. Config values should be treated as read-only. Provider options may be reused across applications; each app receives its own loader and values.

Testing

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

func TestServerConfig(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)
	}
}

No app.Start is needed to inspect configuration values: loading already happened during construction. In a real app, unrelated constructors can run during fx.New; this is not a general-purpose dry-run mode.

Manifest and documentation

Manifest and export APIs belong to confmaker. See its manifest guide for .env.example, JSON and Markdown generation.

confx does not currently expose a manifest-only mode for a list of Provide options. Do not construct an entire Fx application just to extract metadata: use the core APIs for standalone descriptions. The simple Provide API remains the application registration entry point.

Reference

Acknowledgements

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

License

MIT

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.

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. 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.

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

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Module

func Module(opts ...confmaker.EnvOption) 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.

An application that calls Provide or ProvideNamed must include Module exactly once.

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. confmaker validates the name and derives its default ENV prefix; confmaker.WithPrefix overrides only the prefix. Module is required. The config is loaded during fx.New even when no constructor consumes it.

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>". 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.

Jump to

Keyboard shortcuts

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