mirror of
https://github.com/aykhans/dodo.git
synced 2025-09-01 00:53:34 +00:00
Add 'ParseString' to the utils. Refactor enums of the 'ConfigFile'.
This commit is contained in:
@@ -59,7 +59,6 @@ linters:
|
|||||||
- inamedparam
|
- inamedparam
|
||||||
- interfacebloat
|
- interfacebloat
|
||||||
- intrange
|
- intrange
|
||||||
- ireturn
|
|
||||||
- loggercheck
|
- loggercheck
|
||||||
- makezero
|
- makezero
|
||||||
- mirror
|
- mirror
|
||||||
@@ -110,6 +109,7 @@ linters:
|
|||||||
- perfsprint
|
- perfsprint
|
||||||
- errcheck
|
- errcheck
|
||||||
- gosec
|
- gosec
|
||||||
|
- gocyclo
|
||||||
|
|
||||||
- path: _test\.go$
|
- path: _test\.go$
|
||||||
linters:
|
linters:
|
||||||
|
@@ -187,7 +187,7 @@ func (parser *ConfigCLIParser) Parse() (*Config, error) {
|
|||||||
fieldParseErrors,
|
fieldParseErrors,
|
||||||
*types.NewFieldParseError(
|
*types.NewFieldParseError(
|
||||||
"config-file",
|
"config-file",
|
||||||
fmt.Errorf("file type '%s' not supported (supported types: %s)", err.Type, types.ConfigFileTypeYAML.String()),
|
fmt.Errorf("file type '%s' not supported (supported types: %s)", err.Type, types.ConfigFileTypeYAML),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
|
@@ -6,39 +6,19 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigFileType int
|
type ConfigFileType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ConfigFileTypeYAML ConfigFileType = iota
|
ConfigFileTypeYAML ConfigFileType = "yaml/yml"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t ConfigFileType) String() string {
|
type ConfigFileLocationType string
|
||||||
switch t {
|
|
||||||
case ConfigFileTypeYAML:
|
|
||||||
return "yaml/yml"
|
|
||||||
default:
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigFileLocationType int
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ConfigFileLocationLocal ConfigFileLocationType = iota
|
ConfigFileLocationLocal ConfigFileLocationType = "local"
|
||||||
ConfigFileLocationRemote
|
ConfigFileLocationRemote ConfigFileLocationType = "remote"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l ConfigFileLocationType) String() string {
|
|
||||||
switch l {
|
|
||||||
case ConfigFileLocationLocal:
|
|
||||||
return "local"
|
|
||||||
case ConfigFileLocationRemote:
|
|
||||||
return "remote"
|
|
||||||
default:
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConfigFile struct {
|
type ConfigFile struct {
|
||||||
path string
|
path string
|
||||||
_type ConfigFileType
|
_type ConfigFileType
|
||||||
|
@@ -7,35 +7,6 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConfigFileType_String(t *testing.T) {
|
|
||||||
t.Run("ConfigFileTypeYAML returns yaml", func(t *testing.T) {
|
|
||||||
configType := ConfigFileTypeYAML
|
|
||||||
assert.Equal(t, "yaml/yml", configType.String())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Unknown config file type returns unknown", func(t *testing.T) {
|
|
||||||
configType := ConfigFileType(999)
|
|
||||||
assert.Equal(t, "unknown", configType.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigFileLocationType_String(t *testing.T) {
|
|
||||||
t.Run("ConfigFileLocationLocal returns local", func(t *testing.T) {
|
|
||||||
locationType := ConfigFileLocationLocal
|
|
||||||
assert.Equal(t, "local", locationType.String())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("ConfigFileLocationRemote returns remote", func(t *testing.T) {
|
|
||||||
locationType := ConfigFileLocationRemote
|
|
||||||
assert.Equal(t, "remote", locationType.String())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Unknown location type returns unknown", func(t *testing.T) {
|
|
||||||
locationType := ConfigFileLocationType(999)
|
|
||||||
assert.Equal(t, "unknown", locationType.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigFile_String(t *testing.T) {
|
func TestConfigFile_String(t *testing.T) {
|
||||||
t.Run("String returns the file path", func(t *testing.T) {
|
t.Run("String returns the file path", func(t *testing.T) {
|
||||||
configFile := ConfigFile{path: "/path/to/config.yaml"}
|
configFile := ConfigFile{path: "/path/to/config.yaml"}
|
||||||
|
111
pkg/utils/parse.go
Normal file
111
pkg/utils/parse.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseString attempts to parse the input string `s` into a value of the specified type T.
|
||||||
|
// If parsing the string `s` fails for a supported type, it returns the zero value of T
|
||||||
|
// and the parsing error.
|
||||||
|
// /nolint:forcetypeassert,wrapcheck
|
||||||
|
func ParseString[
|
||||||
|
T string | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float64 | bool | time.Duration | url.URL,
|
||||||
|
](rawValue string) (T, error) {
|
||||||
|
var value T
|
||||||
|
|
||||||
|
switch any(value).(type) {
|
||||||
|
case int:
|
||||||
|
i, err := strconv.Atoi(rawValue)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
value = any(i).(T)
|
||||||
|
case int8:
|
||||||
|
i, err := strconv.ParseInt(rawValue, 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
value = any(int8(i)).(T)
|
||||||
|
case int16:
|
||||||
|
i, err := strconv.ParseInt(rawValue, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
value = any(int16(i)).(T)
|
||||||
|
case int32:
|
||||||
|
i, err := strconv.ParseInt(rawValue, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
value = any(int32(i)).(T)
|
||||||
|
case int64:
|
||||||
|
i, err := strconv.ParseInt(rawValue, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
value = any(i).(T)
|
||||||
|
case uint:
|
||||||
|
u, err := strconv.ParseUint(rawValue, 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
value = any(uint(u)).(T)
|
||||||
|
case uint8:
|
||||||
|
u, err := strconv.ParseUint(rawValue, 10, 8)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
value = any(uint8(u)).(T)
|
||||||
|
case uint16:
|
||||||
|
u, err := strconv.ParseUint(rawValue, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
value = any(uint16(u)).(T)
|
||||||
|
case uint32:
|
||||||
|
u, err := strconv.ParseUint(rawValue, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
value = any(uint32(u)).(T)
|
||||||
|
case uint64:
|
||||||
|
u, err := strconv.ParseUint(rawValue, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
value = any(u).(T)
|
||||||
|
case float64:
|
||||||
|
f, err := strconv.ParseFloat(rawValue, 64)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
value = any(f).(T)
|
||||||
|
case bool:
|
||||||
|
b, err := strconv.ParseBool(rawValue)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
value = any(b).(T)
|
||||||
|
case string:
|
||||||
|
value = any(rawValue).(T)
|
||||||
|
case time.Duration:
|
||||||
|
d, err := time.ParseDuration(rawValue)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
value = any(d).(T)
|
||||||
|
case url.URL:
|
||||||
|
u, err := url.Parse(rawValue)
|
||||||
|
if err != nil {
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
value = any(*u).(T)
|
||||||
|
default:
|
||||||
|
return value, fmt.Errorf("unsupported type: %T", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
528
pkg/utils/parse_test.go
Normal file
528
pkg/utils/parse_test.go
Normal file
@@ -0,0 +1,528 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseString(t *testing.T) {
|
||||||
|
t.Run("ParseString to string", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"empty string", "", ""},
|
||||||
|
{"simple string", "hello", "hello"},
|
||||||
|
{"string with spaces", "hello world", "hello world"},
|
||||||
|
{"numeric string", "123", "123"},
|
||||||
|
{"special characters", "!@#$%^&*()", "!@#$%^&*()"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := ParseString[string](test.input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParseString to int", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected int
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"positive int", "42", 42, false},
|
||||||
|
{"negative int", "-42", -42, false},
|
||||||
|
{"zero", "0", 0, false},
|
||||||
|
{"invalid int", "abc", 0, true},
|
||||||
|
{"float string", "3.14", 0, true},
|
||||||
|
{"empty string", "", 0, true},
|
||||||
|
{"overflow string", "99999999999999999999", 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := ParseString[int](test.input)
|
||||||
|
if test.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParseString to int8", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected int8
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"valid int8", "127", 127, false},
|
||||||
|
{"min int8", "-128", -128, false},
|
||||||
|
{"overflow int8", "128", 0, true},
|
||||||
|
{"underflow int8", "-129", 0, true},
|
||||||
|
{"invalid", "abc", 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := ParseString[int8](test.input)
|
||||||
|
if test.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParseString to int16", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected int16
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"valid int16", "32767", 32767, false},
|
||||||
|
{"min int16", "-32768", -32768, false},
|
||||||
|
{"overflow int16", "32768", 0, true},
|
||||||
|
{"underflow int16", "-32769", 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := ParseString[int16](test.input)
|
||||||
|
if test.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParseString to int32", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected int32
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"valid int32", "2147483647", 2147483647, false},
|
||||||
|
{"min int32", "-2147483648", -2147483648, false},
|
||||||
|
{"overflow int32", "2147483648", 0, true},
|
||||||
|
{"underflow int32", "-2147483649", 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := ParseString[int32](test.input)
|
||||||
|
if test.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParseString to int64", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected int64
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"valid int64", "9223372036854775807", 9223372036854775807, false},
|
||||||
|
{"min int64", "-9223372036854775808", -9223372036854775808, false},
|
||||||
|
{"large number", "123456789012345", 123456789012345, false},
|
||||||
|
{"invalid", "not a number", 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := ParseString[int64](test.input)
|
||||||
|
if test.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParseString to uint", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected uint
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"valid uint", "42", 42, false},
|
||||||
|
{"zero", "0", 0, false},
|
||||||
|
{"large uint", "4294967295", 4294967295, false},
|
||||||
|
{"negative", "-1", 0, true},
|
||||||
|
{"invalid", "abc", 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := ParseString[uint](test.input)
|
||||||
|
if test.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParseString to uint8", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected uint8
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"valid uint8", "255", 255, false},
|
||||||
|
{"min uint8", "0", 0, false},
|
||||||
|
{"overflow uint8", "256", 0, true},
|
||||||
|
{"negative", "-1", 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := ParseString[uint8](test.input)
|
||||||
|
if test.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParseString to uint16", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected uint16
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"valid uint16", "65535", 65535, false},
|
||||||
|
{"min uint16", "0", 0, false},
|
||||||
|
{"overflow uint16", "65536", 0, true},
|
||||||
|
{"negative", "-1", 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := ParseString[uint16](test.input)
|
||||||
|
if test.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParseString to uint32", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected uint32
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"valid uint32", "4294967295", 4294967295, false},
|
||||||
|
{"min uint32", "0", 0, false},
|
||||||
|
{"overflow uint32", "4294967296", 0, true},
|
||||||
|
{"negative", "-1", 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := ParseString[uint32](test.input)
|
||||||
|
if test.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParseString to uint64", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected uint64
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"valid uint64", "18446744073709551615", 18446744073709551615, false},
|
||||||
|
{"min uint64", "0", 0, false},
|
||||||
|
{"large number", "123456789012345", 123456789012345, false},
|
||||||
|
{"negative", "-1", 0, true},
|
||||||
|
{"invalid", "not a number", 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := ParseString[uint64](test.input)
|
||||||
|
if test.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParseString to float64", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected float64
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"integer", "42", 42.0, false},
|
||||||
|
{"decimal", "3.14159", 3.14159, false},
|
||||||
|
{"negative", "-2.5", -2.5, false},
|
||||||
|
{"scientific notation", "1.23e10", 1.23e10, false},
|
||||||
|
{"zero", "0", 0.0, false},
|
||||||
|
{"invalid", "not a number", 0, true},
|
||||||
|
{"empty", "", 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := ParseString[float64](test.input)
|
||||||
|
if test.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.InDelta(t, test.expected, result, 0.0001)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParseString to bool", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected bool
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"true lowercase", "true", true, false},
|
||||||
|
{"True mixed case", "True", true, false},
|
||||||
|
{"TRUE uppercase", "TRUE", true, false},
|
||||||
|
{"1 as true", "1", true, false},
|
||||||
|
{"false lowercase", "false", false, false},
|
||||||
|
{"False mixed case", "False", false, false},
|
||||||
|
{"FALSE uppercase", "FALSE", false, false},
|
||||||
|
{"0 as false", "0", false, false},
|
||||||
|
{"invalid", "yes", false, true},
|
||||||
|
{"empty", "", false, true},
|
||||||
|
{"numeric non-binary", "2", false, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := ParseString[bool](test.input)
|
||||||
|
if test.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParseString to time.Duration", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected time.Duration
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{"seconds", "10s", 10 * time.Second, false},
|
||||||
|
{"minutes", "5m", 5 * time.Minute, false},
|
||||||
|
{"hours", "2h", 2 * time.Hour, false},
|
||||||
|
{"combined", "1h30m45s", time.Hour + 30*time.Minute + 45*time.Second, false},
|
||||||
|
{"milliseconds", "500ms", 500 * time.Millisecond, false},
|
||||||
|
{"microseconds", "100us", 100 * time.Microsecond, false},
|
||||||
|
{"nanoseconds", "50ns", 50 * time.Nanosecond, false},
|
||||||
|
{"negative", "-5s", -5 * time.Second, false},
|
||||||
|
{"invalid", "5x", 0, true},
|
||||||
|
{"empty", "", 0, true},
|
||||||
|
{"no unit", "100", 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := ParseString[time.Duration](test.input)
|
||||||
|
if test.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ParseString to url.URL", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
checkFunc func(t *testing.T, u url.URL)
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "http URL",
|
||||||
|
input: "http://example.com",
|
||||||
|
checkFunc: func(t *testing.T, u url.URL) {
|
||||||
|
t.Helper()
|
||||||
|
assert.Equal(t, "http", u.Scheme)
|
||||||
|
assert.Equal(t, "example.com", u.Host)
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "https URL with path",
|
||||||
|
input: "https://example.com/path/to/resource",
|
||||||
|
checkFunc: func(t *testing.T, u url.URL) {
|
||||||
|
t.Helper()
|
||||||
|
assert.Equal(t, "https", u.Scheme)
|
||||||
|
assert.Equal(t, "example.com", u.Host)
|
||||||
|
assert.Equal(t, "/path/to/resource", u.Path)
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URL with query parameters",
|
||||||
|
input: "https://example.com/search?q=test&page=1",
|
||||||
|
checkFunc: func(t *testing.T, u url.URL) {
|
||||||
|
t.Helper()
|
||||||
|
assert.Equal(t, "https", u.Scheme)
|
||||||
|
assert.Equal(t, "example.com", u.Host)
|
||||||
|
assert.Equal(t, "/search", u.Path)
|
||||||
|
assert.Equal(t, "q=test&page=1", u.RawQuery)
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URL with port",
|
||||||
|
input: "http://localhost:8080/api",
|
||||||
|
checkFunc: func(t *testing.T, u url.URL) {
|
||||||
|
t.Helper()
|
||||||
|
assert.Equal(t, "http", u.Scheme)
|
||||||
|
assert.Equal(t, "localhost:8080", u.Host)
|
||||||
|
assert.Equal(t, "/api", u.Path)
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URL with fragment",
|
||||||
|
input: "https://example.com/page#section",
|
||||||
|
checkFunc: func(t *testing.T, u url.URL) {
|
||||||
|
t.Helper()
|
||||||
|
assert.Equal(t, "https", u.Scheme)
|
||||||
|
assert.Equal(t, "example.com", u.Host)
|
||||||
|
assert.Equal(t, "/page", u.Path)
|
||||||
|
assert.Equal(t, "section", u.Fragment)
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "relative path",
|
||||||
|
input: "/path/to/resource",
|
||||||
|
checkFunc: func(t *testing.T, u url.URL) {
|
||||||
|
t.Helper()
|
||||||
|
assert.Empty(t, u.Scheme)
|
||||||
|
assert.Empty(t, u.Host)
|
||||||
|
assert.Equal(t, "/path/to/resource", u.Path)
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty string",
|
||||||
|
input: "",
|
||||||
|
checkFunc: func(t *testing.T, u url.URL) {
|
||||||
|
t.Helper()
|
||||||
|
assert.Empty(t, u.String())
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
result, err := ParseString[url.URL](test.input)
|
||||||
|
if test.expectError {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
if test.checkFunc != nil {
|
||||||
|
test.checkFunc(t, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Edge cases", func(t *testing.T) {
|
||||||
|
t.Run("whitespace handling for numeric types", func(t *testing.T) {
|
||||||
|
result, err := ParseString[int](" 42 ")
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, 0, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("leading zeros for int", func(t *testing.T) {
|
||||||
|
result, err := ParseString[int]("007")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 7, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("plus sign for positive numbers", func(t *testing.T) {
|
||||||
|
result, err := ParseString[int]("+42")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 42, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("case sensitivity for bool", func(t *testing.T) {
|
||||||
|
testCases := []string{"t", "T", "f", "F"}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
result, err := ParseString[bool](tc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if tc == "t" || tc == "T" {
|
||||||
|
assert.True(t, result)
|
||||||
|
} else {
|
||||||
|
assert.False(t, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
Reference in New Issue
Block a user