Skip to content
This repository has been archived by the owner on Jan 10, 2021. It is now read-only.

Commit

Permalink
feat(defaults): added workarround when fields are structs with pointers
Browse files Browse the repository at this point in the history
  • Loading branch information
xmlking committed Jun 9, 2020
1 parent 64b2eca commit ed61334
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 40 deletions.
42 changes: 34 additions & 8 deletions README.md
Expand Up @@ -2,6 +2,8 @@

Golang Configuration module that support YAML, JSON, Shell Environment

This is based on [jinzhu/configor's](https://github.com/jinzhu/configor) work, with some bug fixes and enhancements.

## Features

- Strongly typed config with tags
Expand All @@ -14,14 +16,16 @@ Golang Configuration module that support YAML, JSON, Shell Environment
- Config Sources
- YAML files
- Environment Variables
- [x] Command line flags
- Command line flags
- [ ] Kubernetes ConfigMaps
- Merge multiple config sources (Overlays)
- Dynamic Configuration Management (Hot Reconfiguration)
- Remote config push
- Externalized configuration
- Live component reloading / zero-downtime
- Observe Changes - https://play.golang.org/p/41ygGZ-QaB https://gist.github.com/patrickmn/1549985
- Detect Runtime Environment
- [ ] Observe Config [Changes](https://play.golang.org/p/41ygGZ-QaB https://gist.github.com/patrickmn/1549985)
- Detect Runtime Environment (test, development, production)
- Support Embed config files in Go binaries via [pkger](https://github.com/markbates/pkger)

```golang
type Item struct {
Expand All @@ -48,13 +52,13 @@ import (
)

var Config = struct {
APPName string `default:"app name"`
APPName string `default:"app name" yaml:",omitempty"`

DB struct {
Name string
User string `default:"root"`
User string `default:"root" yaml:",omitempty"`
Password string `required:"true" env:"DBPassword"`
Port uint `default:"3306"`
Port uint `default:"3306" yaml:",omitempty"`
}

Contacts []struct {
Expand Down Expand Up @@ -91,12 +95,19 @@ Debug/Verbose mode is helpful when debuging your application, `debug mode` will

```go
// Enable debug mode or set env `CONFIGOR_DEBUG_MODE` to true when running your application
configor.New(&configor.Config{Debug: true}).Load(&Config, "config.json")
configor.New(&configor.Config{Debug: true}).Load(&Config, "config.yaml")

// Enable verbose mode or set env `CONFIGOR_VERBOSE_MODE` to true when running your application
configor.New(&configor.Config{Verbose: true}).Load(&Config, "config.json")
configor.New(&configor.Config{Verbose: true}).Load(&Config, "config.yaml")

// You can create custom Configor once and reuse to load multiple different configs
Configor := configor.New(&configor.Config{Debug: true})
Configor.Load(&Config2, "config2.yaml")
Configor.Load(&Config3, "config3.yaml")
```

## Load

# Advanced Usage

* Load mutiple configurations
Expand Down Expand Up @@ -159,6 +170,17 @@ $ go run config.go
// Will load `config.example.yml` automatically if `config.yml` not found and print warning message
```

* Load files Via [Pkger](https://github.com/markbates/pkger)

> Enable Pkger or set via env `CONFIGOR_VERBOSE_MODE` to true to use Pkger for loading files
```go
// config.go
configor.New(&configor.Config{UsePkger: true}).Load(&Config, "/config/config.json")
# or set via Environment Variable
$ CONFIGOR_USE_PKGER=true go run config.go
```

* Load From Shell Environment

```go
Expand Down Expand Up @@ -207,3 +229,7 @@ func main() {
// configor.Load(&Config) // only load configurations from shell env & flag
}
```

## Gotchas
- Defaults not initialized for `Map` type fields
- Overlaying (merging) not working for `Map` type fields
4 changes: 4 additions & 0 deletions configor.go
Expand Up @@ -43,6 +43,10 @@ func New(config *Config) *Configor {
config.Silent = true
}

if os.Getenv("CONFIGOR_USE_PKGER") != "" {
config.UsePkger = true
}

return &Configor{Config: config}
}

Expand Down
77 changes: 46 additions & 31 deletions configor_test.go
Expand Up @@ -16,49 +16,40 @@ type Anonymous struct {
Description string
}

type testConfig struct {
APPName string `default:"configor" json:",omitempty"`
Hosts []string

DB struct {
Name string
User string `default:"root"`
Password string `required:"true" env:"DBPassword"`
Port uint `default:"3306" json:",omitempty"`
SSL bool `default:"true" json:",omitempty"`
}
type Database struct {
Name string
User string `yaml:",omitempty" default:"root"`
Password string `required:"true" env:"DBPassword"`
Port uint `default:"3306" yaml:",omitempty" json:",omitempty"`
SSL bool `default:"true" yaml:",omitempty" json:",omitempty"`
}

Contacts []struct {
Name string
Email string `required:"true"`
}
type Contact struct {
Name string
Email string `required:"true"`
}

type testConfig struct {
APPName string `default:"configor" yaml:",omitempty" json:",omitempty"`
Hosts []string
DB *Database
Contacts []Contact
Anonymous `anonymous:"true"`

private string
private string
}

func generateDefaultConfig() testConfig {
return testConfig{
APPName: "configor",
Hosts: []string{"http://example.org", "http://jinzhu.me"},
DB: struct {
Name string
User string `default:"root"`
Password string `required:"true" env:"DBPassword"`
Port uint `default:"3306" json:",omitempty"`
SSL bool `default:"true" json:",omitempty"`
}{
DB: &Database{
Name: "configor",
User: "configor",
Password: "configor",
Port: 3306,
SSL: true,
},
Contacts: []struct {
Name string
Email string `required:"true"`
}{
Contacts: []Contact{
{
Name: "Jinzhu",
Email: "wosmvp@gmail.com",
Expand Down Expand Up @@ -203,6 +194,30 @@ func TestUnmatchedKeyInYamltestConfigFile(t *testing.T) {
}
}

func TestYamlDefaultValue(t *testing.T) {
config := generateDefaultConfig()
config.APPName = ""
config.DB.Port = 0
config.DB.SSL = false

if bytes, err := yaml.Marshal(config); err == nil {
if file, err := ioutil.TempFile("/tmp", "configor.*.yaml"); err == nil {
defer file.Close()
defer os.Remove(file.Name())
file.Write(bytes)

var result testConfig
Load(&result, file.Name())

if !reflect.DeepEqual(result, generateDefaultConfig()) {
t.Errorf("result should be set default value correctly")
}
}
} else {
t.Errorf("failed to marshal config")
}
}

func TestLoadtestConfigurationByEnvironment(t *testing.T) {
config := generateDefaultConfig()
config2 := struct {
Expand Down Expand Up @@ -546,7 +561,7 @@ func TestValidation(t *testing.T) {
Slient bool
}

cfg := &config{Email: "a@b.com", Email2: " ", AuthorIP: "1.1"}
cfg := &config{Email: "a@b.com", Email2: "", AuthorIP: "1.1"}
err := Load(cfg)
fmt.Printf("%+v\n", cfg)
if err != nil {
Expand All @@ -569,12 +584,12 @@ func TestUsePkger(t *testing.T) {
file.Write(bytes)

var result testConfig
New(&Config{UsePkger: true}).Load(&result, "/" + file.Name())
New(&Config{UsePkger: true}).Load(&result, "/"+file.Name())
if !reflect.DeepEqual(result, config) {
t.Errorf("result should equal to original configuration")
}
}
} else {
t.Errorf("failed to marshal config")
}
}
}
13 changes: 12 additions & 1 deletion utils.go
Expand Up @@ -185,10 +185,17 @@ func (configor *Configor) processDefaults(config interface{}) error {
}

for field.Kind() == reflect.Ptr {
if field.IsNil() {
if field.CanAddr() {
field.Set(reflect.New(field.Type().Elem()))
} else {
return fmt.Errorf("ProcessDefaults: field(%v) is nil", field.Type().String())
}
}
field = field.Elem()
}

switch field.Kind() {
switch field.Kind() { // TODO reflect.Map
case reflect.Struct:
if err := configor.processDefaults(field.Addr().Interface()); err != nil {
return err
Expand Down Expand Up @@ -328,6 +335,10 @@ func (configor *Configor) load(config interface{}, files ...string) (err error)
return err
}

if configor.Config.Verbose {
fmt.Printf("Configuration after Defaults set, and before loading :\n %#v\n", config)
}

for _, file := range configFiles {
if configor.Config.Debug || configor.Config.Verbose {
fmt.Printf("Loading configurations from file '%v'...\n", file)
Expand Down

0 comments on commit ed61334

Please sign in to comment.