Cannot load multiple gRPC definitions #667

Closed
opened 2025-12-29 15:30:23 +01:00 by adam · 11 comments
Owner

Originally created by @tuncancn on GitHub (Jan 31, 2025).

Originally assigned to: @StefH on GitHub.

It works fine if I add single greet.proto file definition. But if I split the proto file to 2 files, request.proto and greet.proto, the former has HelloRequest definition only.
I always get 404 error.

System.AggregateException : One or more errors occurred. (Status(StatusCode="Unimplemented", Detail="Bad gRPC response. HTTP status code: 404"))
  ----> Grpc.Core.RpcException : Status(StatusCode="Unimplemented", Detail="Bad gRPC response. HTTP status code: 404")

Stack Trace: 
Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
Task`1.GetResultCore(Boolean waitCompletionNotification)
GrpcMockTest.MultipleProtoTest() line 83
RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
--RpcException

Originally created by @tuncancn on GitHub (Jan 31, 2025). Originally assigned to: @StefH on GitHub. It works fine if I add single greet.proto file definition. But if I split the proto file to 2 files, request.proto and greet.proto, the former has HelloRequest definition only. I always get 404 error. System.AggregateException : One or more errors occurred. (Status(StatusCode="Unimplemented", Detail="Bad gRPC response. HTTP status code: 404")) ----> Grpc.Core.RpcException : Status(StatusCode="Unimplemented", Detail="Bad gRPC response. HTTP status code: 404") Stack Trace:  Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) Task`1.GetResultCore(Boolean waitCompletionNotification) GrpcMockTest.MultipleProtoTest() line 83 RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) --RpcException
adam added the question label 2025-12-29 15:30:23 +01:00
adam closed this issue 2025-12-29 15:30:23 +01:00
Author
Owner

@StefH commented on GitHub (Jan 31, 2025):

Can you please provide the complete code?

@StefH commented on GitHub (Jan 31, 2025): Can you please provide the complete code?
Author
Owner

@tuncancn commented on GitHub (Jan 31, 2025):

Hi Stef, please see the souce code below.

Proto file, request.proto

syntax = "proto3";

package greet;

message HelloRequest {
  string name = 1;
}

Proto file, greet.proto

syntax = "proto3";

import "request.proto";

package greet;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloReply {
  string message = 1;
}
public class GrpcMockTest
{
    private WireMockServer _server;

    [OneTimeSetUp]
    public void Setup()
    {
        var protoDefinitionText = File.ReadAllText(@"D:\Projects\DOTNET\WireMock.Net\Grpc.Mock.Test\proto\greet.proto");
        var protoDefinitionText1 = File.ReadAllText(@"D:\Projects\DOTNET\WireMock.Net\Grpc.Mock.Test\proto\request.proto");

        var protoDefinitionId = "GrpcGreet";

        _server = WireMockServer.Start(new WireMock.Settings.WireMockServerSettings
        {
            UseHttp2 = true,
            Port = 9003,
            StartAdminInterface = true,
        });

        _server
          .AddProtoDefinition(protoDefinitionId, protoDefinitionText1, protoDefinitionText)
          .Given(Request.Create()
            .UsingPost()
            .WithPath("/greet.Greeter/SayHello")
            .WithBodyAsProtoBuf("greet.HelloRequest", new JsonMatcher(new { name = "stef" }))
          )
          .WithProtoDefinition(protoDefinitionId)
          .RespondWith(Response.Create()
            .WithHeader("Content-Type", "application/grpc")
            .WithTrailingHeader("grpc-status", "0")
            .WithBodyAsProtoBuf("greet.HelloReply",
            new
            {
                message = "hi {{request.BodyAsJson.name}} {{request.method}}"
            })
            .WithTransformer()
          );
    }

    [Test]
    public void MultipleProtoTest()
    {
        var channel = GrpcChannel.ForAddress(_server.Url!);

        var client = new Greeter.GreeterClient(channel);

        var reply = client.SayHelloAsync(new HelloRequest { Name = "stef" }).ResponseAsync.Result;

        // Assert
        Assert.That(reply.Message, Is.EqualTo("hi stef POST"));
    }

