.
This commit is contained in:
@@ -8,48 +8,42 @@ import (
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// Environment variable names
|
||||
// Environment variable names.
|
||||
const (
|
||||
EnvLogLevel = "LOG_LEVEL"
|
||||
EnvRootPath = "ROOT_PATH"
|
||||
EnvNumWorkers = "NUM_OF_WORKERS"
|
||||
EnvWorkerBatchSize = "WORKER_BATCH_SIZE"
|
||||
EnvMaxResponseSize = "MAX_RESPONSE_SIZE"
|
||||
EnvResponseTimeout = "RESPONSE_TIMEOUT"
|
||||
EnvLogLevel = "LOG_LEVEL"
|
||||
EnvNumWorkers = "NUM_OF_WORKERS"
|
||||
EnvWorkerBatchSize = "WORKER_BATCH_SIZE"
|
||||
EnvMaxResponseSize = "MAX_RESPONSE_SIZE"
|
||||
EnvResponseTimeout = "RESPONSE_TIMEOUT"
|
||||
EnvPanicOnUnexpectedError = "PANIC_ON_UNEXPECTED_ERROR"
|
||||
EnvBlacklistPath = "BLACKLIST_PATH"
|
||||
)
|
||||
|
||||
// Config holds the application configuration loaded from environment variables
|
||||
// Config holds the application configuration loaded from environment variables.
|
||||
type Config struct {
|
||||
LogLevel zerolog.Level // Logging level (debug, info, warn, error)
|
||||
RootPath string // Root path for the application
|
||||
MaxResponseSize int // Maximum size of response in bytes
|
||||
NumOfWorkers int // Number of concurrent workers
|
||||
ResponseTimeout int // Timeout for responses in seconds
|
||||
WorkerBatchSize int // Batch size for worker processing
|
||||
LogLevel zerolog.Level // Logging level (debug, info, warn, error)
|
||||
MaxResponseSize int // Maximum size of response in bytes
|
||||
NumOfWorkers int // Number of concurrent workers
|
||||
ResponseTimeout int // Timeout for responses in seconds
|
||||
WorkerBatchSize int // Batch size for worker processing
|
||||
PanicOnUnexpectedError bool // Panic on unexpected errors when visiting a URL
|
||||
BlacklistPath string // File that has blacklisted strings of "host:port"
|
||||
}
|
||||
|
||||
// String returns a string representation of the configuration
|
||||
func (c *Config) String() string {
|
||||
return fmt.Sprintf(
|
||||
"Config{LogLevel: %s, RootPath: %s, MaxResponseSize: %d, NumWorkers: %d, ResponseTimeout: %d, WorkerBatchSize: %d}",
|
||||
c.LogLevel, c.RootPath, c.MaxResponseSize, c.NumOfWorkers, c.ResponseTimeout, c.WorkerBatchSize,
|
||||
)
|
||||
}
|
||||
var CONFIG Config //nolint:gochecknoglobals
|
||||
|
||||
var CONFIG Config
|
||||
|
||||
// parsePositiveInt parses and validates positive integer values
|
||||
// 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{
|
||||
return 0, ValidationError{
|
||||
Param: param,
|
||||
Value: value,
|
||||
Reason: "must be a valid integer",
|
||||
}
|
||||
}
|
||||
if val <= 0 {
|
||||
return 0, &ValidationError{
|
||||
return 0, ValidationError{
|
||||
Param: param,
|
||||
Value: value,
|
||||
Reason: "must be positive",
|
||||
@@ -58,6 +52,18 @@ func parsePositiveInt(param, value string) (int, error) {
|
||||
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
|
||||
func GetConfig() *Config {
|
||||
config := &Config{}
|
||||
@@ -67,7 +73,7 @@ func GetConfig() *Config {
|
||||
EnvLogLevel: func(v string) error {
|
||||
level, err := zerolog.ParseLevel(v)
|
||||
if err != nil {
|
||||
return &ValidationError{
|
||||
return ValidationError{
|
||||
Param: EnvLogLevel,
|
||||
Value: v,
|
||||
Reason: "must be one of: debug, info, warn, error",
|
||||
@@ -76,16 +82,6 @@ func GetConfig() *Config {
|
||||
config.LogLevel = level
|
||||
return nil
|
||||
},
|
||||
EnvRootPath: func(v string) error {
|
||||
if _, err := os.Stat(v); err != nil {
|
||||
return &ConfigError{
|
||||
Param: EnvRootPath,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
config.RootPath = v
|
||||
return nil
|
||||
},
|
||||
EnvNumWorkers: func(v string) error {
|
||||
val, err := parsePositiveInt(EnvNumWorkers, v)
|
||||
if err != nil {
|
||||
@@ -118,6 +114,18 @@ func GetConfig() *Config {
|
||||
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
|
||||
},
|
||||
EnvBlacklistPath: func(v string) error {
|
||||
config.BlacklistPath = v
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// Process each environment variable
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetConfig(t *testing.T) {
|
||||
// Set up test environment variables
|
||||
envVars := map[string]string{
|
||||
EnvLogLevel: "debug",
|
||||
EnvRootPath: ".",
|
||||
EnvNumWorkers: "5",
|
||||
EnvWorkerBatchSize: "100",
|
||||
EnvMaxResponseSize: "1048576",
|
||||
EnvResponseTimeout: "30",
|
||||
}
|
||||
|
||||
for k, v := range envVars {
|
||||
os.Setenv(k, v)
|
||||
defer os.Unsetenv(k)
|
||||
}
|
||||
|
||||
// Get configuration
|
||||
config := GetConfig()
|
||||
|
||||
// Assert configuration values
|
||||
assert.Equal(t, zerolog.DebugLevel, config.LogLevel)
|
||||
assert.Equal(t, ".", config.RootPath)
|
||||
assert.Equal(t, 5, config.NumOfWorkers)
|
||||
assert.Equal(t, 100, config.WorkerBatchSize)
|
||||
assert.Equal(t, 1048576, config.MaxResponseSize)
|
||||
assert.Equal(t, 30, config.ResponseTimeout)
|
||||
}
|
||||
|
||||
func TestParsePositiveInt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
param string
|
||||
input string
|
||||
want int
|
||||
wantErr bool
|
||||
errType interface{}
|
||||
errMessage string
|
||||
}{
|
||||
{
|
||||
name: "valid positive",
|
||||
param: "TEST_PARAM",
|
||||
input: "42",
|
||||
want: 42,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "zero",
|
||||
param: "TEST_PARAM",
|
||||
input: "0",
|
||||
wantErr: true,
|
||||
errType: &ValidationError{},
|
||||
errMessage: "invalid value '0' for TEST_PARAM: must be positive",
|
||||
},
|
||||
{
|
||||
name: "negative",
|
||||
param: "TEST_PARAM",
|
||||
input: "-1",
|
||||
wantErr: true,
|
||||
errType: &ValidationError{},
|
||||
errMessage: "invalid value '-1' for TEST_PARAM: must be positive",
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
param: "TEST_PARAM",
|
||||
input: "abc",
|
||||
wantErr: true,
|
||||
errType: &ValidationError{},
|
||||
errMessage: "invalid value 'abc' for TEST_PARAM: must be a valid integer",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := parsePositiveInt(tt.param, tt.input)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, tt.errType, err)
|
||||
if tt.errMessage != "" {
|
||||
assert.Equal(t, tt.errMessage, err.Error())
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
envVars map[string]string
|
||||
wantErr bool
|
||||
errMessage string
|
||||
}{
|
||||
{
|
||||
name: "invalid log level",
|
||||
envVars: map[string]string{
|
||||
EnvLogLevel: "invalid",
|
||||
},
|
||||
wantErr: true,
|
||||
errMessage: "invalid value 'invalid' for LOG_LEVEL: must be one of: debug, info, warn, error",
|
||||
},
|
||||
{
|
||||
name: "invalid worker count",
|
||||
envVars: map[string]string{
|
||||
EnvLogLevel: "debug",
|
||||
EnvRootPath: ".",
|
||||
EnvNumWorkers: "-1",
|
||||
},
|
||||
wantErr: true,
|
||||
errMessage: "invalid value '-1' for NUM_OF_WORKERS: must be positive",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Clear environment
|
||||
os.Clearenv()
|
||||
|
||||
// Set required environment variables
|
||||
for k, v := range tt.envVars {
|
||||
os.Setenv(k, v)
|
||||
}
|
||||
|
||||
// Defer cleanup
|
||||
defer os.Clearenv()
|
||||
|
||||
if tt.wantErr {
|
||||
assert.PanicsWithError(t, tt.errMessage, func() {
|
||||
GetConfig()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,27 +2,13 @@ package config
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ConfigError represents a configuration error
|
||||
type ConfigError struct {
|
||||
Param string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *ConfigError) Error() string {
|
||||
return fmt.Sprintf("configuration error for %s: %v", e.Param, e.Err)
|
||||
}
|
||||
|
||||
func (e *ConfigError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// ValidationError represents a validation error
|
||||
// ValidationError represents a config validation error
|
||||
type ValidationError struct {
|
||||
Param string
|
||||
Value string
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() 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