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
?
Ok, after few years. Put defaults in the NewOptions function. Instead of constants.
Add a Validate function that uses the Option functions
Where the Host and the Port are
We can fill them form flags and from config files. Then call the Validate. And can use the
(opts ..Option)
approach.We got rid out of defaults in constants. The list of default values just moved from file heading into the NewOptions function. Using pkg.go.dev a end user can navigate into code and see. Yep, not in the document... A little lost. A project documentation, if it's a private library, for example, developer have to extract the defaults and put in package documentation. So, just keep creating open source projects to solve it without additional efforts.