options API

options

package

API reference for the options package.

T
type

Option

Option is a functional option for configuration.

pkg/options/options.go:8-8
type Option func(*T)
F
function

Apply

Apply applies functional options to a config struct.

Parameters

cfg
*T
opts
...Option[T]
pkg/options/options.go:16-20
func Apply[T any](cfg *T, opts ...Option[T])

{
	for _, opt := range opts {
		opt(cfg)
	}
}

Example

cfg := &Config{}
options.Apply(cfg, WithHost("localhost"))
S
struct

Builder

Builder provides a fluent interface for building config with options.

pkg/options/options.go:29-32
type Builder struct

Example

cfg := options.NewBuilder(defaultCfg).
	With(WithHost("localhost")).
	Build()

Fields

Name Type Description
cfg T
opts []Option[T]
F
function

NewBuilder

NewBuilder creates a new options builder with default config.

Parameters

defaults
T

Returns

*Builder[T]
pkg/options/options.go:35-37
func NewBuilder[T any](defaults T) *Builder[T]

{
	return &Builder[T]{cfg: defaults}
}
F
function

Merge

Merge combines multiple configurations of the same type.
Later values always overwrite earlier ones (LastWins semantics).
For SkipZero semantics, use MergeConfig from options_ext.go.

Parameters

cfgs
...T

Returns

T
pkg/options/options.go:68-80
func Merge[T any](cfgs ...T) T

{
	var result T
	resultVal := reflect.ValueOf(&result).Elem()
	resultType := resultVal.Type()

	for _, cfg := range cfgs {
		cfgVal := reflect.ValueOf(cfg)
		for i := 0; i < resultType.NumField(); i++ {
			resultVal.Field(i).Set(cfgVal.Field(i))
		}
	}
	return result
}

Example

cfg := options.Merge(defaultCfg, userCfg)
S
struct

Options

Options wraps a validated configuration section for DI consumption.
Similar to .NET IOptions.

pkg/options/options_ext.go:10-12
type Options struct

Fields

Name Type Description
Value T
F
function

Configure

Configure registers an Options[T] in the DI container by binding
from the given configuration section at startup.

Usage:

di.ConfigureAppOptions)

Parameters

value
T

Returns

Options[T]
pkg/options/options_ext.go:20-22
func Configure[T any](value T) Options[T]

{
	return Options[T]{Value: value}
}
T
type

MergeWithSemantics

MergeWithSemantics controls how Merge handles zero values.

pkg/options/options_ext.go:25-25
type MergeWithSemantics int
F
function

MergeConfig

MergeConfig combines configs with explicit merge semantics.

Parameters

semantics
cfgs
...T

Returns

T
pkg/options/options_ext.go:35-55
func MergeConfig[T any](semantics MergeWithSemantics, cfgs ...T) T

{
	var result T
	resultVal := reflect.ValueOf(&result).Elem()
	resultType := resultVal.Type()

	for _, cfg := range cfgs {
		cfgVal := reflect.ValueOf(cfg)
		for i := 0; i < resultType.NumField(); i++ {
			field := cfgVal.Field(i)
			switch semantics {
			case LastWins:
				resultVal.Field(i).Set(field)
			case SkipZero:
				if !field.IsZero() {
					resultVal.Field(i).Set(field)
				}
			}
		}
	}
	return result
}
F
function

MustValidate

MustValidate panics if the struct contains zero-valued required fields.
Required fields are marked with required:"true" tag.

Parameters

cfg
T
pkg/options/options_ext.go:59-74
func MustValidate[T any](cfg T)

{
	val := reflect.ValueOf(cfg)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}
	typ := val.Type()

	for i := 0; i < typ.NumField(); i++ {
		field := typ.Field(i)
		if field.Tag.Get("required") == "true" {
			if val.Field(i).IsZero() {
				panic(fmt.Sprintf("options: required field %s is zero", field.Name))
			}
		}
	}
}
F
function

TestMergeConfig_LastWins

Parameters

pkg/options/options_ext_test.go:7-23
func TestMergeConfig_LastWins(t *testing.T)

{
	type Config struct {
		Debug bool
		Port  int
	}

	defaults := Config{Debug: true, Port: 8080}
	override := Config{Debug: false, Port: 0}

	result := MergeConfig(LastWins, defaults, override)
	if result.Debug != false {
		t.Errorf("Debug: got %v, want false", result.Debug)
	}
	if result.Port != 0 {
		t.Errorf("Port: got %d, want 0", result.Port)
	}
}
F
function

