11package main
22
33import (
4- "log"
4+ "errors"
5+ "fmt"
56 "os"
7+ "slices"
68 "time"
79)
810
@@ -18,44 +20,72 @@ type Config struct {
1820 LogLevel string
1921}
2022
21- func LoadConfig () Config {
22- return Config {
23+ var validLogLevels = []string {"debug" , "info" , "warn" , "error" }
24+
25+ // LoadConfig reads configuration from the environment.
26+ // It collects every problem instead of stopping at the first one,
27+ // so a misconfigured deployment reports all mistakes at once.
28+ func LoadConfig () (Config , error ) {
29+ var errs []error
30+
31+ requireEnv := func (k string ) string {
32+ v := os .Getenv (k )
33+ if v == "" {
34+ errs = append (errs , fmt .Errorf ("missing required env: %s" , k ))
35+ }
36+ return v
37+ }
38+
39+ envDuration := func (k string , def time.Duration ) time.Duration {
40+ v := os .Getenv (k )
41+ if v == "" {
42+ return def
43+ }
44+ d , err := time .ParseDuration (v )
45+ if err != nil {
46+ errs = append (errs , fmt .Errorf ("invalid duration for %s: %q" , k , v ))
47+ return def
48+ }
49+ return d
50+ }
51+
52+ cfg := Config {
2353 ListenAddr : env ("LISTEN_ADDR" , ":8080" ),
2454 SQLitePath : env ("SQLITE_PATH" , "/data/cacheppuccino.db" ),
25- TranslationBaseURL : mustEnv ("TRANSLATION_BASE_URL" ),
26- TranslationApplicationID : mustEnv ("TRANSLATION_APPLICATION_ID" ),
27- TranslationAPIKey : mustEnv ("TRANSLATION_API_KEY" ),
55+ TranslationBaseURL : requireEnv ("TRANSLATION_BASE_URL" ),
56+ TranslationApplicationID : requireEnv ("TRANSLATION_APPLICATION_ID" ),
57+ TranslationAPIKey : requireEnv ("TRANSLATION_API_KEY" ),
2858 HTTPTimeout : envDuration ("HTTP_TIMEOUT" , 30 * time .Second ),
2959 PullInterval : envDuration ("PULL_INTERVAL" , 10 * time .Minute ),
30- InitialPullDeadline : envDuration ("INITIAL_PULL_DEADLINE" , 60 * time .Second ),
60+ InitialPullDeadline : envDuration ("INITIAL_PULL_DEADLINE" , 45 * time .Second ),
3161 LogLevel : env ("LOG_LEVEL" , "info" ),
3262 }
33- }
3463
35- func env (k , def string ) string {
36- v := os .Getenv (k )
37- if v == "" {
38- return def
64+ if cfg .HTTPTimeout <= 0 {
65+ errs = append (errs , fmt .Errorf ("HTTP_TIMEOUT must be positive, got %s" , cfg .HTTPTimeout ))
66+ }
67+ if cfg .PullInterval <= 0 {
68+ errs = append (errs , fmt .Errorf ("PULL_INTERVAL must be positive, got %s" , cfg .PullInterval ))
69+ }
70+ // Zero means "no deadline" for the initial pull; only negatives are invalid.
71+ if cfg .InitialPullDeadline < 0 {
72+ errs = append (errs , fmt .Errorf ("INITIAL_PULL_DEADLINE must not be negative, got %s" , cfg .InitialPullDeadline ))
3973 }
40- return v
41- }
4274
43- func mustEnv (k string ) string {
44- v := os .Getenv (k )
45- if v == "" {
46- log .Fatalf ("missing required env: %s" , k )
75+ if ! slices .Contains (validLogLevels , cfg .LogLevel ) {
76+ errs = append (errs , fmt .Errorf ("invalid LOG_LEVEL: %q (valid: debug, info, warn, error)" , cfg .LogLevel ))
4777 }
48- return v
78+
79+ if len (errs ) > 0 {
80+ return Config {}, errors .Join (errs ... )
81+ }
82+ return cfg , nil
4983}
5084
51- func envDuration ( k string , def time. Duration ) time. Duration {
85+ func env ( k , def string ) string {
5286 v := os .Getenv (k )
5387 if v == "" {
5488 return def
5589 }
56- d , err := time .ParseDuration (v )
57- if err != nil {
58- return def
59- }
60- return d
90+ return v
6191}
0 commit comments