Replace environment variable config with CLI flag configuration

- Migrate from environment variables to CLI flags for configuration
- Add support for --listen, --root-path, --dir-indexing, --log-level, --response-timeout flags
- Remove config validation error struct as it's no longer needed
- Update .gitignore to exclude /dist directory
- Simplify configuration loading with flag.Parse()
This commit is contained in:
2025-05-26 13:27:44 +03:00
parent 05ff646284
commit 4456308d48
3 changed files with 48 additions and 118 deletions

View File

@@ -1,126 +1,69 @@
package config
import (
"flag"
"fmt"
"log/slog"
"os"
"strconv"
"github.com/rs/zerolog"
"strings"
)
// Environment variable names.
const (
EnvLogLevel = "LOG_LEVEL"
EnvResponseTimeout = "RESPONSE_TIMEOUT"
EnvPanicOnUnexpectedError = "PANIC_ON_UNEXPECTED_ERROR"
EnvRootPath = "ROOT_PATH"
EnvDirIndexingEnabled = "DIR_INDEXING_ENABLED"
)
// Config holds the application configuration loaded from environment variables.
// Config holds the application configuration loaded from CLI flags.
type Config struct {
LogLevel zerolog.Level // Logging level (debug, info, warn, error)
ResponseTimeout int // Timeout for responses in seconds
PanicOnUnexpectedError bool // Panic on unexpected errors when visiting a URL
RootPath string // Path to serve files from
DirIndexingEnabled bool // Allow client to browse directories or not
LogLevel slog.Level // Logging level (debug, info, warn, error)
ResponseTimeout int // Timeout for responses in seconds
RootPath string // Path to serve files from
DirIndexingEnabled bool // Allow client to browse directories or not
Listen string // Address to listen on
}
var CONFIG Config //nolint:gochecknoglobals
// parsePositiveInt parses and validates positive integer values.
func parsePositiveInt(param, value string) (int, error) {
val, err := strconv.Atoi(value)
if err != nil {
return 0, ValidationError{
Param: param,
Value: value,
Reason: "must be a valid integer",
}
// parseLogLevel parses a log level string into slog.Level
func parseLogLevel(level string) (slog.Level, error) {
switch strings.ToLower(level) {
case "debug":
return slog.LevelDebug, nil
case "info":
return slog.LevelInfo, nil
case "warn", "warning":
return slog.LevelWarn, nil
case "error":
return slog.LevelError, nil
default:
return slog.LevelInfo, fmt.Errorf("invalid log level: %s", level)
}
if val <= 0 {
return 0, ValidationError{
Param: param,
Value: value,
Reason: "must be positive",
}
}
return val, nil
}
func parseBool(param, value string) (bool, error) {
val, err := strconv.ParseBool(value)
if err != nil {
return false, ValidationError{
Param: param,
Value: value,
Reason: "cannot be converted to boolean",
}
}
return val, nil
}
// GetConfig loads and validates configuration from environment variables
// GetConfig loads and validates configuration from CLI flags
func GetConfig() *Config {
config := &Config{}
// Define CLI flags with defaults
logLevel := flag.String("log-level", "info", "Logging level (debug, info, warn, error)")
responseTimeout := flag.Int("response-timeout", 30, "Timeout for responses in seconds")
rootPath := flag.String("root-path", "", "Path to serve files from")
dirIndexing := flag.Bool("dir-indexing", false, "Allow client to browse directories")
listen := flag.String("listen", "localhost:1965", "Address to listen on")
// Map of environment variables to their parsing functions
parsers := map[string]func(string) error{
EnvLogLevel: func(v string) error {
level, err := zerolog.ParseLevel(v)
if err != nil {
return ValidationError{
Param: EnvLogLevel,
Value: v,
Reason: "must be one of: debug, info, warn, error",
}
}
config.LogLevel = level
return nil
},
EnvResponseTimeout: func(v string) error {
val, err := parsePositiveInt(EnvResponseTimeout, v)
if err != nil {
return err
}
config.ResponseTimeout = val
return nil
},
EnvPanicOnUnexpectedError: func(v string) error {
val, err := parseBool(EnvPanicOnUnexpectedError, v)
if err != nil {
return err
}
config.PanicOnUnexpectedError = val
return nil
},
EnvRootPath: func(v string) error {
config.RootPath = v
return nil
},
EnvDirIndexingEnabled: func(v string) error {
val, err := parseBool(EnvDirIndexingEnabled, v)
if err != nil {
return err
}
config.DirIndexingEnabled = val
return nil
},
flag.Parse()
// Parse and validate log level
level, err := parseLogLevel(*logLevel)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Invalid log level '%s': must be one of: debug, info, warn, error\n", *logLevel)
os.Exit(1)
}
// Process each environment variable
for envVar, parser := range parsers {
value, ok := os.LookupEnv(envVar)
if !ok {
_, _ = fmt.Fprintf(os.Stderr, "Missing required environment variable: %s\n", envVar)
os.Exit(1)
}
if err := parser(value); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Configuration error: %v\n", err)
os.Exit(1)
}
// Validate response timeout
if *responseTimeout <= 0 {
_, _ = fmt.Fprintf(os.Stderr, "Invalid response timeout '%d': must be positive\n", *responseTimeout)
os.Exit(1)
}
return config
return &Config{
LogLevel: level,
ResponseTimeout: *responseTimeout,
RootPath: *rootPath,
DirIndexingEnabled: *dirIndexing,
Listen: *listen,
}
}