bconf
is a configuration framework that makes it easy to define, load, and validate application configuration values.
go get github.com/rheisen/bconf
bconf
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) error
GetField(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.Field
DefaultGenerator
parameter - Ability to define custom configuration value validation with the
bconf.Field
Validator
parameter - Ability to conditionally load a
bconf.FieldSet
by definingbconf.LoadConditions
- Ability to conditionally load a
bconf.Field
by definingbconf.LoadConditions
- Ability to get a safe map of configuration values from the
bconf.AppConfig
ConfigMap()
function- (the configuration map will obfuscate values from fields with
Sensitive
parameter 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
/--help
options - Additional configuration loaders