Stop startup when a setting does not parse (closes #219)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
config.New read the number and true/false settings with viper's GetInt, GetFloat64 and GetBool. These return zero or false for a value that does not parse, so a typo silently changed how the server ran. config.New now parses these settings with strconv. It also checks that MESSAGE_MAX_AGE, QUEUE_MAX_AGE and SESSION_IDLE_TIMEOUT are valid Go durations. A value that does not parse makes config.New return an error naming the setting and its value, and startup stops. Unset settings keep their defaults. Model: opus-5-5
This commit is contained in:
@@ -3,8 +3,11 @@ package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/fx"
|
||||
@@ -63,6 +66,8 @@ type Config struct {
|
||||
}
|
||||
|
||||
// New creates a new Config by reading from files and environment variables.
|
||||
// It returns an error when a number, true/false or duration setting does
|
||||
// not parse.
|
||||
func New(
|
||||
_ fx.Lifecycle, params Params,
|
||||
) (*Config, error) {
|
||||
@@ -107,29 +112,27 @@ func New(
|
||||
|
||||
cfg := &Config{
|
||||
DBURL: viper.GetString("DBURL"),
|
||||
Debug: viper.GetBool("DEBUG"),
|
||||
Port: viper.GetInt("PORT"),
|
||||
SentryDSN: viper.GetString("SENTRY_DSN"),
|
||||
MaintenanceMode: viper.GetBool("MAINTENANCE_MODE"),
|
||||
MetricsUsername: viper.GetString("METRICS_USERNAME"),
|
||||
MetricsPassword: viper.GetString("METRICS_PASSWORD"),
|
||||
MessageMaxAge: viper.GetString("MESSAGE_MAX_AGE"),
|
||||
MaxMessageSize: viper.GetInt("MAX_MESSAGE_SIZE"),
|
||||
QueueMaxAge: viper.GetString("QUEUE_MAX_AGE"),
|
||||
MOTD: viper.GetString("MOTD"),
|
||||
ServerName: viper.GetString("SERVER_NAME"),
|
||||
FederationKey: viper.GetString("FEDERATION_KEY"),
|
||||
SessionIdleTimeout: viper.GetString("SESSION_IDLE_TIMEOUT"),
|
||||
HashcashBits: viper.GetInt("NEOIRC_HASHCASH_BITS"),
|
||||
OperName: viper.GetString("NEOIRC_OPER_NAME"),
|
||||
OperPassword: viper.GetString("NEOIRC_OPER_PASSWORD"),
|
||||
LoginRateLimit: viper.GetFloat64("LOGIN_RATE_LIMIT"),
|
||||
LoginRateBurst: viper.GetInt("LOGIN_RATE_BURST"),
|
||||
IRCListenAddr: viper.GetString("IRC_LISTEN_ADDR"),
|
||||
log: log,
|
||||
params: ¶ms,
|
||||
}
|
||||
|
||||
err = parseSettings(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Viper counts an empty environment variable as unset, but an
|
||||
// empty IRC_LISTEN_ADDR must turn the IRC listener off.
|
||||
ircListenAddr, set := os.LookupEnv("IRC_LISTEN_ADDR")
|
||||
@@ -144,3 +147,68 @@ func New(
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// parseSettings reads the number and true/false settings into cfg and
|
||||
// checks that each duration setting is a valid Go duration. Its error
|
||||
// names each setting whose value does not parse.
|
||||
func parseSettings(cfg *Config) error {
|
||||
return errors.Join(
|
||||
parseInt("PORT", &cfg.Port),
|
||||
parseInt("MAX_MESSAGE_SIZE", &cfg.MaxMessageSize),
|
||||
parseInt("NEOIRC_HASHCASH_BITS", &cfg.HashcashBits),
|
||||
parseInt("LOGIN_RATE_BURST", &cfg.LoginRateBurst),
|
||||
parseFloat("LOGIN_RATE_LIMIT", &cfg.LoginRateLimit),
|
||||
parseBool("DEBUG", &cfg.Debug),
|
||||
parseBool("MAINTENANCE_MODE", &cfg.MaintenanceMode),
|
||||
checkDuration("MESSAGE_MAX_AGE", cfg.MessageMaxAge),
|
||||
checkDuration("QUEUE_MAX_AGE", cfg.QueueMaxAge),
|
||||
checkDuration("SESSION_IDLE_TIMEOUT", cfg.SessionIdleTimeout),
|
||||
)
|
||||
}
|
||||
|
||||
// parseInt sets *value to the setting key read as a whole number.
|
||||
func parseInt(key string, value *int) error {
|
||||
parsed, err := strconv.Atoi(viper.GetString(key))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse %s: %w", key, err)
|
||||
}
|
||||
|
||||
*value = parsed
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseFloat sets *value to the setting key read as a decimal number.
|
||||
func parseFloat(key string, value *float64) error {
|
||||
parsed, err := strconv.ParseFloat(viper.GetString(key), 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse %s: %w", key, err)
|
||||
}
|
||||
|
||||
*value = parsed
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseBool sets *value to the setting key read as true or false.
|
||||
func parseBool(key string, value *bool) error {
|
||||
parsed, err := strconv.ParseBool(viper.GetString(key))
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse %s: %w", key, err)
|
||||
}
|
||||
|
||||
*value = parsed
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkDuration returns an error naming the setting key when value is
|
||||
// not a valid Go duration.
|
||||
func checkDuration(key, value string) error {
|
||||
_, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse %s: %w", key, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user