diff --git a/README.md b/README.md index a737f10..f626d62 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@
Zero-dependency, type-safe and powerful post-processing for your existing config solution.
+Zero-dependency, type-safe and powerful post-processing for your existing config solution in Go.
@@ -14,87 +14,29 @@ -```go -type BaseDevice struct { - Enabled bool - Type string -} - -type LightDevice struct { - BaseDevice - Brightness int -} +# About -// Other device types... - -type SmartHomeConfig struct { - Devices []any -} - -// Use your preferred way of loading the config, e.g., Viper, Koanf, etc. - -// Let Konfetty handle the complexity of recursively setting deeply nested defaults. -cfg, err := konfetty.FromConfig(&SmartHomeConfig{}). - WithDefaults( - // Defaults for all BaseDevice instances. - BaseDevice{Enabled: false, Type: "unknown"}, - // Defaults for all LightDevice instances, atomically overriding defaults for BaseDevice. - LightDevice{BaseDevice: BaseDevice{Type: "light"}, Brightness: 50}, - ). - WithTransformer(func(cfg *SmartHomeConfig) { - // Apply type-safe, custom transformations to the config. - }). - WithValidator(func(cfg *SmartHomeConfig) error { - // Apply type-safe, custom validations to the config. - }). - Build() - -// Result: All devices are disabled by default, with the type set to "unknown". Light devices have a brightness of 50 and a type of "light". -``` +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. -Konfetty is a zero-dependency, type-safe, and powerful post-processing library for your existing configuration solution. It enhances your current setup by providing a seamless way to apply hierarchical defaults, custom transformations, and validations to your configuration. +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 -> Take a moment and think about how you'd ensure that every device in the `SmartHomeConfig` structure has the correct default values. You'd need to write recursive functions, type switches, and careful merging logic. Konfetty handles all of this for you, seamlessly. +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 - -```bash -go get -u github.com/nikoksr/konfetty -``` - -## Key Features - -1. **Intelligent Hierarchical Default System**: - Define defaults once for base types and let konfetty recursively apply them throughout your entire config structure. Easily override these base defaults for more specific types, all while maintaining full type safety and maximum simplicity. - - This feature is the main motivation for me personally behind creating Konfetty. I've worked on several projects where managing defaults for complex configurations was a nightmare. Konfetty solves this problem elegantly and efficiently. Features like the transformation and validation steps were added to round up the library and provide a complete solution for post-processing configurations. - -2. **Type-Safe Configuration**: Utilize Go's generics for fully typed access to your config structs at every stage of processing. Compared to struct-tag based solutions, Konfetty provides a safer, more robust config management experience that catches errors at compile-time rather than runtime. - -3. **Seamless Integration**: Works with your current config loading mechanism (Viper, Koanf, etc.). Simply plug your config in directly or use one of the other supported config-provision methods. Konfetty enhances your existing setup by providing a powerful post-processing step for your configuration. - -4. **Streamline transformation and validation**: Pipeline your configuration through custom transformation and validation functions. Konfetty provides a clean, type-safe way to apply transformations and validations to your configuration after filling the gaps with defaults. - -## Why Konfetty? - -### Powerful Hierarchical Default System - -Konfetty's approach to default values sets it apart: - -- Define defaults for base types once, and have them applied automatically throughout your config structure, even in nested slices of different types. -- Easily override lower-level defaults with more specific ones, giving you fine-grained control over your configuration. -- Maintain full type safety throughout the default application process, unlike solutions using struct tags or reflection-based approaches. - -This system shines when dealing with complex configurations: +## Quick Start ```go -type Device interface { - IsEnabled() bool -} +package main + +import "github.com/nikoksr/konfetty" type BaseDevice struct { Enabled bool - Type string } type LightDevice struct { @@ -104,65 +46,120 @@ type LightDevice struct { type ThermostatDevice struct { BaseDevice - TargetTemp float64 + Temperature float64 } type RoomConfig struct { - Devices []Device + Devices []any } -type HomeConfig struct { - Rooms []RoomConfig +func main() { + // Stubbing a configuration, usually pre-populated by your config provider. + cfg := &RoomConfig{ + Devices: []any{ + &LightDevice{BaseDevice: BaseDevice{Enabled: true}}, // A light device that's enabled by default + &LightDevice{Brightness: 75}, // A light device with a custom brightness + &ThermostatDevice{}, // An empty thermostat device + }, + } + + cfg, err := konfetty.FromConfig(cfg). + WithDefaults( + BaseDevice{Enabled: false}, // Devices are disabled by default, unless overridden, as seen above + LightDevice{Brightness: 50}, // Light devices have a default brightness of 50, unless overridden + ThermostatDevice{ + BaseDevice: BaseDevice{Enabled: true}, // Override the base device default for thermostats + Temperature: 20.0, // Thermostat devices have a default temperature of 20.0, unless overridden + }, + ). + 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: + // + // &RoomConfig{ + // Devices: []any{ + // &LightDevice{BaseDevice: BaseDevice{Enabled: true}, Brightness: 50}, // The first light device stays enabled and was given a brightness of 50 + // &LightDevice{BaseDevice: BaseDevice{Enabled: false}, Brightness: 75}, // The second light device was disabled and given a brightness of 75 + // &ThermostatDevice{BaseDevice: BaseDevice{Enabled: true}, Temperature: 20.0}, // The thermostat device was enabled and given a temperature of 20.0 + // }, + // } + + // Continue using your config struct as usual ... } -konfetty.FromConfig(&HomeConfig{}). - WithDefaults( - BaseDevice{Enabled: true, Type: "unknown"}, - LightDevice{BaseDevice: BaseDevice{Type: "light"}, Brightness: 50}, - ThermostatDevice{TargetTemp: 22.0}, - ) ``` -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 `HomeConfig` structure, including within slices, all while maintaining type safety. +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 type safety. + +## Installation + +```bash +go get -u github.com/nikoksr/konfetty +``` + +## How Konfetty Works -### Seamless Integration with Existing Solutions +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 -Konfetty doesn't replace your current config loading mechanism—it enhances it: +## Integration -- Use Konfetty as a powerful post-processing step after loading your config with Viper, Koanf, or any other solution. -- No need to rewrite your initial config loading logic; Konfetty works with your existing setup. +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 ```go viper.ReadInConfig() viper.Unmarshal(&config) -processedConfig, err := konfetty.FromConfig(&config). +config, err := konfetty.FromConfig(&config). WithDefaults(defaultConfig). WithTransformer(transformer). WithValidator(validator). Build() ``` -### Type-Safe Configuration Handling - -Konfetty leverages Go's type system to provide a safer, more robust config management experience: +### With Koanf -- Fully typed access to your config structs at every stage of processing. -- Catch configuration errors at compile-time rather than runtime. -- No need for type assertions when working with complex, nested structures. +```go +k := koanf.New(".") +k.Load(file.Provider("config.yaml"), yaml.Parser()) +k.Unmarshal("", &config) -## Usage +config, err := konfetty.FromConfig(&config). + WithDefaults(defaultConfig). + Build() +``` -Take a look at our examples to see different ways to use Konfetty: +## Usage Examples - [Simple Example](examples/simple/main.go): A basic example demonstrating how to use Konfetty with a simple configuration structure. - - [Complex Example](examples/complex/main.go): A more complex example showcasing the power of Konfetty's hierarchical default system. +- [Viper Integration](examples/viper/main.go): A full example demonstrating how to integrate Konfetty with Viper. +- [Koanf Integration](examples/koanf/main.go): A full example demonstrating how to integrate Konfetty with Koanf. + +## Contributing + +Contributions are welcome! Please see our [Contributing Guide](CONTRIBUTING.md) for more details. -- [Viper Integration](examples/viper/main.go): An example demonstrating how to integrate Konfetty with Viper. +## Support -- [Koanf Integration](examples/koanf/main.go): An example demonstrating how to integrate Konfetty with Koanf. +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. -## Contributing +While you're here, feel free to check out my other work: -Contributions are welcome. Please see our [Contributing Guide](CONTRIBUTING.md) for more details. +- [nikoksr/notify](https://github.com/nikoksr/notify) - A dead simple Go library for sending notifications to various messaging services.