Skip to content

environment variable overrides are processed after the config struct is already initialized #34

@chrispickard

Description

@chrispickard

consider a config struct like

type Config struct {
	Foo *CustomFoo `fig:"foo_config_file"`
}

where CustomFoo:

type CustomFoo struct {
	Bar string `json:"bar"`
	Baz string `json:"baz"`
}

func (c *CustomFoo) UnmarshalString(s string) error {
	fmt.Println(s)
	file, err := os.ReadFile(s)
	if err != nil {
		return err
	}
	err = json.Unmarshal(file, &c)
	if err != nil {
		return err
	}
	return nil
}

if you attempt to fill CustomFoo by passing a filename to UnmarshalString, it should read that file, then unmarshal into the CustomFoo struct. However, if you attempt to override the config with environment variables, the struct will attempt to be built twice, the first time with the value from config.yaml and the second time with the environment variable. if UnmarshalString returns an error it fails the config loading process. I'm attaching a runnable reproducer at the bottom, but IMO fig should wait until it has merged the different sources of config before it attempts to actually construct the final struct it will return to the caller.

expected result:

the Config struct is generated with Foo.Bar and Foo.Baz populated

actual result:

the foo.json file referenced in config.yaml is opened (and doesn't exist), rather than the foo_custom.json file referenced in the environment variables

error: 1 error(s) decoding:

* error decoding 'foo_config_file': open foo.json: no such file or directory
exit status 1
full example
package main

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

	"github.com/kkyr/fig"
)

type CustomFoo struct {
	Bar string `json:"bar"`
	Baz string `json:"baz"`
}

func (c *CustomFoo) UnmarshalString(s string) error {
	fmt.Println(s)
	file, err := os.ReadFile(s)
	if err != nil {
		return err
	}
	err = json.Unmarshal(file, &c)
	if err != nil {
		return err
	}
	return nil
}

type Config struct {
	Foo *CustomFoo `fig:"foo_config_file"`
}

func main() {
	err := os.Setenv("MYAPP_FOO_CONFIG_FILE", "custom_foo.json")
	if err != nil {
		fmt.Printf("error: %v\n", err)
		os.Exit(1)
	}
	var cfg Config
	err = fig.Load(&cfg, fig.UseEnv("MYAPP"))
	if err != nil {
		fmt.Printf("error: %v\n", err)
		os.Exit(1)
	}
	fmt.Printf("%v\n", cfg)
}

config.yaml:

foo_config_file: "foo.json"

foo.json: non-existent

custom_foo.json:

{
  "bar": "hello",
  "baz": "world"
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions