Functional options: https://gist.github.com/travisjeffery/8265ca411735f638db80e2e34bdbd3ae
In real world we need to validate input. This functional options kills global namespace. Take a look my average config:
import (
"domain.tld/user/log"
)
// default configs
const (
Host string = "127.0.0.1" // default host address
Port int = 8897 // default port
)
// A Config represents an average config for this comment
type Config struct {
Log log.Config
Host string
Port int
}
// Validate the Config values
func (c *Config) Validate() (err error) {
if err = c.Log.Validate(); err != nil {
return
}
if c.Host == "" {
return ErrEmptyHost
}
if c.Port < 0 {
return ErrNegtivePort
}
return
}
// NewConfig returns default configurations
func NewConfig() (c *Config) {
c = new(Config)
c.Log = log.NewConfig()
c.Host = Host
c.Port = Port
return
}
// FromFlags obtains values from command-line flags. Call this method before `flag.Parse` and
// validate the Config after that
func (c *Config) FromFlags() {
c.Log.FromFlags()
flag.StringVar(&c.Host,
"h",
c.Host,
"host address")
flag.StringVar(&c.Port,
"p",
c.Port,
"port")
}
- the main problem is inventing new name for
Host
andPort
constants
How to validate configs using the functional options?
func Option func(c *Config) error
// Host ... what I need to write here?
func Host(address string) Option {
if address == "" {
return func(*Config) error { return ErrEmptyAddress }
}
return func(c *Config) (_ error) {
c.Host = address
return
}
}
But we also need to validate configs from flags
func validateHost(address string) (err error) {
if address == "" {
err = ErrEmptyAddress
}
return
}
func Host(address string) Option {
return func(c *Config) (err error) {
if err = validateHost(address); err != nil {
return
}
c.Host = address
return
}
}
Let's see global namespace
Host
Port
Config
NewConfig
vs
defaultHost
defaultPort
validateHost
validatePort
Host
Port
Config
NewConfig
How to deal with the Config.Log
?
I'd solve the defaults problem two ways. This first is to provide a default to flag:
func (f *FlagSet) String(name string, value string, usage string) *string
(this is the
value
field)The second thing is to validate your inputs before you get to injecting them as options. You'd probably call this validation from NewConfig.
If you use github.com/spf13/viper, you can blend flags, environment variables, and default config values for a mature set of fallbacks.