boruto/internal/config/config.go
Maxime Duchene-Savard 66ec1ec60a first commit
2026-06-19 03:23:44 -04:00

128 lines
2.5 KiB
Go

package config
import (
"fmt"
"os"
"strconv"
"time"
)
type Config struct {
AppEnv string
HTTP HTTPConfig
Database DatabaseConfig
}
type HTTPConfig struct {
Addr string
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
}
type DatabaseConfig struct {
URL string
MaxOpenConns int
MaxIdleConns int
ConnMaxLifetime time.Duration
}
func Load() (Config, error) {
cfg := Config{
AppEnv: getString("APP_ENV", "development"),
HTTP: HTTPConfig{
Addr: getString("HTTP_ADDR", ":8080"),
ReadTimeout: getDuration("HTTP_READ_TIMEOUT", 5*time.Second),
WriteTimeout: getDuration("HTTP_WRITE_TIMEOUT", 10*time.Second),
IdleTimeout: getDuration("HTTP_IDLE_TIMEOUT", 60*time.Second),
},
Database: DatabaseConfig{
URL: getString("DATABASE_URL", ""),
MaxOpenConns: getInt("DB_MAX_OPEN_CONNS", 25),
MaxIdleConns: getInt("DB_MAX_IDLE_CONNS", 25),
ConnMaxLifetime: getDuration("DB_CONN_MAX_LIFETIME", 5*time.Minute),
},
}
if err := cfg.Validate(); err != nil {
return Config{}, err
}
return cfg, nil
}
func (c Config) Validate() error {
if c.AppEnv == "" {
return fmt.Errorf("APP_ENV is required")
}
switch c.AppEnv {
case "development", "test", "staging", "production":
default:
return fmt.Errorf("APP_ENV must be one of: development, test, staging, production")
}
if c.HTTP.Addr == "" {
return fmt.Errorf("HTTP_ADDR is required")
}
if c.Database.URL == "" && c.AppEnv != "test" {
return fmt.Errorf("DATABASE_URL is required")
}
if c.Database.MaxOpenConns < 1 {
return fmt.Errorf("DB_MAX_OPEN_CONNS must be at least 1")
}
if c.Database.MaxIdleConns < 1 {
return fmt.Errorf("DB_MAX_IDLE_CONNS must be at least 1")
}
if c.Database.MaxIdleConns > c.Database.MaxOpenConns {
return fmt.Errorf("DB_MAX_IDLE_CONNS cannot exceed DB_MAX_OPEN_CONNS")
}
return nil
}
func getString(key, fallback string) string {
value, ok := os.LookupEnv(key)
if !ok || value == "" {
return fallback
}
return value
}
func getInt(key string, fallback int) int {
value, ok := os.LookupEnv(key)
if !ok || value == "" {
return fallback
}
parsed, err := strconv.Atoi(value)
if err != nil {
return fallback
}
return parsed
}
func getDuration(key string, fallback time.Duration) time.Duration {
value, ok := os.LookupEnv(key)
if !ok || value == "" {
return fallback
}
parsed, err := time.ParseDuration(value)
if err != nil {
return fallback
}
return parsed
}