dpstir0081 2018-04-11 16:45
浏览 10
已采纳

如何在Go中实现零散的配置文件验证

I try to configure a CLI app via config file (yaml). The app has several "components" (lets say a persistence layer, an integrated web server etc.). Those components are managed in subpackages to keep the code tidy. The configuration of those components is defined inside their packages and "merged" in a config package to a struct that represents the config file. See this code as a demo implementation:

package main

import (
    "errors"
    "fmt"

    yaml "gopkg.in/yaml.v2"
)

//
// This would be in package 'webserver'
// Only the Config part is shown, there would be a constructor and the implementation of
// the webserver as well...
//

// WebserverConfig holds all required configuration params
type WebserverConfig struct {
    Listen    string `yaml:"listen"`
    Autostart bool   `yaml:"autostart"`
}

// Validate checks if all fields make sense and returns a list of error string if problems
// are found
func (wsc WebserverConfig) Validate() (error, []string) {
    errs := []string{}
    if wsc.Listen == "" {
        errs = append(errs, "Field 'listen' must not be empty.")
    }
    if len(errs) > 0 {
        err := errors.New("Config of 'webserver' has errors")
        return err, errs
    }
    return nil, errs
}

// UnmarshalYAML implements a custom unmarshaller to make sure defaults are set
// to meaningful values
func (wsc *WebserverConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
    type WebserverConfigDefaulted WebserverConfig
    var defaults = WebserverConfigDefaulted{
        Listen:    ":8080",
        Autostart: true,
    }

    out := defaults
    err := unmarshal(&out)
    *wsc = WebserverConfig(out)
    return err
}

//
// This would be in package conf
//
//

// Config is the global configuration composed of all component configuration sections
type Config struct {
    Webserver WebserverConfig `yaml:"webserver"`
}

// Load reads the bytes into the global config
func Load(data string) (Config, error) {
    c := Config{}
    err := yaml.Unmarshal([]byte(data), &c)
    if err != nil {
        return c, fmt.Errorf("Error while unmarshalling configuration from yaml: %s", err.Error())
    }
    return c, nil
}

// Validate should call each individual component configurations validate function (if it exists)
func (c Config) Validate() error {
    // TODO: IMPLEMPNT...
    return nil
}

//
// This is finally in main
//
//

var yamlFile = `---
webserver:
  listen: ":9999" 
`

func main() {
    c, err := Load(yamlFile)
    if err != nil {
        fmt.Println(err)
    }
    err = c.Validate()
    if err != nil {
        fmt.Println(err)
    }
}

Find it on the playground here (does not run because of the dep to yaml): https://play.golang.org/p/i3EAZJP7aYz

QUESTION: At this point I have no idea how the global validation function func (c Config) Validate() error could be implemented to call all Validate funcs of the 'component configurations' (such as func (wsc WebserverConfig) Validate() (error, []string)). Any idea or hints?

  • 写回答

1条回答 默认 最新

  • duandun2136 2018-04-12 18:46
    关注

    I'd recommend calling c.Webserver.Validate() from c.Validate().

    The downside of this is you need to update c.Validate() every time you add a new config, which might be easy to forget. However, it is "simple, readable and explicit" (quoting from your comment).

    Alternatively, you could loop over every element of the config to see which ones match a Validater interface then call Validate on them. But, this would be much more complicated and more difficult to understand & debug. The usual recommendation is to avoid reflection unless absolutely necessary.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 程序不包含适用于入口点的静态Main方法
  • ¥15 素材场景中光线烘焙后灯光失效
  • ¥15 请教一下各位,为什么我这个没有实现模拟点击
  • ¥15 执行 virtuoso 命令后,界面没有,cadence 启动不起来
  • ¥50 comfyui下连接animatediff节点生成视频质量非常差的原因
  • ¥20 有关区间dp的问题求解
  • ¥15 多电路系统共用电源的串扰问题
  • ¥15 slam rangenet++配置
  • ¥15 有没有研究水声通信方面的帮我改俩matlab代码
  • ¥15 ubuntu子系统密码忘记