Documentation ¶
Overview ¶
package cfg loads configuration files into Go structs with extra juice for validating fields and setting defaults.
Config files may be defined in in yaml, json or toml format.
When you call `Load()`, cfg takes the following steps:
- Finds config file
- Loads file into config struct
- Fills config struct from the environment (if enabled)
- Sets defaults (where applicable)
- Validates required fields (where applicable)
Example ¶
Define your configuration file in the root of your project:
# config.yaml build: "2020-01-09T12:30:00Z" server: ports: - 8080 cleanup: 1h logger: level: "warn" trace: true
Define your struct and load it:
package main import ( "fmt" "github.com/notnull-co/cfg" ) type Config struct { Build time.Time `cfg:"build" validate:"required"` Server struct { Host string `cfg:"host" default:"127.0.0.1"` Ports []int `cfg:"ports" default:"[80,443]"` Cleanup time.Duration `cfg:"cleanup" default:"30m"` } Logger struct { Level string `cfg:"level" default:"info"` Trace bool `cfg:"trace"` } } func main() { var cfg Config _ = fig.Load(&cfg) fmt.Printf("%+v\n", cfg) // Output: {Build:2019-12-25 00:00:00 +0000 UTC Server:{Host:127.0.0.1 Ports:[8080] Cleanup:1h0m0s} Logger:{Level:warn Trace:true}} }
By default cfg searches for a file named `config.yaml` in the directory it is run from. It can be configured to look elsewhere.
Configuration ¶
Pass options as additional parameters to `Load()` to configure fig's behaviour.
File ¶
Change the file and directories cfg searches in with `File()`.
fig.Load(&cfg, fig.File("settings.json"), fig.Dirs(".", "home/user/myapp", "/opt/myapp"), )
Fig searches for the file in dirs sequentially and uses the first matching file.
The decoder (yaml/json/toml) used is picked based on the file's extension.
Tag ¶
The struct tag key tag cfg looks for to find the field's alt name can be changed using `Tag()`.
type Config struct { Host string `yaml:"host" validate:"required"` Level string `yaml:"level" default:"info"` } var cfg Config fig.Load(&cfg, fig.Tag("yaml"))
By default cfg uses the tag key `fig`.
Environment ¶
Fig can be configured to additionally set fields using the environment. This will happen after the struct is loaded from a config file and thus any values found in the environment will overwrite existing values in the struct.
This is meant to be used in conjunction with loading from a file. There is no support to ONLY load from the environment. You could, but you'd still have to provide an (empty) config file.
This behaviour is disabled by default and can be enabled using the option `UseEnv(prefix)`. Prefix is a string that will be prepended to the keys that are searched in the environment. Although discouraged, prefix may be left empty.
Fig searches for keys in the form PREFIX_FIELD_PATH, or if prefix is left empty then FIELD_PATH.
A field's path is formed by prepending its name with the names of all the surrounding structs up to the root struct, upper-cased and separated by an underscore.
If a field has an alt name defined in its struct tag then that name is preferred over its struct name.
type Config struct { Build time.Time LogLevel string `cfg:"log_level"` Server struct { Host string } }
With the struct above and `UseEnv("myapp")` cfg would search for the following environment variables:
MYAPP_BUILD MYAPP_LOG_LEVEL MYAPP_SERVER_HOST
Fields contained in struct slices whose elements already exists can be also be set via the environment in the form PARENT_IDX_FIELD, where idx is the index of the field in the slice.
type Config struct { Server []struct { Host string } }
With the config above individual servers may be configured with the following environment variable:
MYAPP_SERVER_0_HOST MYAPP_SERVER_1_HOST ...
Note: the Server slice must already have members inside it (i.e. from loading of the configuration file) for the containing fields to be altered via the environment. cfg will not instantiate and insert elements into the slice.
Time ¶
Change the layout cfg uses to parse times using `TimeLayout()`.
type Config struct { Date time.Time `cfg:"date" default:"12-25-2019"` } var cfg Config fig.Load(&cfg, fig.TimeLayout("01-02-2006")) fmt.Printf("%+v", cfg) // Output: {Date:2019-12-25 00:00:00 +0000 UTC}
By default cfg parses time using the `RFC.3339` layout (`2006-01-02T15:04:05Z07:00`).
Required ¶
A validate key with a required value in the field's struct tag makes cfg check if the field has been set after it's been loaded. Required fields that are not set are returned as an error.
type Config struct { Host string `cfg:"host" validate:"required"` // or simply `validate:"required"` }
Fig uses the following properties to check if a field is set:
basic types: != to its zero value ("" for str, 0 for int, etc.) slices, arrays: len() > 0 pointers*, interfaces: != nil structs: always true (use a struct pointer to check for struct presence) time.Time: !time.IsZero() time.Duration: != 0 *pointers to non-struct types (with the exception of time.Time) are de-referenced if they are non-nil and then checked
See example below to help understand:
type Config struct { A string `validate:"required"` B *string `validate:"required"` C int `validate:"required"` D *int `validate:"required"` E []float32 `validate:"required"` F struct{} `validate:"required"` G *struct{} `validate:"required"` H struct { I interface{} `validate:"required"` J interface{} `validate:"required"` } `validate:"required"` K *[]bool `validate:"required"` L []uint `validate:"required"` M *time.Time `validate:"required"` N *regexp.Regexp `validate:"required"` } var cfg Config // simulate loading of config file b := "" cfg.B = &b cfg.H.I = 5.5 cfg.K = &[]bool{} cfg.L = []uint{5} m := time.Time{} cfg.M = &m err := fig.Load(&cfg) fmt.Print(err) // A: required validation failed, B: required validation failed, C: required validation failed, D: required validation failed, E: required validation failed, G: required validation failed, H.J: required validation failed, K: required validation failed, M: required validation failed, N: required validation failed
Default ¶
A default key in the field tag makes cfg fill the field with the value specified when the field is not otherwise set.
Fig attempts to parse the value based on the field's type. If parsing fails then an error is returned.
type Config struct { Port int `cfg:"port" default:"8000"` // or simply `default:"8000"` }
A default value can be set for the following types:
all basic types except bool and complex time.Time time.Duration *regexp.Regexp slices (of above types)
Successive elements of slice defaults should be separated by a comma. The entire slice can optionally be enclosed in square brackets:
type Config struct { Durations []time.Duration `default:"[30m,1h,90m,2h]"` // or `default:"30m,1h,90m,2h"` }
Note: the default setter knows if it should fill a field or not by comparing if the current value of the field is equal to the corresponding zero value for that field's type. This happens after the configuration is loaded and has the implication that the zero value set explicitly by the user will get overwritten by any default value registered for that field. It's for this reason that defaults on booleans are not permitted, as a boolean field with a default value of `true` would always be true (since if it were set to false it'd be overwritten).
Mutual exclusion ¶
The required validation and the default field tags are mutually exclusive as they are contradictory.
This is not allowed:
type Config struct { Level string `validate:"required" default:"warn"` // will result in an error }
Errors ¶
A wrapped error `ErrFileNotFound` is returned when cfg is not able to find a config file to load. This can be useful for instance to fallback to a different configuration loading mechanism.
var cfg Config err := fig.Load(&cfg) if errors.Is(err, fig.ErrFileNotFound) { // load config from elsewhere }
Index ¶
Constants ¶
const ( // DefaultFilename is the default filename of the config file that cfg looks for. DefaultFilename = "config.yaml" // DefaultSecondaryFilename is the secondary default filename of the config file that cfg looks for. DefaultSecondaryFilename = "secret.yaml" // DefaultDir is the default directory that cfg searches in for the config file. DefaultDir = "." // DefaultTag is the default struct tag key that cfg uses to find the field's alt // name. DefaultTag = "cfg" // DefaultTimeLayout is the default time layout that cfg uses to parse times. DefaultTimeLayout = time.RFC3339 )
Variables ¶
var ErrFileNotFound = fmt.Errorf("file not found")
ErrFileNotFound is returned as a wrapped error by `Load` when the config file is not found in the given search dirs.
Functions ¶
func Load ¶
Load reads a configuration file and loads it into the given struct. The parameter `cfg` must be a pointer to a struct.
By default cfg looks for a file `config.yaml` in the current directory and uses the struct field tag `fig` for matching field names and validation. To alter this behaviour pass additional parameters as options.
A field can be marked as required by adding a `required` key in the field's struct tag. If a required field is not set by the configuration file an error is returned.
type Config struct { Env string `cfg:"env" validate:"required"` // or just `validate:"required"` }
A field can be configured with a default value by adding a `default` key in the field's struct tag. If a field is not set by the configuration file then the default value is set.
type Config struct { Level string `cfg:"level" default:"info"` // or just `default:"info"` }
A single field may not be marked as both `required` and `default`.
Types ¶
type Option ¶
type Option func(f *cfg)
Option configures how cfg loads the configuration.
func Dirs ¶
Dirs returns an option that configures the directories that cfg searches to find the configuration file.
Directories are searched sequentially and the first one with a matching config file is used.
This is useful when you don't know where exactly your configuration will be during run-time:
fig.Load(&cfg, fig.Dirs(".", "/etc/myapp", "/home/user/myapp"))
If this option is not used then cfg looks in the directory it is run from.
func File ¶
File returns an option that configures the filename that fig looks for to provide the config values.
The name must include the extension of the file. Supported file types are `yaml`, `yml`, `json` and `toml`.
fig.Load(&cfg, fig.File("config.toml"))
If this option is not used then cfg looks for a file with name `config.yaml`.
func Tag ¶
Tag returns an option that configures the tag key that cfg uses when for the alt name struct tag key in fields.
fig.Load(&cfg, fig.Tag("config"))
If this option is not used then cfg uses the tag `fig`.
func TimeLayout ¶
TimeLayout returns an option that conmfigures the time layout that cfg uses when parsing a time in a config file or in the default tag for time.Time fields.
fig.Load(&cfg, fig.TimeLayout("2006-01-02"))
If this option is not used then cfg parses times using `time.RFC3339` layout.
func UseEnv ¶
UseEnv returns an option that configures cfg to additionally load values from the environment, after it has loaded values from a config file.
fig.Load(&cfg, fig.UseEnv("my_app"))
This is meant to be used in conjunction with loading from a file. There is no support to ONLY load from the environment.
cfg looks for environment variables in the format PREFIX_FIELD_PATH or FIELD_PATH if prefix is empty. Prefix is capitalised regardless of what is provided. The field's path is formed by prepending its name with the names of all surrounding fields up to the root struct. If a field has an alternative name defined inside a struct tag then that name is preferred.
type Config struct { Build time.Time LogLevel string `cfg:"log_level"` Server struct { Host string } }
With the struct above and UseEnv("myapp") cfg would search for the following environment variables:
MYAPP_BUILD MYAPP_LOG_LEVEL MYAPP_SERVER_HOST