Skip to content

Commit

Permalink
Add support for configuration subsets
Browse files Browse the repository at this point in the history
  • Loading branch information
skizot722 committed Apr 23, 2016
1 parent 9a227d3 commit 913c1b2
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 0 deletions.
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ GetStringMapString(key string) : map[string]string
GetStringSlice(key string) : []string
GetTime(key string) : time.Time
IsSet(key string) : bool
NewConfigSubset(prefix string) : *confer.Config
```

### Deep Configuration Data
Expand All @@ -120,6 +121,47 @@ Because periods aren't valid environment variable characters, when using automat
LOGGER_STDOUT=/var/log/myapp go run server.go
```

### Configuration Subsets
An alternative to the `GetStringMap` / *Materialized paths* is a configuration subset. This allows you to create a new *confer.Config object which is a subset of the full configuration, specified by a key `prefix`. This makes passing around a subset of the configuration easy, allowing you to access the values using the getter methods listed above as you would normally. The setter methods are also supported on a configuration subset, as well as nested configuration subsets.

##### Example Config
```yml
---
my-app:
logging:
enabled: true
level: debug
server:
workers:
- 127.0.0.1
- 127.0.0.2
- 127.0.0.3
database:
bird:
host: feathers
user: postgres
password: superl33+
wind:
host: sea-spray
user: postgres
password: easyPZ
```
##### Config Subset
```go
windDbConfig := config.NewConfigSubset("my-app.database.wind")
host := windDbConfig.GetString("host")
```

##### Nested Config Subset

```go
dbConfig := config.NewConfigSubset("my-app.database")
windDbConfig := dbConfig.NewConfigSubset("wind")
host := windDbConfig.GetString("host")
```

### Environment Bindings


Expand Down
37 changes: 37 additions & 0 deletions confer.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ type Config struct {

// The root path for configuration files.
rootPath string

// Key prefix for config subsets. The prefixes defines the root of the subset.
keyPrefix string
}

func NewConfig() *Config {
Expand All @@ -53,6 +56,7 @@ func NewConfig() *Config {
manager.attributes = NewConfigSource()
manager.env = NewEnvSource()
manager.rootPath = ""
manager.keyPrefix = ""

return manager
}
Expand Down Expand Up @@ -152,6 +156,8 @@ func (manager *Config) BindEnv(input ...string) (err error) {
// Get returns an interface..
// Must be typecast or used by something that will typecast
func (manager *Config) Get(key string) interface{} {
key = manager.GetFullKey(key)

jww.TRACE.Println("Looking for", key)

v := manager.Find(key)
Expand All @@ -178,6 +184,19 @@ func (manager *Config) Get(key string) interface{} {
return v
}

// NewConfigSubset returns a new config based on
func (manager *Config) NewConfigSubset(prefix string) *Config {
configSubset := &Config{}
*configSubset = *manager

// Use GetFullKey here, as the config may already be a subset. This allows
// support of nested config subsets.
prefix = manager.GetFullKey(prefix)
configSubset.keyPrefix = prefix

return configSubset
}

// Returns true if the config key exists and is non-nil.
func (manager *Config) IsSet(key string) bool {
t := manager.Get(key)
Expand All @@ -194,13 +213,15 @@ func (manager *Config) AutomaticEnv() {

// Returns true if the key provided exists in our configuration.
func (manager *Config) InConfig(key string) bool {
key = manager.GetFullKey(key)
_, exists := manager.attributes.Get(key)
return exists
}

// Set the default value for this key.
// Default only used when no value is provided by the user via flag, config or ENV.
func (manager *Config) SetDefault(key string, value interface{}) {
key = manager.GetFullKey(key)
if !manager.IsSet(key) {
manager.attributes.Set(key, value)
}
Expand All @@ -209,6 +230,7 @@ func (manager *Config) SetDefault(key string, value interface{}) {
// Explicitly sets a value. Will not override command line arguments or
// environment variables, as those sources have higher precedence.
func (manager *Config) Set(key string, value interface{}) {
key = manager.GetFullKey(key)
manager.attributes.Set(key, value)
}

Expand Down Expand Up @@ -288,6 +310,10 @@ func (manager *Config) AllKeys() []string {
leaves := map[string]struct{}{}
for _, key := range keys {

if manager.keyPrefix != "" && !strings.HasPrefix(key, manager.keyPrefix) {
continue
}

// Filter out leaves. This is really ineffecient.
val := manager.Get(key)
if val == nil {
Expand Down Expand Up @@ -323,3 +349,14 @@ func (manager *Config) Debug() {
fmt.Println("Config file attributes:")
pretty.Println(manager.attributes)
}

// GetFullKey accounts for a key prefix (config subset) and returns the full key.
func (manager *Config) GetFullKey(key string) string {
if manager.keyPrefix != "" {
jww.TRACE.Println(key, "Using key prefix in operation: ", manager.keyPrefix)

key = manager.keyPrefix + "." + key
}

return key
}
49 changes: 49 additions & 0 deletions confer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,55 @@ func TestSpec(t *testing.T) {
})
})

Convey("Config Subset", func() {
config.ReadPaths("test/fixtures/application.yaml")
keyPrefix := "app.database"
configSubset := config.NewConfigSubset(keyPrefix)

Convey("Returning a config", func() {
So(configSubset, ShouldHaveSameTypeAs, NewConfig())
})

Convey("Subset gets", func() {
So(configSubset.GetString("host"), ShouldEqual, "localhost")
So(configSubset.GetString("user"), ShouldEqual, "postgres")
So(configSubset.InConfig("password"), ShouldEqual, true)
})

Convey("Subset sets", func() {
configSubset.SetDefault("port", 5432)
configSubset.Set("ssl.mode", "disable")
configSubset.Set("max_open_conns", 30)

So(configSubset.Get("ssl.mode"), ShouldEqual, "disable")
So(configSubset.Get("max_open_conns"), ShouldEqual, 30)
So(configSubset.IsSet("port"), ShouldEqual, true)
So(configSubset.Get("port"), ShouldEqual, 5432)

// Keys for config subset should be (8 in total): [
// app.database app.database.host app.database.user app.database.password
// app.database.port app.database.ssl app.database.ssl.mode
// app.database.max_open_conns
// ]
So(len(configSubset.AllKeys()), ShouldEqual, 8)
})

Convey("Nested Subsets", func() {
configSubset.Set("ssl.mode", "verify-full")
configSubsetNested := configSubset.NewConfigSubset("ssl")
configSubsetNested.SetDefault("connect_timeout", 0)

So(configSubsetNested.IsSet("mode"), ShouldEqual, true)
So(configSubsetNested.GetString("mode"), ShouldEqual, "verify-full")
So(configSubsetNested.InConfig("connect_timeout"), ShouldEqual, true)

// Keys for nested config subset should be (3 in total): [
// app.database.ssl app.database.ssl.mode app.database.ssl.connect_timeout
// ]
So(len(configSubsetNested.AllKeys()), ShouldEqual, 3)
})
})

Convey("Helpers", func() {
Convey("Returning an integer", func() {
config.Set("port", func() interface{} {
Expand Down

0 comments on commit 913c1b2

Please sign in to comment.