条件性地将 viper 配置(toml 格式)解析为结构体的惯用方法

huangapple go评论89阅读模式
英文:

Idiomatic way to conditionally unmarshal a viper config (toml) into structs

问题

我是Go语言的新手,想知道如何以惯用的方式解决以下问题:

我正在使用viper将配置文件加载到程序中。我选择了toml格式,因为我希望配置文件可以指定多种不同格式的必需输入:例如,Alpha提供程序需要一个apikey,而Beta提供程序需要用户名和密码。

[alpha]
apikey = "123"
domain = "example.com"

# [beta]
# username = ""
# password = ""
# domain = ""
type ProviderService interface {                                 
    PrintDomain()                                                  
}                                                                
                                                                 
type Provider struct {                                           
    Alpha `mapstructure:"alpha"`                                 
    Beta  `mapstructure:"beta"`                                                     
}                                                                
                                                                 
type Alpha struct {                                              
    Apikey string `mapstructure:"apikey"`                        
    Domain string `mapstructure:"domain"`                        
}                                                                
                                                                 
type Beta struct {                                               
    Username string `mapstructure:"username"`                    
    Password string `mapstructure:"password"`                    
    Domain   string `mapstructure:"domain"`                      
}                                                                
                                                                 
func main() {                                                    
    provider := loadConfig()                                     
    fmt.Printf("%+v\n", provider)                                 
    // provider.DoAThing()  # <==== Want to do this; currently results in "ambiguous selector provider.DoAThing"                              
}                                                                
                                                                 
func (a Alpha) DoAThing() {                                   
    fmt.Println("domain", a.Domain)                              
}                                                                
func (b Beta) DoAThing() {                                    
    fmt.Println("domain", b.Domain)                              
}                                                                
                                                                 
func loadConfig() (p Provider) {                                 
    viper.AddConfigPath("./")                                    
    viper.SetConfigName("config")                                
    viper.SetConfigType("toml")                                  
                                                                 
    err := viper.ReadInConfig()                                  
    if err != nil {                                              
        panic(fmt.Errorf("Fatal error config file: %w \n", err)) 
    }                                                            
                                                                 
    err = viper.Unmarshal(&p)                                    
    if err != nil {                                              
        log.Fatal("unable to decode into struct", err)           
    }                                                            
                                                                 
    return p                                                     
}

上面的代码结果是{Alpha:{Apikey:123 Domain:example.com} Beta:{Username: Password: Domain:}},其中空/未使用的结构仍然存在。

最终,我希望ProviderService接口是与提供程序无关的,这样我就可以简单地调用provider.PrintDomain(),而不是provider.Alpha.PrintDomain(),并且代码中不会充斥着if/else语句。我也可以接受其他结构代码的方式来实现这个目标。

提前谢谢!

英文:

I'm new to Go and would like to know how to solve the following in an idiomatic fashion:

I am using viper to load config files into the program. I chose the toml format because I want to have a config file which can specify several different formats of required input: for instance the Alpha provider requires an apikey, while the Beta provider requires username and password.

[alpha]
apikey = &quot;123&quot;
domain = &quot;example.com&quot;
# [beta]
# username = &quot;&quot;
# password = &quot;&quot;
# domain = &quot;&quot;
type ProviderService interface {                                 
PrintDomain()                                                
}                                                                
type Provider struct {                                           
Alpha `mapstructure:&quot;alpha&quot;`                                 
Beta  `mapstructure:&quot;beta&quot;`                                                     
}                                                                
type Alpha struct {                                              
Apikey string `mapstructure:&quot;apikey&quot;`                        
Domain string `mapstructure:&quot;domain&quot;`                        
}                                                                
type Beta struct {                                               
Username string `mapstructure:&quot;username&quot;`                    
Password string `mapstructure:&quot;password&quot;`                    
Domain   string `mapstructure:&quot;domain&quot;`                      
}                                                                
func main() {                                                    
provider := loadConfig()                                     
fmt.Printf(&quot;%+v\n&quot;, provider)                                
// provider.DoAThing()  # &lt;==== Want to do this; currently results in &quot;ambiguous selector provider.DoAThing&quot;                              
}                                                                
func (a Alpha) DoAThing() {                                   
fmt.Println(&quot;domain&quot;, a.Domain)                              
}                                                                
func (b Beta) DoAThing() {                                    
fmt.Println(&quot;domain&quot;, b.Domain)                              
}                                                                
func loadConfig() (p Provider) {                                 
viper.AddConfigPath(&quot;./&quot;)                                    
viper.SetConfigName(&quot;config&quot;)                                
viper.SetConfigType(&quot;toml&quot;)                                  
err := viper.ReadInConfig()                                  
if err != nil {                                              
panic(fmt.Errorf(&quot;Fatal error config file: %w \n&quot;, err)) 
}                                                            
err = viper.Unmarshal(&amp;p)                                    
if err != nil {                                              
log.Fatal(&quot;unable to decode into struct&quot;, err)           
}                                                            
return p                                                     
}

