Skip to content

Commit

Permalink
Added array & map delimiter options
Browse files Browse the repository at this point in the history
  • Loading branch information
sonnes committed Aug 7, 2023
1 parent f308936 commit 4828029
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 43 deletions.
70 changes: 50 additions & 20 deletions xload/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (

func ExampleLoadEnv() {
type AppConf struct {
Host string `config:"HOST"`
Debug bool `config:"DEBUG"`
Timeout time.Duration `config:"TIMEOUT"`
Host string `env:"HOST"`
Debug bool `env:"DEBUG"`
Timeout time.Duration `env:"TIMEOUT"`
}

var conf AppConf
Expand Down Expand Up @@ -65,9 +65,9 @@ func ExampleLoad_customLoader() {

func ExampleLoad_prefixLoader() {
type AppConf struct {
Host string `config:"HOST"`
Debug bool `config:"DEBUG"`
Timeout time.Duration `config:"TIMEOUT"`
Host string `env:"HOST"`
Debug bool `env:"DEBUG"`
Timeout time.Duration `env:"TIMEOUT"`
}

var conf AppConf
Expand All @@ -84,9 +84,9 @@ func ExampleLoad_prefixLoader() {

func ExampleLoadEnv_required() {
type AppConf struct {
Host string `config:"HOST,required"`
Debug bool `config:"DEBUG"`
Timeout time.Duration `config:"TIMEOUT"`
Host string `env:"HOST,required"`
Debug bool `env:"DEBUG"`
Timeout time.Duration `env:"TIMEOUT"`
}

var conf AppConf
Expand All @@ -100,17 +100,17 @@ func ExampleLoadEnv_required() {

func ExampleLoadEnv_structs() {
type DBConf struct {
Host string `config:"HOST"` // will be loaded from DB_HOST
Port int `config:"PORT"` // will be loaded from DB_PORT
Host string `env:"HOST"` // will be loaded from DB_HOST
Port int `env:"PORT"` // will be loaded from DB_PORT
}

type HTTPConf struct {
Host string `config:"HTTP_HOST"` // will be loaded from HTTP_HOST
Port int `config:"HTTP_PORT"` // will be loaded from HTTP_PORT
Host string `env:"HTTP_HOST"` // will be loaded from HTTP_HOST
Port int `env:"HTTP_PORT"` // will be loaded from HTTP_PORT
}

type AppConf struct {
DB DBConf `config:",prefix=DB_"` // example of prefix for nested struct
DB DBConf `env:",prefix=DB_"` // example of prefix for nested struct
HTTP HTTPConf // example of embedded struct
}

Expand Down Expand Up @@ -141,9 +141,9 @@ func ExampleLoadEnv_customDecoder() {
// implements the Decoder interface.

type AppConf struct {
Host Host `config:"HOST"`
Debug bool `config:"DEBUG"`
Timeout time.Duration `config:"TIMEOUT"`
Host Host `env:"HOST"`
Debug bool `env:"DEBUG"`
Timeout time.Duration `env:"TIMEOUT"`
}

var conf AppConf
Expand All @@ -156,9 +156,9 @@ func ExampleLoadEnv_customDecoder() {

func ExampleLoadEnv_transformFieldName() {
type AppConf struct {
Host string `config:"MYAPP_HOST"`
Debug bool `config:"MYAPP_DEBUG"`
Timeout time.Duration `config:"MYAPP_TIMEOUT"`
Host string `env:"MYAPP_HOST"`
Debug bool `env:"MYAPP_DEBUG"`
Timeout time.Duration `env:"MYAPP_TIMEOUT"`
}

var conf AppConf
Expand All @@ -182,3 +182,33 @@ func ExampleLoadEnv_transformFieldName() {
panic(err)
}
}

func ExampleLoadEnv_arrayDelimiter() {
type AppConf struct {
// value will be split by |, instead of ,
// e.g. HOSTS=host1|host2|host3
Hosts []string `env:"HOSTS,delimiter=|"`
}

var conf AppConf

err := xload.LoadEnv(context.Background(), &conf)
if err != nil {
panic(err)
}
}

func ExampleLoadEnv_mapSeparator() {
type AppConf struct {
// key value pair will be split by :, instead of =
// e.g. HOSTS=db:localhost,cache:localhost
Hosts map[string]string `env:"HOSTS,separator=:"`
}

var conf AppConf

err := xload.LoadEnv(context.Background(), &conf)
if err != nil {
panic(err)
}
}
41 changes: 27 additions & 14 deletions xload/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@ var (
)

const (
optRequired = "required"
optPrefix = "prefix="
optRequired = "required"
optPrefix = "prefix="
optDelimiter = "delimiter="
optSeparator = "separator="

defaultDelimiter = ","
defaultSeparator = "="
)

// LoadEnv loads values from OS environment using default options.
Expand Down Expand Up @@ -175,7 +180,7 @@ func process(ctx context.Context, obj any, tagKey string, loader Loader) error {
}

// set value
err = setVal(fVal, val)
err = setVal(fVal, val, meta)
if err != nil {
return err
}
Expand All @@ -185,17 +190,21 @@ func process(ctx context.Context, obj any, tagKey string, loader Loader) error {
}

type field struct {
name string
prefix string
required bool
name string
prefix string
required bool
delimiter string
separator string
}

func parseField(tag string) (*field, error) {
parts := strings.Split(tag, ",")
key, tagOpts := strings.TrimSpace(parts[0]), parts[1:]

f := &field{
name: key,
name: key,
delimiter: defaultDelimiter,
separator: defaultSeparator,
}

for _, opt := range tagOpts {
Expand All @@ -210,6 +219,10 @@ func parseField(tag string) (*field, error) {
f.required = true
case strings.HasPrefix(opt, optPrefix):
f.prefix = strings.TrimPrefix(opt, optPrefix)
case strings.HasPrefix(opt, optDelimiter):
f.delimiter = strings.TrimPrefix(opt, optDelimiter)
case strings.HasPrefix(opt, optSeparator):
f.separator = strings.TrimPrefix(opt, optSeparator)
default:
return nil, ErrUnknownTagOption
}
Expand All @@ -219,7 +232,7 @@ func parseField(tag string) (*field, error) {
}

//nolint:funlen,nestif
func setVal(field reflect.Value, val string) error {
func setVal(field reflect.Value, val string, meta *field) error {
for field.Kind() == reflect.Ptr {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
Expand Down Expand Up @@ -297,11 +310,11 @@ func setVal(field reflect.Value, val string) error {
field.SetFloat(f)

case reflect.Map:
vals := strings.Split(val, ",")
vals := strings.Split(val, meta.delimiter)
m := reflect.MakeMapWithSize(ty, len(vals))

for _, v := range vals {
kv := strings.Split(v, ":")
kv := strings.Split(v, meta.separator)
if len(kv) != 2 {
return ErrInvalidMapValue
}
Expand All @@ -310,14 +323,14 @@ func setVal(field reflect.Value, val string) error {

key := reflect.New(ty.Key()).Elem()

err := setVal(key, k)
err := setVal(key, k, meta)
if err != nil {
return err
}

value := reflect.New(ty.Elem()).Elem()

err = setVal(value, v)
err = setVal(value, v, meta)
if err != nil {
return err
}
Expand All @@ -335,13 +348,13 @@ func setVal(field reflect.Value, val string) error {
return nil
}

vals := strings.Split(val, ",")
vals := strings.Split(val, meta.delimiter)
slice := reflect.MakeSlice(ty, len(vals), len(vals))

for i, v := range vals {
v = strings.TrimSpace(v)

err := setVal(slice.Index(i), v)
err := setVal(slice.Index(i), v, meta)
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit 4828029

Please sign in to comment.