structconfig

package module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Nov 23, 2025 License: MIT Imports: 4 Imported by: 7

README

Go Report Card Go Reference

structconfig

Map default values, environment variables, and command-line arguments to struct tags.

Installation

go get github.com/berquerant/structconfig

Examples

Default values

type T struct {
  I int `default:"10"`
}

sc := structconfig.New[T]()
var got T
if err := sc.FromDefault(&got); err != nil {
  panic(err)
}
// got.I == 10

Environment variables

type T struct {
  I int `name:"int_value"`
}

os.Setenv("INT_VALUE", "10")
sc := structconfig.New[T]()
var got T
if err := sc.FromEnv(&got); err != nil {
  panic(err)
}
// got.I == 10

Command-line flags (pflag)

type T struct {
  I int `name:"int_value" default:"10"`
}

var fs *pflag.FlagSet = // ...
sc := structconfig.New[T]()
if err := sc.SetFlags(fs); err != nil {
  panic(err)
}
if err := fs.Parse([]string{"--int_value", "100"}); err != nil {
  panic(err)
}
var got T
if err := sc.FromFlags(&got, fs); err != nil {
  panic(err)
}
// got.I == 100

More examples

Documentation

Index

Examples

Constants

View Source
const (
	TagName    = internal.TagName
	TagUsage   = internal.TagUsage
	TagDefault = internal.TagDefault
	TagShort   = internal.TagShort
)

Variables

View Source
var (
	ErrStructConfig     = internal.ErrStructConfig
	ErrNotStruct        = internal.ErrNotStruct
	ErrNotStructPointer = internal.ErrNotStructPointer
)

Functions

func IsSupportedKind

func IsSupportedKind(k reflect.Kind) bool

func NewConfigWithMerge added in v0.4.0

func NewConfigWithMerge[T any](
	sc *StructConfig[T],
	merger *Merger[T],
	fs *pflag.FlagSet,
	opt ...Option,
) (*T, error)

NewConfigWithMerge generates a Config by taking into account default values, environment variables, and command-line arguments.

It overrides the default values with values obtained from environment variables and further overrides them with the values from command-line arguments. The command-line arguments are obtained by calling StructConfig.SetFlags on the fs and then parsing with pflag.FlagSet.Parse.

In this process, the values to be parsed are those specified with WithArguments. If none are specified, it uses os.Args.

Example
package main

import (
	"fmt"
	"os"

	"github.com/berquerant/structconfig"
	"github.com/spf13/pflag"
)

func main() {
	type T struct {
		Default  string `name:"default_value" default:"default"`
		Env      string `name:"env_value" default:"env_default"`
		Flag     string `name:"flag_value" default:"flag_default"`
		Override string `name:"override_value" default:"override_default"`
	}
	envs := map[string]string{
		"ENV_VALUE":      "from_env",
		"OVERRIDE_VALUE": "should_not_appear",
	}
	args := []string{
		"--flag_value", "from_flag",
		"--override_value", "overrided",
	}

	for k, v := range envs {
		os.Setenv(k, v)
	}
	defer func() {
		for k := range envs {
			os.Unsetenv(k)
		}
	}()
	fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
	c, err := structconfig.NewConfigWithMerge[T](
		structconfig.New[T](),
		structconfig.NewMerger[T](),
		fs,
		structconfig.WithArguments(args),
	)
	if err != nil {
		panic(err)
	}
	fmt.Println(c.Default, c.Env, c.Flag, c.Override)
}
Output:

default from_env from_flag overrided

Types

type AnyCallbackFunc

type AnyCallbackFunc = func(StructField, string, func() reflect.Value) error

type AnyEqualFunc

type AnyEqualFunc = func(left, right any) (bool, error)

type AnyReceptor

type AnyReceptor = internal.AnyReceptor

type BoolReceptor

type BoolReceptor = internal.BoolReceptor

type Builder added in v0.5.0

type Builder[T any] struct {
	// contains filtered or unexported fields
}
Example
package main

import (
	"encoding/json"
	"fmt"
	"os"

	"github.com/berquerant/structconfig"
	"github.com/spf13/pflag"
)

