bconf is a configuration framework that makes it easy to define, load, and validate application configuration values.
go get github.com/rheisen/bconfbconf provides tooling to write your configuration package by package. With bconf, configuration lives right
alongside the code that needs it. This also makes it so that configuration is more easily re-used and composible by
multiple applications (just like your packages should be).
bconf accomplishes this with bconf.FieldSets, which provide a namespace and logical grouping for related
configuration. Independent packages define their bconf.FieldSets, and then application executables can attach them
to a bconf.AppConfig, which provides a unified structure for loading and retrieving configuration values.
Within bconf.FieldSets, you define bconf.Fields, with each field defining the expected format and behavior of a
configuration value.
Check out the documentation and introductory examples below, and see if bconf is right for your project!
- Environment (
bconf.EnvironmentLoader) - Flags (
bconf.FlagLoader) - JSON files (
bconf.JSONFileLoader) - Overrides (setter functions)
In Progress
- YAML files (
bconf.YAMLFileLoader) - TOML files (
bconf.TOMLFileLoader)
FillStruct(configStruct any) errorGetField(fieldSetKey, fieldKey string) (*bconf.Field, error)GetString(fieldSetKey, fieldKey string) (string, error)GetStrings(fieldSetKey, fieldKey string) ([]string, error)GetInt(fieldSetKey, fieldKey string) (int, error)GetInts(fieldSetKey, fieldKey string) ([]int, error)GetBool(fieldSetKey, fieldKey string) (bool, error)GetBools(fieldSetKey, fieldKey string) ([]bool, error)GetTime(fieldSetKey, fieldKey string) (time.Time, error)GetTimes(fieldSetKey, fieldKey string) ([]time.Time, error)GetDuration(fieldSetKey, fieldKey string) (time.Duration, error)GetDurations(fieldSetKey, fieldKey string) ([]time.Duration, error)
- Ability to generate default configuration values with the
bconf.FieldDefaultGeneratorparameter - Ability to define custom configuration value validation with the
bconf.FieldValidatorparameter - Ability to conditionally load a
bconf.FieldSetby definingbconf.LoadConditions - Ability to conditionally load a
bconf.Fieldby definingbconf.LoadConditions - Ability to get a safe map of configuration values from the
bconf.AppConfigConfigMap()function- (the configuration map will obfuscate values from fields with
Sensitiveparameter set totrue)
- (the configuration map will obfuscate values from fields with
- Ability to reload field-sets and individual fields via the
bconf.AppConfig - Ability to fill configuration structures with values from a
bconf.AppConfig
- No support for watching / automatically updating configuration values
Below is an example of a bconf.AppConfig defined first with builders, and then with structs. Below these code blocks
the behavior of the example is discussed.
configuration := bconf.NewAppConfig(
"external_http_api",
"HTTP API for user authentication and authorization",
)
_ = configuration.SetLoaders(
&bconf.EnvironmentLoader{KeyPrefix: "ext_http_api"},
&bconf.FlagLoader{},
)
_ = configuration.AddFieldSets(
bconf.NewFieldSetBuilder().Key("app").Fields(
bconf.NewFieldBuilder().
Key("id").Type(bconf.String).
Description("Application identifier").
DefaultGenerator(
func() (any, error) {
return fmt.Sprintf("%s", uuid.NewV4().String()), nil
},
).Create(),
bconf.FB(). // FB() is a shorthand function for NewFieldBuilder()
Key("session_secret").Type(bconf.String).
Description("Application secret for session management").
Sensitive().Required().
Validator(
func(fieldValue any) error {
secret, _ := fieldValue.(string)
minLength := 20
if len(secret) < minLength {
return fmt.Errorf(
"expected string of minimum %d characters (len=%d)",
minLength,
len(secret),
)
}
return nil
},
).Create(),
).Create(),
bconf.FSB().Key("log").Fields( // FSB() is a shorthand function for NewFieldSetBuilder()
bconf.FB().
Key("level").Type(bconf.String).Default("info").
Description("Logging level").
Enumeration("debug", "info", "warn", "error").Create(),
bconf.FB().
Key("format").Type(bconf.String).Default("json").
Description("Logging format").
Enumeration("console", "json").Create(),
bconf.FB().
Key("color_enabled").Type(bconf.Bool).Default(true).
Description("Colored logs when format is 'console'").
Create(),
).Create(),
)
// Register with the option to handle --help / -h flag set to true
if errs := configuration.Register(true); len(errs) > 0 {
// handle configuration load errors
}
// returns the log level found in order of: default -> environment -> flag -> user override
// (based on the loaders set above).
logLevel, err := configuration.GetString("log", "level")
if err != nil {
// handle error
}
fmt.Printf("log-level: %s\n", logLevel)
type loggerConfig struct {
bconf.ConfigStruct `bconf:"log"`
Level string `bconf:"level"`
Format string `bconf:"format"`
ColorEnabled bool `bconf:"color_enabled"`
}
logConfig := &loggerConfig{}
if err := configuration.FillStruct(logConfig); err != nil {
// handle error
}
fmt.Printf("log config: %v\n", logConfig)configuration := bconf.NewAppConfig(
"external_http_api",
"HTTP API for user authentication and authorization",
)
_ = configuration.SetLoaders(
&bconf.EnvironmentLoader{KeyPrefix: "ext_http_api"},
&bconf.FlagLoader{},
)
_ = configuration.AddFieldSets(
&bconf.FieldSet{
Key: "app",
Fields: bconf.Fields{
{
Key: "id",
Type: bconf.String,
Description: "Application identifier",
DefaultGenerator: func() (any, error) {
return uuid.NewV4().String(), nil
},
},
{
Key: "session_secret",
Type: bconf.String,
Description: "Application secret for session management",
Sensitive: true,
Required: true,
Validator: func(fieldValue any) error {
secret, _ := fieldValue.(string)
minLength := 20
if len(secret) < minLength {
return fmt.Errorf(
"expected string of minimum %d characters (len=%d)",
minLength,
len(secret),
)
}
return nil
},
},
},
},
&bconf.FieldSet{
Key: "log",
Fields: bconf.Fields{
{
Key: "level",
Type: bconf.String,
Description: "Logging level",
Default: "info",
Enumeration: []any{"debug", "info", "warn", "error"},
},
{
Key: "format",
Type: bconf.String,
Description: "Logging format",
Default: "json",
Enumeration: []any{"console", "json"},
},
{
Key: "color_enabled",
Type: bconf.Bool,
Description: "Colored logs when format is 'console'",
Default: true,
},
},
},
)
// Register with the option to handle --help / -h flag set to true
if errs := configuration.Register(true); len(errs) > 0 {
// handle configuration load errors here
}
// returns the log level found in order of: default -> environment -> flag -> user override
// (based on the loaders set above).
logLevel, err := configuration.GetString("log", "level")
if err != nil {
// handle error
}
fmt.Printf("log-level: %s\n", logLevel)
type loggerConfig struct {
bconf.ConfigStruct `bconf:"log"`
Level string `bconf:"level"`
Format string `bconf:"format"`
ColorEnabled bool `bconf:"color_enabled"`
}
logConfig := &loggerConfig{}
if err := configuration.FillStruct(logConfig); err != nil {
// handle error
}
fmt.Printf("log config: %v\n", logConfig)In both of the code blocks above, a bconf.AppConfig is defined with two field-sets (which group configuration related
to the application and logging in this case), and registered with help flag parsing.
If this code was executed in a main() function, it would print the log level picked up by the configuration from the
flags or run-time environment before falling back on the defined default value of "info". It would then fill the
logConfig struct with multiple values from the log field-set fields, and print those values as well.
If this code was executed inside the main() function and passed a --help or -h flag, it would print the following
output:
Usage of 'external_http_api':
HTTP API for user authentication and authorization
Required Configuration:
app_session_secret string
Application secret for session management
Environment key: 'EXT_HTTP_API_APP_SESSION_SECRET'
Flag argument: '--app_session_secret'
Optional Configuration:
app_id string
Application identifier
Default value: <generated-at-run-time>
Environment key: 'EXT_HTTP_API_APP_ID'
Flag argument: '--app_id'
log_color_enabled bool
Colored logs when format is 'console'
Default value: 'true'
Environment key: 'EXT_HTTP_API_LOG_COLOR_ENABLED'
Flag argument: '--log_color_enabled'
log_format string
Logging format
Accepted values: ['console', 'json']
Default value: 'json'
Environment key: 'EXT_HTTP_API_LOG_FORMAT'
Flag argument: '--log_format'
log_level string
Logging level
Accepted values: ['debug', 'info', 'warn', 'error']
Default value: 'info'
Environment key: 'EXT_HTTP_API_LOG_LEVEL'
Flag argument: '--log_level'
This is a simple example where all the configuration code is in one place, but it doesn't need to be!
To view more examples, including a real-world example showcasing how configuration can live alongside package code, please visit github.com/rheisen/bconf-examples.
- Additional
-h/--helpoptions - Additional configuration loaders