Пользовательские параметры protobuf сообщения типа Any in Go

У меня есть служба GRPC, определенная как:

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);
}

Я также определил настраиваемый параметр сообщения:

extend google.protobuf.MessageOptions {
  // event_name is the unique name of the event sent by the clients
  string event_name = 50000;
}

Чего я хочу добиться, так это того, чтобы клиенты создавали собственные прото-сообщения, в которых опция event_name устанавливалась на константу. Например:

message SomeCustomEvent {
  option (mypackage.event_name) = "some_custom_event";

  string data = 1;
  ...
}

Таким образом, служба может отслеживать, какие события отправляются. Когда я делаю что-то подобное, я могу получить значение параметра из определенного proto.Message:

_, md := descriptor.MessageDescriptorProto(SomeCustomEvent)
mOpts := md.GetOptions()
eventName := proto.GetExtension(mOpts, mypackage.E_EventName)

Однако, когда сообщение имеет тип github.com/golang/protobuf/ptypes/any.Any, параметры равны нулю. Как я могу получить event_name из сообщения? Я столкнулся с protoregistry.MessageTypeResolver, похоже, это может помочь, но мне нужно найти способ динамически обновлять определения прототипов событий при интеграции клиентов.


person Boyan Kushlev    schedule 19.01.2021    source источник
comment
Если вы используете тип ANY, не добавлена ​​ли в код Go функциональность, позволяющая узнать, какой тип был отправлен? Или вы не можете сделать утверждение типа? Это должно сделать статическое значение ненужным.   -  person TehSphinX    schedule 20.01.2021


Ответы (2)


Чтобы получить параметры типа Any, вам нужен его конкретный protoreflect.MessageType, чтобы вы могли распаковать его в конкретное сообщение. Чтобы получить тип сообщения, вам нужен файл MessageTypeResolver.

Any содержит поле type_url, которое можно использовать для этой цели. Чтобы разобрать объект Any в сообщение существующего типа сообщения:

// 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)

Получив конкретное сообщение, вы можете просто получить нужный вам вариант:

msgOpts := msg.ProtoReflect().Descriptor().Options()
eventName := proto.GetExtension(msgOpts, mypackage.E_EventName)

Обратите внимание, что proto.GetExtension будет паниковать, если сообщение не расширяет параметр event_name, и его необходимо восстановить. Этот блок можно добавить в начало функции:

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)
    }
}()

РЕДАКТИРОВАТЬ: обратите внимание, что приложение должно импортировать пакет, содержащий определения proto, чтобы protoregistry.GlobalTypes распознал тип. Вы можете сделать что-то подобное в своем коде:

var _ mypackage.SomeEvent

person Boyan Kushlev    schedule 22.01.2021

any.ANY type в Go содержит поле TypeUrl, которое содержит тип отправленного сообщения. Затем вы можете использовать это для UnmarshalAny для правильного сгенерированного типа Go. .

Ссылка на полное руководство о том, как работать с any.ANI.

person TehSphinX    schedule 20.01.2021
comment
Это только наполовину помогло мне. Но я нашел решение, которое я опубликую позже сегодня. - person Boyan Kushlev; 22.01.2021