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?