go-template/internal/config/config.go
2026-03-05 21:52:10 +01:00

62 lines
1.6 KiB
Go

// Package config loads application configuration from a YAML file + env vars.
// Priority (highest → lowest): env vars → config file → defaults.
//
// Usage:
//
// cfg, err := config.Load("config/dev.yaml")
// fmt.Println(cfg.App.Port)
package config
import (
"fmt"
"github.com/spf13/viper"
)
// Config is the root configuration object. Add sub-structs as the app grows.
type Config struct {
App AppConfig `mapstructure:"app"`
Logger LoggerConfig `mapstructure:"logger"`
}
// AppConfig holds generic application settings.
type AppConfig struct {
Name string `mapstructure:"name"`
Port int `mapstructure:"port"`
Env string `mapstructure:"env"` // dev | staging | prod
}
// LoggerConfig controls logging behavior.
type LoggerConfig struct {
Level string `mapstructure:"level"` // debug | info | warn | error
}
// Load reads the YAML file at path and overlays any matching environment
// variables (e.g. APP_PORT overrides app.port).
func Load(path string) (*Config, error) {
v := viper.New()
v.SetConfigFile(path)
// Allow every config key to be overridden by an env var.
// e.g. APP_PORT=9090 overrides app.port
v.AutomaticEnv()
// Sensible defaults so the binary works without a config file.
v.SetDefault("app.name", "go-template")
v.SetDefault("app.port", 8080)
v.SetDefault("app.env", "dev")
v.SetDefault("logger.level", "info")
if err := v.ReadInConfig(); err != nil {
return nil, fmt.Errorf("config: reading %q: %w", path, err)
}
var cfg Config
if err := v.Unmarshal(&cfg); err != nil {
return nil, fmt.Errorf("config: unmarshalling: %w", err)
}
return &cfg, nil
}