feat: Improve error handling with custom error types and detailed messages
This commit is contained in:
@@ -39,13 +39,21 @@ func (c *Config) String() string {
|
|||||||
var CONFIG Config
|
var CONFIG Config
|
||||||
|
|
||||||
// parsePositiveInt parses and validates positive integer values
|
// 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)
|
val, err := strconv.Atoi(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, &ValidationError{
|
||||||
|
Param: param,
|
||||||
|
Value: value,
|
||||||
|
Reason: "must be a valid integer",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if val <= 0 {
|
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
|
return val, nil
|
||||||
}
|
}
|
||||||
@@ -59,46 +67,53 @@ func GetConfig() *Config {
|
|||||||
EnvLogLevel: func(v string) error {
|
EnvLogLevel: func(v string) error {
|
||||||
level, err := zerolog.ParseLevel(v)
|
level, err := zerolog.ParseLevel(v)
|
||||||
if err != nil {
|
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
|
config.LogLevel = level
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
EnvRootPath: func(v string) error {
|
EnvRootPath: func(v string) error {
|
||||||
if _, err := os.Stat(v); err != nil {
|
if _, err := os.Stat(v); err != nil {
|
||||||
return fmt.Errorf("invalid root path: %w", err)
|
return &ConfigError{
|
||||||
|
Param: EnvRootPath,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
config.RootPath = v
|
config.RootPath = v
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
EnvNumWorkers: func(v string) error {
|
EnvNumWorkers: func(v string) error {
|
||||||
val, err := parsePositiveInt(v)
|
val, err := parsePositiveInt(EnvNumWorkers, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid number of workers: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
config.NumOfWorkers = val
|
config.NumOfWorkers = val
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
EnvWorkerBatchSize: func(v string) error {
|
EnvWorkerBatchSize: func(v string) error {
|
||||||
val, err := parsePositiveInt(v)
|
val, err := parsePositiveInt(EnvWorkerBatchSize, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid worker batch size: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
config.WorkerBatchSize = val
|
config.WorkerBatchSize = val
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
EnvMaxResponseSize: func(v string) error {
|
EnvMaxResponseSize: func(v string) error {
|
||||||
val, err := parsePositiveInt(v)
|
val, err := parsePositiveInt(EnvMaxResponseSize, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid max response size: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
config.MaxResponseSize = val
|
config.MaxResponseSize = val
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
EnvResponseTimeout: func(v string) error {
|
EnvResponseTimeout: func(v string) error {
|
||||||
val, err := parsePositiveInt(v)
|
val, err := parsePositiveInt(EnvResponseTimeout, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid response timeout: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
config.ResponseTimeout = val
|
config.ResponseTimeout = val
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -39,21 +39,55 @@ func TestGetConfig(t *testing.T) {
|
|||||||
func TestParsePositiveInt(t *testing.T) {
|
func TestParsePositiveInt(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
param string
|
||||||
input string
|
input string
|
||||||
want int
|
want int
|
||||||
wantErr bool
|
wantErr bool
|
||||||
|
errType interface{}
|
||||||
|
errMessage string
|
||||||
}{
|
}{
|
||||||
{"valid positive", "42", 42, false},
|
{
|
||||||
{"zero", "0", 0, true},
|
name: "valid positive",
|
||||||
{"negative", "-1", 0, true},
|
param: "TEST_PARAM",
|
||||||
{"invalid", "abc", 0, true},
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := parsePositiveInt(tt.input)
|
got, err := parsePositiveInt(tt.param, tt.input)
|
||||||
if tt.wantErr {
|
if tt.wantErr {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
assert.IsType(t, tt.errType, err)
|
||||||
|
if tt.errMessage != "" {
|
||||||
|
assert.Equal(t, tt.errMessage, err.Error())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, got)
|
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
28
config/errors.go
Normal 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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user