From b8f6675ca7dcf01cd953105773b550a2721b81c6 Mon Sep 17 00:00:00 2001 From: Johan Brandhorst-Satzkorn Date: Wed, 31 May 2023 11:22:28 -0700 Subject: [PATCH] protoc-gen-openapiv2: correct httpbody stream response Fixes #1274 --- .../internal/integration/integration_test.go | 137 +++++++------ .../internal/proto/examplepb/stream.pb.go | 182 +++++++++++++----- .../internal/proto/examplepb/stream.pb.gw.go | 27 ++- .../internal/proto/examplepb/stream.proto | 8 +- .../proto/examplepb/stream.swagger.json | 30 ++- .../proto/examplepb/stream_grpc.pb.go | 20 +- .../internal/server/a_bit_of_everything.go | 26 +-- .../internal/genopenapi/BUILD.bazel | 2 + .../internal/genopenapi/template.go | 11 ++ .../internal/genopenapi/template_test.go | 125 ++++++++++++ 10 files changed, 420 insertions(+), 148 deletions(-) diff --git a/examples/internal/integration/integration_test.go b/examples/internal/integration/integration_test.go index 14461193037..5f959c5c2fa 100644 --- a/examples/internal/integration/integration_test.go +++ b/examples/internal/integration/integration_test.go @@ -1779,80 +1779,91 @@ func TestResponseBody(t *testing.T) { } func testResponseBody(t *testing.T, port int) { - tests := []struct { - name string - url string - wantStatus int - wantBody string - }{{ - name: "unary case", - url: "http://localhost:%d/responsebody/foo", - wantStatus: http.StatusOK, - wantBody: `{"data":"foo"}`, - }} - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - apiURL := fmt.Sprintf(tt.url, port) - resp, err := http.Get(apiURL) - if err != nil { - t.Fatalf("http.Get(%q) failed with %v; want success", apiURL, err) - } + apiURL := fmt.Sprintf("http://localhost:%d/responsebody/foo", port) + resp, err := http.Get(apiURL) + if err != nil { + t.Fatalf("http.Get(%q) failed with %v; want success", apiURL, err) + } - defer resp.Body.Close() - buf, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatalf("io.ReadAll(resp.Body) failed with %v; want success", err) - } + defer resp.Body.Close() + buf, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("io.ReadAll(resp.Body) failed with %v; want success", err) + } - if got, want := resp.StatusCode, tt.wantStatus; got != want { - t.Errorf("resp.StatusCode = %d; want %d", got, want) - t.Logf("%s", buf) - } + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("resp.StatusCode = %d; want %d", got, want) + t.Logf("%s", buf) + } - if got, want := string(buf), tt.wantBody; got != want { - t.Errorf("response = %q; want %q", got, want) - } - }) + if diff := cmp.Diff(string(buf), `{"data":"foo"}`); diff != "" { + t.Errorf(diff) } } func TestResponseBodyStream(t *testing.T) { - tests := []struct { - name string - url string - wantStatus int - wantBody []string - }{{ - name: "stream case", - url: "http://localhost:%d/responsebody/stream/foo", - wantStatus: http.StatusOK, - wantBody: []string{`{"result":{"data":"first foo"}}`, `{"result":{"data":"second foo"}}`}, - }} + apiURL := "http://localhost:8088/responsebody/stream/foo" + resp, err := http.Get(apiURL) + if err != nil { + t.Fatalf("http.Get(%q) failed with %v; want success", apiURL, err) + } - port := 8088 - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - apiURL := fmt.Sprintf(tt.url, port) - resp, err := http.Get(apiURL) - if err != nil { - t.Fatalf("http.Get(%q) failed with %v; want success", apiURL, err) - } + defer resp.Body.Close() + body, err := readAll(resp.Body) + if err != nil { + t.Fatalf("readAll(resp.Body) failed with %v; want success", err) + } - defer resp.Body.Close() - body, err := readAll(resp.Body) - if err != nil { - t.Fatalf("readAll(resp.Body) failed with %v; want success", err) - } + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("resp.StatusCode = %d; want %d", got, want) + } - if got, want := resp.StatusCode, tt.wantStatus; got != want { - t.Errorf("resp.StatusCode = %d; want %d", got, want) - } + if diff := cmp.Diff(body, []string{`{"result":{"data":"first foo"}}`, `{"result":{"data":"second foo"}}`}); diff != "" { + t.Errorf(diff) + } +} - if !reflect.DeepEqual(tt.wantBody, body) { - t.Errorf("response = %v; want %v", body, tt.wantBody) - } - }) +func TestResponseBodyStreamHttpBody(t *testing.T) { + apiURL := "http://localhost:8088/v1/example/download" + resp, err := http.Get(apiURL) + if err != nil { + t.Fatalf("http.Get(%q) failed with %v; want success", apiURL, err) + } + + defer resp.Body.Close() + body, err := readAll(resp.Body) + if err != nil { + t.Fatalf("readAll(resp.Body) failed with %v; want success", err) + } + + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("resp.StatusCode = %d; want %d", got, want) + } + + if diff := cmp.Diff(body, []string{"Hello 1", "Hello 2"}); diff != "" { + t.Errorf(diff) + } +} + +func TestResponseBodyStreamHttpBodyError(t *testing.T) { + apiURL := "http://localhost:8088/v1/example/download?error=true" + resp, err := http.Get(apiURL) + if err != nil { + t.Fatalf("http.Get(%q) failed with %v; want success", apiURL, err) + } + + defer resp.Body.Close() + body, err := readAll(resp.Body) + if err != nil { + t.Fatalf("readAll(resp.Body) failed with %v; want success", err) + } + + if got, want := resp.StatusCode, http.StatusOK; got != want { + t.Errorf("resp.StatusCode = %d; want %d", got, want) + } + + if diff := cmp.Diff(body, []string{"Hello 1", "Hello 2", `{"error":{"code":3,"message":"error","details":[]}}`}); diff != "" { + t.Errorf(diff) } } diff --git a/examples/internal/proto/examplepb/stream.pb.go b/examples/internal/proto/examplepb/stream.pb.go index 1ebf52dd4ec..0f94dbc54aa 100644 --- a/examples/internal/proto/examplepb/stream.pb.go +++ b/examples/internal/proto/examplepb/stream.pb.go @@ -14,6 +14,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" + sync "sync" ) const ( @@ -23,6 +24,53 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type Options struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Error bool `protobuf:"varint,1,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *Options) Reset() { + *x = Options{} + if protoimpl.UnsafeEnabled { + mi := &file_examples_internal_proto_examplepb_stream_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Options) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Options) ProtoMessage() {} + +func (x *Options) ProtoReflect() protoreflect.Message { + mi := &file_examples_internal_proto_examplepb_stream_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Options.ProtoReflect.Descriptor instead. +func (*Options) Descriptor() ([]byte, []int) { + return file_examples_internal_proto_examplepb_stream_proto_rawDescGZIP(), []int{0} +} + +func (x *Options) GetError() bool { + if x != nil { + return x.Error + } + return false +} + var File_examples_internal_proto_examplepb_stream_proto protoreflect.FileDescriptor var file_examples_internal_proto_examplepb_stream_proto_rawDesc = []byte{ @@ -43,66 +91,87 @@ var file_examples_internal_proto_examplepb_stream_proto_rawDesc = []byte{ 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x62, 0x6f, 0x64, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xc7, - 0x04, 0x0a, 0x0d, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x12, 0x99, 0x01, 0x0a, 0x0a, 0x42, 0x75, 0x6c, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, + 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1f, + 0x0a, 0x07, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x32, + 0x89, 0x05, 0x0a, 0x0d, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x99, 0x01, 0x0a, 0x0a, 0x42, 0x75, 0x6c, 0x6b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x12, 0x40, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, + 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x70, + 0x62, 0x2e, 0x41, 0x42, 0x69, 0x74, 0x4f, 0x66, 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69, + 0x6e, 0x67, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x2f, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x29, 0x3a, 0x01, 0x2a, 0x22, 0x24, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x2f, 0x61, 0x5f, 0x62, 0x69, 0x74, 0x5f, 0x6f, 0x66, 0x5f, 0x65, 0x76, 0x65, 0x72, + 0x79, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x62, 0x75, 0x6c, 0x6b, 0x28, 0x01, 0x12, 0xac, 0x01, + 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x37, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61, + 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x65, 0x78, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x70, 0x62, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x40, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x70, 0x62, 0x2e, 0x41, 0x42, 0x69, 0x74, 0x4f, 0x66, 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69, 0x6e, - 0x67, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x2f, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x29, 0x3a, 0x01, 0x2a, 0x22, 0x24, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, - 0x65, 0x2f, 0x61, 0x5f, 0x62, 0x69, 0x74, 0x5f, 0x6f, 0x66, 0x5f, 0x65, 0x76, 0x65, 0x72, 0x79, - 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x62, 0x75, 0x6c, 0x6b, 0x28, 0x01, 0x12, 0x8b, 0x01, 0x0a, - 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x40, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x65, 0x78, 0x61, - 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x70, 0x62, 0x2e, 0x41, - 0x42, 0x69, 0x74, 0x4f, 0x66, 0x45, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x22, - 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, - 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x61, 0x5f, 0x62, 0x69, 0x74, 0x5f, 0x6f, 0x66, 0x5f, 0x65, 0x76, - 0x65, 0x72, 0x79, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x30, 0x01, 0x12, 0xb1, 0x01, 0x0a, 0x08, 0x42, - 0x75, 0x6c, 0x6b, 0x45, 0x63, 0x68, 0x6f, 0x12, 0x37, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, - 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2e, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, - 0x75, 0x62, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x1a, 0x37, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, - 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x75, 0x62, 0x2e, 0x53, 0x74, 0x72, 0x69, - 0x6e, 0x67, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x2f, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x29, 0x3a, 0x01, 0x2a, 0x22, 0x24, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, - 0x65, 0x2f, 0x61, 0x5f, 0x62, 0x69, 0x74, 0x5f, 0x6f, 0x66, 0x5f, 0x65, 0x76, 0x65, 0x72, 0x79, - 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x28, 0x01, 0x30, 0x01, 0x12, 0x58, - 0x0a, 0x08, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x1a, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, - 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x64, 0x6f, - 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x30, 0x01, 0x42, 0x4d, 0x5a, 0x4b, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x65, 0x63, 0x6f, 0x73, - 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x67, 0x61, 0x74, 0x65, 0x77, - 0x61, 0x79, 0x2f, 0x76, 0x32, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x78, - 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x67, 0x22, 0x27, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21, 0x12, 0x1f, 0x2f, 0x76, 0x31, 0x2f, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x61, 0x5f, 0x62, 0x69, 0x74, 0x5f, 0x6f, 0x66, 0x5f, + 0x65, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x30, 0x01, 0x12, 0xb1, 0x01, 0x0a, + 0x08, 0x42, 0x75, 0x6c, 0x6b, 0x45, 0x63, 0x68, 0x6f, 0x12, 0x37, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x73, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x73, 0x75, 0x62, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x1a, 0x37, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x75, 0x62, 0x2e, 0x53, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x2f, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x29, 0x3a, 0x01, 0x2a, 0x22, 0x24, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x2f, 0x61, 0x5f, 0x62, 0x69, 0x74, 0x5f, 0x6f, 0x66, 0x5f, 0x65, 0x76, 0x65, + 0x72, 0x79, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2f, 0x65, 0x63, 0x68, 0x6f, 0x28, 0x01, 0x30, 0x01, + 0x12, 0x79, 0x0a, 0x08, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x37, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x65, 0x78, 0x61, 0x6d, + 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x70, 0x62, 0x2e, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x42, 0x6f, 0x64, 0x79, 0x22, 0x1c, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x16, 0x12, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x2f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x30, 0x01, 0x42, 0x4d, 0x5a, 0x4b, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x65, + 0x63, 0x6f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2d, 0x67, 0x61, + 0x74, 0x65, 0x77, 0x61, 0x79, 0x2f, 0x76, 0x32, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x73, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_examples_internal_proto_examplepb_stream_proto_rawDescOnce sync.Once + file_examples_internal_proto_examplepb_stream_proto_rawDescData = file_examples_internal_proto_examplepb_stream_proto_rawDesc +) + +func file_examples_internal_proto_examplepb_stream_proto_rawDescGZIP() []byte { + file_examples_internal_proto_examplepb_stream_proto_rawDescOnce.Do(func() { + file_examples_internal_proto_examplepb_stream_proto_rawDescData = protoimpl.X.CompressGZIP(file_examples_internal_proto_examplepb_stream_proto_rawDescData) + }) + return file_examples_internal_proto_examplepb_stream_proto_rawDescData } +var file_examples_internal_proto_examplepb_stream_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_examples_internal_proto_examplepb_stream_proto_goTypes = []interface{}{ - (*ABitOfEverything)(nil), // 0: grpc.gateway.examples.internal.proto.examplepb.ABitOfEverything - (*emptypb.Empty)(nil), // 1: google.protobuf.Empty + (*Options)(nil), // 0: grpc.gateway.examples.internal.proto.examplepb.Options + (*ABitOfEverything)(nil), // 1: grpc.gateway.examples.internal.proto.examplepb.ABitOfEverything (*sub.StringMessage)(nil), // 2: grpc.gateway.examples.internal.proto.sub.StringMessage - (*httpbody.HttpBody)(nil), // 3: google.api.HttpBody + (*emptypb.Empty)(nil), // 3: google.protobuf.Empty + (*httpbody.HttpBody)(nil), // 4: google.api.HttpBody } var file_examples_internal_proto_examplepb_stream_proto_depIdxs = []int32{ - 0, // 0: grpc.gateway.examples.internal.proto.examplepb.StreamService.BulkCreate:input_type -> grpc.gateway.examples.internal.proto.examplepb.ABitOfEverything - 1, // 1: grpc.gateway.examples.internal.proto.examplepb.StreamService.List:input_type -> google.protobuf.Empty + 1, // 0: grpc.gateway.examples.internal.proto.examplepb.StreamService.BulkCreate:input_type -> grpc.gateway.examples.internal.proto.examplepb.ABitOfEverything + 0, // 1: grpc.gateway.examples.internal.proto.examplepb.StreamService.List:input_type -> grpc.gateway.examples.internal.proto.examplepb.Options 2, // 2: grpc.gateway.examples.internal.proto.examplepb.StreamService.BulkEcho:input_type -> grpc.gateway.examples.internal.proto.sub.StringMessage - 1, // 3: grpc.gateway.examples.internal.proto.examplepb.StreamService.Download:input_type -> google.protobuf.Empty - 1, // 4: grpc.gateway.examples.internal.proto.examplepb.StreamService.BulkCreate:output_type -> google.protobuf.Empty - 0, // 5: grpc.gateway.examples.internal.proto.examplepb.StreamService.List:output_type -> grpc.gateway.examples.internal.proto.examplepb.ABitOfEverything + 0, // 3: grpc.gateway.examples.internal.proto.examplepb.StreamService.Download:input_type -> grpc.gateway.examples.internal.proto.examplepb.Options + 3, // 4: grpc.gateway.examples.internal.proto.examplepb.StreamService.BulkCreate:output_type -> google.protobuf.Empty + 1, // 5: grpc.gateway.examples.internal.proto.examplepb.StreamService.List:output_type -> grpc.gateway.examples.internal.proto.examplepb.ABitOfEverything 2, // 6: grpc.gateway.examples.internal.proto.examplepb.StreamService.BulkEcho:output_type -> grpc.gateway.examples.internal.proto.sub.StringMessage - 3, // 7: grpc.gateway.examples.internal.proto.examplepb.StreamService.Download:output_type -> google.api.HttpBody + 4, // 7: grpc.gateway.examples.internal.proto.examplepb.StreamService.Download:output_type -> google.api.HttpBody 4, // [4:8] is the sub-list for method output_type 0, // [0:4] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name @@ -116,18 +185,33 @@ func file_examples_internal_proto_examplepb_stream_proto_init() { return } file_examples_internal_proto_examplepb_a_bit_of_everything_proto_init() + if !protoimpl.UnsafeEnabled { + file_examples_internal_proto_examplepb_stream_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Options); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_examples_internal_proto_examplepb_stream_proto_rawDesc, NumEnums: 0, - NumMessages: 0, + NumMessages: 1, NumExtensions: 0, NumServices: 1, }, GoTypes: file_examples_internal_proto_examplepb_stream_proto_goTypes, DependencyIndexes: file_examples_internal_proto_examplepb_stream_proto_depIdxs, + MessageInfos: file_examples_internal_proto_examplepb_stream_proto_msgTypes, }.Build() File_examples_internal_proto_examplepb_stream_proto = out.File file_examples_internal_proto_examplepb_stream_proto_rawDesc = nil diff --git a/examples/internal/proto/examplepb/stream.pb.gw.go b/examples/internal/proto/examplepb/stream.pb.gw.go index 14489d172a6..955ce61f2d1 100644 --- a/examples/internal/proto/examplepb/stream.pb.gw.go +++ b/examples/internal/proto/examplepb/stream.pb.gw.go @@ -22,7 +22,6 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/emptypb" ) // Suppress "imported and not used" errors @@ -77,10 +76,21 @@ func request_StreamService_BulkCreate_0(ctx context.Context, marshaler runtime.M } +var ( + filter_StreamService_List_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + func request_StreamService_List_0(ctx context.Context, marshaler runtime.Marshaler, client StreamServiceClient, req *http.Request, pathParams map[string]string) (StreamService_ListClient, runtime.ServerMetadata, error) { - var protoReq emptypb.Empty + var protoReq Options var metadata runtime.ServerMetadata + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_StreamService_List_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + stream, err := client.List(ctx, &protoReq) if err != nil { return nil, metadata, err @@ -137,10 +147,21 @@ func request_StreamService_BulkEcho_0(ctx context.Context, marshaler runtime.Mar return stream, metadata, nil } +var ( + filter_StreamService_Download_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + func request_StreamService_Download_0(ctx context.Context, marshaler runtime.Marshaler, client StreamServiceClient, req *http.Request, pathParams map[string]string) (StreamService_DownloadClient, runtime.ServerMetadata, error) { - var protoReq emptypb.Empty + var protoReq Options var metadata runtime.ServerMetadata + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_StreamService_Download_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + stream, err := client.Download(ctx, &protoReq) if err != nil { return nil, metadata, err diff --git a/examples/internal/proto/examplepb/stream.proto b/examples/internal/proto/examplepb/stream.proto index c40616df792..8d1a58db8bd 100644 --- a/examples/internal/proto/examplepb/stream.proto +++ b/examples/internal/proto/examplepb/stream.proto @@ -18,7 +18,7 @@ service StreamService { body: "*" }; } - rpc List(google.protobuf.Empty) returns (stream ABitOfEverything) { + rpc List(Options) returns (stream ABitOfEverything) { option (google.api.http) = {get: "/v1/example/a_bit_of_everything"}; } rpc BulkEcho(stream grpc.gateway.examples.internal.proto.sub.StringMessage) returns (stream grpc.gateway.examples.internal.proto.sub.StringMessage) { @@ -28,7 +28,11 @@ service StreamService { }; } - rpc Download(google.protobuf.Empty) returns (stream google.api.HttpBody) { + rpc Download(Options) returns (stream google.api.HttpBody) { option (google.api.http) = {get: "/v1/example/download"}; } } + +message Options { + bool error = 1; +} diff --git a/examples/internal/proto/examplepb/stream.swagger.json b/examples/internal/proto/examplepb/stream.swagger.json index e3f2b67b397..4d0b691d092 100644 --- a/examples/internal/proto/examplepb/stream.swagger.json +++ b/examples/internal/proto/examplepb/stream.swagger.json @@ -42,6 +42,14 @@ } } }, + "parameters": [ + { + "name": "error", + "in": "query", + "required": false, + "type": "boolean" + } + ], "tags": [ "StreamService" ] @@ -130,16 +138,10 @@ "200": { "description": "A successful response.(streaming responses)", "schema": { - "type": "object", - "properties": { - "result": { - "$ref": "#/definitions/apiHttpBody" - }, - "error": { - "$ref": "#/definitions/rpcStatus" - } - }, - "title": "Stream result of apiHttpBody" + "type": "string", + "format": "binary", + "properties": {}, + "title": "Free form byte stream" } }, "default": { @@ -149,6 +151,14 @@ } } }, + "parameters": [ + { + "name": "error", + "in": "query", + "required": false, + "type": "boolean" + } + ], "tags": [ "StreamService" ] diff --git a/examples/internal/proto/examplepb/stream_grpc.pb.go b/examples/internal/proto/examplepb/stream_grpc.pb.go index 0ccadecb6b1..f627e81121b 100644 --- a/examples/internal/proto/examplepb/stream_grpc.pb.go +++ b/examples/internal/proto/examplepb/stream_grpc.pb.go @@ -26,9 +26,9 @@ const _ = grpc.SupportPackageIsVersion7 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type StreamServiceClient interface { BulkCreate(ctx context.Context, opts ...grpc.CallOption) (StreamService_BulkCreateClient, error) - List(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (StreamService_ListClient, error) + List(ctx context.Context, in *Options, opts ...grpc.CallOption) (StreamService_ListClient, error) BulkEcho(ctx context.Context, opts ...grpc.CallOption) (StreamService_BulkEchoClient, error) - Download(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (StreamService_DownloadClient, error) + Download(ctx context.Context, in *Options, opts ...grpc.CallOption) (StreamService_DownloadClient, error) } type streamServiceClient struct { @@ -73,7 +73,7 @@ func (x *streamServiceBulkCreateClient) CloseAndRecv() (*emptypb.Empty, error) { return m, nil } -func (c *streamServiceClient) List(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (StreamService_ListClient, error) { +func (c *streamServiceClient) List(ctx context.Context, in *Options, opts ...grpc.CallOption) (StreamService_ListClient, error) { stream, err := c.cc.NewStream(ctx, &StreamService_ServiceDesc.Streams[1], "/grpc.gateway.examples.internal.proto.examplepb.StreamService/List", opts...) if err != nil { return nil, err @@ -136,7 +136,7 @@ func (x *streamServiceBulkEchoClient) Recv() (*sub.StringMessage, error) { return m, nil } -func (c *streamServiceClient) Download(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (StreamService_DownloadClient, error) { +func (c *streamServiceClient) Download(ctx context.Context, in *Options, opts ...grpc.CallOption) (StreamService_DownloadClient, error) { stream, err := c.cc.NewStream(ctx, &StreamService_ServiceDesc.Streams[3], "/grpc.gateway.examples.internal.proto.examplepb.StreamService/Download", opts...) if err != nil { return nil, err @@ -173,9 +173,9 @@ func (x *streamServiceDownloadClient) Recv() (*httpbody.HttpBody, error) { // for forward compatibility type StreamServiceServer interface { BulkCreate(StreamService_BulkCreateServer) error - List(*emptypb.Empty, StreamService_ListServer) error + List(*Options, StreamService_ListServer) error BulkEcho(StreamService_BulkEchoServer) error - Download(*emptypb.Empty, StreamService_DownloadServer) error + Download(*Options, StreamService_DownloadServer) error } // UnimplementedStreamServiceServer should be embedded to have forward compatible implementations. @@ -185,13 +185,13 @@ type UnimplementedStreamServiceServer struct { func (UnimplementedStreamServiceServer) BulkCreate(StreamService_BulkCreateServer) error { return status.Errorf(codes.Unimplemented, "method BulkCreate not implemented") } -func (UnimplementedStreamServiceServer) List(*emptypb.Empty, StreamService_ListServer) error { +func (UnimplementedStreamServiceServer) List(*Options, StreamService_ListServer) error { return status.Errorf(codes.Unimplemented, "method List not implemented") } func (UnimplementedStreamServiceServer) BulkEcho(StreamService_BulkEchoServer) error { return status.Errorf(codes.Unimplemented, "method BulkEcho not implemented") } -func (UnimplementedStreamServiceServer) Download(*emptypb.Empty, StreamService_DownloadServer) error { +func (UnimplementedStreamServiceServer) Download(*Options, StreamService_DownloadServer) error { return status.Errorf(codes.Unimplemented, "method Download not implemented") } @@ -233,7 +233,7 @@ func (x *streamServiceBulkCreateServer) Recv() (*ABitOfEverything, error) { } func _StreamService_List_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(emptypb.Empty) + m := new(Options) if err := stream.RecvMsg(m); err != nil { return err } @@ -280,7 +280,7 @@ func (x *streamServiceBulkEchoServer) Recv() (*sub.StringMessage, error) { } func _StreamService_Download_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(emptypb.Empty) + m := new(Options) if err := stream.RecvMsg(m); err != nil { return err } diff --git a/examples/internal/server/a_bit_of_everything.go b/examples/internal/server/a_bit_of_everything.go index d129edca2e8..fc94846a2d0 100644 --- a/examples/internal/server/a_bit_of_everything.go +++ b/examples/internal/server/a_bit_of_everything.go @@ -138,7 +138,7 @@ func (s *_ABitOfEverythingServer) Lookup(ctx context.Context, msg *sub2.IdMessag return nil, status.Errorf(codes.NotFound, "not found") } -func (s *_ABitOfEverythingServer) List(_ *emptypb.Empty, stream examples.StreamService_ListServer) error { +func (s *_ABitOfEverythingServer) List(opt *examples.Options, stream examples.StreamService_ListServer) error { s.m.Lock() defer s.m.Unlock() @@ -155,20 +155,17 @@ func (s *_ABitOfEverythingServer) List(_ *emptypb.Empty, stream examples.StreamS } } - // return error when metadata includes error header - if header, ok := metadata.FromIncomingContext(stream.Context()); ok { - if v, ok := header["error"]; ok { - stream.SetTrailer(metadata.New(map[string]string{ - "foo": "foo2", - "bar": "bar2", - })) - return status.Errorf(codes.InvalidArgument, "error metadata: %v", v) - } + if opt.Error { + stream.SetTrailer(metadata.New(map[string]string{ + "foo": "foo2", + "bar": "bar2", + })) + return status.Error(codes.InvalidArgument, "error") } return nil } -func (s *_ABitOfEverythingServer) Download(_ *emptypb.Empty, stream examples.StreamService_DownloadServer) error { +func (s *_ABitOfEverythingServer) Download(opt *examples.Options, stream examples.StreamService_DownloadServer) error { msgs := []*httpbody.HttpBody{{ ContentType: "text/html", Data: []byte("Hello 1"), @@ -185,6 +182,13 @@ func (s *_ABitOfEverythingServer) Download(_ *emptypb.Empty, stream examples.Str time.Sleep(5 * time.Millisecond) } + if opt.Error { + stream.SetTrailer(metadata.New(map[string]string{ + "foo": "foo2", + "bar": "bar2", + })) + return status.Error(codes.InvalidArgument, "error") + } return nil } diff --git a/protoc-gen-openapiv2/internal/genopenapi/BUILD.bazel b/protoc-gen-openapiv2/internal/genopenapi/BUILD.bazel index 7218690bd20..a98cea10054 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/BUILD.bazel +++ b/protoc-gen-openapiv2/internal/genopenapi/BUILD.bazel @@ -64,6 +64,8 @@ go_test( "@org_golang_google_protobuf//encoding/prototext", "@org_golang_google_protobuf//proto", "@org_golang_google_protobuf//reflect/protodesc", + "@org_golang_google_protobuf//reflect/protoreflect", + "@org_golang_google_protobuf//reflect/protoregistry", "@org_golang_google_protobuf//types/descriptorpb", "@org_golang_google_protobuf//types/known/anypb", "@org_golang_google_protobuf//types/known/durationpb", diff --git a/protoc-gen-openapiv2/internal/genopenapi/template.go b/protoc-gen-openapiv2/internal/genopenapi/template.go index 0d49f07becf..056d33ef2a3 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/template.go +++ b/protoc-gen-openapiv2/internal/genopenapi/template.go @@ -1440,6 +1440,17 @@ func renderServices(services []*descriptor.Service, paths openapiPathsObject, re }) } } + + // Special case HttpBody responses, they will be unformatted bytes + if meth.ResponseType.FQMN() == ".google.api.HttpBody" { + responseSchema.Type = "string" + responseSchema.Format = "binary" + responseSchema.Title = "Free form byte stream" + // The error response is still JSON, but technically the full response + // is still unformatted, so don't include the error response structure. + props = nil + } + responseSchema.Properties = &props responseSchema.Ref = "" } diff --git a/protoc-gen-openapiv2/internal/genopenapi/template_test.go b/protoc-gen-openapiv2/internal/genopenapi/template_test.go index f9d0882c499..0e1fc3def25 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/template_test.go +++ b/protoc-gen-openapiv2/internal/genopenapi/template_test.go @@ -22,6 +22,8 @@ import ( "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protodesc" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/descriptorpb" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/durationpb" @@ -9042,3 +9044,126 @@ func TestQueryParameterType(t *testing.T) { t.Errorf("got: %s", fmt.Sprint(result)) } } + +func TestApplyTemplateRequestWithServerStreamingHttpBody(t *testing.T) { + meth := &descriptorpb.MethodDescriptorProto{ + Name: proto.String("Echo"), + InputType: proto.String(".google.api.HttpBody"), + OutputType: proto.String(".google.api.HttpBody"), + ClientStreaming: proto.Bool(false), + ServerStreaming: proto.Bool(true), + } + svc := &descriptorpb.ServiceDescriptorProto{ + Name: proto.String("ExampleService"), + Method: []*descriptorpb.MethodDescriptorProto{meth}, + } + httpBodyFile, err := protoregistry.GlobalFiles.FindFileByPath("google/api/httpbody.proto") + if err != nil { + t.Fatal(err) + } + httpBodyFile.SourceLocations() + desc, err := protoregistry.GlobalFiles.FindDescriptorByName("google.api.HttpBody") + if err != nil { + t.Fatal(err) + } + msg := &descriptor.Message{ + DescriptorProto: protodesc.ToDescriptorProto(desc.(protoreflect.MessageDescriptor)), + File: &descriptor.File{ + FileDescriptorProto: protodesc.ToFileDescriptorProto(httpBodyFile), + }, + } + anyFile, err := protoregistry.GlobalFiles.FindFileByPath("google/protobuf/any.proto") + if err != nil { + t.Fatal(err) + } + file := descriptor.File{ + FileDescriptorProto: &descriptorpb.FileDescriptorProto{ + SourceCodeInfo: &descriptorpb.SourceCodeInfo{}, + Name: proto.String("example.proto"), + Package: proto.String("example"), + Dependency: []string{ + "google/api/httpbody.proto", + }, + Service: []*descriptorpb.ServiceDescriptorProto{svc}, + Options: &descriptorpb.FileOptions{ + GoPackage: proto.String("github.com/grpc-ecosystem/grpc-gateway/runtime/internal/examplepb;example"), + }, + }, + GoPkg: descriptor.GoPackage{ + Path: "example.com/path/to/example/example.pb", + Name: "example_pb", + }, + Services: []*descriptor.Service{ + { + ServiceDescriptorProto: svc, + Methods: []*descriptor.Method{ + { + MethodDescriptorProto: meth, + RequestType: msg, + ResponseType: msg, + Bindings: []*descriptor.Binding{ + { + HTTPMethod: "POST", + PathTmpl: httprule.Template{ + Version: 1, + OpCodes: []int{0, 0}, + Template: "/v1/echo", + }, + }, + }, + }, + }, + }, + }, + } + reg := descriptor.NewRegistry() + if err := AddErrorDefs(reg); err != nil { + t.Errorf("AddErrorDefs(%#v) failed with %v; want success", reg, err) + return + } + err = reg.Load(&pluginpb.CodeGeneratorRequest{ + ProtoFile: []*descriptorpb.FileDescriptorProto{ + protodesc.ToFileDescriptorProto(anyFile), + protodesc.ToFileDescriptorProto(httpBodyFile), + file.FileDescriptorProto, + }, + }) + if err != nil { + t.Fatalf("failed to load code generator request: %v", err) + } + result, err := applyTemplate(param{File: crossLinkFixture(&file), reg: reg}) + if err != nil { + t.Errorf("applyTemplate(%#v) failed with %v; want success", file, err) + return + } + + if want, got, name := 3, len(result.Definitions), "len(Definitions)"; !reflect.DeepEqual(got, want) { + t.Errorf("applyTemplate(%#v).%s = %d want to be %d", file, name, got, want) + } + if _, ok := result.Paths["/v1/echo"].Post.Responses["200"]; !ok { + t.Errorf("applyTemplate(%#v).%s = expected 200 response to be defined", file, `result.Paths["/v1/echo"].Post.Responses["200"]`) + } else { + if want, got, name := "A successful response.(streaming responses)", result.Paths["/v1/echo"].Post.Responses["200"].Description, `result.Paths["/v1/echo"].Post.Responses["200"].Description`; !reflect.DeepEqual(got, want) { + t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) + } + streamExampleExampleMessage := result.Paths["/v1/echo"].Post.Responses["200"].Schema + if want, got, name := "string", streamExampleExampleMessage.Type, `result.Paths["/v1/echo"].Post.Responses["200"].Schema.Type`; !reflect.DeepEqual(got, want) { + t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) + } + if want, got, name := "binary", streamExampleExampleMessage.Format, `result.Paths["/v1/echo"].Post.Responses["200"].Schema.Format`; !reflect.DeepEqual(got, want) { + t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) + } + if want, got, name := "Free form byte stream", streamExampleExampleMessage.Title, `result.Paths["/v1/echo"].Post.Responses["200"].Schema.Title`; !reflect.DeepEqual(got, want) { + t.Errorf("applyTemplate(%#v).%s = %s want to be %s", file, name, got, want) + } + if len(*streamExampleExampleMessage.Properties) != 0 { + t.Errorf("applyTemplate(%#v).Properties should be empty", file) + } + } + + // If there was a failure, print out the input and the json result for debugging. + if t.Failed() { + t.Errorf("had: %s", file) + t.Errorf("got: %s", fmt.Sprint(result)) + } +}