Skip to content

Commit 5c40758

Browse files
authored
fix: Exclude methods without HTTP bindings from OpenAPI definitions (#6030)
* fix Signed-off-by: iamrajiv <rajivperfect007@gmail.com> * add test --------- Signed-off-by: iamrajiv <rajivperfect007@gmail.com>
1 parent d2d5e58 commit 5c40758

File tree

2 files changed

+144
-1
lines changed

2 files changed

+144
-1
lines changed

protoc-gen-openapiv2/internal/genopenapi/generator_test.go

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ func TestGenerateExtension(t *testing.T) {
8383
name: "Test"
8484
input_type: ".example.v1.Foo"
8585
output_type: ".example.v1.Foo"
86-
options: {}
86+
options: {
87+
[google.api.http]: {
88+
get: "/v1/test"
89+
}
90+
}
8791
}
8892
}
8993
options: {
@@ -1938,3 +1942,136 @@ func TestGenerateXGoType(t *testing.T) {
19381942
})
19391943
}
19401944
}
1945+
1946+
// TestIssue5684_UnusedMethodsNotInOpenAPI tests that methods without HTTP bindings
1947+
// do not appear in the OpenAPI definitions.
1948+
// See https://github.com/grpc-ecosystem/grpc-gateway/issues/5684
1949+
func TestIssue5684_UnusedMethodsNotInOpenAPI(t *testing.T) {
1950+
t.Parallel()
1951+
1952+
// Create a proto definition similar to the issue report:
1953+
// - Service with two methods: Add (no HTTP binding) and Show (with HTTP binding)
1954+
// - Only Show should appear in the OpenAPI output
1955+
// - AddRequest and AddResponse should NOT appear in definitions
1956+
const in = `
1957+
file_to_generate: "account/account.proto"
1958+
proto_file: {
1959+
name: "account/account.proto"
1960+
package: "account"
1961+
1962+
message_type: {
1963+
name: "Money"
1964+
field: {
1965+
name: "amount"
1966+
number: 1
1967+
label: LABEL_OPTIONAL
1968+
type: TYPE_INT64
1969+
json_name: "amount"
1970+
}
1971+
}
1972+
1973+
message_type: {
1974+
name: "AddRequest"
1975+
field: {
1976+
name: "money"
1977+
number: 1
1978+
label: LABEL_OPTIONAL
1979+
type: TYPE_MESSAGE
1980+
type_name: ".account.Money"
1981+
json_name: "money"
1982+
}
1983+
}
1984+
1985+
message_type: {
1986+
name: "AddResponse"
1987+
}
1988+
1989+
message_type: {
1990+
name: "ShowRequest"
1991+
}
1992+
1993+
message_type: {
1994+
name: "ShowResponse"
1995+
field: {
1996+
name: "money"
1997+
number: 1
1998+
label: LABEL_OPTIONAL
1999+
type: TYPE_MESSAGE
2000+
type_name: ".account.Money"
2001+
json_name: "money"
2002+
}
2003+
}
2004+
2005+
service: {
2006+
name: "AccountService"
2007+
method: {
2008+
name: "Add"
2009+
input_type: ".account.AddRequest"
2010+
output_type: ".account.AddResponse"
2011+
}
2012+
method: {
2013+
name: "Show"
2014+
input_type: ".account.ShowRequest"
2015+
output_type: ".account.ShowResponse"
2016+
options: {
2017+
[google.api.http]: {
2018+
get: "/v1/account"
2019+
}
2020+
}
2021+
}
2022+
}
2023+
2024+
options: {
2025+
go_package: "accounts/pkg/account;account"
2026+
}
2027+
}`
2028+
2029+
var req pluginpb.CodeGeneratorRequest
2030+
if err := prototext.Unmarshal([]byte(in), &req); err != nil {
2031+
t.Fatalf("failed to unmarshal proto: %v", err)
2032+
}
2033+
2034+
resp := requireGenerate(t, &req, genopenapi.FormatYAML, false, false)
2035+
if len(resp) != 1 {
2036+
t.Fatalf("invalid count, expected: 1, actual: %d", len(resp))
2037+
}
2038+
2039+
var openAPIDoc map[string]interface{}
2040+
if err := yaml.Unmarshal([]byte(resp[0].GetContent()), &openAPIDoc); err != nil {
2041+
t.Fatalf("failed to parse OpenAPI YAML: %v", err)
2042+
}
2043+
2044+
definitions, ok := openAPIDoc["definitions"].(map[string]interface{})
2045+
if !ok {
2046+
t.Fatalf("no definitions found in OpenAPI document")
2047+
}
2048+
2049+
if _, exists := definitions["accountAddRequest"]; exists {
2050+
t.Error("accountAddRequest found in definitions, but should be excluded (Add method has no HTTP binding)")
2051+
}
2052+
2053+
if _, exists := definitions["accountAddResponse"]; exists {
2054+
t.Error("accountAddResponse found in definitions, but should be excluded (Add method has no HTTP binding)")
2055+
}
2056+
2057+
if _, exists := definitions["accountShowResponse"]; !exists {
2058+
t.Error("accountShowResponse not found in definitions, but should be included (Show method has HTTP binding)")
2059+
}
2060+
2061+
if _, exists := definitions["accountMoney"]; !exists {
2062+
t.Error("accountMoney not found in definitions, but should be included (referenced by ShowResponse)")
2063+
}
2064+
2065+
paths, ok := openAPIDoc["paths"].(map[string]interface{})
2066+
if !ok {
2067+
t.Fatalf("no paths found in OpenAPI document")
2068+
}
2069+
2070+
if _, exists := paths["/v1/account"]; !exists {
2071+
t.Error("/v1/account path not found, but should be included (Show method)")
2072+
}
2073+
2074+
if len(paths) != 1 {
2075+
t.Errorf("expected exactly 1 path, got %d paths", len(paths))
2076+
}
2077+
}

protoc-gen-openapiv2/internal/genopenapi/template.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,12 @@ func findServicesMessagesAndEnumerations(s []*descriptor.Service, reg *descripto
457457
continue
458458
}
459459

460+
// Only process methods with HTTP bindings (exposed via HTTP annotations)
461+
// This prevents unused message definitions from appearing in the OpenAPI document
462+
if len(meth.Bindings) == 0 {
463+
continue
464+
}
465+
460466
swgReqName, ok := fullyQualifiedNameToOpenAPIName(meth.RequestType.FQMN(), reg)
461467
if !ok {
462468
grpclog.Errorf("couldn't resolve OpenAPI name for FQMN %q", meth.RequestType.FQMN())

0 commit comments

Comments
 (0)