Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add amino compatibility layer for proto Any #6151

Merged
merged 9 commits into from
May 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
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