Skip to content

Commit

Permalink
Add amino compatibility layer for proto Any (#6151)
Browse files Browse the repository at this point in the history
* WIP on Any amino compatibility layer

* Add tests & JSON

* Refactor Marshal/UnmarshalAny

* remove extra test

* Add support for nested Any's

* Add docs

* Update codec/any_test.go

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
  • Loading branch information
aaronc and fedekunze authored May 6, 2020
1 parent f3e3a30 commit 9d022c1
Show file tree
Hide file tree
Showing 14 changed files with 939 additions and 94 deletions.
70 changes: 67 additions & 3 deletions codec/amino_codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,52 +16,116 @@ func NewAminoCodec(amino *Codec) Marshaler {
return &AminoCodec{amino}
}

func (ac *AminoCodec) marshalAnys(o ProtoMarshaler) error {
return types.UnpackInterfaces(o, types.AminoPacker{Cdc: ac.amino})
}

func (ac *AminoCodec) unmarshalAnys(o ProtoMarshaler) error {
return types.UnpackInterfaces(o, types.AminoUnpacker{Cdc: ac.amino})
}

func (ac *AminoCodec) jsonMarshalAnys(o interface{}) error {
return types.UnpackInterfaces(o, types.AminoJSONPacker{Cdc: ac.amino})
}

func (ac *AminoCodec) jsonUnmarshalAnys(o interface{}) error {
return types.UnpackInterfaces(o, types.AminoJSONUnpacker{Cdc: ac.amino})
}

func (ac *AminoCodec) MarshalBinaryBare(o ProtoMarshaler) ([]byte, error) {
err := ac.marshalAnys(o)
if err != nil {
return nil, err
}
return ac.amino.MarshalBinaryBare(o)
}

func (ac *AminoCodec) MustMarshalBinaryBare(o ProtoMarshaler) []byte {
err := ac.marshalAnys(o)
if err != nil {
panic(err)
}
return ac.amino.MustMarshalBinaryBare(o)
}

func (ac *AminoCodec) MarshalBinaryLengthPrefixed(o ProtoMarshaler) ([]byte, error) {
err := ac.marshalAnys(o)
if err != nil {
return nil, err
}
return ac.amino.MarshalBinaryLengthPrefixed(o)
}

func (ac *AminoCodec) MustMarshalBinaryLengthPrefixed(o ProtoMarshaler) []byte {
err := ac.marshalAnys(o)
if err != nil {
panic(err)
}
return ac.amino.MustMarshalBinaryLengthPrefixed(o)
}

func (ac *AminoCodec) UnmarshalBinaryBare(bz []byte, ptr ProtoMarshaler) error {
return ac.amino.UnmarshalBinaryBare(bz, ptr)
err := ac.amino.UnmarshalBinaryBare(bz, ptr)
if err != nil {
return err
}
return ac.unmarshalAnys(ptr)
}

func (ac *AminoCodec) MustUnmarshalBinaryBare(bz []byte, ptr ProtoMarshaler) {
ac.amino.MustUnmarshalBinaryBare(bz, ptr)
err := ac.unmarshalAnys(ptr)
if err != nil {
panic(err)
}
}

func (ac *AminoCodec) UnmarshalBinaryLengthPrefixed(bz []byte, ptr ProtoMarshaler) error {
return ac.amino.UnmarshalBinaryLengthPrefixed(bz, ptr)
err := ac.amino.UnmarshalBinaryLengthPrefixed(bz, ptr)
if err != nil {
return err
}
return ac.unmarshalAnys(ptr)
}

func (ac *AminoCodec) MustUnmarshalBinaryLengthPrefixed(bz []byte, ptr ProtoMarshaler) {
ac.amino.MustUnmarshalBinaryLengthPrefixed(bz, ptr)
err := ac.unmarshalAnys(ptr)
if err != nil {
panic(err)
}
}

func (ac *AminoCodec) MarshalJSON(o interface{}) ([]byte, error) {
err := ac.jsonMarshalAnys(o)
if err != nil {
return nil, err
}
return ac.amino.MarshalJSON(o)
}

func (ac *AminoCodec) MustMarshalJSON(o interface{}) []byte {
err := ac.jsonMarshalAnys(o)
if err != nil {
panic(err)
}
return ac.amino.MustMarshalJSON(o)
}

func (ac *AminoCodec) UnmarshalJSON(bz []byte, ptr interface{}) error {
return ac.amino.UnmarshalJSON(bz, ptr)
err := ac.amino.UnmarshalJSON(bz, ptr)
if err != nil {
return err
}
return ac.jsonUnmarshalAnys(ptr)
}

func (ac *AminoCodec) MustUnmarshalJSON(bz []byte, ptr interface{}) {
ac.amino.MustUnmarshalJSON(bz, ptr)
err := ac.jsonUnmarshalAnys(ptr)
if err != nil {
panic(err)
}
}

func (*AminoCodec) UnpackAny(*types.Any, interface{}) error {
Expand Down
13 changes: 13 additions & 0 deletions codec/amino_codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package codec_test
import (
"testing"

"github.com/cosmos/cosmos-sdk/codec/types"

"github.com/stretchr/testify/require"
amino "github.com/tendermint/go-amino"

Expand All @@ -21,6 +23,9 @@ func createTestCodec() *amino.Codec {
}

func TestAminoCodec(t *testing.T) {
any, err := types.NewAnyWithValue(&testdata.Dog{Name: "rufus"})
require.NoError(t, err)

testCases := []struct {
name string
codec codec.Marshaler
Expand All @@ -45,6 +50,14 @@ func TestAminoCodec(t *testing.T) {
false,
true,
},
{
"any marshaling",
codec.NewAminoCodec(createTestCodec()),
&testdata.HasAnimal{Animal: any},
&testdata.HasAnimal{Animal: any},
false,
false,
},
}

for _, tc := range testCases {
Expand Down
44 changes: 44 additions & 0 deletions codec/any.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package codec

import (
"fmt"

"github.com/gogo/protobuf/proto"

"github.com/cosmos/cosmos-sdk/codec/types"
)

// MarshalAny is a convenience function for packing the provided value in an
// Any and then proto marshaling it to bytes
func MarshalAny(m Marshaler, x interface{}) ([]byte, error) {
msg, ok := x.(proto.Message)
if !ok {
return nil, fmt.Errorf("can't proto marshal %T", x)
}

any := &types.Any{}
err := any.Pack(msg)
if err != nil {
return nil, err
}

return m.MarshalBinaryBare(any)
}

// UnmarshalAny is a convenience function for proto unmarshaling an Any from
// bz and then unpacking it to the interface pointer passed in as iface using
// the provided AnyUnpacker or returning an error
//
// Ex:
// var x MyInterface
// err := UnmarshalAny(unpacker, &x, bz)
func UnmarshalAny(m Marshaler, iface interface{}, bz []byte) error {
any := &types.Any{}

err := m.UnmarshalBinaryBare(bz, any)
if err != nil {
return err
}

return m.UnpackAny(any, iface)
}
54 changes: 54 additions & 0 deletions codec/any_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package codec

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/cosmos/cosmos-sdk/codec/testdata"
"github.com/cosmos/cosmos-sdk/codec/types"
)

func NewTestInterfaceRegistry() types.InterfaceRegistry {
registry := types.NewInterfaceRegistry()
registry.RegisterInterface("Animal", (*testdata.Animal)(nil))
registry.RegisterImplementations(
(*testdata.Animal)(nil),
&testdata.Dog{},
&testdata.Cat{},
)
return registry
}

func TestMarshalAny(t *testing.T) {
registry := types.NewInterfaceRegistry()

cdc := NewProtoCodec(registry)

kitty := &testdata.Cat{Moniker: "Kitty"}
bz, err := MarshalAny(cdc, kitty)
require.NoError(t, err)

var animal testdata.Animal

// empty registry should fail
err = UnmarshalAny(cdc, &animal, bz)
require.Error(t, err)

// wrong type registration should fail
registry.RegisterImplementations((*testdata.Animal)(nil), &testdata.Dog{})
err = UnmarshalAny(cdc, &animal, bz)
require.Error(t, err)

// should pass
registry = NewTestInterfaceRegistry()
cdc = NewProtoCodec(registry)
err = UnmarshalAny(cdc, &animal, bz)
require.NoError(t, err)
require.Equal(t, kitty, animal)

// nil should fail
registry = NewTestInterfaceRegistry()
err = UnmarshalAny(cdc, nil, bz)
require.Error(t, err)
}
44 changes: 44 additions & 0 deletions codec/testdata/animal.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,47 @@ func (m HasAnimal) UnpackInterfaces(unpacker types.AnyUnpacker) error {
var animal Animal
return unpacker.UnpackAny(m.Animal, &animal)
}

type HasAnimalI interface {
TheAnimal() Animal
}

var _ HasAnimalI = &HasAnimal{}

func (m HasAnimal) TheAnimal() Animal {
return m.Animal.GetCachedValue().(Animal)
}

type HasHasAnimalI interface {
TheHasAnimal() HasAnimalI
}

var _ HasHasAnimalI = &HasHasAnimal{}

func (m HasHasAnimal) TheHasAnimal() HasAnimalI {
return m.HasAnimal.GetCachedValue().(HasAnimalI)
}

var _ types.UnpackInterfacesMessage = HasHasAnimal{}

func (m HasHasAnimal) UnpackInterfaces(unpacker types.AnyUnpacker) error {
var animal HasAnimalI
return unpacker.UnpackAny(m.HasAnimal, &animal)
}

type HasHasHasAnimalI interface {
TheHasHasAnimal() HasHasAnimalI
}

var _ HasHasAnimalI = &HasHasAnimal{}

func (m HasHasHasAnimal) TheHasHasAnimal() HasHasAnimalI {
return m.HasHasAnimal.GetCachedValue().(HasHasAnimalI)
}

var _ types.UnpackInterfacesMessage = HasHasHasAnimal{}

func (m HasHasHasAnimal) UnpackInterfaces(unpacker types.AnyUnpacker) error {
var animal HasHasAnimalI
return unpacker.UnpackAny(m.HasHasAnimal, &animal)
}
Loading

0 comments on commit 9d022c1

Please sign in to comment.