TestMergeConfig_SkipZero

Parameters

pkg/options/options_ext_test.go:25-41
func TestMergeConfig_SkipZero(t *testing.T)

{
	type Config struct {
		Port  int
		Host  string
	}

	defaults := Config{Port: 8080, Host: "localhost"}
	override := Config{Port: 0, Host: "overridden"}

	result := MergeConfig(SkipZero, defaults, override)
	if result.Port != 8080 {
		t.Errorf("Port: got %d, want 8080 (zero should not overwrite)", result.Port)
	}
	if result.Host != "overridden" {
		t.Errorf("Host: got %q, want %q", result.Host, "overridden")
	}
}
F
function

TestConfigure

Parameters

pkg/options/options_ext_test.go:43-53
func TestConfigure(t *testing.T)

{
	type AppOptions struct {
		Host string `config:"host" required:"true"`
		Port int    `config:"port"`
	}

	opts := Configure(AppOptions{Host: "localhost", Port: 8080})
	if opts.Value.Host != "localhost" {
		t.Errorf("Configure: got %q, want %q", opts.Value.Host, "localhost")
	}
}
F
function

TestMustValidate

Parameters

pkg/options/options_ext_test.go:55-78
func TestMustValidate(t *testing.T)

{
	type Config struct {
		Name string `required:"true"`
		Port int
	}

	t.Run("valid", func(t *testing.T) {
		defer func() {
			if r := recover(); r != nil {
				t.Errorf("should not panic: %v", r)
			}
		}()
		MustValidate(Config{Name: "app"})
	})

	t.Run("missing required", func(t *testing.T) {
		defer func() {
			if r := recover(); r == nil {
				t.Error("should panic for missing required field")
			}
		}()
		MustValidate(Config{Name: ""})
	})
}
S
struct

ServerConfig

pkg/options/options_test.go:5-9
type ServerConfig struct

Fields

Name Type Description
Host string
Port int
Timeout int
F
function

WithHost

Parameters

h
string

Returns

func(*ServerConfig)
pkg/options/options_test.go:11-13
func WithHost(h string) func(*ServerConfig)

{
	return func(c *ServerConfig) { c.Host = h }
}
F
function

WithPort

Parameters

p
int

Returns

func(*ServerConfig)
pkg/options/options_test.go:15-17
func WithPort(p int) func(*ServerConfig)

{
	return func(c *ServerConfig) { c.Port = p }
}
F
function

WithTimeout

Parameters

t
int

Returns

func(*ServerConfig)
pkg/options/options_test.go:19-21
func WithTimeout(t int) func(*ServerConfig)

{
	return func(c *ServerConfig) { c.Timeout = t }
}
F
function

TestApply

Parameters

pkg/options/options_test.go:23-34
func TestApply(t *testing.T)

{
	cfg := &ServerConfig{Host: "localhost", Port: 8080}

	Apply(cfg, WithHost("0.0.0.0"), WithPort(9000))

	if cfg.Host != "0.0.0.0" {
		t.Errorf("Host: got %q, want %q", cfg.Host, "0.0.0.0")
	}
	if cfg.Port != 9000 {
		t.Errorf("Port: got %d, want %d", cfg.Port, 9000)
	}
}
F
function

TestBuilder

Parameters

pkg/options/options_test.go:36-53
func TestBuilder(t *testing.T)

{
	defaults := ServerConfig{Host: "localhost", Port: 8080, Timeout: 30}

	cfg := NewBuilder(defaults).
		With(WithHost("127.0.0.1")).
		With(WithTimeout(60)).
		Build()

	if cfg.Host != "127.0.0.1" {
		t.Errorf("Host: got %q, want %q", cfg.Host, "127.0.0.1")
	}
	if cfg.Port != 8080 {
		t.Errorf("Port should remain default: got %d, want %d", cfg.Port, 8080)
	}
	if cfg.Timeout != 60 {
		t.Errorf("Timeout: got %d, want %d", cfg.Timeout, 60)
	}
}
F
function

TestBuilder_Ptr

Parameters

pkg/options/options_test.go:55-68
func TestBuilder_Ptr(t *testing.T)

{
	defaults := ServerConfig{Port: 3000}

	cfg := NewBuilder(defaults).
		With(WithHost("api.example.com")).
		Ptr()

	if cfg.Host != "api.example.com" {
		t.Errorf("Host: got %q, want %q", cfg.Host, "api.example.com")
	}
	if cfg.Port != 3000 {
		t.Errorf("Port: got %d, want %d", cfg.Port, 3000)
	}
}