feat(codec): Add collections value codec for interfaces. (#15898)
Co-authored-by: unknown unknown <unknown@unknown>
This commit is contained in:
parent
294027cf2d
commit
295c261b18
@ -1,6 +1,9 @@
|
||||
package codec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
gogotypes "github.com/cosmos/gogoproto/types"
|
||||
|
||||
"cosmossdk.io/collections"
|
||||
@ -79,3 +82,47 @@ func (c collValue[T, PT]) Stringify(value T) string {
|
||||
func (c collValue[T, PT]) ValueType() string {
|
||||
return "gogoproto/" + proto.MessageName(PT(new(T)))
|
||||
}
|
||||
|
||||
// CollInterfaceValue instantiates a new collections.ValueCodec for a generic
|
||||
// interface value. The codec must be able to marshal and unmarshal the
|
||||
// interface.
|
||||
func CollInterfaceValue[T proto.Message](codec BinaryCodec) collcodec.ValueCodec[T] {
|
||||
var x T // assertion
|
||||
if reflect.TypeOf(&x).Elem().Kind() != reflect.Interface {
|
||||
panic("CollInterfaceValue can only be used with interface types")
|
||||
}
|
||||
return collInterfaceValue[T]{codec.(Codec)}
|
||||
}
|
||||
|
||||
type collInterfaceValue[T proto.Message] struct {
|
||||
codec Codec
|
||||
}
|
||||
|
||||
func (c collInterfaceValue[T]) Encode(value T) ([]byte, error) {
|
||||
return c.codec.MarshalInterface(value)
|
||||
}
|
||||
|
||||
func (c collInterfaceValue[T]) Decode(b []byte) (T, error) {
|
||||
var value T
|
||||
err := c.codec.UnmarshalInterface(b, &value)
|
||||
return value, err
|
||||
}
|
||||
|
||||
func (c collInterfaceValue[T]) EncodeJSON(value T) ([]byte, error) {
|
||||
return c.codec.MarshalInterfaceJSON(value)
|
||||
}
|
||||
|
||||
func (c collInterfaceValue[T]) DecodeJSON(b []byte) (T, error) {
|
||||
var value T
|
||||
err := c.codec.UnmarshalInterfaceJSON(b, &value)
|
||||
return value, err
|
||||
}
|
||||
|
||||
func (c collInterfaceValue[T]) Stringify(value T) string {
|
||||
return value.String()
|
||||
}
|
||||
|
||||
func (c collInterfaceValue[T]) ValueType() string {
|
||||
var t T
|
||||
return fmt.Sprintf("%T", t)
|
||||
}
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package codec
|
||||
package codec_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"cosmossdk.io/collections/colltest"
|
||||
@ -11,22 +14,22 @@ import (
|
||||
)
|
||||
|
||||
func TestCollectionsCorrectness(t *testing.T) {
|
||||
cdc := NewProtoCodec(codectypes.NewInterfaceRegistry())
|
||||
t.Run("CollValue", func(t *testing.T) {
|
||||
colltest.TestValueCodec(t, CollValue[gogotypes.UInt64Value](cdc), gogotypes.UInt64Value{
|
||||
cdc := codec.NewProtoCodec(codectypes.NewInterfaceRegistry())
|
||||
colltest.TestValueCodec(t, codec.CollValue[gogotypes.UInt64Value](cdc), gogotypes.UInt64Value{
|
||||
Value: 500,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("BoolValue", func(t *testing.T) {
|
||||
colltest.TestValueCodec(t, BoolValue, true)
|
||||
colltest.TestValueCodec(t, BoolValue, false)
|
||||
colltest.TestValueCodec(t, codec.BoolValue, true)
|
||||
colltest.TestValueCodec(t, codec.BoolValue, false)
|
||||
|
||||
// asserts produced bytes are equal
|
||||
valueAssert := func(b bool) {
|
||||
wantBytes, err := (&gogotypes.BoolValue{Value: b}).Marshal()
|
||||
require.NoError(t, err)
|
||||
gotBytes, err := BoolValue.Encode(b)
|
||||
gotBytes, err := codec.BoolValue.Encode(b)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, wantBytes, gotBytes)
|
||||
}
|
||||
@ -34,4 +37,18 @@ func TestCollectionsCorrectness(t *testing.T) {
|
||||
valueAssert(true)
|
||||
valueAssert(false)
|
||||
})
|
||||
|
||||
t.Run("CollInterfaceValue", func(t *testing.T) {
|
||||
cdc := codec.NewProtoCodec(codectypes.NewInterfaceRegistry())
|
||||
cdc.InterfaceRegistry().RegisterInterface("animal", (*testdata.Animal)(nil), &testdata.Dog{}, &testdata.Cat{})
|
||||
valueCodec := codec.CollInterfaceValue[testdata.Animal](cdc)
|
||||
|
||||
colltest.TestValueCodec[testdata.Animal](t, valueCodec, &testdata.Dog{Name: "Doggo"})
|
||||
colltest.TestValueCodec[testdata.Animal](t, valueCodec, &testdata.Cat{Moniker: "Kitty"})
|
||||
|
||||
// assert if used with a non interface type it yields a panic.
|
||||
require.Panics(t, func() {
|
||||
codec.CollInterfaceValue[*testdata.Dog](cdc)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -1063,3 +1063,57 @@ func (k Keeper) getNextAccountNumber() uint64 {
|
||||
return 0
|
||||
}
|
||||
```
|
||||
|
||||
## Collections with interfaces as values
|
||||
|
||||
Although cosmos-sdk is shifting away from the usage of interface registry, there are still some places where it is used.
|
||||
In order to support old code, we have to support collections with interface values.
|
||||
|
||||
The generic `codec.CollValue` is not able to handle interface values, so we need to use a special type `codec.CollValueInterface`.
|
||||
`codec.CollValueInterface` takes a `codec.BinaryCodec` as an argument, and uses it to marshal and unmarshal values as interfaces.
|
||||
The `codec.CollValueInterface` lives in the `codec` package, whose import path is `github.com/cosmos/cosmos-sdk/codec`.
|
||||
|
||||
### Instantiating Collections with interface values
|
||||
|
||||
In order to instantiate a collection with interface values, we need to use `codec.CollValueInterface` instead of `codec.CollValue`.
|
||||
|
||||
```go
|
||||
package example
|
||||
|
||||
import (
|
||||
"cosmossdk.io/collections"
|
||||
storetypes "cosmossdk.io/store/types"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
)
|
||||
|
||||
var AccountsPrefix = collections.NewPrefix(0)
|
||||
|
||||
type Keeper struct {
|
||||
Schema collections.Schema
|
||||
Accounts *collections.Map[sdk.AccAddress, sdk.AccountI]
|
||||
}
|
||||
|
||||
func NewKeeper(cdc codec.BinaryCodec, storeKey *storetypes.KVStoreKey) Keeper {
|
||||
sb := collections.NewSchemaBuilder(sdk.OpenKVStore(storeKey))
|
||||
return Keeper{
|
||||
Accounts: collections.NewMap(
|
||||
sb, AccountsPrefix, "accounts",
|
||||
sdk.AccAddressKey, codec.CollInterfaceValue[sdk.AccountI](cdc),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (k Keeper) SaveBaseAccount(ctx sdk.Context, account authtypes.BaseAccount) error {
|
||||
return k.Accounts.Set(ctx, account.GetAddress(), account)
|
||||
}
|
||||
|
||||
func (k Keeper) SaveModuleAccount(ctx sdk.Context, account authtypes.ModuleAccount) error {
|
||||
return k.Accounts.Set(ctx, account.GetAddress(), account)
|
||||
}
|
||||
|
||||
func (k Keeper) GetAccount(ctx sdk.context, addr sdk.AccAddress) (sdk.AccountI, error) {
|
||||
return k.Accounts.Get(ctx, addr)
|
||||
}
|
||||
```
|
||||
|
||||
Loading…
Reference in New Issue
Block a user