grpc / grpc-swift

The Swift language implementation of gRPC.
Apache License 2.0
2k stars 412 forks source link

Extensions are not deserialized #1555

Open maznikoff opened 1 year ago

maznikoff commented 1 year ago

Describe the bug

Having a service and messages like these:

service MyService {
    rpc RequestMyData (MyRequest) returns (MyResponse) {}
  }

  message MyRequest {
  }

  message MyResponse {
    MyData data = 1;
  }
message MyData {
  required string id = 1;

  extensions 1000 to max;
}
message MyCustomData {
    required string customString = 1;

    extend MyData {
        optional MyCustomData customData = 1000;
    }
}

Golang server sends a response and sets MyCustomData as extension of MyData. iOS client receives the MyResponse message, but getting nil for the extension of MyData.

Both server and client are able to set & get extensions of the messages created locally.

GRPC Serialization code shows a sign that extensions are just ignored, because it just passes serialized data to the Message init and doesn't pass the extensions:

public struct ProtobufDeserializer<Message: SwiftProtobuf.Message>: MessageDeserializer {
  @inlinable
  public func deserialize(byteBuffer: ByteBuffer) throws -> Message {
    var buffer = byteBuffer
    // '!' is okay; we can always read 'readableBytes'.
    let data = buffer.readData(length: buffer.readableBytes)!
    return try Message(serializedData: data)
  }
}

swift-protobuf Message sources:

  @inlinable
  public init(
    serializedData data: Data,
    extensions: ExtensionMap? = nil,
    partial: Bool = false,
    options: BinaryDecodingOptions = BinaryDecodingOptions()
  ) throws {
    self.init()
#if swift(>=5.0)
    try merge(contiguousBytes: data, extensions: extensions, partial: partial, options: options)
#else
    try merge(serializedData: data, extensions: extensions, partial: partial, options: options)
#endif
  }

protoc-gen-grpc-swift version: latest

glbrntt commented 1 year ago

Feeding in extension data (or decoding options) is not currently possible; we should add support for this though.

For the client, you can work around it by providing a wrapper type for your messages:

struct ProtoWrapper<ProtoMessage: Message>: GRPCPayload {
  var message: ProtoMessage

  init(_ message: ProtoMessage) {
    self.message = message
  }

  init(serializedByteBuffer buffer: inout ByteBuffer) throws {
    let extensions = ... 
    self.message = try .init(serializedData: Data(buffer: buffer), extensions: extensions)
  }

  func serialize(into buffer: inout ByteBuffer) throws {
    buffer.writeData(try self.message.serializedData())
  }
}

extension Message {
  var wrapped: ProtoWrapper<Self> { return .init(self) }
}

And then manually making calls with your client:

let client = GRPCAnyServiceClient(channel: ...)
let call = client.makeUnaryCall(path: "/echo.Echo/Get", request: request.wrapped, responseType: ProtoMessage<Response>.self)