fig

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 12, 2023 License: Apache-2.0 Imports: 13 Imported by: 52

README

fig

godoc semver tag go report card coverage status license

fig

fig is a tiny library for loading an application's configuration into a Go struct.

Why fig?

  • 🛠️ Define your configuration, validations and defaults all within a single struct.
  • 🌍 Easily load your configuration from a file, the environment, or both.
  • ⏰ Decode strings into Time, Duration, Regexp, or any custom type that satisfies the StringUnmarshaler interface.
  • 🗂️ Compatible with yaml, json, and toml file formats.
  • 🧩 Only three external dependencies.

Getting Started

$ go get -d github.com/kkyr/fig

Define your config file:

# config.yaml

build: "2020-01-09T12:30:00Z"

server:
    ports:
      - 8080
    cleanup: 1h

logger:
    level: "warn"
    trace: true

Define your struct along with validations or defaults:

package main

import (
  "fmt"

  "github.com/kkyr/fig"
)

type Config struct {
  Build  time.Time `fig:"build" validate:"required"`
  Server struct {
    Host    string        `fig:"host" default:"127.0.0.1"`
    Ports   []int         `fig:"ports" default:"[80,443]"`
    Cleanup time.Duration `fig:"cleanup" default:"30m"`
  }
  Logger struct {
    Level   string         `fig:"level" default:"info"`
    Pattern *regexp.Regexp `fig:"pattern" default:".*"`
    Trace   bool           `fig:"trace"`
  }
}

func main() {
  var cfg Config
  err := fig.Load(&cfg)
  // error handling omitted
  
  fmt.Printf("%+v\n", cfg)
  // {Build:2019-12-25T00:00:00Z Server:{Host:127.0.0.1 Ports:[8080] Cleanup:1h0m0s} Logger:{Level:warn Pattern:.* Trace:true}}
}

Fields marked as required are checked to ensure they're not empty, and default values are applied to fill in those that are empty.

Environment

By default, fig will only look for values in a config file. To also include values from the environment, use the UseEnv option:

fig.Load(&cfg, fig.UseEnv("APP_PREFIX"))

In case of conflicts, values from the environment take precedence.

Usage

See usage examples.

Documentation

For detailed documentation, visit go.dev.

Contributing

PRs are welcome! Please explain your motivation for the change in your PR and ensure your change is properly tested and documented.

Documentation

Overview

Package fig loads configuration files and/or environment variables into Go structs with extra juice for validating fields and setting defaults.

Config files may be defined in yaml, json or toml format.