func main() {
	type T struct {
		Default  string `json:"default" name:"default_value" default:"default"`
		Env      string `json:"env" name:"env_value" default:"env_default"`
		Flag     string `json:"flag" name:"flag_value" default:"flag_default"`
		File     string `json:"file" name:"file_value" default:"file_default"`
		Override string `json:"override" name:"override_value" default:"override_default"`
	}
	file := []byte(`{"file":"from_file","override":"from_file"}`)
	envs := map[string]string{
		"ENV_VALUE":      "from_env",
		"OVERRIDE_VALUE": "should_not_appear",
	}
	args := []string{
		"--flag_value", "from_flag",
		"--override_value", "overrided",
	}

	for k, v := range envs {
		os.Setenv(k, v)
	}
	defer func() {
		for k := range envs {
			os.Unsetenv(k)
		}
	}()

	fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
	c, err := structconfig.NewBuilder[T](structconfig.New[T](), structconfig.NewMerger[T]()).
		Add(func(sc *structconfig.StructConfig[T]) (*T, error) {
			var t T
			if err := sc.FromDefault(&t); err != nil {
				return nil, err
			}
			if err := json.Unmarshal(file, &t); err != nil {
				return nil, err
			}
			return &t, nil
		}).
		Add(func(sc *structconfig.StructConfig[T]) (*T, error) {
			var t T
			if err := sc.FromEnv(&t); err != nil {
				return nil, err
			}
			return &t, nil
		}).
		Add(func(sc *structconfig.StructConfig[T]) (*T, error) {
			if err := sc.SetFlags(fs); err != nil {
				return nil, err
			}
			if err := fs.Parse(args); err != nil {
				return nil, err
			}
			var t T
			if err := sc.FromFlags(&t, fs); err != nil {
				return nil, err
			}
			return &t, nil
		}).
		Build()
	if err != nil {
		panic(err)
	}
	fmt.Println(c.Default, c.Env, c.Flag, c.File, c.Override)
}
Output:

default from_env from_flag from_file overrided

func NewBuilder added in v0.5.0

func NewBuilder[T any](sc *StructConfig[T], merger *Merger[T]) *Builder[T]

func (*Builder[T]) Add added in v0.5.0

func (b *Builder[T]) Add(f func(*StructConfig[T]) (*T, error)) *Builder[T]

Add adds a Config generator to the Builder.

func (*Builder[T]) Build added in v0.5.0

func (b *Builder[T]) Build() (*T, error)

Build generates a Config in order from the generators added earlier and override them accordingly.

type Config

type Config struct {
	AnyCallback *ConfigItem[AnyCallbackFunc]
	AnyEqual    *ConfigItem[AnyEqualFunc]
	Prefix      *ConfigItem[string]
	Arguments   *ConfigItem[[]string]
}

func (*Config) Apply

func (s *Config) Apply(opt ...Option)

type ConfigBuilder

type ConfigBuilder struct {
	// contains filtered or unexported fields
}

func NewConfigBuilder

func NewConfigBuilder() *ConfigBuilder

func (*ConfigBuilder) AnyCallback

func (s *ConfigBuilder) AnyCallback(v AnyCallbackFunc) *ConfigBuilder

func (*ConfigBuilder) AnyEqual

func (s *ConfigBuilder) AnyEqual(v AnyEqualFunc) *ConfigBuilder

func (*ConfigBuilder) Arguments added in v0.4.0

func (s *ConfigBuilder) Arguments(v []string) *ConfigBuilder

func (*ConfigBuilder) Build

func (s *ConfigBuilder) Build() *Config

func (*ConfigBuilder) Prefix

func (s *ConfigBuilder) Prefix(v string) *ConfigBuilder

type ConfigItem

type ConfigItem[T any] struct {
	// contains filtered or unexported fields
}

func NewConfigItem

func NewConfigItem[T any](defaultValue T) *ConfigItem[T]

func (*ConfigItem[T]) Default

func (s *ConfigItem[T]) Default() T

func (*ConfigItem[T]) Get

func (s *ConfigItem[T]) Get() T

func (*ConfigItem[T]) IsModified

func (s *ConfigItem[T]) IsModified() bool

func (*ConfigItem[T]) Set

func (s *ConfigItem[T]) Set(value T)

type EnvVar

type EnvVar = internal.EnvVar

type FloatReceptor

type FloatReceptor = internal.FloatReceptor

type IntReceptor

type IntReceptor = internal.IntReceptor

