feat: Improve error handling with custom error types and detailed messages

This commit is contained in:
2024-11-11 15:01:20 +02:00
parent 6346c9a829
commit 2bb8589eb7
3 changed files with 148 additions and 22 deletions

View File

@@ -39,13 +39,21 @@ func (c *Config) String() string {
var CONFIG Config
// parsePositiveInt parses and validates positive integer values
func parsePositiveInt(value string) (int, error) {
func parsePositiveInt(param, value string) (int, error) {
val, err := strconv.Atoi(value)
if err != nil {
return 0, err
return 0, &ValidationError{
Param: param,
Value: value,
Reason: "must be a valid integer",
}
}
if val <= 0 {
return 0, fmt.Errorf("value must be positive")
return 0, &ValidationError{
Param: param,
Value: value,
Reason: "must be positive",
}
}
return val, nil
}
@@ -59,46 +67,53 @@ func GetConfig() *Config {
EnvLogLevel: func(v string) error {
level, err := zerolog.ParseLevel(v)
if err != nil {
return fmt.Errorf("invalid log level: %w", err)
return &ValidationError{
Param: EnvLogLevel,
Value: v,
Reason: "must be one of: debug, info, warn, error",
}
}
config.LogLevel = level
return nil
},
EnvRootPath: func(v string) error {
if _, err := os.Stat(v); err != nil {
return fmt.Errorf("invalid root path: %w", err)
return &ConfigError{
Param: EnvRootPath,
Err: err,
}
}
config.RootPath = v
return nil
},
EnvNumWorkers: func(v string) error {
val, err := parsePositiveInt(v)
val, err := parsePositiveInt(EnvNumWorkers, v)
if err != nil {
return fmt.Errorf("invalid number of workers: %w", err)
return err
}
config.NumOfWorkers = val
return nil
},
EnvWorkerBatchSize: func(v string) error {
val, err := parsePositiveInt(v)
val, err := parsePositiveInt(EnvWorkerBatchSize, v)
if err != nil {
return fmt.Errorf("invalid worker batch size: %w", err)
return err
}
config.WorkerBatchSize = val
return nil
},
EnvMaxResponseSize: func(v string) error {
val, err := parsePositiveInt(v)
val, err := parsePositiveInt(EnvMaxResponseSize, v)
if err != nil {
return fmt.Errorf("invalid max response size: %w", err)
return err
}
config.MaxResponseSize = val
return nil
},
EnvResponseTimeout: func(v string) error {
val, err := parsePositiveInt(v)
val, err := parsePositiveInt(EnvResponseTimeout, v)
if err != nil {
return fmt.Errorf("invalid response timeout: %w", err)
return err
}
config.ResponseTimeout = val
return nil

View File

@@ -39,21 +39,55 @@ func TestGetConfig(t *testing.T) {
func TestParsePositiveInt(t *testing.T) {
tests := []struct {
name string
param string
input string
want int
wantErr bool
errType interface{}
errMessage string
}{
{"valid positive", "42", 42, false},
{"zero", "0", 0, true},
{"negative", "-1", 0, true},
{"invalid", "abc", 0, true},
{
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.input)
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)
@@ -61,3 +95,52 @@ func TestParsePositiveInt(t *testing.T) {
})
}
}
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()
})
}
})
}
}

28
config/errors.go Normal file
View File

@@ -0,0 +1,28 @@
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
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)
}