import (
"fmt"
"github.com/fsnotify/fsnotify"
"github.com/go-playground/validator/v10"
"github.com/spf13/viper"
"go.uber.org/multierr"
"os"
"os/user"
"path"
"path/filepath"
"strings"
)
const PathEnvName = "CONFIG_PATH"
const NameEnvName = "CONFIG_NAME"
const TypeEnvName = "CONFIG_TYPE"
const DefaultName = "config"
const DefaultType = "yaml"
var Config = new(config)
func init() { getConfig()
}
type config struct { Mode string `mapstructure:"mode" json:"mode" yaml:"mode" validate:"required,oneof=dev staging product"`
Port int `mapstructure:"port" json:"port" yaml:"port" validate:"omitempty,gte=0,lte=65535"`
}
func (c *config) String() string { return fmt.Sprintf("config{mode=%s, port=%d}", c.Mode, c.Port)}
func (c *config) Validate() error { validate := validator.New()
if errs := validate.Struct(c); errs != nil { var result error
for _, err := range errs.(validator.ValidationErrors) { result = multierr.Append(result, err)
}
return result
}
return nil
}
func getConfig() { v := viper.New()
for _, path_ := range getPaths() { v.AddConfigPath(path_)
}
v.SetConfigName(getName())
v.SetConfigType(getType())
if err := v.ReadInConfig(); err != nil { panic(err.Error())
}
if err := v.Unmarshal(Config); err != nil { panic(err.Error())
} else { if err := Config.Validate(); err != nil { panic(err.Error())
}
}
v.OnConfigChange(func(fsnotify.Event) { newConfig := new(config)
if err := v.Unmarshal(newConfig); err == nil { if err := newConfig.Validate(); err == nil { Config = newConfig
}
}
})
v.WatchConfig()
}
func getSubPaths(dir string) []string { return []string{dir, path.Join(dir, "config")}}
func getPaths() []string { var configPaths = make([]string, 0)
if paths := os.Getenv(PathEnvName); paths != "" { for _, path_ := range strings.Split(paths, string(os.PathListSeparator)) { configPaths = append(configPaths, getSubPaths(path_)...)
}
}
if curDir, err := os.Getwd(); err == nil { configPaths = append(configPaths, getSubPaths(curDir)...)
}
if exe, err := os.Executable(); err == nil { configPaths = append(configPaths, getSubPaths(filepath.Dir(exe))...)
}
if u, err := user.Current(); err == nil { configPaths = append(configPaths, getSubPaths(u.HomeDir)...)
}
configPaths = append(configPaths, getSubPaths(filepath.FromSlash("/etc"))...) return configPaths
}
func getName() string { if name := os.Getenv(NameEnvName); name != "" { return name
} else { return DefaultName
}
}
func getType() string { if type_ := os.Getenv(TypeEnvName); type_ != "" { return type_
} else { return DefaultType
}
}