Documentation
¶
Index ¶
- Constants
- Variables
- func Load(cfg any, ld Loader, opts ...Loader) (err error)
- func WithEnv(prefix string, opts ...string) envLoader
- func WithErrOnUnknown() optsLoader
- func WithJSON(src any) jsonLoader
- func WithMockedSSM(client SSMAPI) optsMockedSSMLoader
- func WithSSM(key string, opts ...string) ssmLoader
- type Loader
- type SSMAPI
Examples ¶
- Load (Env)
- Load (Env_complex)
- Load (Env_error_not_pointer)
- Load (Env_error_not_struct)
- Load (Env_error_on_unknown)
- Load (Env_error_parse_int)
- Load (Env_struct_tag_override)
- Load (Env_unsupported_bools)
- Load (Env_with_acronyms)
- Load (Json_and_env)
- Load (Json_bytes)
- Load (Json_file)
- Load (Json_file_not_found)
- Load (Json_reader)
- Load (Json_readseeker)
- Load (Json_unknown_fields)
- Load (Json_unsupported_type)
- Load (Ssm)
- Load (Ssm_error)
- Load (Ssm_param_not_found)
Constants ¶
const DefaultAWSRegion = "us-east-1"
const DefaultSeparator = ","
Variables ¶
var ( ErrUnknownFields = errors.New("unknown fields in config") ErrNoDataSource = errors.New("no data source for JSON loader") )
Functions ¶
func Load ¶
Load applies one or more loader functions to populate the given config which MUST be a pointer to a struct.
The first argument must be a pointer to a struct. Each loader (such as WithEnv, WithSSM, WithJSON) is applied in order, with later loaders overriding values from earlier ones.
You can optionally pass options. Currently, the only supported option is WithErrOnUnknown, which controls whether to return an error when unknown fields are present in the source but not defined in the target config.
Returns an error if the config pointer is nil, not a struct, or if any loader fails.
Example usage:
cfg := MyConfig{}
err := confetti.Load(&cfg, confetti.WithJSON("./config.json"), confetti.WithEnv("MYAPP"))
if err != nil { panic(err) }
Example (Env) ¶
package main
import (
"fmt"
"os"
"github.com/alexaandru/confetti"
)
type ExampleConfig struct {
Host string
Port int
Debug bool
Nested struct {
Value string
Deep struct {
Foo string
}
}
Strs []string
Ints []int
}
func main() {
os.Setenv("MYAPP1_HOST", "127.0.0.1")
os.Setenv("MYAPP1_PORT", "1234")
os.Setenv("MYAPP1_DEBUG", "true")
os.Setenv("MYAPP1_NESTED_VALUE", "bar")
os.Setenv("MYAPP1_NESTED_DEEP_FOO", "baz")
os.Setenv("MYAPP1_STRS", "a,b,c")
os.Setenv("MYAPP1_INTS", "1,2,3")
cfg := &ExampleConfig{}
if err := confetti.Load(cfg, confetti.WithEnv("MYAPP1")); err != nil {
panic(err)
}
fmt.Printf("Host=%s\n", cfg.Host)
fmt.Printf("Port=%d\n", cfg.Port)
fmt.Printf("Debug=%v\n", cfg.Debug)
fmt.Printf("Nested.Value=%s\n", cfg.Nested.Value)
fmt.Printf("Nested.Deep.Foo=%s\n", cfg.Nested.Deep.Foo)
fmt.Printf("Strs=%#v\n", cfg.Strs)
fmt.Printf("Ints=%#v\n", cfg.Ints)
}
Output: Host=127.0.0.1 Port=1234 Debug=true Nested.Value=bar Nested.Deep.Foo=baz Strs=[]string{"a", "b", "c"} Ints=[]int{1, 2, 3}
Example (Env_complex) ¶
package main
import (
"fmt"
"os"
"time"
"github.com/alexaandru/confetti"
)
type ComplexConfig struct {
Str string
Int int
Uint uint
Bool bool
Float float64
Strs []string
Ints []int
Uints []uint
Bools []bool
Floats []float64
Dur time.Duration
Nested struct {
Strs []string
Deep struct {
Int int
}
}
}
func main() {
os.Setenv("CPLX_STR", "foo")
os.Setenv("CPLX_INT", "42")
os.Setenv("CPLX_UINT", "7")
os.Setenv("CPLX_BOOL", "true")
os.Setenv("CPLX_FLOAT", "3.14")
os.Setenv("CPLX_STRS", "a,b,c")
os.Setenv("CPLX_INTS", "1,2,3")
os.Setenv("CPLX_UINTS", "4,5,6")
os.Setenv("CPLX_FLOATS", "1.1,2.2,3.3")
os.Setenv("CPLX_DUR", "1h30m")
os.Setenv("CPLX_NESTED_STRS", "x,y")
os.Setenv("CPLX_NESTED_DEEP_INT", "99")
cfg := &ComplexConfig{}
if err := confetti.Load(cfg, confetti.WithEnv("CPLX")); err != nil {
panic(err)
}
fmt.Printf("Str=%s\n", cfg.Str)
fmt.Printf("Int=%d\n", cfg.Int)
fmt.Printf("Uint=%d\n", cfg.Uint)
fmt.Printf("Bool=%v\n", cfg.Bool)
fmt.Printf("Float=%.2f\n", cfg.Float)
fmt.Printf("Strs=%#v\n", cfg.Strs)
fmt.Printf("Ints=%#v\n", cfg.Ints)
fmt.Printf("Uints=%#v\n", cfg.Uints)
fmt.Printf("Bools=%#v\n", cfg.Bools)
fmt.Printf("Floats=%#v\n", cfg.Floats)
fmt.Printf("Dur=%s\n", cfg.Dur)
fmt.Printf("Nested.Strs=%#v\n", cfg.Nested.Strs)
fmt.Printf("Nested.Deep.Int=%d\n", cfg.Nested.Deep.Int)
}
Output: Str=foo Int=42 Uint=7 Bool=true Float=3.14 Strs=[]string{"a", "b", "c"} Ints=[]int{1, 2, 3} Uints=[]uint{0x4, 0x5, 0x6} Bools=[]bool(nil) Floats=[]float64{1.1, 2.2, 3.3} Dur=1h30m0s Nested.Strs=[]string{"x", "y"} Nested.Deep.Int=99
Example (Env_error_not_pointer) ¶
package main
import (
"fmt"
"time"
"github.com/alexaandru/confetti"
)
type ComplexConfig struct {
Str string
Int int
Uint uint
Bool bool
Float float64
Strs []string
Ints []int
Uints []uint
Bools []bool
Floats []float64
Dur time.Duration
Nested struct {
Strs []string
Deep struct {
Int int
}
}
}
func main() {
cfg := ComplexConfig{} // not a pointer
err := confetti.Load(cfg, confetti.WithEnv("MYAPP2"))
fmt.Println(err)
}
Output: config must be a pointer to a struct (got confetti_test.ComplexConfig)
Example (Env_error_not_struct) ¶
package main
import (
"fmt"
"github.com/alexaandru/confetti"
)
func main() {
var x int
err := confetti.Load(&x, confetti.WithEnv("MYAPP3"))
fmt.Println(err)
}
Output: config must be a pointer to a struct (got *int)
Example (Env_error_on_unknown) ¶
package main
import (
"fmt"
"os"
"github.com/alexaandru/confetti"
)
func main() {
os.Setenv("MYAPP4_DEBUG", "true")
os.Setenv("MYAPP4_UNUSED", "unknown") // should trigger an error
os.Setenv("CUSTOM_PORT", "9999")
os.Setenv("CUSTOM_NESTED_VALUE", "tagged")
type TaggedConfig struct {
Port int `env:"CUSTOM_PORT"`
Debug bool
Nested struct {
Value string `env:"CUSTOM_NESTED_VALUE"`
}
}
cfg := &TaggedConfig{}
err := confetti.Load(cfg, confetti.WithErrOnUnknown(), confetti.WithEnv("MYAPP4"))
fmt.Printf("Port=%d Debug=%v Nested.Value=%s\n", cfg.Port, cfg.Debug, cfg.Nested.Value)
fmt.Println(err)
}
Output: Port=9999 Debug=true Nested.Value=tagged unknown environment variables: [MYAPP4_UNUSED]
Example (Env_error_parse_int) ¶
package main
import (
"fmt"
"os"
"time"
"github.com/alexaandru/confetti"
)
type ComplexConfig struct {
Str string
Int int
Uint uint
Bool bool
Float float64
Strs []string
Ints []int
Uints []uint
Bools []bool
Floats []float64
Dur time.Duration
Nested struct {
Strs []string
Deep struct {
Int int
}
}
}
func main() {
os.Setenv("CPLX_INT", "notanint")
cfg := &ComplexConfig{}
err := confetti.Load(cfg, confetti.WithEnv("CPLX"))
fmt.Println(err)
}
Output: env CPLX_INT: strconv.ParseInt: parsing "notanint": invalid syntax
Example (Env_struct_tag_override) ¶
package main
import (
"fmt"
"os"
"github.com/alexaandru/confetti"
)
func main() {
os.Setenv("CUSTOM_PORT", "9999")
os.Setenv("MYAPP4_DEBUG", "true")
os.Setenv("MYAPP4_UNUSED", "unknown") // should be ignored
os.Setenv("CUSTOM_NESTED_VALUE", "tagged")
type TaggedConfig struct {
Port int `env:"CUSTOM_PORT"`
Debug bool
Nested struct {
Value string `env:"CUSTOM_NESTED_VALUE"`
}
}
cfg := &TaggedConfig{}
err := confetti.Load(cfg, confetti.WithEnv("MYAPP4"))
fmt.Printf("Port=%d Debug=%v Nested.Value=%s\n", cfg.Port, cfg.Debug, cfg.Nested.Value)
fmt.Println(err)
}
Output: Port=9999 Debug=true Nested.Value=tagged <nil>
Example (Env_unsupported_bools) ¶
package main
import (
"fmt"
"os"
"time"
"github.com/alexaandru/confetti"
)
type ComplexConfig struct {
Str string
Int int
Uint uint
Bool bool
Float float64
Strs []string
Ints []int
Uints []uint
Bools []bool
Floats []float64
Dur time.Duration
Nested struct {
Strs []string
Deep struct {
Int int
}
}
}
func main() {
os.Setenv("CPLX_BOOLS", "true,false,yes,0,n")
cfg := &ComplexConfig{}
err := confetti.Load(cfg, confetti.WithEnv("CPLX"))
fmt.Printf("%#v\n", cfg.Bools)
fmt.Println(err)
}
Output: []bool{true, false, true, false, false} <nil>
Example (Env_with_acronyms) ¶
package main
import (
"fmt"
"os"
"github.com/alexaandru/confetti"
)
func main() {
os.Setenv("MYAPP5_SQS_QUEUE", "sqs1")
os.Setenv("MYAPP5_SOME_SNS_TOPIC", "sns1") // should trigger an error
type TaggedConfig struct {
SQSQueue string
SomeSNSTopic string
}
cfg := &TaggedConfig{}
err := confetti.Load(cfg, confetti.WithEnv("MYAPP5"))
fmt.Printf("SQS: %s; SNS: %s\n", cfg.SQSQueue, cfg.SomeSNSTopic)
fmt.Println(err)
}
Output: SQS: sqs1; SNS: sns1 <nil>
Example (Json_and_env) ¶
// JSON provides Host and Nested.Value, ENV provides Port, Debug, and Nested.Deep.Foo
jsonData := `{"Host":"localhost","Nested":{"Value":"foo"}}`
file := "test_config.json"
if err := os.WriteFile(file, []byte(jsonData), 0o644); err != nil {
panic("failed to write test file: " + err.Error())
}
defer os.Remove(file)
os.Setenv("MYAPP_PORT", "8080")
os.Setenv("MYAPP_DEBUG", "true")
os.Setenv("MYAPP_NESTED_DEEP_FOO", "baz")
cfg := &ExampleConfig{}
if err := confetti.Load(cfg,
confetti.WithJSON(file),
confetti.WithEnv("MYAPP"),
); err != nil {
panic(err)
}
fmt.Printf("Host=%s\n", cfg.Host)
fmt.Printf("Port=%d\n", cfg.Port)
fmt.Printf("Debug=%v\n", cfg.Debug)
fmt.Printf("Nested.Value=%s\n", cfg.Nested.Value)
fmt.Printf("Nested.Deep.Foo=%s\n", cfg.Nested.Deep.Foo)
Output: Host=localhost Port=8080 Debug=true Nested.Value=foo Nested.Deep.Foo=baz
Example (Json_bytes) ¶
cfg := &ExampleConfig{}
if err := confetti.Load(cfg, confetti.WithJSON([]byte(jsonData))); err != nil {
panic("Load failed: " + err.Error())
}
fmt.Printf("Host=%s\n", cfg.Host)
fmt.Printf("Port=%d\n", cfg.Port)
fmt.Printf("Debug=%v\n", cfg.Debug)
fmt.Printf("Nested.Value=%s\n", cfg.Nested.Value)
fmt.Printf("Nested.Deep.Foo=%s\n", cfg.Nested.Deep.Foo)
Output: Host=localhost Port=8080 Debug=true Nested.Value=foo Nested.Deep.Foo=baz
Example (Json_file) ¶
file := "test_config.json"
if err := os.WriteFile(file, []byte(jsonData), 0o644); err != nil {
panic("failed to write test file: " + err.Error())
}
defer os.Remove(file)
cfg := &ExampleConfig{}
if err := confetti.Load(cfg, confetti.WithJSON(file)); err != nil {
panic("Load failed: " + err.Error())
}
fmt.Printf("Host=%s\n", cfg.Host)
fmt.Printf("Port=%d\n", cfg.Port)
fmt.Printf("Debug=%v\n", cfg.Debug)
fmt.Printf("Nested.Value=%s\n", cfg.Nested.Value)
fmt.Printf("Nested.Deep.Foo=%s\n", cfg.Nested.Deep.Foo)
Output: Host=localhost Port=8080 Debug=true Nested.Value=foo Nested.Deep.Foo=baz
Example (Json_file_not_found) ¶
cfg := &ExampleConfig{}
err := confetti.Load(cfg, confetti.WithJSON("no_such_file.json"))
fmt.Printf("Error: %v\n", err)
Output: Error: open no_such_file.json: no such file or directory
Example (Json_reader) ¶
cfg := &ExampleConfig{}
r := bytes.NewBufferString(jsonData)
if err := confetti.Load(cfg, confetti.WithJSON(r)); err != nil {
panic("Load failed: " + err.Error())
}
fmt.Printf("Host=%s\n", cfg.Host)
fmt.Printf("Port=%d\n", cfg.Port)
fmt.Printf("Debug=%v\n", cfg.Debug)
fmt.Printf("Nested.Value=%s\n", cfg.Nested.Value)
fmt.Printf("Nested.Deep.Foo=%s\n", cfg.Nested.Deep.Foo)
Output: Host=localhost Port=8080 Debug=true Nested.Value=foo Nested.Deep.Foo=baz
Example (Json_readseeker) ¶
cfg := &ExampleConfig{}
r := bytes.NewReader([]byte(jsonData))
if err := confetti.Load(cfg, confetti.WithJSON(r)); err != nil {
panic("Load failed: " + err.Error())
}
fmt.Printf("Host=%s\n", cfg.Host)
fmt.Printf("Port=%d\n", cfg.Port)
fmt.Printf("Debug=%v\n", cfg.Debug)
fmt.Printf("Nested.Value=%s\n", cfg.Nested.Value)
fmt.Printf("Nested.Deep.Foo=%s\n", cfg.Nested.Deep.Foo)
Output: Host=localhost Port=8080 Debug=true Nested.Value=foo Nested.Deep.Foo=baz
Example (Json_unknown_fields) ¶
file := "test_config.json"
if err := os.WriteFile(file, []byte(jsonData), 0o644); err != nil {
panic("failed to write test file: " + err.Error())
}
defer os.Remove(file)
cfg := &ExampleConfig{}
err := confetti.Load(cfg, confetti.WithErrOnUnknown(), confetti.WithJSON(file))
fmt.Printf("Host=%s\n", cfg.Host)
fmt.Printf("Port=%d\n", cfg.Port)
fmt.Printf("Debug=%v\n", cfg.Debug)
fmt.Printf("Nested.Value=%s\n", cfg.Nested.Value)
fmt.Printf("Nested.Deep.Foo=%s\n", cfg.Nested.Deep.Foo)
fmt.Printf("Error=%s\n", err)
Output: Host=localhost Port=8080 Debug=true Nested.Value=foo Nested.Deep.Foo=baz Error=unknown fields in config: json: unknown field "Unused"
Example (Json_unsupported_type) ¶
cfg := &ExampleConfig{}
err := confetti.Load(cfg, confetti.WithJSON(123))
fmt.Printf("Error: %v\n", err)
Output: Error: unsupported type for WithJSON: int
Example (Ssm) ¶
jsonValue := `{"Host":"ssmhost","Port":9000,"Debug":true,"Nested":{"Value":"ssmval","Deep":{"Foo":"ssmdeep", "Unknown":"unknown"}}}`
ssmName := "CONFETTI_TEST"
region := "us-east-1"
cfg := &ExampleConfig{}
err := confetti.Load(cfg,
confetti.WithErrOnUnknown(),
confetti.WithMockedSSM(&mockSSM{value: jsonValue}),
confetti.WithSSM(ssmName, region),
)
fmt.Printf("Host=%s\n", cfg.Host)
fmt.Printf("Port=%d\n", cfg.Port)
fmt.Printf("Debug=%v\n", cfg.Debug)
fmt.Printf("Nested.Value=%s\n", cfg.Nested.Value)
fmt.Printf("Nested.Deep.Foo=%s\n", cfg.Nested.Deep.Foo)
fmt.Printf("Error=%v\n", err)
Output: Host=ssmhost Port=9000 Debug=true Nested.Value=ssmval Nested.Deep.Foo=ssmdeep Error=unknown fields in config: json: unknown field "Unknown"
Example (Ssm_error) ¶
cfg := &ExampleConfig{}
err := confetti.Load(cfg,
confetti.WithMockedSSM(&mockSSM{value: "error: mock SSM error"}),
confetti.WithSSM("fail", "us-east-1"),
)
fmt.Printf("Error: %v\n", err)
Output: Error: failed to get SSM parameter fail: mock SSM error
Example (Ssm_param_not_found) ¶
cfg := &ExampleConfig{}
err := confetti.Load(cfg,
confetti.WithMockedSSM(&mockSSM{value: ""}),
confetti.WithSSM("missing", "us-east-1"),
)
fmt.Printf("Error: %v\n", err)
Output: Error: parameter missing not found or has no value
func WithEnv ¶
WithEnv returns a loader that populates struct fields from environment variables.
The prefix is prepended to each field name (in UPPER_SNAKE_CASE) to form the env var name.
The optional separator argument sets the delimiter for slice fields (default is ","). Supports primitive types and slices of primitives (string, int, uint, float, bool).
func WithErrOnUnknown ¶ added in v1.1.0
func WithErrOnUnknown() optsLoader
WithErrOnUnknown sets whether to return an error if is present in the source but not defined in the config struct. NOTE: It currently only applies to json and ssm loaders.
func WithJSON ¶
func WithJSON(src any) jsonLoader
WithJSON returns a loader that loads the config struct from a JSON source, which can be: a file path (string), []byte, io.ReadSeeker or io.Reader.
func WithMockedSSM ¶ added in v1.2.1
func WithMockedSSM(client SSMAPI) optsMockedSSMLoader
WithMockedSSM returns a loader that uses a mocked SSM client for testing.
func WithSSM ¶
WithSSM returns a loader that loads the config struct from an AWS SSM parameter.
The key is the SSM parameter name. The optional region and profile arguments override the default AWS region/profile. The SSM parameter value must be a JSON string matching the config struct.
Usage:
confetti.WithSSM("/my/param", "us-west-2", "myprofile")