package config import ( "errors" "fmt" "io" "os" "path/filepath" "github.com/pelletier/go-toml" ) var ErrNotFound = errors.New("no config file found") type Config struct { ListenAddr string `toml:"ListenAddr"` LogLevel string `toml:"LogLevel"` } type InvalidValueError struct { Key string } func (ive *InvalidValueError) Error() string { return fmt.Sprintf("invalid value: config key %s has invalid value", ive.Key) } func FromReader(r io.Reader) (*Config, error) { var c Config // Set some defaults c.ListenAddr = ":5566" c.LogLevel = "INFO" decoder := toml.NewDecoder(r) if err := decoder.Decode(&c); err != nil { return nil, fmt.Errorf("error parsing config file: %w", err) } return &c, c.Verify() } func (c *Config) Verify() error { // Check that LogLevel is valid switch c.LogLevel { case "DEBUG", "INFO", "WARN", "ERROR": default: return &InvalidValueError{Key: "LogLevel"} } return nil } func (c *Config) UpdateFromEnv() error { if loglevel, found := os.LookupEnv("DOGTAMER_LOGLEVEL"); found { c.LogLevel = loglevel } if listenAddr, found := os.LookupEnv("DOGTAMER_LISTENADDR"); found { c.ListenAddr = listenAddr } return c.Verify() } func FromFile(path string) (*Config, error) { f, err := os.Open(path) if err != nil { return nil, fmt.Errorf("Error opening config file: %w", err) } return FromReader(f) } func FromDefaultLocations(path string) (*Config, error) { defaultLocations := []string{ "dogtamer.toml", "/etc/dogtamer.toml", } baseConfigDir, err := os.UserConfigDir() if err == nil { userConfigFile := filepath.Join(baseConfigDir, "dogtamer", "dogtamer.toml") defaultLocations = append(defaultLocations, userConfigFile) } for _, fname := range defaultLocations { if _, err := os.Stat(fname); os.IsNotExist(err) { continue } cfg, err := FromFile(fname) if err != nil { continue } return cfg, cfg.UpdateFromEnv() } return nil, ErrNotFound }