Skip to content

Gonfig is a flexible library for simplifying configuration management. It supports loading settings from environment variables, flags, and various file formats. The library is extensible, allowing new formats to be added, and key components can be replaced, such as using `spf13/pflag` instead of the standard `flag` package.

License

Notifications You must be signed in to change notification settings

im-kulikov/gonfig

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gonfig

GitHub Workflow Status Go Report Card Go version PkgGoDev

What is gonfig?

gonfig is a flexible and extensible configuration library designed to simplify working with application settings. It supports loading configurations from environment variables, command-line flags, and various configuration file formats. Additionally, it offers an easy way to extend support for new formats. One of its key features is the ability to replace or customize components, such as using spf13/pflag instead of the standard flag package from the Go standard library.

This library simplifies configuration management, making it easy to define, override, and merge settings in your applications.

Why Use Gonfig?

  • Multiple Sources – Load configurations from config-files, flags, environment variables, or custom sources.
  • Easy to Use – Simple API for defining and managing configurations.
  • Extensible – You can implement custom loaders to fit your needs.
  • Lightweight – No unnecessary dependencies, optimized for performance.

General Priority Hierarchy:

The priority described below is considered the default priority and can be modified through configuration settings.

  1. Defaults — These are basic configuration values embedded in the application's code. They ensure the application can run even if no external configurations are provided.

  2. Environment Variables — Environment variables are usually used to configure deployment-related parameters (e.g., logins, ports, database addresses). These variables often have a higher priority as they can be dynamically set depending on the environment.

  3. Flags — Command-line flags usually have the highest priority since they allow direct overriding of any settings at application startup. This is useful when a quick configuration change is needed without modifying the code or config files.

  4. Config File — A configuration file stored on disk, typically containing predefined parameters for a specific environment. This can be in formats like JSON, YAML, TOML, etc.

  5. Remote Config — This is a configuration retrieved from external sources, such as configuration servers or cloud services (e.g., Consul, Etcd, or AWS SSM). These systems usually allow centralized management of settings across different applications.

Installation

go get github.com/im-kulikov/gonfig

Note: gonfig uses Go Modules to manage dependencies.

Explain Hierarchy:

  1. Defaults — Set in the code. For example, the default server port is 8080.
  2. Environment Variables — Environment variables can be used to set database connections or other services linked to the environment.
  3. Flags — Command-line arguments always have the highest priority, as they can be specified at application startup to override any other parameter.
  4. Config File — The configuration file specifies more detailed parameters, such as database connections or the application's operating mode.
  5. Remote Config — Configuration retrieved from a remote server can override settings from the config file.

Current status

  • Load defaults
  • Load environments
  • Load flags
  • Mark as required
  • Load YAML WithYAMLLoader
  • Load JSON WithJSONLoader
  • Load TOML WithTOMLLoader
  • Other formats, you can write it using custom loader

Examples

Simple

package main

import (
    "fmt"

	"github.com/im-kulikov/gonfig"
)

type Config struct {
	Config string `flag:"config,short:c,config:true"`
	Field  string `flag:"field" env:"FIELD" default:"default-value" usage:"description for flags" require:"true"`
}

// go run /path/to/main/folder --config /path/to/config.yml
func main() {
	var cfg Config
	if err := gonfig.Load(&cfg,
		gonfig.WithYAMLLoader(),
		gonfig.WithDefaults(gonfig.FlagTag, map[string]any{
            "field": "some-custom-default-value",
        })); err != nil {
		panic(err)
	}

	fmt.Printf("Loaded config: %+v\n", cfg)
}

Extended (using New.Load)

package main

import (
    "fmt"

	"github.com/im-kulikov/gonfig"
)

type Config struct {
	Field string `flag:"field" env:"FIELD" default:"default-value" usage:"description for flags" require:"true"`
}

func main() {
	var cfg Config
	if err := gonfig.New(gonfig.Config{}).Load(&cfg); err != nil {
		panic(err)
	}

	fmt.Printf("Loaded config: %+v\n", cfg)
}

Use YAML loader

package main

import (
	"fmt"
	
	"github.com/im-kulikov/gonfig"
)

type Config struct {
	Config  string `flag:"config,short:c,config:true"`
	Port    int    `yaml:"port"`
}

