Skip to content

Commit

Permalink
Add support for pointer of pointer (etc.pp.), Fromer, and additional …
Browse files Browse the repository at this point in the history
…test cases
  • Loading branch information
ntnn committed Jan 15, 2024
1 parent 828793c commit fa2d499
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 119 deletions.
84 changes: 83 additions & 1 deletion map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,35 @@ func TestMap_To_Tag(t *testing.T) {
assert.Equal(t, "sic dolor amet", ts2.Varying)
}

func TestMap_To_Embedded(t *testing.T) {
func TestMap_To_AnonStructPtr(t *testing.T) {
type testStruct struct {
A int
B string `dataparse:"msg"`
C *struct {
CA int
CB string `dataparse:"submsg"`
} `dataparse:"sub"`
}

m, err := NewMap(map[string]any{
"A": 3,
"msg": "outer",
"sub": map[string]any{
"CA": 15,
"submsg": "inner",
},
})
require.Nil(t, err)

var ts testStruct
require.Nil(t, m.To(&ts))
assert.Equal(t, 3, ts.A)
assert.Equal(t, "outer", ts.B)
assert.Equal(t, 15, ts.C.CA)
assert.Equal(t, "inner", ts.C.CB)
}

func TestMap_To_AnonStruct(t *testing.T) {
type testStruct struct {
A int
B string `dataparse:"msg"`
Expand Down Expand Up @@ -390,3 +418,57 @@ func TestMap_To_DotNotation(t *testing.T) {
assert.Equal(t, 5, ts.A)
assert.Equal(t, "lorem ipsum", ts.B)
}

type testMapValueToConst int

const (
testMapValueToConst1 testMapValueToConst = iota
testMapValueToConst2
testMapValueToConst3
testMapValueToConst4
testMapValueToConst5
)

func (c *testMapValueToConst) From(v Value) error {
i, err := v.Int()
if err != nil {
return err
}

*c = testMapValueToConst(i)
return nil
}

func TestMap_To_Const(t *testing.T) {
type testStruct struct {
A testMapValueToConst `dataparse:"a.b"`
}

m, err := NewMap(map[string]any{
"a": map[string]any{
"b": 2,
},
})
require.Nil(t, err)

var ts testStruct
require.Nil(t, m.To(&ts))
assert.Equal(t, testMapValueToConst3, ts.A)
}

func TestMap_To_PtrConst(t *testing.T) {
type testStruct struct {
A *testMapValueToConst `dataparse:"a.b"`
}

m, err := NewMap(map[string]any{
"a": map[string]any{
"b": 2,
},
})
require.Nil(t, err)

var ts testStruct
require.Nil(t, m.To(&ts))
assert.Equal(t, testMapValueToConst3, *ts.A)
}
209 changes: 91 additions & 118 deletions value.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,135 +25,108 @@ func (v Value) IsNil() bool {
return v.Data == nil
}

type Fromer interface {
From(Value) error
}

// To transforms the stored data into the target type and returns any
// occurring errors.
//
// The passed value must be a pointer.
//
// To utilizes the various transformation methods and returns their
// errors.
//
// If the parameter satisfies the Fromer interface it will be used to
// set the value.
func (v Value) To(other any) error {
if fromer, ok := other.(Fromer); ok {
return fromer.From(v)
}

target := reflect.ValueOf(other)

if target.Kind() != reflect.Pointer {
return ErrValueIsNotPointer
}

// dereference until the target is a pointer but the value pointer
// to is not
// for target.Kind() == reflect.Pointer && target.Elem().Kind() == reflect.Pointer {
for target.Kind() == reflect.Pointer {
if target.IsNil() {
// initialize pointer with a valid value
target.Set(reflect.New(target.Type().Elem()))
}
// handle pointers to constants or structs that satisfy the
// Fromer interface
if fromer, ok := target.Interface().(Fromer); ok {
return fromer.From(v)
}
target = target.Elem()
}

// If the passed value is a pointer to a struct try
// converting Value to map and call .To
if target.Kind() == reflect.Struct {
m, err := v.Map()
if err != nil {
return err
}
return m.To(other)
}

var newValue any
var err error
switch typed := other.(type) {
case *string:
*typed, err = v.String()
case **string:
var p string
p, err = v.String()
*typed = &p
case *[]string:
*typed, err = v.ListString(",")
case **[]string:
var p []string
p, err = v.ListString(",")
*typed = &p
case *int:
*typed, err = v.Int()
case **int:
var p int
p, err = v.Int()
*typed = &p
case *int8:
*typed, err = v.Int8()
case **int8:
var p int8
p, err = v.Int8()
*typed = &p
case *int16:
*typed, err = v.Int16()
case **int16:
var p int16
p, err = v.Int16()
*typed = &p
case *int32:
*typed, err = v.Int32()
case **int32:
var p int32
p, err = v.Int32()
*typed = &p
case *int64:
*typed, err = v.Int64()
case **int64:
var p int64
p, err = v.Int64()
*typed = &p
case *uint:
*typed, err = v.Uint()
case **uint:
var p uint
p, err = v.Uint()
*typed = &p
case *uint8:
*typed, err = v.Uint8()
case **uint8:
var p uint8
p, err = v.Uint8()
*typed = &p
case *uint16:
*typed, err = v.Uint16()
case **uint16:
var p uint16
p, err = v.Uint16()
*typed = &p
case *uint32:
*typed, err = v.Uint32()
case **uint32:
var p uint32
p, err = v.Uint32()
*typed = &p
case *uint64:
*typed, err = v.Uint64()
case **uint64:
var p uint64
p, err = v.Uint64()
*typed = &p
case *float32:
*typed, err = v.Float32()
case **float32:
var p float32
p, err = v.Float32()
*typed = &p
case *float64:
*typed, err = v.Float64()
case **float64:
var p float64
p, err = v.Float64()
*typed = &p
case *bool:
*typed, err = v.Bool()
case **bool:
var p bool
p, err = v.Bool()
*typed = &p
case *net.IP:
*typed, err = v.IP()
case **net.IP:
var p net.IP
p, err = v.IP()
*typed = &p
case *time.Time:
*typed, err = v.Time()
case **time.Time:
var t time.Time
t, err = v.Time()
*typed = &t
// case *byte:
// *typed = v.MustByte()
// case *[]byte:
// *typed = v.MustBytes()

switch typed := target.Interface().(type) {
case string:
newValue, err = v.String()
case []string:
newValue, err = v.ListString(",")
case int:
newValue, err = v.Int()
case int8:
newValue, err = v.Int8()
case int16:
newValue, err = v.Int16()
case int32:
newValue, err = v.Int32()
case int64:
newValue, err = v.Int64()
case uint:
newValue, err = v.Uint()
case uint8:
newValue, err = v.Uint8()
case uint16:
newValue, err = v.Uint16()
case uint32:
newValue, err = v.Uint32()
case uint64:
newValue, err = v.Uint64()
case float32:
newValue, err = v.Float32()
case float64:
newValue, err = v.Float64()
case bool:
newValue, err = v.Bool()
case net.IP:
newValue, err = v.IP()
case time.Time:
newValue, err = v.Time()
// case *byte:
// *typed = v.MustByte()
// case *[]byte:
// *typed = v.MustBytes()
default:
// If the passed value is a pointer to a struct try
// converting Value to map and call .To
if reflect.ValueOf(other).Elem().Kind() == reflect.Struct {
m, err := v.Map()
if err != nil {
return err
}
return m.To(other)
}
return fmt.Errorf("dataparse: unhandled type: %T", other)
return fmt.Errorf("dataparse: unhandled type: %T", typed)
}
return err
if err != nil {
return err
}

target.Set(reflect.ValueOf(newValue))
return nil
}

// List returns the underlying data as a slice of Values.
Expand Down
32 changes: 32 additions & 0 deletions value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,39 @@ func (v *Value) Fuzz(c fuzz.Continue) {
}
}

type testValueToConst int

const (
testValueToConst1 testValueToConst = iota
testValueToConst2
testValueToConst3
testValueToConst4
testValueToConst5
)

func (c *testValueToConst) From(v Value) error {
i, err := v.Int()
if err != nil {
return err
}

*c = testValueToConst(i)
return nil
}

func TestValue_To(t *testing.T) {
// test passing pointer
v := NewValue("test")
s := ""
require.Nil(t, v.To(&s))
assert.Equal(t, "test", s)

// test passing pointer of pointer
var s2 *string
require.Nil(t, v.To(&s2))
assert.Equal(t, "test", *s2)

// test some random types
v = NewValue(5)
var i int = 0
require.Nil(t, v.To(&i))
Expand All @@ -95,6 +122,11 @@ func TestValue_To(t *testing.T) {
var ip net.IP
require.Nil(t, v.To(&ip))
assert.Equal(t, net.ParseIP("192.168.1.1"), ip)

v = NewValue(2)
var c testValueToConst
require.Nil(t, v.To(&c))
assert.Equal(t, testValueToConst3, c)
}

func TestValue_List(t *testing.T) {
Expand Down

0 comments on commit fa2d499

Please sign in to comment.