mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-26 19:11:08 +01:00
refactor: remove net.URL and net.CIDR types, improved unmarshal handling
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@@ -42,7 +45,14 @@ var (
|
||||
tagAliases = "aliases" // declare aliases for fields
|
||||
)
|
||||
|
||||
var mapUnmarshalerType = reflect.TypeFor[MapUnmarshaller]()
|
||||
var (
|
||||
typeDuration = reflect.TypeFor[time.Duration]()
|
||||
typeURL = reflect.TypeFor[url.URL]()
|
||||
typeCIDR = reflect.TypeFor[*net.IPNet]()
|
||||
|
||||
typeMapMarshaller = reflect.TypeFor[MapMarshaller]()
|
||||
typeMapUnmarshaler = reflect.TypeFor[MapUnmarshaller]()
|
||||
)
|
||||
|
||||
var defaultValues = functional.NewMapOf[reflect.Type, func() any]()
|
||||
|
||||
@@ -196,7 +206,7 @@ func MapUnmarshalValidate(src SerializedObject, dst any) (err gperr.Error) {
|
||||
return gperr.Errorf("unmarshal: src is %w and dst is not settable", ErrNilValue)
|
||||
}
|
||||
|
||||
if dstT.Implements(mapUnmarshalerType) {
|
||||
if dstT.Implements(typeMapUnmarshaler) {
|
||||
dstV, _, err = dive(dstV)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -370,7 +380,7 @@ func Convert(src reflect.Value, dst reflect.Value) gperr.Error {
|
||||
}
|
||||
obj, ok := src.Interface().(SerializedObject)
|
||||
if !ok {
|
||||
return ErrUnsupportedConversion.Subject(dstT.String() + " to " + srcT.String())
|
||||
return ErrUnsupportedConversion.Subjectf("%s to %s", srcT, dstT)
|
||||
}
|
||||
return MapUnmarshalValidate(obj, dst.Addr().Interface())
|
||||
case srcKind == reflect.Slice:
|
||||
@@ -378,7 +388,7 @@ func Convert(src reflect.Value, dst reflect.Value) gperr.Error {
|
||||
return nil
|
||||
}
|
||||
if dstT.Kind() != reflect.Slice {
|
||||
return ErrUnsupportedConversion.Subject(dstT.String() + " to " + srcT.String())
|
||||
return ErrUnsupportedConversion.Subjectf("%s to %s", srcT, dstT)
|
||||
}
|
||||
sliceErrs := gperr.NewBuilder("slice conversion errors")
|
||||
newSlice := reflect.MakeSlice(dstT, src.Len(), src.Len())
|
||||
@@ -402,6 +412,10 @@ func Convert(src reflect.Value, dst reflect.Value) gperr.Error {
|
||||
return ErrUnsupportedConversion.Subjectf("%s to %s", srcT, dstT)
|
||||
}
|
||||
|
||||
func nilPointer[T any]() reflect.Value {
|
||||
return reflect.ValueOf((*T)(nil))
|
||||
}
|
||||
|
||||
func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gperr.Error) {
|
||||
convertible = true
|
||||
dstT := dst.Type()
|
||||
@@ -417,10 +431,10 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
|
||||
return
|
||||
}
|
||||
switch dstT {
|
||||
case reflect.TypeFor[time.Duration]():
|
||||
case typeDuration:
|
||||
if src == "" {
|
||||
dst.Set(reflect.Zero(dstT))
|
||||
return
|
||||
return false, nil
|
||||
}
|
||||
d, err := time.ParseDuration(src)
|
||||
if err != nil {
|
||||
@@ -431,7 +445,31 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
|
||||
}
|
||||
dst.Set(reflect.ValueOf(d))
|
||||
return
|
||||
default:
|
||||
case typeURL:
|
||||
if src == "" {
|
||||
dst.Addr().Set(nilPointer[*url.URL]())
|
||||
return
|
||||
}
|
||||
u, err := url.Parse(src)
|
||||
if err != nil {
|
||||
return true, gperr.Wrap(err)
|
||||
}
|
||||
dst.Set(reflect.ValueOf(u).Elem())
|
||||
return
|
||||
case typeCIDR:
|
||||
if src == "" {
|
||||
dst.Addr().Set(nilPointer[*net.IPNet]())
|
||||
return
|
||||
}
|
||||
if !strings.Contains(src, "/") {
|
||||
src += "/32" // single IP
|
||||
}
|
||||
_, ipnet, err := net.ParseCIDR(src)
|
||||
if err != nil {
|
||||
return true, gperr.Wrap(err)
|
||||
}
|
||||
dst.Set(reflect.ValueOf(ipnet).Elem())
|
||||
return
|
||||
}
|
||||
if dstKind := dst.Kind(); isIntFloat(dstKind) {
|
||||
var i any
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -42,7 +45,7 @@ func TestDeserialize(t *testing.T) {
|
||||
var s2 S
|
||||
err := MapUnmarshalValidate(testStructSerialized, &s2)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, s2, testStruct)
|
||||
ExpectEqualValues(t, s2, testStruct)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -62,15 +65,15 @@ func TestDeserializeAnonymousField(t *testing.T) {
|
||||
// t.Fatalf("anon %v, all %v", anon, all)
|
||||
err := MapUnmarshalValidate(map[string]any{"a": 1, "b": 2, "c": 3}, &s)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, s.A, 1)
|
||||
ExpectEqual(t, s.B, 2)
|
||||
ExpectEqual(t, s.C, 3)
|
||||
ExpectEqualValues(t, s.A, 1)
|
||||
ExpectEqualValues(t, s.B, 2)
|
||||
ExpectEqualValues(t, s.C, 3)
|
||||
|
||||
err = MapUnmarshalValidate(map[string]any{"a": 1, "b": 2, "c": 3}, &s2)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, s2.A, 1)
|
||||
ExpectEqual(t, s2.B, 2)
|
||||
ExpectEqual(t, s2.C, 3)
|
||||
ExpectEqualValues(t, s2.A, 1)
|
||||
ExpectEqualValues(t, s2.B, 2)
|
||||
ExpectEqualValues(t, s2.C, 3)
|
||||
}
|
||||
|
||||
func TestStringIntConvert(t *testing.T) {
|
||||
@@ -91,42 +94,42 @@ func TestStringIntConvert(t *testing.T) {
|
||||
|
||||
ExpectTrue(t, ok)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, test.i8, int8(127))
|
||||
ExpectEqualValues(t, test.i8, int8(127))
|
||||
|
||||
ok, err = ConvertString(s, reflect.ValueOf(&test.i16))
|
||||
ExpectTrue(t, ok)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, test.i16, int16(127))
|
||||
ExpectEqualValues(t, test.i16, int16(127))
|
||||
|
||||
ok, err = ConvertString(s, reflect.ValueOf(&test.i32))
|
||||
ExpectTrue(t, ok)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, test.i32, int32(127))
|
||||
ExpectEqualValues(t, test.i32, int32(127))
|
||||
|
||||
ok, err = ConvertString(s, reflect.ValueOf(&test.i64))
|
||||
ExpectTrue(t, ok)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, test.i64, int64(127))
|
||||
ExpectEqualValues(t, test.i64, int64(127))
|
||||
|
||||
ok, err = ConvertString(s, reflect.ValueOf(&test.u8))
|
||||
ExpectTrue(t, ok)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, test.u8, uint8(127))
|
||||
ExpectEqualValues(t, test.u8, uint8(127))
|
||||
|
||||
ok, err = ConvertString(s, reflect.ValueOf(&test.u16))
|
||||
ExpectTrue(t, ok)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, test.u16, uint16(127))
|
||||
ExpectEqualValues(t, test.u16, uint16(127))
|
||||
|
||||
ok, err = ConvertString(s, reflect.ValueOf(&test.u32))
|
||||
ExpectTrue(t, ok)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, test.u32, uint32(127))
|
||||
ExpectEqualValues(t, test.u32, uint32(127))
|
||||
|
||||
ok, err = ConvertString(s, reflect.ValueOf(&test.u64))
|
||||
ExpectTrue(t, ok)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, test.u64, uint64(127))
|
||||
ExpectEqualValues(t, test.u64, uint64(127))
|
||||
}
|
||||
|
||||
type testModel struct {
|
||||
@@ -150,19 +153,19 @@ func TestConvertor(t *testing.T) {
|
||||
m := new(testModel)
|
||||
ExpectNoError(t, MapUnmarshalValidate(map[string]any{"Test": "123"}, m))
|
||||
|
||||
ExpectEqual(t, m.Test.foo, 123)
|
||||
ExpectEqual(t, m.Test.bar, "123")
|
||||
ExpectEqualValues(t, m.Test.foo, 123)
|
||||
ExpectEqualValues(t, m.Test.bar, "123")
|
||||
})
|
||||
|
||||
t.Run("int_to_string", func(t *testing.T) {
|
||||
m := new(testModel)
|
||||
ExpectNoError(t, MapUnmarshalValidate(map[string]any{"Test": "123"}, m))
|
||||
|
||||
ExpectEqual(t, m.Test.foo, 123)
|
||||
ExpectEqual(t, m.Test.bar, "123")
|
||||
ExpectEqualValues(t, m.Test.foo, 123)
|
||||
ExpectEqualValues(t, m.Test.bar, "123")
|
||||
|
||||
ExpectNoError(t, MapUnmarshalValidate(map[string]any{"Baz": 123}, m))
|
||||
ExpectEqual(t, m.Baz, "123")
|
||||
ExpectEqualValues(t, m.Baz, "123")
|
||||
})
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
@@ -177,21 +180,21 @@ func TestStringToSlice(t *testing.T) {
|
||||
convertible, err := ConvertString("a,b,c", reflect.ValueOf(&dst))
|
||||
ExpectTrue(t, convertible)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, dst, []string{"a", "b", "c"})
|
||||
ExpectEqualValues(t, dst, []string{"a", "b", "c"})
|
||||
})
|
||||
t.Run("yaml-like", func(t *testing.T) {
|
||||
dst := make([]string, 0)
|
||||
convertible, err := ConvertString("- a\n- b\n- c", reflect.ValueOf(&dst))
|
||||
ExpectTrue(t, convertible)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, dst, []string{"a", "b", "c"})
|
||||
ExpectEqualValues(t, dst, []string{"a", "b", "c"})
|
||||
})
|
||||
t.Run("single-line-yaml-like", func(t *testing.T) {
|
||||
dst := make([]string, 0)
|
||||
convertible, err := ConvertString("- a", reflect.ValueOf(&dst))
|
||||
ExpectTrue(t, convertible)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, dst, []string{"a"})
|
||||
ExpectEqualValues(t, dst, []string{"a"})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -215,7 +218,7 @@ func TestStringToMap(t *testing.T) {
|
||||
convertible, err := ConvertString(" a: b\n c: d", reflect.ValueOf(&dst))
|
||||
ExpectTrue(t, convertible)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, dst, map[string]string{"a": "b", "c": "d"})
|
||||
ExpectEqualValues(t, dst, map[string]string{"a": "b", "c": "d"})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -242,7 +245,7 @@ func TestStringToStruct(t *testing.T) {
|
||||
convertible, err := ConvertString(" A: a\n B: 123", reflect.ValueOf(&dst))
|
||||
ExpectTrue(t, convertible)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, dst, struct {
|
||||
ExpectEqualValues(t, dst, struct {
|
||||
A string
|
||||
B int
|
||||
}{"a", 123})
|
||||
|
||||
@@ -21,50 +21,55 @@ func Must[Result any](r Result, err error) Result {
|
||||
return r
|
||||
}
|
||||
|
||||
func ExpectNoError(t *testing.T, err error) {
|
||||
func ExpectNoError(t *testing.T, err error, msgAndArgs ...any) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, err, msgAndArgs...)
|
||||
}
|
||||
|
||||
func ExpectHasError(t *testing.T, err error) {
|
||||
func ExpectHasError(t *testing.T, err error, msgAndArgs ...any) {
|
||||
t.Helper()
|
||||
require.Error(t, err)
|
||||
require.Error(t, err, msgAndArgs...)
|
||||
}
|
||||
|
||||
func ExpectError(t *testing.T, expected error, err error) {
|
||||
func ExpectError(t *testing.T, expected error, err error, msgAndArgs ...any) {
|
||||
t.Helper()
|
||||
require.ErrorIs(t, err, expected)
|
||||
require.ErrorIs(t, err, expected, msgAndArgs...)
|
||||
}
|
||||
|
||||
func ExpectErrorT[T error](t *testing.T, err error) {
|
||||
func ExpectErrorT[T error](t *testing.T, err error, msgAndArgs ...any) {
|
||||
t.Helper()
|
||||
var errAs T
|
||||
require.ErrorAs(t, err, &errAs)
|
||||
require.ErrorAs(t, err, &errAs, msgAndArgs...)
|
||||
}
|
||||
|
||||
func ExpectEqual[T any](t *testing.T, got T, want T) {
|
||||
func ExpectEqual[T any](t *testing.T, got T, want T, msgAndArgs ...any) {
|
||||
t.Helper()
|
||||
require.EqualValues(t, got, want)
|
||||
require.Equal(t, want, got, msgAndArgs...)
|
||||
}
|
||||
|
||||
func ExpectContains[T any](t *testing.T, got T, wants []T) {
|
||||
func ExpectEqualValues(t *testing.T, got any, want any, msgAndArgs ...any) {
|
||||
t.Helper()
|
||||
require.Contains(t, wants, got)
|
||||
require.EqualValues(t, want, got, msgAndArgs...)
|
||||
}
|
||||
|
||||
func ExpectTrue(t *testing.T, got bool) {
|
||||
func ExpectContains[T any](t *testing.T, got T, wants []T, msgAndArgs ...any) {
|
||||
t.Helper()
|
||||
require.True(t, got)
|
||||
require.Contains(t, wants, got, msgAndArgs...)
|
||||
}
|
||||
|
||||
func ExpectFalse(t *testing.T, got bool) {
|
||||
func ExpectTrue(t *testing.T, got bool, msgAndArgs ...any) {
|
||||
t.Helper()
|
||||
require.False(t, got)
|
||||
require.True(t, got, msgAndArgs...)
|
||||
}
|
||||
|
||||
func ExpectType[T any](t *testing.T, got any) (_ T) {
|
||||
func ExpectFalse(t *testing.T, got bool, msgAndArgs ...any) {
|
||||
t.Helper()
|
||||
require.False(t, got, msgAndArgs...)
|
||||
}
|
||||
|
||||
func ExpectType[T any](t *testing.T, got any, msgAndArgs ...any) (_ T) {
|
||||
t.Helper()
|
||||
_, ok := got.(T)
|
||||
require.True(t, ok)
|
||||
require.True(t, ok, msgAndArgs...)
|
||||
return got.(T)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user