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
}
}