// go run /path/to/main/folder --config /path/to/config.yml
func main() {
	var cfg Config
	if err := gonfig.Load(&cfg, gonfig.WithYAMLLoader()); err != nil {
		panic(err)
	}

	fmt.Printf("Loaded config: %+v\n", cfg)
}

Use TOML loader

package main

import (
	"fmt"
	
	"github.com/im-kulikov/gonfig"
)

type Config struct {
	Config  string `flag:"config,short:c,config:true"`
	Port    int    `toml:"port"`
}

// go run /path/to/main/folder --config /path/to/config.toml
func main() {
	var cfg Config
	if err := gonfig.Load(&cfg, gonfig.WithTOMLLoader()); err != nil {
		panic(err)
	}

	fmt.Printf("Loaded config: %+v\n", cfg)
}

Use JSON loader

package main

import (
	"fmt"
	
	"github.com/im-kulikov/gonfig"
)

type Config struct {
	Config  string `flag:"config,short:c,config:true"`
	Port    int    `json:"port"`
}

// go run /path/to/main/folder --config /path/to/config.json
func main() {
	var cfg Config
	if err := gonfig.Load(&cfg, gonfig.WithJSONLoader()); err != nil {
		panic(err)
	}

	fmt.Printf("Loaded config: %+v\n", cfg)
}

Custom Loaders

You can implement your own configuration loaders by implementing the Parser interface.

type CustomLoader struct {}

func (c *CustomLoader) Load(dest interface{}) error {
	// Custom loading logic here
	return nil
}

func (c *CustomLoader) Type() gonfig.ParserType {
	return "custom-loader"
}

or with config-path defining

type CustomLoader struct {
	path string
}

func (c *CustomLoader) SetPath(path string) { c.path = path }

func (c *CustomLoader) Load(dest interface{}) error {
	// Custom loading logic here
	// for example - json / toml / etc
	return nil
}

func (c *CustomLoader) Type() gonfig.ParserType {
	return "custom-loader"
}

Custom validation

Introduced in Issue #3

A new Go interface, LoaderValidator. Any struct that implements this interface will have its Validate() method called automatically by the loading mechanism.

  1. Define the interface:

    type LoaderValidator interface {
        Validate() error
    }
  2. Integrated into the load process: We modified the existing configuration loading function(s) to include a check for this interface. The execution flow should be:

    • Load the configuration (e.g., from YAML/JSON).
    • Run basic validation (e.g., the existing ValidateRequiredFields).
    • Check if the loaded config struct implements LoaderValidator.
    • If it does, call the config.Validate() method and return any error it produces.

A user can now easily add sophisticated validation to their config:

// User's configuration struct
type AppConfig struct {
    Port     int    `yaml:"port" required:"true"`
    Username string `yaml:"username" required:"true"`
    Email    string `yaml:"email"`
}

// Implement the LoaderValidator interface
func (c *AppConfig) Validate() error {
    // Custom validation 1: Check if port is in the valid range
    if c.Port < 1024 || c.Port > 65535 {
        return fmt.Errorf("port must be between 1024 and 65535, got %d", c.Port)
    }

    // Custom validation 2: Check email format if provided
    if c.Email != "" {
        if !isValidEmail(c.Email) {
            return fmt.Errorf("invalid email format: %s", c.Email)
        }
    }
    return nil
}

// Helper function (user-defined)
func isValidEmail(email string) bool {
    // ... simple regex check for example purposes
    re := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
    return re.MatchString(email)
}

With this implementation, when the user loads their AppConfig, the system will automatically check that Port and Username are provided (basic validation) and then run the custom checks to ensure the port number is acceptable and the email format is valid.

  • Backward Compatible: This is a purely additive change. Existing code without the Validate() method will continue to work unchanged.
  • Clean and Idiomatic: It follows Go's common pattern of using interfaces for extensibility (e.g., Stringer, Error).
  • Powerful and Flexible: Users are no longer limited to just required checks and can implement any validation logic their application requires (cross-field validation, business logic, formatting, etc.).
  • Centralized Validation: The validation logic lives alongside the data structure it validates, making the code more organized and maintainable.

About

Gonfig is a flexible library for simplifying configuration management. It supports loading settings from environment variables, flags, and various file formats. The library is extensible, allowing new formats to be added, and key components can be replaced, such as using `spf13/pflag` instead of the standard `flag` package.

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •  

Languages