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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
|||||||
**/*~
|
**/*~
|
||||||
/.idea
|
/.idea
|
||||||
/run.sh
|
/run.sh
|
||||||
|
/dist
|
||||||
|
|||||||
139
config/config.go
139
config/config.go
@@ -1,126 +1,69 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strings"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Environment variable names.
|
// Config holds the application configuration loaded from CLI flags.
|
||||||
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.
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
LogLevel zerolog.Level // Logging level (debug, info, warn, error)
|
LogLevel slog.Level // Logging level (debug, info, warn, error)
|
||||||
ResponseTimeout int // Timeout for responses in seconds
|
ResponseTimeout int // Timeout for responses in seconds
|
||||||
PanicOnUnexpectedError bool // Panic on unexpected errors when visiting a URL
|
|
||||||
RootPath string // Path to serve files from
|
RootPath string // Path to serve files from
|
||||||
DirIndexingEnabled bool // Allow client to browse directories or not
|
DirIndexingEnabled bool // Allow client to browse directories or not
|
||||||
|
Listen string // Address to listen on
|
||||||
}
|
}
|
||||||
|
|
||||||
var CONFIG Config //nolint:gochecknoglobals
|
var CONFIG Config //nolint:gochecknoglobals
|
||||||
|
|
||||||
// parsePositiveInt parses and validates positive integer values.
|
// parseLogLevel parses a log level string into slog.Level
|
||||||
func parsePositiveInt(param, value string) (int, error) {
|
func parseLogLevel(level string) (slog.Level, error) {
|
||||||
val, err := strconv.Atoi(value)
|
switch strings.ToLower(level) {
|
||||||
if err != nil {
|
case "debug":
|
||||||
return 0, ValidationError{
|
return slog.LevelDebug, nil
|
||||||
Param: param,
|
case "info":
|
||||||
Value: value,
|
return slog.LevelInfo, nil
|
||||||
Reason: "must be a valid integer",
|
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) {
|
// GetConfig loads and validates configuration from CLI flags
|
||||||
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
|
|
||||||
func GetConfig() *Config {
|
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
|
flag.Parse()
|
||||||
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
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process each environment variable
|
// Parse and validate log level
|
||||||
for envVar, parser := range parsers {
|
level, err := parseLogLevel(*logLevel)
|
||||||
value, ok := os.LookupEnv(envVar)
|
if err != nil {
|
||||||
if !ok {
|
_, _ = fmt.Fprintf(os.Stderr, "Invalid log level '%s': must be one of: debug, info, warn, error\n", *logLevel)
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "Missing required environment variable: %s\n", envVar)
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := parser(value); err != nil {
|
// Validate response timeout
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "Configuration error: %v\n", err)
|
if *responseTimeout <= 0 {
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "Invalid response timeout '%d': must be positive\n", *responseTimeout)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
return &Config{
|
||||||
|
LogLevel: level,
|
||||||
|
ResponseTimeout: *responseTimeout,
|
||||||
|
RootPath: *rootPath,
|
||||||
|
DirIndexingEnabled: *dirIndexing,
|
||||||
|
Listen: *listen,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// ValidationError represents a config validation error
|
|
||||||
type ValidationError struct {
|
|
||||||
Param string
|
|
||||||
Value string
|
|
||||||
Reason string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ValidationError) Error() string {
|
|
||||||
return fmt.Sprintf("invalid value '%s' for %s: %s", e.Value, e.Param, e.Reason)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user