From f9a0422e88d5e787058e2b2cd70c7605205f088f Mon Sep 17 00:00:00 2001 From: lucas Date: Wed, 31 Aug 2016 23:25:51 -0300 Subject: [PATCH] Add tenant feature --- README.md | 50 ++++++++++++++++ viper.go | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++ viper_test.go | 34 +++++++++++ 3 files changed, 246 insertions(+) diff --git a/README.md b/README.md index cf17560..d766057 100644 --- a/README.md +++ b/README.md @@ -599,6 +599,56 @@ y.SetDefault("ContentDir", "foobar") When working with multiple vipers, it is up to the user to keep track of the different vipers. +### Working with tenant + +Sometime you want your config to be override by a more specific one, use tenant for it. + +Example: + +config file: +```json +[shared] +name = "name_shared" + +[tenant_a] +name = "name_a" + +[tenant_b] + +``` + +code: +```go +viper.SetTenantDefault("shared") +viper.SetConfigName("teste") +viper.AddConfigPath(".") +viper.ReadInConfig() + +fmt.Printf(viper.GetStringTenant("tenant_a", "name")) // name_a +fmt.Printf(viper.GetStringTenant("tenant_b", "name")) // name_shared +``` + +config file: +```json +name = "name_shared" + +[tenant_a] +name = "name_a" + +[tenant_b] + +``` + +code: +```go +viper.SetConfigName("teste") +viper.AddConfigPath(".") +viper.ReadInConfig() + +fmt.Printf(viper.GetStringTenant("tenant_a", "name")) // name_a +fmt.Printf(viper.GetStringTenant("tenant_b", "name")) // name_shared +``` + ## Q & A Q: Why not INI files? diff --git a/viper.go b/viper.go index f17790e..11518d5 100644 --- a/viper.go +++ b/viper.go @@ -157,6 +157,9 @@ type Viper struct { typeByDefValue bool onConfigChange func(fsnotify.Event) + + //name of default tenant + tenantDefault string } // Returns an initialized Viper instance. @@ -270,6 +273,12 @@ func (v *Viper) WatchConfig() { }() } +// Explicitly define the tenant default that will be override by others tenants +func SetTenantDefault(tenDefault string) { v.SetTenantDefault(tenDefault) } +func (v *Viper) SetTenantDefault(tenDefault string) { + v.tenantDefault = tenDefault +} + // Explicitly define the path, name and extension of the config file // Viper will use this and not check any of the config paths func SetConfigFile(in string) { v.SetConfigFile(in) } @@ -542,66 +551,219 @@ func (v *Viper) Sub(key string) *Viper { } } +// Returns the value associated with the key as a string considering the tenant selected +func GetStringTenant(tenant string, key string) string { return v.GetStringTenant(tenant, key) } +func (v *Viper) GetStringTenant(tenant string, key string) string { + if v.IsSet(tenant + "." + key) { + return cast.ToString(v.Get(tenant + "." + key)) + } + + if v.tenantDefault != "" { + return cast.ToString(v.Get(v.tenantDefault + "." + key)) + } + return cast.ToString(v.Get(key)) +} + // Returns the value associated with the key as a string func GetString(key string) string { return v.GetString(key) } func (v *Viper) GetString(key string) string { return cast.ToString(v.Get(key)) } +// Returns the value associated with the key as a boolean considering the tenant selected +func GetBoolTenant(tenant string, key string) bool { return v.GetBoolTenant(tenant, key) } +func (v *Viper) GetBoolTenant(tenant string, key string) bool { + if v.IsSet(tenant + "." + key) { + return cast.ToBool(v.Get(tenant + "." + key)) + } + + if v.tenantDefault != "" { + return cast.ToBool(v.Get(v.tenantDefault + "." + key)) + } + return cast.ToBool(v.Get(key)) +} + // Returns the value associated with the key as a boolean func GetBool(key string) bool { return v.GetBool(key) } func (v *Viper) GetBool(key string) bool { return cast.ToBool(v.Get(key)) } +// Returns the value associated with the key as a int considering the tenant selected +func GetIntTenant(tenant string, key string) int { return v.GetIntTenant(tenant, key) } +func (v *Viper) GetIntTenant(tenant string, key string) int { + if v.IsSet(tenant + "." + key) { + return cast.ToInt(v.Get(tenant + "." + key)) + } + + if v.tenantDefault != "" { + return cast.ToInt(v.Get(v.tenantDefault + "." + key)) + } + return cast.ToInt(v.Get(key)) +} + // Returns the value associated with the key as an integer func GetInt(key string) int { return v.GetInt(key) } func (v *Viper) GetInt(key string) int { return cast.ToInt(v.Get(key)) } +// Returns the value associated with the key as a int considering the tenant selected +func GetInt64Tenant(tenant string, key string) int64 { return v.GetInt64Tenant(tenant, key) } +func (v *Viper) GetInt64Tenant(tenant string, key string) int64 { + if v.IsSet(tenant + "." + key) { + return cast.ToInt64(v.Get(tenant + "." + key)) + } + + if v.tenantDefault != "" { + return cast.ToInt64(v.Get(v.tenantDefault + "." + key)) + } + return cast.ToInt64(v.Get(key)) +} + // Returns the value associated with the key as an integer func GetInt64(key string) int64 { return v.GetInt64(key) } func (v *Viper) GetInt64(key string) int64 { return cast.ToInt64(v.Get(key)) } +// Returns the value associated with the key as a float considering the tenant selected +func GetFloat64Tenant(tenant string, key string) float64 { return v.GetFloat64Tenant(tenant, key) } +func (v *Viper) GetFloat64Tenant(tenant string, key string) float64 { + if v.IsSet(tenant + "." + key) { + return cast.ToFloat64(v.Get(tenant + "." + key)) + } + + if v.tenantDefault != "" { + return cast.ToFloat64(v.Get(v.tenantDefault + "." + key)) + } + return cast.ToFloat64(v.Get(key)) +} + // Returns the value associated with the key as a float64 func GetFloat64(key string) float64 { return v.GetFloat64(key) } func (v *Viper) GetFloat64(key string) float64 { return cast.ToFloat64(v.Get(key)) } +// Returns the value associated with the key as a time considering the tenant selected +func GetTimeTenant(tenant string, key string) time.Time { return v.GetTimeTenant(tenant, key) } +func (v *Viper) GetTimeTenant(tenant string, key string) time.Time { + if v.IsSet(tenant + "." + key) { + return cast.ToTime(v.Get(tenant + "." + key)) + } + + if v.tenantDefault != "" { + return cast.ToTime(v.Get(v.tenantDefault + "." + key)) + } + return cast.ToTime(v.Get(key)) +} + // Returns the value associated with the key as time func GetTime(key string) time.Time { return v.GetTime(key) } func (v *Viper) GetTime(key string) time.Time { return cast.ToTime(v.Get(key)) } +// Returns the value associated with the key as a duration considering the tenant selected +func GetDurationTenant(tenant string, key string) time.Duration { + return v.GetDurationTenant(tenant, key) +} +func (v *Viper) GetDurationTenant(tenant string, key string) time.Duration { + if v.IsSet(tenant + "." + key) { + return cast.ToDuration(v.Get(tenant + "." + key)) + } + + if v.tenantDefault != "" { + return cast.ToDuration(v.Get(v.tenantDefault + "." + key)) + } + return cast.ToDuration(v.Get(key)) +} + // Returns the value associated with the key as a duration func GetDuration(key string) time.Duration { return v.GetDuration(key) } func (v *Viper) GetDuration(key string) time.Duration { return cast.ToDuration(v.Get(key)) } +// Returns the value associated with the key as a slice of strings considering the tenant selected +func GetStringSliceTenant(tenant string, key string) []string { + return v.GetStringSliceTenant(tenant, key) +} +func (v *Viper) GetStringSliceTenant(tenant string, key string) []string { + if v.IsSet(tenant + "." + key) { + return cast.ToStringSlice(v.Get(tenant + "." + key)) + } + + if v.tenantDefault != "" { + return cast.ToStringSlice(v.Get(v.tenantDefault + "." + key)) + } + return cast.ToStringSlice(v.Get(key)) +} + // Returns the value associated with the key as a slice of strings func GetStringSlice(key string) []string { return v.GetStringSlice(key) } func (v *Viper) GetStringSlice(key string) []string { return cast.ToStringSlice(v.Get(key)) } +// Returns the value associated with the key as a map of interfaces considering the tenant selected +func GetStringMapTenant(tenant string, key string) map[string]interface{} { + return v.GetStringMapTenant(tenant, key) +} +func (v *Viper) GetStringMapTenant(tenant string, key string) map[string]interface{} { + if v.IsSet(tenant + "." + key) { + return cast.ToStringMap(v.Get(tenant + "." + key)) + } + + if v.tenantDefault != "" { + return cast.ToStringMap(v.Get(v.tenantDefault + "." + key)) + } + return cast.ToStringMap(v.Get(key)) +} + // Returns the value associated with the key as a map of interfaces func GetStringMap(key string) map[string]interface{} { return v.GetStringMap(key) } func (v *Viper) GetStringMap(key string) map[string]interface{} { return cast.ToStringMap(v.Get(key)) } +// Returns the value associated with the key as a map of strings considering the tenant selected +func GetStringMapStringTenant(tenant string, key string) map[string]string { + return v.GetStringMapStringTenant(tenant, key) +} +func (v *Viper) GetStringMapStringTenant(tenant string, key string) map[string]string { + if v.IsSet(tenant + "." + key) { + return cast.ToStringMapString(v.Get(tenant + "." + key)) + } + + if v.tenantDefault != "" { + return cast.ToStringMapString(v.Get(v.tenantDefault + "." + key)) + } + return cast.ToStringMapString(v.Get(key)) +} + // Returns the value associated with the key as a map of strings func GetStringMapString(key string) map[string]string { return v.GetStringMapString(key) } func (v *Viper) GetStringMapString(key string) map[string]string { return cast.ToStringMapString(v.Get(key)) } +// Returns the value associated with the key as map to a slice of strings considering the tenant selected +func GetStringMapStringSliceTenant(tenant string, key string) map[string][]string { + return v.GetStringMapStringSliceTenant(tenant, key) +} +func (v *Viper) GetStringMapStringSliceTenant(tenant string, key string) map[string][]string { + if v.IsSet(tenant + "." + key) { + return cast.ToStringMapStringSlice(v.Get(tenant + "." + key)) + } + + if v.tenantDefault != "" { + return cast.ToStringMapStringSlice(v.Get(v.tenantDefault + "." + key)) + } + return cast.ToStringMapStringSlice(v.Get(key)) +} + // Returns the value associated with the key as a map to a slice of strings. func GetStringMapStringSlice(key string) map[string][]string { return v.GetStringMapStringSlice(key) } func (v *Viper) GetStringMapStringSlice(key string) map[string][]string { diff --git a/viper_test.go b/viper_test.go index 0c0c7e5..37666d4 100644 --- a/viper_test.go +++ b/viper_test.go @@ -102,6 +102,21 @@ var remoteExample = []byte(`{ "newkey":"remote" }`) +var tenantExample = []byte(` +title = "TOML Example" +attr = "Foo" + +[owner] +title = "TOML!"`) + +var tenantExampleWithDefault = []byte(` +[default] +title = "TOML Example" +attr = "Foo" + +[owner] +title = "TOML!"`) + func initConfigs() { Reset() SetConfigType("yaml") @@ -760,6 +775,25 @@ func TestSub(t *testing.T) { assert.Equal(t, subv, (*Viper)(nil)) } +func TestTenant(t *testing.T) { + v := New() + v.SetConfigType("toml") + v.ReadConfig(bytes.NewBuffer(tenantExample)) + + assert.Equal(t, v.GetStringTenant("owner", "title"), "TOML!") + assert.Equal(t, v.GetStringTenant("owner", "attr"), "Foo") +} + +func TestTenantWithDefault(t *testing.T) { + v := New() + v.SetConfigType("toml") + v.SetTenantDefault("default") + v.ReadConfig(bytes.NewBuffer(tenantExampleWithDefault)) + + assert.Equal(t, v.GetStringTenant("owner", "title"), "TOML!") + assert.Equal(t, v.GetStringTenant("owner", "attr"), "Foo") +} + var yamlMergeExampleTgt = []byte(` hello: pop: 37890