When you call `Load()`, fig takes the following steps:

  1. Fills config struct from the config file (if enabled)
  2. Fills config struct from the environment (if enabled)
  3. Sets defaults (where applicable)
  4. 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/kkyr/fig"
)

 type Config struct {
   Build  time.Time `fig:"build" validate:"required"`
   Server struct {
     Host    string        `fig:"host" default:"127.0.0.1"`
     Ports   []int         `fig:"ports" default:"[80,443]"`
     Cleanup time.Duration `fig:"cleanup" default:"30m"`
   }
   Logger struct {
     Level string `fig:"level" default:"info"`
     Trace bool   `fig:"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}}
}

Configuration

Pass options as additional parameters to `Load()` to configure fig's behaviour.

IgnoreFile

Do not look for any configuration file with `IgnoreFile()`.

fig.Load(&cfg, fig.IgnoreFile())

If IgnoreFile is given then any other configuration file related options like `File` and `Dirs` are simply ignored.

File & Dirs

By default fig searches for a file named `config.yaml` in the directory it is run from. Change the file and directories fig searches in with `File()` and `Dirs()`.

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 fig 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 fig uses the tag key `fig`.

Environment

Fig can be configured to additionally set fields using the environment. This behaviour can be enabled using the option `UseEnv(prefix)`. If loading from file is also enabled then first the struct is loaded from a config file and thus any values found in the environment will overwrite existing values in the struct.

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 `fig:"log_level"`
  Server   struct {
    Host string
  }
}

With the struct above and `UseEnv("myapp")` fig 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. Fig will not instantiate and insert elements into the slice.

Environment Limitations

Maps and map values cannot be populated from the environment.

Time

Change the layout fig uses to parse times using `TimeLayout()`.

type Config struct {
  Date time.Time `fig:"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 fig parses time using the `RFC.3339` layout (`2006-01-02T15:04:05Z07:00`).

Strict Parsing

By default fig ignores any fields in the config file that are not present in the struct. This behaviour can be changed using `UseStrict()` to achieve strict parsing. When strict parsing is enabled, extra fields in the config file will cause an error.

Required

A validate key with a required value in the field's struct tag makes fig 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 `fig:"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 fig 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 `fig:"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"`
}

Defaults Limitations

  1. Boolean values: Fig cannot distinguish between false and an unset value for boolean types. As a result, default values for booleans are not currently supported.

  2. Maps: Maps are not supported because providing a map in a string form would be complex and error-prone. Users are encouraged to use structs instead for more reliable and structured data handling.

  3. Map values: Values retrieved from a map through reflection are not addressable. Therefore, setting default values for map values is not currently supported.

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 fig 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

View Source
const (
	// DefaultFilename is the default filename of the config file that fig looks for.
	DefaultFilename = "config.yaml"
	// DefaultDir is the default directory that fig searches in for the config file.
	DefaultDir = "."
	// DefaultTag is the default struct tag key that fig uses to find the field's alt
	// name.
	DefaultTag = "fig"
	// DefaultTimeLayout is the default time layout that fig uses to parse times.
	DefaultTimeLayout = time.RFC3339
)

Variables

View Source
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

func Load(cfg interface{}, options ...Option) error

Load reads a configuration file and loads it into the given struct. The parameter `cfg` must be a pointer to a struct.

By default fig 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 `fig:"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 `fig:"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 *fig)

Option configures how fig loads the configuration.

func Dirs

func Dirs(dirs ...string) Option

Dirs returns an option that configures the directories that fig 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 fig looks in the directory it is run from.

func File

func File(name string) Option

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 fig looks for a file with name `config.yaml`.

func IgnoreFile added in v0.3.1

func IgnoreFile() Option

IgnoreFile returns an option which disables any file lookup.

This option effectively renders any `File` and `Dir` options useless. This option is most useful in conjunction with the `UseEnv` option when you want to provide config values only via environment variables.

fig.Load(&cfg, fig.IgnoreFile(), fig.UseEnv("my_app"))

func Tag

func Tag(tag string) Option

Tag returns an option that configures the tag key that fig uses when for the alt name struct tag key in fields.

fig.Load(&cfg, fig.Tag("config"))

If this option is not used then fig uses the tag `fig`.

func TimeLayout

func TimeLayout(layout string) Option

TimeLayout returns an option that conmfigures the time layout that fig 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 fig parses times using `time.RFC3339` layout.

func UseEnv added in v0.2.0

func UseEnv(prefix string) Option

UseEnv returns an option that configures fig to additionally load values from the environment.

fig.Load(&cfg, fig.UseEnv("my_app"))

Values loaded from the environment overwrite values loaded by the config file (if any).

Fig 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 `fig:"log_level"`
  Server   struct {
    Host string
  }
}

With the struct above and UseEnv("myapp") fig would search for the following environment variables:

MYAPP_BUILD
MYAPP_LOG_LEVEL
MYAPP_SERVER_HOST

func UseStrict added in v0.3.2

func UseStrict() Option

UseStrict returns an option that configures fig to return an error if there exists additional fields in the config file that are not defined in the config struct.

fig.Load(&cfg, fig.UseStrict())

If this option is not used then fig ignores any additional fields in the config file.

type StringUnmarshaler added in v0.4.0

type StringUnmarshaler interface {
	UnmarshalString(s string) error
}

StringUnmarshaler is an interface designed for custom string unmarshaling.

This interface is used when a field of a custom type needs to define its own method for unmarshaling from a string. This is particularly useful for handling different string representations that need to be converted into a specific type.

To use this, the custom type must implement this interface and a corresponding string value should be provided in the configuration. Fig automatically detects this and handles the rest.

Example usage:

type ListenerType uint

const (
	ListenerUnix ListenerType = iota
	ListenerTCP
	ListenerTLS
)

func (l *ListenerType) UnmarshalType(v string) error {
	switch strings.ToLower(v) {
	case "unix":
		*l = ListenerUnix
	case "tcp":
		*l = ListenerTCP
	case "tls":
		*l = ListenerTLS
	default:
		return fmt.Errorf("unknown listener type: %s", v)
	}
	return nil
}

type Config struct {
	Listener ListenerType `fig:"listener_type" default:"tcp"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL