Skip to content

Zero-dependency, type-safe and powerful post-processing for your existing config solution in Go.

License

Notifications You must be signed in to change notification settings

nikoksr/konfetty

Repository files navigation

 

konfetty 🎉

Zero-dependency, type-safe and powerful post-processing for your existing config solution in Go.

 

go.dev reference codecov Go Report Card Maintainability

 

About

Konfetty is a Go library that simplifies the management of hierarchical default values in complex struct hierarchies, primarily designed for, but not limited to, configuration management. It addresses the challenge of applying defaults to nested structs, interfaces, and embedded types while maintaining type safety.

Key features:

  • 🔍 Recursively applies defaults through nested structures
  • 🏗️ Respects type hierarchies, allowing base type defaults to be overridden by more specific types
  • 🛡️ Maintains full type safety without relying on struct tags or runtime type assertions
  • 🔌 Integrates with existing configuration loading solutions as a post-processing step
  • 🧩 Applicable to any struct-based hierarchies, not just configurations (e.g., middleware chains, complex domain models)
  • 🔧 Supports custom transformations and validations as part of the processing pipeline

Konfetty aims to reduce the boilerplate typically associated with setting default values in Go struct hierarchies, allowing developers to focus on their core application logic rather than complex default value management.

Installation

go get -u github.com/nikoksr/konfetty

Quick Start

package main

import "github.com/nikoksr/konfetty"

type BaseDevice struct {
    Enabled bool
}

type LightDevice struct {
    BaseDevice
    Brightness int
}

type ThermostatDevice struct {
    BaseDevice
    Temperature float64
}

type RoomConfig struct {
    Devices []any
}

func main() {
	// Stubbing a configuration, usually pre-populated by your config provider, e.g., Viper or Koanf.
    cfg := &RoomConfig{
        Devices: []any{
            // A light device that's enabled by default
            &LightDevice{BaseDevice: BaseDevice{Enabled: true}},

            // A light device with a custom brightness
            &LightDevice{Brightness: 75},

            // An empty thermostat device
            &ThermostatDevice{},
        },
    }

    cfg, err := konfetty.FromStruct(cfg).
        WithDefaults(
        	// Devices are disabled by default
            BaseDevice{Enabled: false},

            // Light devices have a default brightness of 50
            LightDevice{Brightness: 50},

            // Thermostat devices have a default temperature of 20.0 and are enabled by default
            ThermostatDevice{
                // Override the base device default for thermostats
                BaseDevice: BaseDevice{Enabled: true},
			    Temperature: 20.0,
            },
        ).
        WithTransformer(func(cfg *RoomConfig) {
        	// Optional custom transformation logic for more complex processing
        }).
        WithValidator(func(cfg *RoomConfig) error {
            // Optional custom validation logic
            return nil
        }).
        Build()

    // Handle error ...

    // The processed config would look like this:
    //
    // {
    //   "Devices": [
    //     {
    //       // LightDevice
    //       "Enabled": true,     // Kept original value
    //       "Brightness": 50     // Used LightDevice default
    //     },
    //     {
    //       // LightDevice
    //       "Enabled": false,    // Used BaseDevice default
    //       "Brightness": 75     // Kept original value
    //     },
    //     {
    //       // ThermostatDevice
    //       "Enabled": true,     // Used ThermostatDevice default, overriding BaseDevice default
    //       "Temperature": 20.0  // Used ThermostatDevice default
    //     }
    //   ]
    // }

    // Continue using your config struct as usual ...
}

In this example, Konfetty automatically applies the BaseDevice defaults to all devices, then overlays the specific defaults for LightDevice and ThermostatDevice. This happens recursively through the entire RoomConfig structure all while maintaining compile-time type safety.

How Konfetty Works

Konfetty's approach to default values sets it apart:

  • Define defaults for base types once, and have them applied automatically throughout your struct hierarchy, even in nested slices of different types.
  • Easily override lower-level defaults with more specific ones, giving you fine-grained control.
  • Maintain full type safety throughout the default application process, unlike solutions using struct tags or reflection-based approaches.

The processing pipeline: Recursively apply defaults > apply (optional) transformations > run (optional) validations

Integration

Konfetty doesn't replace your current config loading mechanism — it enhances it. Use it as a powerful post-processing step after loading your config with Viper, Koanf, or any other solution.

With Viper

viper.ReadInConfig()
viper.Unmarshal(&config)

config, err := konfetty.FromStruct(&config).
    WithDefaults(defaultConfig).
    WithTransformer(transformer).
    WithValidator(validator).
    Build()

With Koanf

k := koanf.New(".")
k.Load(file.Provider("config.yaml"), yaml.Parser())
k.Unmarshal("", &config)

config, err := konfetty.FromStruct(&config).
    WithDefaults(defaultConfig).
    WithTransformer(transformer).
    WithValidator(validator).
    Build()

Usage Examples

  • Simple Example: A basic example demonstrating how to use Konfetty with a simple configuration structure.
  • Complex Example: A more complex example showcasing the power of Konfetty's hierarchical default system.
  • Viper Integration: A full example demonstrating how to integrate Konfetty with Viper.
  • Koanf Integration: A full example demonstrating how to integrate Konfetty with Koanf.

Contributing

Contributions are welcome! Please see our Contributing Guide for more details.

Support

If you find this project useful, consider giving it a ⭐️! Your support helps bring more attention to the project, allowing us to enhance it even further.

While you're here, feel free to check out my other work:

  • nikoksr/notify - A dead simple Go library for sending notifications to various messaging services.