init: Implement ADR 032 typed events (#7564)
* Add EmitTypedEvent in events * Add parseTypedEvent method * Use jsonpb * Modify unmarshal proto in events * Add a test for typed events * Fix reflect issue in parseTypedEvent * Modify event tests and add comments * Add EmitTypedEvents and refactor other methods * Fix golangci-lint issues * Update ProtoMarshalJSON params * Address PR comments Co-authored-by: anilCSE <anil@vitwit.com> Co-authored-by: Jack Zampolin <jack.zampolin@gmail.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
parent
afb6771726
commit
c3638adddc
|
@ -1,10 +1,15 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/gogo/protobuf/jsonpb"
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
|
@ -25,11 +30,13 @@ func NewEventManager() *EventManager {
|
|||
func (em *EventManager) Events() Events { return em.events }
|
||||
|
||||
// EmitEvent stores a single Event object.
|
||||
// Deprecated: Use EmitTypedEvent
|
||||
func (em *EventManager) EmitEvent(event Event) {
|
||||
em.events = em.events.AppendEvent(event)
|
||||
}
|
||||
|
||||
// EmitEvents stores a series of Event objects.
|
||||
// Deprecated: Use EmitTypedEvents
|
||||
func (em *EventManager) EmitEvents(events Events) {
|
||||
em.events = em.events.AppendEvents(events)
|
||||
}
|
||||
|
@ -39,6 +46,97 @@ func (em EventManager) ABCIEvents() []abci.Event {
|
|||
return em.events.ToABCIEvents()
|
||||
}
|
||||
|
||||
// EmitTypedEvent takes typed event and emits converting it into Event
|
||||
func (em *EventManager) EmitTypedEvent(tev proto.Message) error {
|
||||
event, err := TypedEventToEvent(tev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
em.EmitEvent(event)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmitTypedEvents takes series of typed events and emit
|
||||
func (em *EventManager) EmitTypedEvents(tevs ...proto.Message) error {
|
||||
events := make(Events, len(tevs))
|
||||
for i, tev := range tevs {
|
||||
res, err := TypedEventToEvent(tev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
events[i] = res
|
||||
}
|
||||
|
||||
em.EmitEvents(events)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TypedEventToEvent takes typed event and converts to Event object
|
||||
func TypedEventToEvent(tev proto.Message) (Event, error) {
|
||||
evtType := proto.MessageName(tev)
|
||||
evtJSON, err := codec.ProtoMarshalJSON(tev, nil)
|
||||
if err != nil {
|
||||
return Event{}, err
|
||||
}
|
||||
|
||||
var attrMap map[string]json.RawMessage
|
||||
err = json.Unmarshal(evtJSON, &attrMap)
|
||||
if err != nil {
|
||||
return Event{}, err
|
||||
}
|
||||
|
||||
attrs := make([]abci.EventAttribute, 0, len(attrMap))
|
||||
for k, v := range attrMap {
|
||||
attrs = append(attrs, abci.EventAttribute{
|
||||
Key: []byte(k),
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
|
||||
return Event{
|
||||
Type: evtType,
|
||||
Attributes: attrs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseTypedEvent converts abci.Event back to typed event
|
||||
func ParseTypedEvent(event abci.Event) (proto.Message, error) {
|
||||
concreteGoType := proto.MessageType(event.Type)
|
||||
if concreteGoType == nil {
|
||||
return nil, fmt.Errorf("failed to retrieve the message of type %q", event.Type)
|
||||
}
|
||||
|
||||
var value reflect.Value
|
||||
if concreteGoType.Kind() == reflect.Ptr {
|
||||
value = reflect.New(concreteGoType.Elem())
|
||||
} else {
|
||||
value = reflect.Zero(concreteGoType)
|
||||
}
|
||||
|
||||
protoMsg, ok := value.Interface().(proto.Message)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%q does not implement proto.Message", event.Type)
|
||||
}
|
||||
|
||||
attrMap := make(map[string]json.RawMessage)
|
||||
for _, attr := range event.Attributes {
|
||||
attrMap[string(attr.Key)] = attr.Value
|
||||
}
|
||||
|
||||
attrBytes, err := json.Marshal(attrMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = jsonpb.Unmarshal(strings.NewReader(string(attrBytes)), protoMsg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return protoMsg, nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Events
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
@ -2,11 +2,14 @@ package types_test
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
testdata "github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
|
@ -68,6 +71,37 @@ func (s *eventsTestSuite) TestEventManager() {
|
|||
s.Require().Equal(em.Events(), events.AppendEvent(event))
|
||||
}
|
||||
|
||||
func (s *eventsTestSuite) TestEventManagerTypedEvents() {
|
||||
em := sdk.NewEventManager()
|
||||
|
||||
coin := sdk.NewCoin("fakedenom", sdk.NewInt(1999999))
|
||||
cat := testdata.Cat{
|
||||
Moniker: "Garfield",
|
||||
Lives: 6,
|
||||
}
|
||||
animal, err := codectypes.NewAnyWithValue(&cat)
|
||||
s.Require().NoError(err)
|
||||
hasAnimal := testdata.HasAnimal{
|
||||
X: 1000,
|
||||
Animal: animal,
|
||||
}
|
||||
|
||||
s.Require().NoError(em.EmitTypedEvents(&coin))
|
||||
s.Require().NoError(em.EmitTypedEvent(&hasAnimal))
|
||||
s.Require().Len(em.Events(), 2)
|
||||
|
||||
msg1, err := sdk.ParseTypedEvent(em.Events().ToABCIEvents()[0])
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(coin.String(), msg1.String())
|
||||
s.Require().Equal(reflect.TypeOf(&coin), reflect.TypeOf(msg1))
|
||||
|
||||
msg2, err := sdk.ParseTypedEvent(em.Events().ToABCIEvents()[1])
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(reflect.TypeOf(&hasAnimal), reflect.TypeOf(msg2))
|
||||
response := msg2.(*testdata.HasAnimal)
|
||||
s.Require().Equal(hasAnimal.Animal.String(), response.Animal.String())
|
||||
}
|
||||
|
||||
func (s *eventsTestSuite) TestStringifyEvents() {
|
||||
e := sdk.Events{
|
||||
sdk.NewEvent("message", sdk.NewAttribute("sender", "foo")),
|
||||
|
|
Loading…
Reference in New Issue