From 6fba73250e8f4d4f62ebdc5c3c22d305d79b916c Mon Sep 17 00:00:00 2001 From: iamrajiv Date: Thu, 23 Oct 2025 12:30:18 +0530 Subject: [PATCH 1/2] fix Signed-off-by: iamrajiv --- protoc-gen-openapiv2/internal/genopenapi/generator_test.go | 6 +++++- protoc-gen-openapiv2/internal/genopenapi/template.go | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/protoc-gen-openapiv2/internal/genopenapi/generator_test.go b/protoc-gen-openapiv2/internal/genopenapi/generator_test.go index 8c52489d72b..956a5a37dfa 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/generator_test.go +++ b/protoc-gen-openapiv2/internal/genopenapi/generator_test.go @@ -83,7 +83,11 @@ func TestGenerateExtension(t *testing.T) { name: "Test" input_type: ".example.v1.Foo" output_type: ".example.v1.Foo" - options: {} + options: { + [google.api.http]: { + get: "/v1/test" + } + } } } options: { diff --git a/protoc-gen-openapiv2/internal/genopenapi/template.go b/protoc-gen-openapiv2/internal/genopenapi/template.go index 6265da8fe48..bbb918117c0 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/template.go +++ b/protoc-gen-openapiv2/internal/genopenapi/template.go @@ -451,6 +451,12 @@ func findServicesMessagesAndEnumerations(s []*descriptor.Service, reg *descripto continue } + // Only process methods with HTTP bindings (exposed via HTTP annotations) + // This prevents unused message definitions from appearing in the OpenAPI document + if len(meth.Bindings) == 0 { + continue + } + swgReqName, ok := fullyQualifiedNameToOpenAPIName(meth.RequestType.FQMN(), reg) if !ok { grpclog.Errorf("couldn't resolve OpenAPI name for FQMN %q", meth.RequestType.FQMN()) From 486cec709586df29957e9a6e505dbeff3e09df8d Mon Sep 17 00:00:00 2001 From: iamrajiv Date: Mon, 1 Dec 2025 18:59:38 +0530 Subject: [PATCH 2/2] add test --- .../internal/genopenapi/generator_test.go | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/protoc-gen-openapiv2/internal/genopenapi/generator_test.go b/protoc-gen-openapiv2/internal/genopenapi/generator_test.go index 956a5a37dfa..1255d7ae5cb 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/generator_test.go +++ b/protoc-gen-openapiv2/internal/genopenapi/generator_test.go @@ -1942,3 +1942,136 @@ func TestGenerateXGoType(t *testing.T) { }) } } + +// TestIssue5684_UnusedMethodsNotInOpenAPI tests that methods without HTTP bindings +// do not appear in the OpenAPI definitions. +// See https://github.com/grpc-ecosystem/grpc-gateway/issues/5684 +func TestIssue5684_UnusedMethodsNotInOpenAPI(t *testing.T) { + t.Parallel() + + // Create a proto definition similar to the issue report: + // - Service with two methods: Add (no HTTP binding) and Show (with HTTP binding) + // - Only Show should appear in the OpenAPI output + // - AddRequest and AddResponse should NOT appear in definitions + const in = ` + file_to_generate: "account/account.proto" + proto_file: { + name: "account/account.proto" + package: "account" + + message_type: { + name: "Money" + field: { + name: "amount" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_INT64 + json_name: "amount" + } + } + + message_type: { + name: "AddRequest" + field: { + name: "money" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_MESSAGE + type_name: ".account.Money" + json_name: "money" + } + } + + message_type: { + name: "AddResponse" + } + + message_type: { + name: "ShowRequest" + } + + message_type: { + name: "ShowResponse" + field: { + name: "money" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_MESSAGE + type_name: ".account.Money" + json_name: "money" + } + } + + service: { + name: "AccountService" + method: { + name: "Add" + input_type: ".account.AddRequest" + output_type: ".account.AddResponse" + } + method: { + name: "Show" + input_type: ".account.ShowRequest" + output_type: ".account.ShowResponse" + options: { + [google.api.http]: { + get: "/v1/account" + } + } + } + } + + options: { + go_package: "accounts/pkg/account;account" + } + }` + + var req pluginpb.CodeGeneratorRequest + if err := prototext.Unmarshal([]byte(in), &req); err != nil { + t.Fatalf("failed to unmarshal proto: %v", err) + } + + resp := requireGenerate(t, &req, genopenapi.FormatYAML, false, false) + if len(resp) != 1 { + t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) + } + + var openAPIDoc map[string]interface{} + if err := yaml.Unmarshal([]byte(resp[0].GetContent()), &openAPIDoc); err != nil { + t.Fatalf("failed to parse OpenAPI YAML: %v", err) + } + + definitions, ok := openAPIDoc["definitions"].(map[string]interface{}) + if !ok { + t.Fatalf("no definitions found in OpenAPI document") + } + + if _, exists := definitions["accountAddRequest"]; exists { + t.Error("accountAddRequest found in definitions, but should be excluded (Add method has no HTTP binding)") + } + + if _, exists := definitions["accountAddResponse"]; exists { + t.Error("accountAddResponse found in definitions, but should be excluded (Add method has no HTTP binding)") + } + + if _, exists := definitions["accountShowResponse"]; !exists { + t.Error("accountShowResponse not found in definitions, but should be included (Show method has HTTP binding)") + } + + if _, exists := definitions["accountMoney"]; !exists { + t.Error("accountMoney not found in definitions, but should be included (referenced by ShowResponse)") + } + + paths, ok := openAPIDoc["paths"].(map[string]interface{}) + if !ok { + t.Fatalf("no paths found in OpenAPI document") + } + + if _, exists := paths["/v1/account"]; !exists { + t.Error("/v1/account path not found, but should be included (Show method)") + } + + if len(paths) != 1 { + t.Errorf("expected exactly 1 path, got %d paths", len(paths)) + } +}