    [OneTimeTearDown]
    public void OneTimeTeardown()
    {
        _server.Dispose();
    }
}
@tuncancn commented on GitHub (Jan 31, 2025): Hi Stef, please see the souce code below. Proto file, request.proto ``` syntax = "proto3"; package greet; message HelloRequest { string name = 1; } ``` Proto file, greet.proto ``` syntax = "proto3"; import "request.proto"; package greet; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloReply { string message = 1; } ``` ``` public class GrpcMockTest { private WireMockServer _server; [OneTimeSetUp] public void Setup() { var protoDefinitionText = File.ReadAllText(@"D:\Projects\DOTNET\WireMock.Net\Grpc.Mock.Test\proto\greet.proto"); var protoDefinitionText1 = File.ReadAllText(@"D:\Projects\DOTNET\WireMock.Net\Grpc.Mock.Test\proto\request.proto"); var protoDefinitionId = "GrpcGreet"; _server = WireMockServer.Start(new WireMock.Settings.WireMockServerSettings { UseHttp2 = true, Port = 9003, StartAdminInterface = true, }); _server .AddProtoDefinition(protoDefinitionId, protoDefinitionText1, protoDefinitionText) .Given(Request.Create() .UsingPost() .WithPath("/greet.Greeter/SayHello") .WithBodyAsProtoBuf("greet.HelloRequest", new JsonMatcher(new { name = "stef" })) ) .WithProtoDefinition(protoDefinitionId) .RespondWith(Response.Create() .WithHeader("Content-Type", "application/grpc") .WithTrailingHeader("grpc-status", "0") .WithBodyAsProtoBuf("greet.HelloReply", new { message = "hi {{request.BodyAsJson.name}} {{request.method}}" }) .WithTransformer() ); } [Test] public void MultipleProtoTest() { var channel = GrpcChannel.ForAddress(_server.Url!); var client = new Greeter.GreeterClient(channel); var reply = client.SayHelloAsync(new HelloRequest { Name = "stef" }).ResponseAsync.Result; // Assert Assert.That(reply.Message, Is.EqualTo("hi stef POST")); } [OneTimeTearDown] public void OneTimeTeardown() { _server.Dispose(); } } ```
Author
Owner

@StefH commented on GitHub (Jan 31, 2025):

What if you switch?
So

.AddProtoDefinition(protoDefinitionId, protoDefinitionText, protoDefinitionText1)
@StefH commented on GitHub (Jan 31, 2025): What if you switch? So ``` c# .AddProtoDefinition(protoDefinitionId, protoDefinitionText, protoDefinitionText1) ```
Author
Owner

@tuncancn commented on GitHub (Feb 1, 2025):

Still not working, same error.
After I debugged into WireMock.Net source code, I can see it's trying to solve the request object type from first proto definition in ProtoBufMatcher.DecodeAsync(byte[]? input, bool throwException, CancellationToken cancellationToken). So I have to put the request definition in the front of the definition array.
Unfortunately, my response is not defined in the same proto as request object, ProtoBufUtils.GetProtoBufMessageWithHeaderAsync(...) tries to get the response type from the first proto definition again.

@tuncancn commented on GitHub (Feb 1, 2025): Still not working, same error. After I debugged into WireMock.Net source code, I can see it's trying to solve the request object type from first proto definition in ProtoBufMatcher.DecodeAsync(byte[]? input, bool throwException, CancellationToken cancellationToken). So I have to put the request definition in the front of the definition array. Unfortunately, my response is not defined in the same proto as request object, ProtoBufUtils.GetProtoBufMessageWithHeaderAsync(...) tries to get the response type from the first proto definition again.
Author
Owner

@StefH commented on GitHub (Feb 1, 2025):

@tuncancn

It's a bit more complicated, sorry for that.

1.

You need the main proto file as first item (protoDefinitionText)

2.

You need to add a comment to the request proto file, so that WireMock.Net knows how to resolve.

// request.proto
syntax = "proto3";

package greet;

message HelloRequest {
  string name = 1;
}

Can you try that?

@StefH commented on GitHub (Feb 1, 2025): @tuncancn It's a bit more complicated, sorry for that. ### 1. You need the main proto file as first item (`protoDefinitionText`) ### 2. You need to add a comment to the request proto file, so that WireMock.Net knows how to resolve. ``` proto // request.proto syntax = "proto3"; package greet; message HelloRequest { string name = 1; } ``` Can you try that?
Author
Owner

@StefH commented on GitHub (Feb 1, 2025):

https://github.com/WireMock-Net/WireMock.Net/pull/1252

@StefH commented on GitHub (Feb 1, 2025): https://github.com/WireMock-Net/WireMock.Net/pull/1252
Author
Owner

@tuncancn commented on GitHub (Feb 1, 2025):

It works!!! Thank you, Stef!
I have another question, how should I return an Any type in the response object.
For example, I split the response object to a new proto, changed the content like this.

// response/response.proto
syntax = "proto3";

import "google/protobuf/any.proto";

package greet.response;

// The response message containing the greetings
message HelloReply {
  string message = 1;
  google.protobuf.Any data = 2;
}

message Transaction {
    int32 id = 1;
    float price = 2;
    int64 amount = 3;
}

Added a generic class for converting other types to Any.

using Newtonsoft.Json;
public class AnyData {
    [JsonProperty("@type")]
    public string Type { get; set; }
    [JsonProperty("value")]
    public object Value { get; set; }
}

When I response the object like this, I always get an exception "{"No serializer for type System.Object is available for model (default)"}", I'm not sure what value I should set to AnyData in the anonymous initializer.

            .WithBodyAsProtoBuf("greet.response.HelloReply",
            new
            {
                message = "hi {{request.BodyAsJson.name}} {{request.method}}",
                data = new AnyData
                {
                    // I tried "greet.response.Trade", "type/greet.response.Transaction" as well
                    Type = "type.response.proto/greet.response.Transaction",
                    Value = new
                    {
                        Id = 1,
                    }
                }
            })

Here is the json content in debug view:

{{
  "message": "hi stef POST",
  "data": {
    "@type": "type.response.proto/greet.response.Transaction",
    "value": {
      "Id": 1
    }
  }
}}

And if I need to return repeated Any values, what should I do?

@tuncancn commented on GitHub (Feb 1, 2025): It works!!! Thank you, Stef! I have another question, how should I return an Any type in the response object. For example, I split the response object to a new proto, changed the content like this. ``` // response/response.proto syntax = "proto3"; import "google/protobuf/any.proto"; package greet.response; // The response message containing the greetings message HelloReply { string message = 1; google.protobuf.Any data = 2; } message Transaction { int32 id = 1; float price = 2; int64 amount = 3; } ``` Added a generic class for converting other types to Any. ``` using Newtonsoft.Json; public class AnyData { [JsonProperty("@type")] public string Type { get; set; } [JsonProperty("value")] public object Value { get; set; } } ``` When I response the object like this, I always get an exception "{"No serializer for type System.Object is available for model (default)"}", I'm not sure what value I should set to AnyData in the anonymous initializer. ``` .WithBodyAsProtoBuf("greet.response.HelloReply", new { message = "hi {{request.BodyAsJson.name}} {{request.method}}", data = new AnyData { // I tried "greet.response.Trade", "type/greet.response.Transaction" as well Type = "type.response.proto/greet.response.Transaction", Value = new { Id = 1, } } }) ``` Here is the json content in debug view: ``` {{ "message": "hi stef POST", "data": { "@type": "type.response.proto/greet.response.Transaction", "value": { "Id": 1 } } }} ``` And if I need to return **repeated** Any values, what should I do?
Author
Owner

@StefH commented on GitHub (Feb 1, 2025):

Any

For any, the object/json should look like something like this:

{
   "val1":{
      "@type":"type.googleapis.com/google.protobuf.StringValue",
      "value":"stef"
   },
   "val2":{
      "@type":"type.googleapis.com/google.protobuf.Int32Value",
      "value":2147483647
   }
}

See also:
https://github.com/StefH/ProtoBufJsonConverter/blob/main/test/ProtoBufJsonConverterTests/ConverterTests.cs#L741

Repeated

I don't have an example or test for that...

@StefH commented on GitHub (Feb 1, 2025): ## Any For any, the object/json should look like something like this: ``` json { "val1":{ "@type":"type.googleapis.com/google.protobuf.StringValue", "value":"stef" }, "val2":{ "@type":"type.googleapis.com/google.protobuf.Int32Value", "value":2147483647 } } ``` See also: https://github.com/StefH/ProtoBufJsonConverter/blob/main/test/ProtoBufJsonConverterTests/ConverterTests.cs#L741 ## Repeated I don't have an example or test for that...
Author
Owner

@tuncancn commented on GitHub (Feb 1, 2025):

Yeah, I tried to conver the anonymous class to the text in the sample.
But I don't know how to specify the type for my custom class Greet.Response.Transaction, not predefined Protobuf types.

@tuncancn commented on GitHub (Feb 1, 2025): Yeah, I tried to conver the anonymous class to the text in the sample. But I don't know how to specify the type for my custom class Greet.Response.Transaction, not predefined Protobuf types.
Author
Owner

@StefH commented on GitHub (Feb 1, 2025):

Yeah, I tried to conver the anonymous class to the text in the sample. But I don't know how to specify the type for my custom class Greet.Response.Transaction, not predefined Protobuf types.

The ProtoBufJsonConverter does not support anything else "type.googleapis.com" yet... I need to check if I can add this logic.

@StefH commented on GitHub (Feb 1, 2025): > Yeah, I tried to conver the anonymous class to the text in the sample. But I don't know how to specify the type for my custom class Greet.Response.Transaction, not predefined Protobuf types. The ProtoBufJsonConverter does not support anything else "type.googleapis.com" yet... I need to check if I can add this logic.
Author
Owner

@tuncancn commented on GitHub (Feb 3, 2025):

Thank you, Stef!

@tuncancn commented on GitHub (Feb 3, 2025): Thank you, Stef!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/WireMock.Net-wiremock#667