package config import ( "fmt" "os" "slices" "strconv" "strings" "time" ) const ( DatabaseDriverSQLite = "sqlite" DatabaseDriverMySQL = "mysql" DefaultDatabaseDriver = DatabaseDriverSQLite DefaultDatabasePort = "3306" DefaultDatabaseTLS = "false" DefaultMaxOpenConns = "10" DefaultMaxIdleConns = "5" DefaultConnMaxLifetimeSeconds = "300" EnvDatabaseDriver = "DB_DRIVER" envDatabaseHost = "DB_HOST" envDatabasePort = "DB_PORT" envDatabaseUser = "DB_USER" envDatabasePassword = "DB_PASSWORD" envDatabaseName = "DB_NAME" envDatabaseTLS = "DB_TLS" envDatabaseMaxOpenConns = "DB_MAX_OPEN_CONNS" envDatabaseMaxIdleConns = "DB_MAX_IDLE_CONNS" envDatabaseConnMaxLifetimeSecs = "DB_CONN_MAX_LIFETIME_SECONDS" ) var mysqlRequiredEnvVars = []string{envDatabaseHost, envDatabaseUser, envDatabasePassword, envDatabaseName} var DatabaseTLSModes = []string{"false", "true", "skip-verify", "preferred"} type DatabaseSettings struct { Driver string Host string Port string User string Password string Name string TLS string MaxOpenConns int MaxIdleConns int ConnMaxLifetime time.Duration } func DatabaseRequiredEnvVars(driver string) []string { if driver == DatabaseDriverMySQL { return slices.Clone(mysqlRequiredEnvVars) } return nil } func MissingEnvVarError(names []string, lookup func(string) (string, bool)) error { for _, name := range names { if _, isSet := lookup(name); !isSet { return fmt.Errorf("Environment variable %s is not set", name) } } return nil } func databaseDriverFromEnv() string { return envOrDefault(EnvDatabaseDriver, DefaultDatabaseDriver) } func databaseSettingsFromEnv() (DatabaseSettings, error) { driver := databaseDriverFromEnv() if driver != DatabaseDriverMySQL { return DatabaseSettings{Driver: driver}, nil } tls := envOrDefault(envDatabaseTLS, DefaultDatabaseTLS) if !slices.Contains(DatabaseTLSModes, tls) { return DatabaseSettings{}, fmt.Errorf("%s must be one of %s, got %q", envDatabaseTLS, strings.Join(DatabaseTLSModes, ", "), tls) } maxOpen, err := nonNegativeIntEnv(envDatabaseMaxOpenConns, DefaultMaxOpenConns) if err != nil { return DatabaseSettings{}, err } maxIdle, err := nonNegativeIntEnv(envDatabaseMaxIdleConns, DefaultMaxIdleConns) if err != nil { return DatabaseSettings{}, err } lifetimeSeconds, err := nonNegativeIntEnv(envDatabaseConnMaxLifetimeSecs, DefaultConnMaxLifetimeSeconds) if err != nil { return DatabaseSettings{}, err } return DatabaseSettings{ Driver: driver, Host: os.Getenv(envDatabaseHost), Port: envOrDefault(envDatabasePort, DefaultDatabasePort), User: os.Getenv(envDatabaseUser), Password: os.Getenv(envDatabasePassword), Name: os.Getenv(envDatabaseName), TLS: tls, MaxOpenConns: maxOpen, MaxIdleConns: maxIdle, ConnMaxLifetime: time.Duration(lifetimeSeconds) * time.Second, }, nil } func envOrDefault(name string, fallback string) string { if value := os.Getenv(name); value != "" { return value } return fallback } func nonNegativeIntEnv(name string, fallback string) (int, error) { raw := envOrDefault(name, fallback) value, err := strconv.Atoi(raw) if err != nil || value < 0 { return 0, fmt.Errorf("%s must be a non-negative whole number, got %q", name, raw) } return value, nil }