Nested structs and sub configs¶
Nested structs¶
Fields can be grouped into structs. A field with struct type is parsed by its fields, every nested field has its own tags:
type HTTP struct {
Host string `yaml:"host" env:"HTTP_HOST" initial:"localhost"`
Port int `yaml:"port" env:"HTTP_PORT" initial:"8080"`
}
type Config struct {
HTTP HTTP `yaml:"http"`
}
For the yaml driver the tag of the struct field is a key of the nested map, so Host is read from http.host. Names of environment variables are not built from the parent names: Host is read from HTTP_HOST, as it is written in its tag.
Types that are parsed from a single value — time.Time, url.URL and types with UnmarshalText — are not nested structs, see Supported types.
Embedded structs¶
Fields of an embedded struct without the tag are on the level of the embedding struct, like in encoding/json:
type Timeouts struct {
Read time.Duration `yaml:"read_timeout"`
Write time.Duration `yaml:"write_timeout"`
}
type Config struct {
HTTP struct {
Timeouts
Port int `yaml:"port"`
} `yaml:"http"`
}
- The yaml, json, toml, ini, properties, consul, vault and etcd drivers read
Readfromhttp.read_timeout, the embedded struct adds no level.yaml:",inline"works the same way. - With the name in the tag,
Timeouts `yaml:"timeouts"`, the embedded struct is a usual nested struct:http.timeouts.read_timeout. - An embedded struct without the tag is expected, the manager does not warn about it.
- Auto keys and indexed keys of the env driver skip the name of the embedded struct too:
HTTP.Timeouts.ReadisHTTP_READwith auto keys, andSERVERS_0_READfor the struct in the slice.
Structs stored by pointer¶
A struct stored by pointer is parsed the same way, by its fields:
type TLS struct {
Cert string `yaml:"cert" env:"TLS_CERT"`
Key string `yaml:"key" env:"TLS_KEY"`
}
type Config struct {
TLS *TLS `yaml:"tls"`
}
The pointer makes the section optional:
- the struct is created when the first value for its fields is found in any driver;
- without values the pointer is not changed, so it stays
nil; - an existing struct is filled in place, it is not replaced with a new one.
if err := config.Parse(context.Background(), c); err != nil {
panic(err)
}
if c.TLS != nil {
// TLS_CERT, TLS_KEY or the tls section of the file is set
}
Required fields inside such struct are checked even if the pointer is nil: a required field makes the whole section mandatory.
Parsed as a single value
Pointers to pointers (**Struct) and recursive types (Next *Node inside Node) are not parsed by fields. They are single values: JSON in string values, a nested map in files.
Sub configs¶
A part of the registered config can be parsed separately, by the type of the field. It allows a package to get its own config without knowing about the whole one:
c := &Config{}
if err := config.Register(c); err != nil {
panic(err)
}
// HTTP is a type of the field in the registered Config
http := &HTTP{}
if err := config.Parse(context.Background(), http); err != nil {
panic(err)
}
The type can be a type of the struct stored by value or by pointer. Only the fields of the sub config are parsed, with the same tags and keys as in the whole config: http.host in the file, HTTP_HOST in the environment.
If several registered configs have a field with this type, the configs are checked in the registration order and the first found field is used.