Skip to content

Env Vars Starting with USER not loading #319

@jhuggart

Description

@jhuggart

Describe the bug
Env Vars Starting with USER not loading

To Reproduce

package config

import (
	"errors"
	"fmt"
	"github.com/knadh/koanf/providers/env"
	"github.com/knadh/koanf/v2"
	"os"
	"reflect"
	"strings"
	"testing"
)

type Config struct {
	loader *koanf.Koanf
}

var (
	ErrFailedToLoadFromEnv       = errors.New("failed to load from env")
	ErrFailedToUnmarshalToStruct = errors.New("failed to unmarshal to struct")
)

const (
	emptyPrefix = ""
	emptyDelim  = ""
	emptyPath   = ""
)

// New constructs a new config receiver
func New() *Config {
	c := &Config{
		loader: koanf.New(emptyDelim),
	}

	return c
}

// Hydrate takes a pointer to config and hydrates it
func (c *Config) Hydrate(cfgStruct any) error {
	// Load environment variables and merge into the loaded config.
	// emptyPrefix and emptyDelim. There is no env prefix and we do not need a delimiter
	if err := c.loader.Load(env.Provider(emptyPrefix, emptyDelim, func(s string) string {
		return strings.ToLower(s)
	}), nil); err != nil {
		return fmt.Errorf("%w: %v", ErrFailedToLoadFromEnv, err)
	}

	// Unmarshal to `config` tags using "FlatPaths" -> ie, don't bother with nesting config
	if err := c.loader.UnmarshalWithConf(emptyPath, cfgStruct, koanf.UnmarshalConf{
		Tag:       "config",
		FlatPaths: true,
	}); err != nil {
		return fmt.Errorf("%w: %v", ErrFailedToUnmarshalToStruct, err)
	}

	return nil
}

type TestConfig struct {
	ValueHere     string `config:"value_here"`
	UserValueHere string `config:"user_value_here"`
}

func TestHydrate(test *testing.T) {
	cases := []struct {
		name           string
		input          any
		envVals        map[string]string
		expectedOutput any
		expectedError  error
	}{
		{
			name:  "env values set correctly",
			input: TestConfig{},
			envVals: map[string]string{
				"USER_VALUE_HERE": "hi",
				"VALUE_HERE":      "hello",
			},
			expectedOutput: TestConfig{
				UserValueHere: "hi",
				ValueHere:     "hello",
			},
		},
	}

	for _, c := range cases {
		test.Run(c.name, func(t *testing.T) {
			for k, v := range c.envVals {
				if err := os.Setenv(k, v); err != nil {
					t.Fatal("failed to set env var", err)
				}
				defer os.Unsetenv(k)
			}

			conf := New()

			var toHydrate TestConfig
			if err := conf.Hydrate(&toHydrate); !errors.Is(err, c.expectedError) {
				t.Fatalf("mismatched errors - expected %v, got %v", c.expectedError, err)
			}

			if !reflect.DeepEqual(toHydrate, c.expectedOutput) {
				t.Fatalf("mismatched results - expected %v, got %v", c.expectedOutput, toHydrate)
			}
		})
	}
}

Expected behavior
Both env vars are mapped to the input struct

Please provide the following information):

  • OS: mac
  • Koanf Version v2.1.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions