Skip to content

Commit

Permalink
Implement AssignOptionalField
Browse files Browse the repository at this point in the history
  • Loading branch information
lestrrat committed Nov 7, 2022
1 parent 509018a commit 420d192
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 1 deletion.
39 changes: 38 additions & 1 deletion blackmagic.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,43 @@ import (
"reflect"
)

// AssignField is a convenience function to assign a value to
// an optional struct field. In Go, an optional struct field is
// usually denoted by a pointer to T instead of T:
//
// type Object struct {
// Optional *T
// }
//
// This gets a bit cumbersome when you want to assign literals
// or you do not want to worry about taking the address of a
// variable.
//
// Object.Optional = &"foo" // doesn't compile!
//
// Instead you can use this function to do it in one line:
//
// blackmagic.AssignOptionalField(&Object.Optionl, "foo")
func AssignOptionalField(dst, src interface{}) error {
dstRV := reflect.ValueOf(dst)
srcRV := reflect.ValueOf(src)
if dstRV.Kind() != reflect.Pointer || dstRV.Elem().Kind() != reflect.Pointer {
return fmt.Errorf(`dst must be a pointer to a field that is turn a pointer of src (%T)`, src)
}

if !dstRV.Elem().CanSet() {
return fmt.Errorf(`dst (%T) is not assignable`, dstRV.Elem().Interface())
}
if !reflect.PtrTo(srcRV.Type()).AssignableTo(dstRV.Elem().Type()) {
return fmt.Errorf(`cannot assign src (%T) to dst (%T)`, src, dst)
}

ptr := reflect.New(srcRV.Type())
ptr.Elem().Set(srcRV)
dstRV.Elem().Set(ptr)
return nil
}

// AssignIfCompatible is a convenience function to safely
// assign arbitrary values. dst must be a pointer to an
// empty interface, or it must be a pointer to a compatible
Expand All @@ -26,7 +63,7 @@ func AssignIfCompatible(dst, src interface{}) error {

rv := reflect.ValueOf(dst)
if rv.Kind() != reflect.Ptr {
return fmt.Errorf(`argument to AssignIfCompatible() must be a pointer: %T`, dst)
return fmt.Errorf(`destination argument to AssignIfCompatible() must be a pointer: %T`, dst)
}

actualDst := rv.Elem()
Expand Down
13 changes: 13 additions & 0 deletions blackmagic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/lestrrat-go/blackmagic"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestAssignment(t *testing.T) {
Expand Down Expand Up @@ -59,3 +60,15 @@ func TestAssignment(t *testing.T) {
})
}
}

func TestAssignOptionalField(t *testing.T) {
var f struct {
Foo *string
Bar *int
}

require.NoError(t, blackmagic.AssignOptionalField(&f.Foo, "Hello"), `blackmagic.AssignOptionalField should succeed`)
require.Equal(t, *(f.Foo), "Hello")
require.NoError(t, blackmagic.AssignOptionalField(&f.Bar, 1), `blackmagic.AssignOptionalField should succeed`)
require.Equal(t, *(f.Bar), 1)
}

0 comments on commit 420d192

Please sign in to comment.