type Merger

type Merger[T any] struct {
	*internal.Merger[T]
}
Example
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"reflect"

	"github.com/berquerant/structconfig"
)

func main() {
	type T struct {
		I                 int    `name:"i" default:"1"`
		S                 string `name:"s" default:"s"`
		II                []int  `name:"ii" default:"[1]"`
		IgnoreWithoutName int
	}

	callback := func(s structconfig.StructField, v string, fv func() reflect.Value) error {
		if s.Name() != "II" {
			return errors.New("unexpected field name")
		}
		var xs []int
		if err := json.Unmarshal([]byte(v), &xs); err != nil {
			return err
		}
		fv().Set(reflect.ValueOf(xs))
		return nil
	}

	eq := func(a, b any) (bool, error) {
		// expect only []int because int and string are supported by structconfig
		xs, ok := a.([]int)
		if !ok {
			return false, nil
		}
		ys, ok := b.([]int)
		if !ok {
			return false, nil
		}
		if len(xs) != len(ys) {
			return false, nil
		}
		for i, x := range xs {
			if x != ys[i] {
				return false, nil
			}
		}
		return true, nil
	}

	m := structconfig.NewMerger[T](
		structconfig.WithAnyCallback(callback),
		structconfig.WithAnyEqual(eq),
	)
	got, err := m.Merge(
		T{
			I:  100,
			S:  "s", // default
			II: []int{100},
		},
		T{
			I:  1, // default
			S:  "win",
			II: []int{1}, // default
		},
	)
	if err != nil {
		panic(err)
	}
	fmt.Println(got.I, got.S, got.II)
}
Output:

100 win [100]

func NewMerger

func NewMerger[T any](opt ...Option) *Merger[T]

NewMerger returns a new Merger.

AnyCallback parses "default" tag value and set it. AnyEqual reports true if left equals right when kind of arguments are not supported. Prefix adds a prefix to "default" tag name.

func (*Merger[T]) Merge

func (m *Merger[T]) Merge(left, right T) (T, error)

Merge values based on the 'default' tag values. For each field with 'name' and 'default' tags, if the right value is not the default, use it; if not, use the left value. If that is also the default, set the default value. Return this instance.

type Option

type Option func(*Config)

func WithAnyCallback

func WithAnyCallback(v AnyCallbackFunc) Option

func WithAnyEqual

func WithAnyEqual(v AnyEqualFunc) Option

func WithArguments added in v0.4.0

func WithArguments(v []string) Option

func WithPrefix

func WithPrefix(v string) Option

type Receptor

type Receptor = internal.Receptor

type StringReceptor

type StringReceptor = internal.StringReceptor

type StructConfig

type StructConfig[T any] struct {
	// contains filtered or unexported fields
}

func New

func New[T any](opt ...Option) *StructConfig[T]

New returns a new StructConfig.

AnyCallback parses "default" tag value and set it. Prefix adds a prefix to "name", "short", "default" and "usage" tag name.

func (StructConfig[T]) FromDefault

func (sc StructConfig[T]) FromDefault(v *T) error

FromDefault sets "default" tag values to v.

Example
package main

import (
	"fmt"

	"github.com/berquerant/structconfig"
)

func main() {
	type T struct {
		B bool `default:"false"`
		I int
		F float32 `default:"1.1"`
		S string  `default:"str"`
	}

	sc := structconfig.New[T]()
	var got T
	if err := sc.FromDefault(&got); err != nil {
		panic(err)
	}
	fmt.Println(got.B, got.I, got.F, got.S)
}
Output:

false 0 1.1 str

func (StructConfig[T]) FromEnv

func (sc StructConfig[T]) FromEnv(v *T) error

FromEnv sets environment variable values to v.

Environment variable name will be

NewEnvVar("name tag value").String()

All '.' and '-' will be replaced with '_', making it all uppsercase.

Example
package main

import (
	"fmt"
	"os"

	"github.com/berquerant/structconfig"
)