The code above results in {Alpha:{Apikey:123 Domain:example.com} Beta:{Username: Password: Domain:}} where the empty/unused struct is still present.

Ultimately I want the ProviderService interface to be provider agnostic so I can simply call provider.PrintDomain() instead of provider.Alpha.PrintDomain() and have the code littered with if/else statements. I am also open to other ways of structuring the code to achieve this outcome.

Thanks in advance!

答案1

得分: 1

可能的方法是使用reflect

func main() {
    provider := loadConfig()
    fmt.Printf("%+v\n", provider)

    v := reflect.ValueOf(provider)
    for i := 0; i < v.NumField(); i++ {
        field := v.Field(i)
        fmt.Println("domain:", field.FieldByName("Domain"))
    }
}

它将打印Alpha或Beta结构的"domain"字段。

Go Playground

英文:

A possible way could be to use reflect

func main() {
provider := loadConfig()
fmt.Printf(&quot;%+v\n&quot;, provider)
v := reflect.ValueOf(provider)
for i := 0; i &lt; v.NumField(); i++ {
field := v.Field(i)
fmt.Println(&quot;domain:&quot;, field.FieldByName(&quot;Domain&quot;))
}
}

It will print the "domain" field for Alpha or Beta Structures.

Go Playground

答案2

得分: 0

这是我最终得到的代码,它允许我根据在toml文件中指定的内容有条件地加载一个结构,并且仍然可以使用接口将其视为结构不可知。

如果有人对重构此代码以使其更符合惯用方式有任何提示,请告诉我!

type ProviderService interface {
DoAThing()
}
type Alpha struct {
Apikey string `mapstructure:"apikey"`
Domain string `mapstructure:"domain"`
}
type Beta struct {
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
Domain   string `mapstructure:"domain"`
}
func main() {
provider := loadConfig()
if provider == nil {
log.Fatal("无法解析配置文件")
}
provider.DoAThing()
}
func (a Alpha) DoAThing() {
fmt.Println("domain", a.Domain)
}
func (b Beta) DoAThing() {
fmt.Println("domain", b.Domain)
}
func loadConfig() ProviderService {
viper.AddConfigPath("./")
viper.SetConfigName("config")
viper.SetConfigType("toml")
err := viper.ReadInConfig()
if err != nil {
panic(fmt.Errorf("配置文件致命错误:%w \n", err))
}
var a Alpha
_ = viper.UnmarshalKey("alpha", &a)
if a != (Alpha{}) {
return a
}
var b Beta
_ = viper.UnmarshalKey("beta", &b)
if b != (Beta{}) {
return b
}
return nil
}
英文:

This is what I ended up with that allows me to conditionally load a struct based on what is specified in the toml file, and still use an interface to treat it as struct agnostic.

If anyone has any tips on refactoring this to be more idiomatic, please let me know!

type ProviderService interface {
DoAThing()
}
type Alpha struct {
Apikey string `mapstructure:&quot;apikey&quot;`
Domain string `mapstructure:&quot;domain&quot;`
}
type Beta struct {
Username string `mapstructure:&quot;username&quot;`
Password string `mapstructure:&quot;password&quot;`
Domain   string `mapstructure:&quot;domain&quot;`
}
func main() {
provider := loadConfig()
if provider == nil {
log.Fatal(&quot;unable to parse config file&quot;)
}
provider.DoAThing()
}
func (a Alpha) DoAThing() {
fmt.Println(&quot;domain&quot;, a.Domain)
}
func (b Beta) DoAThing() {
fmt.Println(&quot;domain&quot;, b.Domain)
}
func loadConfig() ProviderService {
viper.AddConfigPath(&quot;./&quot;)
viper.SetConfigName(&quot;config&quot;)
viper.SetConfigType(&quot;toml&quot;)
err := viper.ReadInConfig()
if err != nil {
panic(fmt.Errorf(&quot;Fatal error config file: %w \n&quot;, err))
}
var a Alpha
_ = viper.UnmarshalKey(&quot;alpha&quot;, &amp;a)
if a != (Alpha{}) {
return a
}
var b Beta
_ = viper.UnmarshalKey(&quot;beta&quot;, &amp;b)
if b != (Beta{}) {
return b
}
return nil
}

huangapple
  • 本文由 发表于 2022年1月28日 04:15:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/70885156.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定