I have a GRPC service defined like:
message SendEventRequest {
string producer = 1;
google.protobuf.Any event = 2;
}
message SendEventResponse {
string event_name = 1;
string status = 2;
}
service EventService {
rpc Send(SendEventRequest) returns (SendEventResponse);
}
I also have defined a custom message option:
extend google.protobuf.MessageOptions {
// event_name is the unique name of the event sent by the clients
string event_name = 50000;
}
What I want to achieve is have clients create custom proto messages that set the event_name
option to a "constant". For instance:
message SomeCustomEvent {
option (mypackage.event_name) = "some_custom_event";
string data = 1;
...
}
That way the service can keep track of what events are being sent. When I do something like this I'm able to get the value of the option from a specific proto.Message
:
_, md := descriptor.MessageDescriptorProto(SomeCustomEvent)
mOpts := md.GetOptions()
eventName := proto.GetExtension(mOpts, mypackage.E_EventName)
However, when the message is of type github.com/golang/protobuf/ptypes/any.Any
the options are nil. How can I retrieve the event_name
from the message? I've come across the protoregistry.MessageTypeResolver
, which looks like it might help, but I would need to figure out a way to dynamically update the proto definitions of the events when clients integrate.
In order to obtain the options of an Any
type, you need its specific protoreflect.MessageType
so that you can unmarshal it into a specific message. In order to get the message type, you need a MessageTypeResolver
.
Any
contains a type_url
field, which can be used for that purpose. In order to unmarshal the Any
object into a message of an existing message type:
// GlobalTypes contains information about the proto message types
var res protoregistry.MessageTypeResolver = protoregistry.GlobalTypes
typeUrl := anyObject.GetTypeUrl()
msgType, _ := res.FindMessageByURL(typeUrl)
msg := msgType.New().Interface()
unmarshalOptions := proto.UnmarshalOptions{Resolver: res}
unmarshalOptions.Unmarshal(anyObject.GetValue(), msg)
After having the specific message, you can simply get the option you need:
msgOpts := msg.ProtoReflect().Descriptor().Options()
eventName := proto.GetExtension(msgOpts, mypackage.E_EventName)
Note that proto.GetExtension
will panic if the message doesn't extend the event_name
option, and it needs to be recovered. This block can be added at the beginning of the function:
defer func() {
if r := recover(); r != nil {
// err is a named return parameter of the outer function
err = fmt.Errorf("recovering from panic while extracting event_name from proto message: %s", r)
}
}()
EDIT: Note that the application has to import the package containing the proto definitions in order for protoregistry.GlobalTypes
to recognize the type. You could do something like this in your code:
var _ mypackage.SomeEvent