func main() {
	type T struct {
		B  bool   `name:"bool_value"`
		S  string `name:"string_value"`
		N  int    `name:"int_value" default:"10"`
		N2 int
		N3 int `name:"-"`
	}

	envs := map[string]string{
		"BOOL_VALUE":   "true",
		"STRING_VALUE": "str",
	}
	for k, v := range envs {
		os.Setenv(k, v)
	}
	defer func() {
		for k := range envs {
			os.Unsetenv(k)
		}
	}()

	sc := structconfig.New[T]()
	var got T
	if err := sc.FromEnv(&got); err != nil {
		panic(err)
	}
	fmt.Println(got.B, got.S, got.N, got.N2, got.N3)
}
Output:

true str 10 0 0

func (StructConfig[T]) FromFlags

func (sc StructConfig[T]) FromFlags(v *T, fs *pflag.FlagSet) error

FromFlags sets values to v from command-line flags.

Flag name is from "name" tag value.

Example
package main

import (
	"errors"
	"fmt"
	"reflect"
	"sort"
	"strings"

	"github.com/berquerant/structconfig"
	"github.com/spf13/pflag"
)

func main() {
	type T struct {
		B       bool   `name:"bool_value" usage:"BOOL"`
		S       string `name:"string_value" default:"str"`
		X       bool   `name:"bool_short" short:"x"`
		Ignore1 int
		Ignore2 int `name:"-"`
		V       struct {
			S string
		} `name:"struct_value"`
		Ignore3 struct {
			S string
		}
	}

	anyCallback := func(s structconfig.StructField, v string, fv func() reflect.Value) error {
		name, ok := s.Tag().Name()
		if !ok {
			// ignore fields without name
			return nil
		}
		switch name {
		case "struct_value":
			fv().Set(reflect.ValueOf(struct {
				S string
			}{
				S: v,
			}))
			return nil
		default:
			return errors.New("unexpected tag name")
		}
	}

	fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
	sc := structconfig.New[T](structconfig.WithAnyCallback(anyCallback))

	if err := sc.SetFlags(fs); err != nil {
		panic(err)
	}

	flagNames := []string{}
	fs.VisitAll(func(f *pflag.Flag) {
		flagNames = append(flagNames, f.Name)
	})

	if err := fs.Parse([]string{"--bool_value", "--struct_value", "sv", "-x"}); err != nil {
		panic(err)
	}

	var got T
	if err := sc.FromFlags(&got, fs); err != nil {
		panic(err)
	}

	sort.Strings(flagNames)
	fmt.Println(strings.Join(flagNames, ","))
	fmt.Println(got.B, got.S, got.Ignore1, got.Ignore2, got.V.S, got.X)
}
Output:

bool_short,bool_value,string_value,struct_value
true str 0 0 sv true
Example (Prefix)
package main

import (
	"fmt"
	"sort"
	"strings"

	"github.com/berquerant/structconfig"
	"github.com/spf13/pflag"
)

func main() {
	tagPrefix := "sc"
	type T struct {
		B bool   `scname:"bool_value" scusage:"BOOL"`
		S string `scname:"string_value" scdefault:"str"`
	}

	fs := pflag.NewFlagSet("test", pflag.ContinueOnError)
	sc := structconfig.New[T](structconfig.WithPrefix(tagPrefix))

	if err := sc.SetFlags(fs); err != nil {
		panic(err)
	}

	flagNames := []string{}
	fs.VisitAll(func(f *pflag.Flag) {
		flagNames = append(flagNames, f.Name)
	})

	if err := fs.Parse([]string{"--bool_value", "--string_value", "sv"}); err != nil {
		panic(err)
	}

	var got T
	if err := sc.FromFlags(&got, fs); err != nil {
		panic(err)
	}

	sort.Strings(flagNames)
	fmt.Println(strings.Join(flagNames, ","))
	fmt.Println(got.B, got.S)
}
Output:

bool_value,string_value
true sv

func (StructConfig[T]) SetFlags

func (sc StructConfig[T]) SetFlags(fs *pflag.FlagSet) error

SetFlags sets command-line flags.

Flag name is from "name" tag value. Flag shorthand is from "short" tag value. Flag default value is from "default" tag value. Flag usage is from "usage" tag value.

type StructField

type StructField = internal.StructField

type Supported

type Supported = internal.Supported

type Tag

type Tag = internal.Tag

type Type

type Type = internal.Type

func NewType

func NewType(v any, prefix string) (*Type, error)

type UintReceptor

type UintReceptor = internal.UintReceptor

type Unsigned

type Unsigned = internal.Unsigned

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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