Skip to content

WithForwardResponseOption can't cast proto.Message when response_body is defined on the rpc method #1493

Closed
@talanknight

Description

@talanknight

🐛 Bug Report

The WithForwardResponseOption does not allow you to cast to the original response proto when the service has a response_body field set. This makes it very difficult to conditionally attach headers, cookies, or do other conditional logic based off of the response proto.

This is a follow up bug report from a chat with @johanbrandhorst in slack.

To Reproduce

  1. Create a grpc response proto with response_body set.
import "protoc-gen-swagger/options/annotations.proto";
import "google/api/annotations.proto";

service Service {
	rpc GetObject (GetObjectRequest) returns (GetObjectResponse) {
		option (google.api.http) = {
      get: "/object"
      response_body: "value"
    };
	}
}

message GetObjectRequest {
}

message GetObjectResponse {
	string value = 1;
}
  1. Register a WithForwardResponseOption function with test:
type fake struct {}

func (f fake) GetObject(ctx context.Context, request *services.GetObjectRequest) (*services.GetObjectResponse, error) {
	return &services.GetObjectResponse{Value: "this is the value"}, nil
}

func TestGetValue(t *testing.T) {
	type GetValuer interface {
		GetValue() string
	}
	mux := runtime.NewServeMux(runtime.WithForwardResponseOption(func(ctx context.Context, w http.ResponseWriter, m proto.Message) error {
		switch m.(type) {
		case *services.GetObjectResponse:
			t.Logf("Cast to struct ptr")
		case GetValuer:
			t.Logf("Cast to interface")
		default:
			t.Errorf("Couldn't cast response to expected format.")
		}
		return nil
	}))
	services.RegisterServiceHandlerServer(context.Background(), mux, &fake{})

	req := httptest.NewRequest("GET","http://localhost/object", nil)
	resp := httptest.NewRecorder()
	mux.ServeHTTP(resp, req)
}

Expected behavior

Either "Cast to struct ptr" or "Cast to interface" would get logged and the test would pass.
This behavior would allow headers and cookies to be conditionally attached to the response using the fields that sit alongside the "value" field in the response.

Actual Behavior

The test fails with "Couldn't cast response to expected format."

Your Environment

MacOS 10.15.5
Go 1.13

Potential solutions

The reason this cast doesn't work is because the response proto.Message is embedded in a custom internal struct when response_body is defined. See this example

A few potential solutions in order of preference:

  1. Add a method for this internal struct that returns the embedded proto.Message directly so we can then cast to the interface for the wrapped structure and extract out our response proto. Something like the following (name TBD):
func (m response_ResponseBodyService_GetResponseBody_0) GetEmbeddedProtoMessage() proto.Message {
	return m.Message
}
  1. Instead of embedding a proto.Message, have the internal struct embed the response proto type (ResponseBodyOut in this example)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions