Skip to content

Commit

Permalink
Callbacks for the new struct conversion: BeforeConvert{To,From}Callba…
Browse files Browse the repository at this point in the history
…ck, AfterConvert{To,From}Callback (#11)

* Callbacks for the new struct conversion: BeforeConvert{To,From}Callback, AfterConvert{To,From}Callback

* remove callback count
  • Loading branch information
soranoba authored Apr 9, 2024
1 parent c0758e7 commit ec264a9
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 18 deletions.
30 changes: 30 additions & 0 deletions callbacks.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,41 @@
package henge

// BeforeCallback is a callback that is executed before the conversion from a struct to the struct.
// Deprecated: Use BeforeConvertFromCallback instead.
type BeforeCallback interface {
BeforeConvert(src interface{}, store InstanceStore) error
}

// AfterCallback is a callback that is executed after the conversion from a struct to the struct.
// Deprecated: Use AfterConvertFromCallback instead.
type AfterCallback interface {
AfterConvert(src interface{}, store InstanceStore) error
}

// BeforeConvertFromCallback is a callback that is executed before the conversion from a struct to the struct.
// To define structures across packages, you need to define them in the package's struct that imports other packages.
// Within structures of the same package, you must use BeforeConvertFromCallback. It cannot be used simultaneously with BeforeConvertToCallback.
type BeforeConvertFromCallback interface {
BeforeConvertFrom(src interface{}, store InstanceStore) error
}

// AfterConvertFromCallback is a callback that is executed after the conversion from a struct to the struct.
// To define structures across packages, you need to define them in the package's struct that imports other packages.
// Within structures of the same package, you must use AfterConvertFromCallback. It cannot be used simultaneously with AfterConvertToCallback.
type AfterConvertFromCallback interface {
AfterConvertFrom(src interface{}, store InstanceStore) error
}

// BeforeConvertToCallback is a callback that is executed before the conversion from the struct to a struct.
// To define structures across packages, you need to define them in the package's struct that imports other packages.
// Within structures of the same package, you must use BeforeConvertFromCallback. It cannot be used simultaneously with BeforeConvertToCallback.
type BeforeConvertToCallback interface {
BeforeConvertTo(dst interface{}, store InstanceStore) error
}

// AfterConvertToCallback is a callback that is executed after the conversion from the struct to a struct.
// To define structures across packages, you need to define them in the package's struct that imports other packages.
// Within structures of the same package, you must use AfterConvertFromCallback. It cannot be used simultaneously with AfterConvertToCallback.
type AfterConvertToCallback interface {
AfterConvertTo(dst interface{}, store InstanceStore) error
}
24 changes: 23 additions & 1 deletion struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ func (c *StructConverter) convert(outV reflect.Value) error {
goto failed
}
}
if beforeCallback, ok := elemOutV.Addr().Interface().(BeforeConvertFromCallback); ok {
if err = beforeCallback.BeforeConvertFrom(c.value, c.baseConverter); err != nil {
goto failed
}
}
if beforeCallback, ok := reflect.ValueOf(c.value).Interface().(BeforeConvertToCallback); ok {
if err = beforeCallback.BeforeConvertTo(elemOutV.Addr().Interface(), c.baseConverter); err != nil {
goto failed
}
}

switch elemOutV.Kind() {
case reflect.Struct:
Expand Down Expand Up @@ -154,7 +164,19 @@ func (c *StructConverter) convert(outV reflect.Value) error {
}

if afterCallback, ok := elemOutV.Addr().Interface().(AfterCallback); ok {
err = afterCallback.AfterConvert(c.value, c.baseConverter)
if err = afterCallback.AfterConvert(c.value, c.baseConverter); err != nil {
goto failed
}
}
if afterCallback, ok := elemOutV.Addr().Interface().(AfterConvertFromCallback); ok {
if err = afterCallback.AfterConvertFrom(c.value, c.baseConverter); err != nil {
goto failed
}
}
if afterCallback, ok := reflect.ValueOf(c.value).Interface().(AfterConvertToCallback); ok {
if err = afterCallback.AfterConvertTo(elemOutV.Addr().Interface(), c.baseConverter); err != nil {
goto failed
}
}

failed:
Expand Down
100 changes: 83 additions & 17 deletions tests/struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,27 +222,50 @@ func TestStructConverter_Nil(t *testing.T) {
}

type BeforeCallbackT struct {
Name string
Age int
Age int
Company string
}

func (t *BeforeCallbackT) BeforeConvertTo(dst interface{}, store henge.InstanceStore) error {
if u, ok := dst.(*User); ok {
name, _ := store.InstanceGet("name").(string)
u.Name = name
u.Age = 1000
return nil
}
return errors.New("failed")
}

func (t *BeforeCallbackT) BeforeConvert(src interface{}, store henge.InstanceStore) error {
func (t *BeforeCallbackT) BeforeConvertFrom(src interface{}, store henge.InstanceStore) error {
if _, ok := src.(User); ok {
company, _ := store.InstanceGet("company").(string)
t.Company = company
t.Age = 1000
return nil
}
return errors.New("failed")
}

func TestBeforeCallbackT(t *testing.T) {
var _ henge.BeforeCallback = &BeforeCallbackT{}
var _ henge.BeforeConvertFromCallback = &BeforeCallbackT{}
var _ henge.BeforeConvertToCallback = &BeforeCallbackT{}
}

type AfterCallbackT struct {
Name string
Age int
}

func (t *AfterCallbackT) AfterConvert(src interface{}, store henge.InstanceStore) error {
func (t *AfterCallbackT) AfterConvertTo(dst interface{}, store henge.InstanceStore) error {
if u, ok := dst.(*User); ok {
diff, _ := store.InstanceGet("diff").(int)
u.Age = t.Age - diff
return nil
}
return errors.New("failed")
}

func (t *AfterCallbackT) AfterConvertFrom(src interface{}, store henge.InstanceStore) error {
if u, ok := src.(User); ok {
diff, _ := store.InstanceGet("diff").(int)
t.Age = u.Age + diff
Expand All @@ -252,7 +275,8 @@ func (t *AfterCallbackT) AfterConvert(src interface{}, store henge.InstanceStore
}

func TestAfterCallbackT(t *testing.T) {
var _ henge.AfterCallback = &AfterCallbackT{}
var _ henge.AfterConvertFromCallback = &AfterCallbackT{}
var _ henge.AfterConvertToCallback = &AfterCallbackT{}
}

func TestStructConverter_Callbacks(t *testing.T) {
Expand All @@ -263,8 +287,10 @@ func TestStructConverter_Callbacks(t *testing.T) {

// NOTE: BeforeCallbackT converts only from User{} and returns an error otherwise.
out1 := BeforeCallbackT{}
assert.NoError(t, henge.New(user).Struct().Convert(&out1))
assert.Equal(t, user.Name, out1.Name)
conv := henge.New(user)
conv.InstanceSet("company", "skyplace inc.")
assert.NoError(t, conv.Struct().Convert(&out1))
assert.Equal(t, "skyplace inc.", out1.Company)
assert.Equal(t, user.Age, out1.Age)

out1 = BeforeCallbackT{}
Expand All @@ -280,26 +306,66 @@ func TestStructConverter_Callbacks(t *testing.T) {
"Failed to convert from struct { Name string } to tests.BeforeCallbackT: fields=, value=struct { Name string }{Name:\"Bob\"}, error=failed",
)

// NOTE: &BeforeCallbackT{} can convert to User with BeforeConvertTo.
beforeT := BeforeCallbackT{
Age: user.Age,
Company: "skyplace inc.",
}
conv = henge.New(&beforeT)
conv.InstanceSet("name", "Alice")
var out2 User
assert.NoError(t, conv.Struct().Convert(&out2))
assert.Equal(t, "Alice", out2.Name) // BeforeConvertTo has been executed.
assert.Equal(t, beforeT.Age, out2.Age)

// NOTE: BeforeCallbackT{} can convert to User without BeforeConvertTo.
out2 = User{}
conv = henge.New(beforeT)
conv.InstanceSet("name", "Alice")
assert.NoError(t, conv.Struct().Convert(&out2))
assert.Equal(t, "", out2.Name) // BeforeConvertTo has not been executed.
assert.Equal(t, beforeT.Age, out2.Age)

// NOTE: AfterCallbackT converts only from User{} and returns an error otherwise.
out2 := AfterCallbackT{}
conv := henge.New(user)
out3 := AfterCallbackT{}
conv = henge.New(user)
conv.InstanceSet("diff", 23)
assert.NoError(t, conv.Struct().Convert(&out2))
assert.Equal(t, user.Name, out2.Name)
assert.Equal(t, 48, out2.Age)
assert.NoError(t, conv.Struct().Convert(&out3))
assert.Equal(t, user.Name, out3.Name)
assert.Equal(t, 48, out3.Age)

out2 = AfterCallbackT{}
out3 = AfterCallbackT{}
assert.EqualError(
t,
henge.New(&user).Struct().Convert(&out2),
henge.New(&user).Struct().Convert(&out3),
"Failed to convert from *tests.User to tests.AfterCallbackT: fields=, value=&tests.User{Name:\"Alice\", Age:25}, error=failed",
)
out2 = AfterCallbackT{}
out3 = AfterCallbackT{}
assert.EqualError(
t,
henge.New(struct{ Name string }{"Carol"}).Convert(&out2),
henge.New(struct{ Name string }{"Carol"}).Convert(&out3),
"Failed to convert from struct { Name string } to tests.AfterCallbackT: fields=, value=struct { Name string }{Name:\"Carol\"}, error=failed",
)

// NOTE: &AfterCallbackT{} can convert to User with AfterConvertTo.
afterT := AfterCallbackT{
Name: user.Name,
Age: user.Age + 23,
}
conv = henge.New(&afterT)
conv.InstanceSet("diff", 23)
var out4 User
assert.NoError(t, conv.Struct().Convert(&out4))
assert.Equal(t, afterT.Name, out4.Name)
assert.Equal(t, user.Age, out4.Age) // AfterConvertTo has been executed.

// NOTE: AfterCallbackT{} can convert to User without AfterConvertTo.
out4 = User{}
conv = henge.New(afterT)
conv.InstanceSet("diff", 23)
assert.NoError(t, conv.Struct().Convert(&out4))
assert.Equal(t, afterT.Name, out4.Name)
assert.Equal(t, afterT.Age, out4.Age) // AfterConvertTo has not been executed.
}

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

0 comments on commit ec264a9

Please sign in to comment.