279 lines
8.4 KiB
Go
279 lines
8.4 KiB
Go
package types
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/gogo/protobuf/jsonpb"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
)
|
|
|
|
// AnyUnpacker is an interface which allows safely unpacking types packed
|
|
// in Any's against a whitelist of registered types
|
|
type AnyUnpacker interface {
|
|
// UnpackAny unpacks the value in any to the interface pointer passed in as
|
|
// iface. Note that the type in any must have been registered in the
|
|
// underlying whitelist registry as a concrete type for that interface
|
|
// Ex:
|
|
// var msg sdk.Msg
|
|
// err := cdc.UnpackAny(any, &msg)
|
|
// ...
|
|
UnpackAny(any *Any, iface interface{}) error
|
|
}
|
|
|
|
// InterfaceRegistry provides a mechanism for registering interfaces and
|
|
// implementations that can be safely unpacked from Any
|
|
type InterfaceRegistry interface {
|
|
AnyUnpacker
|
|
jsonpb.AnyResolver
|
|
|
|
// RegisterInterface associates protoName as the public name for the
|
|
// interface passed in as iface. This is to be used primarily to create
|
|
// a public facing registry of interface implementations for clients.
|
|
// protoName should be a well-chosen public facing name that remains stable.
|
|
// RegisterInterface takes an optional list of impls to be registered
|
|
// as implementations of iface.
|
|
//
|
|
// Ex:
|
|
// registry.RegisterInterface("cosmos.base.v1beta1.Msg", (*sdk.Msg)(nil))
|
|
RegisterInterface(protoName string, iface interface{}, impls ...proto.Message)
|
|
|
|
// RegisterImplementations registers impls as concrete implementations of
|
|
// the interface iface.
|
|
//
|
|
// Ex:
|
|
// registry.RegisterImplementations((*sdk.Msg)(nil), &MsgSend{}, &MsgMultiSend{})
|
|
RegisterImplementations(iface interface{}, impls ...proto.Message)
|
|
|
|
// ListAllInterfaces list the type URLs of all registered interfaces.
|
|
ListAllInterfaces() []string
|
|
|
|
// ListImplementations lists the valid type URLs for the given interface name that can be used
|
|
// for the provided interface type URL.
|
|
ListImplementations(ifaceTypeURL string) []string
|
|
}
|
|
|
|
// UnpackInterfacesMessage is meant to extend protobuf types (which implement
|
|
// proto.Message) to support a post-deserialization phase which unpacks
|
|
// types packed within Any's using the whitelist provided by AnyUnpacker
|
|
type UnpackInterfacesMessage interface {
|
|
// UnpackInterfaces is implemented in order to unpack values packed within
|
|
// Any's using the AnyUnpacker. It should generally be implemented as
|
|
// follows:
|
|
// func (s *MyStruct) UnpackInterfaces(unpacker AnyUnpacker) error {
|
|
// var x AnyInterface
|
|
// // where X is an Any field on MyStruct
|
|
// err := unpacker.UnpackAny(s.X, &x)
|
|
// if err != nil {
|
|
// return nil
|
|
// }
|
|
// // where Y is a field on MyStruct that implements UnpackInterfacesMessage itself
|
|
// err = s.Y.UnpackInterfaces(unpacker)
|
|
// if err != nil {
|
|
// return nil
|
|
// }
|
|
// return nil
|
|
// }
|
|
UnpackInterfaces(unpacker AnyUnpacker) error
|
|
}
|
|
|
|
type interfaceRegistry struct {
|
|
interfaceNames map[string]reflect.Type
|
|
interfaceImpls map[reflect.Type]interfaceMap
|
|
typeURLMap map[string]reflect.Type
|
|
}
|
|
|
|
type interfaceMap = map[string]reflect.Type
|
|
|
|
// NewInterfaceRegistry returns a new InterfaceRegistry
|
|
func NewInterfaceRegistry() InterfaceRegistry {
|
|
return &interfaceRegistry{
|
|
interfaceNames: map[string]reflect.Type{},
|
|
interfaceImpls: map[reflect.Type]interfaceMap{},
|
|
typeURLMap: map[string]reflect.Type{},
|
|
}
|
|
}
|
|
|
|
func (registry *interfaceRegistry) RegisterInterface(protoName string, iface interface{}, impls ...proto.Message) {
|
|
typ := reflect.TypeOf(iface)
|
|
if typ.Elem().Kind() != reflect.Interface {
|
|
panic(fmt.Errorf("%T is not an interface type", iface))
|
|
}
|
|
registry.interfaceNames[protoName] = typ
|
|
registry.RegisterImplementations(iface, impls...)
|
|
}
|
|
|
|
// RegisterImplementations registers a concrete proto Message which implements
|
|
// the given interface.
|
|
//
|
|
// This function PANICs if different concrete types are registered under the
|
|
// same typeURL.
|
|
func (registry *interfaceRegistry) RegisterImplementations(iface interface{}, impls ...proto.Message) {
|
|
for _, impl := range impls {
|
|
typeURL := "/" + proto.MessageName(impl)
|
|
registry.registerImpl(iface, typeURL, impl)
|
|
}
|
|
}
|
|
|
|
// RegisterCustomTypeURL registers a concrete type which implements the given
|
|
// interface under `typeURL`.
|
|
//
|
|
// This function PANICs if different concrete types are registered under the
|
|
// same typeURL.
|
|
func (registry *interfaceRegistry) RegisterCustomTypeURL(iface interface{}, typeURL string, impl proto.Message) {
|
|
registry.registerImpl(iface, typeURL, impl)
|
|
}
|
|
|
|
// registerImpl registers a concrete type which implements the given
|
|
// interface under `typeURL`.
|
|
//
|
|
// This function PANICs if different concrete types are registered under the
|
|
// same typeURL.
|
|
func (registry *interfaceRegistry) registerImpl(iface interface{}, typeURL string, impl proto.Message) {
|
|
ityp := reflect.TypeOf(iface).Elem()
|
|
imap, found := registry.interfaceImpls[ityp]
|
|
if !found {
|
|
imap = map[string]reflect.Type{}
|
|
}
|
|
|
|
implType := reflect.TypeOf(impl)
|
|
if !implType.AssignableTo(ityp) {
|
|
panic(fmt.Errorf("type %T doesn't actually implement interface %+v", impl, ityp))
|
|
}
|
|
|
|
// Check if we already registered something under the given typeURL. It's
|
|
// okay to register the same concrete type again, but if we are registering
|
|
// a new concrete type under the same typeURL, then we throw an error (here,
|
|
// we panic).
|
|
foundImplType, found := imap[typeURL]
|
|
if found && foundImplType != implType {
|
|
panic(
|
|
fmt.Errorf(
|
|
"concrete type %s has already been registered under typeURL %s, cannot register %s under same typeURL. "+
|
|
"This usually means that there are conflicting modules registering different concrete types "+
|
|
"for a same interface implementation",
|
|
foundImplType,
|
|
typeURL,
|
|
implType,
|
|
),
|
|
)
|
|
}
|
|
|
|
imap[typeURL] = implType
|
|
registry.typeURLMap[typeURL] = implType
|
|
|
|
registry.interfaceImpls[ityp] = imap
|
|
}
|
|
|
|
func (registry *interfaceRegistry) ListAllInterfaces() []string {
|
|
interfaceNames := registry.interfaceNames
|
|
keys := make([]string, 0, len(interfaceNames))
|
|
for key := range interfaceNames {
|
|
keys = append(keys, key)
|
|
}
|
|
return keys
|
|
}
|
|
|
|
func (registry *interfaceRegistry) ListImplementations(ifaceName string) []string {
|
|
typ, ok := registry.interfaceNames[ifaceName]
|
|
if !ok {
|
|
return []string{}
|
|
}
|
|
|
|
impls, ok := registry.interfaceImpls[typ.Elem()]
|
|
if !ok {
|
|
return []string{}
|
|
}
|
|
|
|
keys := make([]string, 0, len(impls))
|
|
for key := range impls {
|
|
keys = append(keys, key)
|
|
}
|
|
return keys
|
|
}
|
|
|
|
func (registry *interfaceRegistry) UnpackAny(any *Any, iface interface{}) error {
|
|
// here we gracefully handle the case in which `any` itself is `nil`, which may occur in message decoding
|
|
if any == nil {
|
|
return nil
|
|
}
|
|
|
|
if any.TypeUrl == "" {
|
|
// if TypeUrl is empty return nil because without it we can't actually unpack anything
|
|
return nil
|
|
}
|
|
|
|
rv := reflect.ValueOf(iface)
|
|
if rv.Kind() != reflect.Ptr {
|
|
return fmt.Errorf("UnpackAny expects a pointer")
|
|
}
|
|
|
|
rt := rv.Elem().Type()
|
|
|
|
cachedValue := any.cachedValue
|
|
if cachedValue != nil {
|
|
if reflect.TypeOf(cachedValue).AssignableTo(rt) {
|
|
rv.Elem().Set(reflect.ValueOf(cachedValue))
|
|
return nil
|
|
}
|
|
}
|
|
|
|
imap, found := registry.interfaceImpls[rt]
|
|
if !found {
|
|
return fmt.Errorf("no registered implementations of type %+v", rt)
|
|
}
|
|
|
|
typ, found := imap[any.TypeUrl]
|
|
if !found {
|
|
return fmt.Errorf("no concrete type registered for type URL %s against interface %T", any.TypeUrl, iface)
|
|
}
|
|
|
|
msg, ok := reflect.New(typ.Elem()).Interface().(proto.Message)
|
|
if !ok {
|
|
return fmt.Errorf("can't proto unmarshal %T", msg)
|
|
}
|
|
|
|
err := proto.Unmarshal(any.Value, msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = UnpackInterfaces(msg, registry)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rv.Elem().Set(reflect.ValueOf(msg))
|
|
|
|
any.cachedValue = msg
|
|
|
|
return nil
|
|
}
|
|
|
|
// Resolve returns the proto message given its typeURL. It works with types
|
|
// registered with RegisterInterface/RegisterImplementations, as well as those
|
|
// registered with RegisterWithCustomTypeURL.
|
|
func (registry *interfaceRegistry) Resolve(typeURL string) (proto.Message, error) {
|
|
typ, found := registry.typeURLMap[typeURL]
|
|
if !found {
|
|
return nil, fmt.Errorf("unable to resolve type URL %s", typeURL)
|
|
}
|
|
|
|
msg, ok := reflect.New(typ.Elem()).Interface().(proto.Message)
|
|
if !ok {
|
|
return nil, fmt.Errorf("can't resolve type URL %s", typeURL)
|
|
}
|
|
|
|
return msg, nil
|
|
}
|
|
|
|
// UnpackInterfaces is a convenience function that calls UnpackInterfaces
|
|
// on x if x implements UnpackInterfacesMessage
|
|
func UnpackInterfaces(x interface{}, unpacker AnyUnpacker) error {
|
|
if msg, ok := x.(UnpackInterfacesMessage); ok {
|
|
return msg.UnpackInterfaces(unpacker)
|
|
}
|
|
return nil
|
|
}
|