Skip to content

Instantly share code, notes, and snippets.

@maratori
Created August 2, 2022 01:04
Show Gist options
  • Save maratori/d469f844eb91bdf1efa5d6c25687d792 to your computer and use it in GitHub Desktop.
Save maratori/d469f844eb91bdf1efa5d6c25687d792 to your computer and use it in GitHub Desktop.
Config reader with the best API for golang
package config
import (
"strings"
"time"
"github.com/pkg/errors"
"github.com/spf13/cast"
"github.com/spf13/viper"
)
func NewReader() *Reader {
v := viper.New()
v.AutomaticEnv()
v.AllowEmptyEnv(true)
return &Reader{
Mandatory: Mandatory{
viper: v,
},
Optional: Optional{
viper: v,
},
}
}
type Reader struct {
Mandatory Mandatory
Optional Optional
}
func (r *Reader) Error() error {
if len(r.Mandatory.missingKeys) > 0 {
return errors.Errorf("missing keys: %s", strings.Join(r.Mandatory.missingKeys, ", "))
}
return nil
}
type Mandatory struct {
viper *viper.Viper
missingKeys []string
}
func (m *Mandatory) String(key string) string {
if !m.viper.IsSet(key) {
m.missingKeys = append(m.missingKeys, key)
return ""
}
return m.viper.GetString(key)
}
func (m *Mandatory) Bool(key string) bool {
if !m.viper.IsSet(key) {
m.missingKeys = append(m.missingKeys, key)
return false
}
return m.viper.GetBool(key)
}
func (m *Mandatory) Int(key string) int {
if !m.viper.IsSet(key) {
m.missingKeys = append(m.missingKeys, key)
return 0
}
return m.viper.GetInt(key)
}
func (m *Mandatory) Duration(key string) time.Duration {
if !m.viper.IsSet(key) {
m.missingKeys = append(m.missingKeys, key)
return 0
}
return m.viper.GetDuration(key)
}
// DurationSlice reads duration slice from env (list should be space separated, for example "1h 2s 3m").
func (m *Mandatory) DurationSlice(key string) []time.Duration {
if !m.viper.IsSet(key) {
m.missingKeys = append(m.missingKeys, key)
return nil
}
return cast.ToDurationSlice(m.viper.GetStringSlice(key))
}
type Optional struct {
viper *viper.Viper
}
func (o *Optional) Bool(key string, value bool) bool {
if !o.viper.IsSet(key) {
return value
}
return o.viper.GetBool(key)
}
func (o *Optional) Int(key string, value int) int {
if !o.viper.IsSet(key) {
return value
}
return o.viper.GetInt(key)
}
func (o *Optional) String(key string, value string) string {
if !o.viper.IsSet(key) {
return value
}
return o.viper.GetString(key)
}
func (o *Optional) Duration(key string, value time.Duration) time.Duration {
if !o.viper.IsSet(key) {
return value
}
return o.viper.GetDuration(key)
}
// DurationSlice reads duration slice from env (list should be space separated, for example "1h 2s 3m").
func (o *Optional) DurationSlice(key string, value []time.Duration) []time.Duration {
if !o.viper.IsSet(key) {
return value
}
return cast.ToDurationSlice(o.viper.GetStringSlice(key))
}
package config_test
import (
"strings"
config "github.com/maratori/???"
)
func ExampleReader() {
cfg, err := ReadConfig()
if err != nil {
painc(err)
}
// use config
_ = cfg
}
type Config struct {
AutomationHandlers bool
DB DBConfig
WebServerAddr string
}
type DBConfig struct {
Host string
Port string
UserName string
Password string
Schema string
Database string
}
func ReadConfig() (Config, error) {
reader := config.NewReader()
isProd := strings.HasPrefix(strings.ToLower(reader.Mandatory.String("APP_ENV")), "prod")
cfg := Config{
AutomationHandlers: !isProd,
DB: DBConfig{
Host: reader.Mandatory.String("DB_HOST"),
Port: reader.Optional.String("DB_PORT", "5432"),
UserName: reader.Mandatory.String("DB_USER_NAME"),
Password: reader.Mandatory.String("DB_PASSWORD"),
Schema: reader.Optional.String("DB_SCHEMA", ""),
Database: reader.Mandatory.String("DB_DATABASE"),
},
WebServerAddr: "0.0.0.0:80",
}
return cfg, reader.Error()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment