knadh / koanf

Simple, extremely lightweight, extensible, configuration management library for Go. Support for JSON, TOML, YAML, env, command line, file, S3 etc. Alternative to viper.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cannot use tags with same delimiter within struct using env provider

UnAfraid opened this issue · comments

Describe the bug
Hi,

I am trying to use koanf with env provider with _ as separator, and i am unable to bind property that contains the same separator, for example MYVAR_NESTED_BASE_URL always comes empty.

Environment variables:
MYVAR_TEST=asdf;MYVAR_NESTED_USERNAME=usr;MYVAR_NESTED_PASSWORD=pwd;MYVAR_NESTED_BASE_URL=https://localhost;MYVAR_NESTED_VALIDATE_SSL=true

To Reproduce

package main

import (
	"fmt"
	"strings"

	"github.com/knadh/koanf"
	"github.com/knadh/koanf/providers/env"
)

func main() {
	var k = koanf.New("_")
	err := k.Load(env.Provider("MYVAR_", "_", func(s string) string {
		return strings.ToLower(strings.TrimPrefix(s, "MYVAR_"))
	}), nil)

	if err != nil {
		panic(err)
	}

	type Nested struct {
		Username    string `koanf:"username"`
		Password    string `koanf:"password"`
		BaseUrl     string `koanf:"base_url"`
		ValidateSSL bool   `koanf:"validate_ssl"`
	}

	type Config struct {
		Test   string `koanf:"test"`
		Nested Nested `koanf:"nested"`
	}

	var config Config
	if err = k.UnmarshalWithConf("", &config, koanf.UnmarshalConf{Tag: "koanf"}); err != nil {
		panic(err)
	}

	fmt.Printf("%#v", config)
}

Output:

main.Config{Test:"asdf", Nested:main.Nested{Username:"usr", Password:"pwd", BaseUrl:"", ValidateSSL:false}}

Expected behavior

main.Config{Test:"asdf", Nested:main.Nested{Username:"usr", Password:"pwd", BaseUrl:"https://localhost", ValidateSSL:true}}

Please provide the following information):

  • OS: Linux
  • Koanf Version v1.4.4
  • Go Version: 1.19.4

Additional context:
If i switch it over to flat path and convert the nested struct into fields within my main struct it works:

package main

import (
	"fmt"
	"strings"

	"github.com/knadh/koanf"
	"github.com/knadh/koanf/providers/env"
)

func main() {
	var k = koanf.New("_")
	err := k.Load(env.Provider("MYVAR_", "_", func(s string) string {
		return strings.ToLower(strings.TrimPrefix(s, "MYVAR_"))
	}), nil)

	if err != nil {
		panic(err)
	}

	type Config struct {
		Test              string `koanf:"test"`
		NestedUsername    string `koanf:"nested_username"`
		NestedPassword    string `koanf:"nested_password"`
		NestedBaseUrl     string `koanf:"nested_base_url"`
		NestedValidateSSL bool   `koanf:"nested_validate_ssl"`
	}

	var config Config
	if err = k.UnmarshalWithConf("", &config, koanf.UnmarshalConf{Tag: "koanf", FlatPaths: true}); err != nil {
		panic(err)
	}

	fmt.Printf("%#v", config)
}

Output

main.Config{Test:"asdf", NestedUsername:"usr", NestedPassword:"pwd", NestedBaseUrl:"https://localhost", NestedValidateSSL:true}

This is correct behaviour @UnAfraid. If your variables have _ in them (eg: base_url) and your nesting separator is also _, it's impossible to figure out what is what. The simple trick here is to use some other character to indicate nesting, for example, __ (double underscore).

Eg: https://github.com/knadh/listmonk/blob/master/cmd/main.go#L116

LISTMONK_app__admin_username which translates to app.admin_username.

Thank you for the quick reply!

The double underscores trick works, but not for me, because it forces me to make changes on existing configuration.
Thanks for the suggestion anyway, i'll figure it out.

Best Regards!

Something like this could work, in that case. But you'll have to replace every instance of such nested keys in the function.

err := k.Load(env.Provider("", "_", func(s string) string {
    return strings.ToLower(strings.Replace(s, "nested_", "nested."))
}), nil)