diff --git a/protoc-gen-openapiv2/internal/genopenapi/generator_test.go b/protoc-gen-openapiv2/internal/genopenapi/generator_test.go index 18e755f266e..1255d7ae5cb 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/generator_test.go +++ b/protoc-gen-openapiv2/internal/genopenapi/generator_test.go @@ -2075,135 +2075,3 @@ func TestIssue5684_UnusedMethodsNotInOpenAPI(t *testing.T) { t.Errorf("expected exactly 1 path, got %d paths", len(paths)) } } - -// TestGenerateMergeFilesWithBodyAndPathParams tests that OpenAPI generation -// doesn't panic when merging files where a service uses body:"*" with path parameters. -// This reproduces the bug from https://github.com/grpc-ecosystem/grpc-gateway/issues/6274 -func TestGenerateMergeFilesWithBodyAndPathParams(t *testing.T) { - t.Parallel() - - // First proto file: contains only message definitions, with swagger option - // This file will be the merge target since it has the swagger option - const messagesProto = ` - proto_file: { - name: "example/v1/messages.proto" - package: "example.v1" - message_type: { - name: "Item" - field: { - name: "id" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_STRING - json_name: "id" - } - field: { - name: "name" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_STRING - json_name: "name" - } - } - message_type: { - name: "UpdateItemRequest" - field: { - name: "id" - number: 1 - label: LABEL_OPTIONAL - type: TYPE_STRING - json_name: "id" - } - field: { - name: "name" - number: 2 - label: LABEL_OPTIONAL - type: TYPE_STRING - json_name: "name" - } - } - options: { - go_package: "example/v1;examplev1" - [grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger]: { - info: { - title: "Test API" - version: "1.0" - } - } - } - syntax: "proto3" - }` - - // Second proto file: contains the service that references messages from first file - // This file does NOT have the swagger option, so it won't be the merge target - const serviceProto = ` - proto_file: { - name: "example/v1/service.proto" - package: "example.v1" - dependency: "example/v1/messages.proto" - service: { - name: "ItemService" - method: { - name: "UpdateItem" - input_type: ".example.v1.UpdateItemRequest" - output_type: ".example.v1.Item" - options: { - [google.api.http]: { - put: "/v1/items/{id}" - body: "*" - } - } - } - } - options: { - go_package: "example/v1;examplev1" - } - syntax: "proto3" - }` - - var msgReq, svcReq pluginpb.CodeGeneratorRequest - if err := prototext.Unmarshal([]byte(messagesProto), &msgReq); err != nil { - t.Fatalf("failed to unmarshal messages proto: %s", err) - } - if err := prototext.Unmarshal([]byte(serviceProto), &svcReq); err != nil { - t.Fatalf("failed to unmarshal service proto: %s", err) - } - - // Combine into a single request with both files to generate - req := &pluginpb.CodeGeneratorRequest{ - ProtoFile: append(msgReq.ProtoFile, svcReq.ProtoFile...), - FileToGenerate: []string{"example/v1/messages.proto", "example/v1/service.proto"}, - } - - formats := [...]genopenapi.Format{ - genopenapi.FormatJSON, - genopenapi.FormatYAML, - } - - for _, format := range formats { - format := format - t.Run(string(format), func(t *testing.T) { - t.Parallel() - - // This should not panic - the bug causes panic with - // "failed to resolve method FQN: '.example.v1.ItemService.UpdateItem'" - resp := requireGenerate(t, req, format, false, true) - if len(resp) != 1 { - t.Fatalf("invalid count, expected: 1, actual: %d", len(resp)) - } - - content := resp[0].GetContent() - t.Log(content) - - // Verify the path exists in output - if !strings.Contains(content, "/v1/items/{id}") { - t.Error("expected /v1/items/{id} path in output") - } - - // Verify the body definition was created (this is what triggers the bug) - if !strings.Contains(content, "ItemServiceUpdateItemBody") { - t.Error("expected ItemServiceUpdateItemBody definition in output") - } - }) - } -} diff --git a/protoc-gen-openapiv2/internal/genopenapi/template.go b/protoc-gen-openapiv2/internal/genopenapi/template.go index 224f60659aa..a1635f5dce2 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/template.go +++ b/protoc-gen-openapiv2/internal/genopenapi/template.go @@ -1569,14 +1569,7 @@ func renderServices(services []*descriptor.Service, paths *openapiPathsObject, r if meth.Name != nil { methFQN, ok := fullyQualifiedNameToOpenAPIName(meth.FQMN(), reg) if !ok { - // Fallback: use FQN naming (strip leading dot) if method FQN not found in mapping. - // This can happen when files are merged and method FQNs are not properly registered. - fqmn := meth.FQMN() - if len(fqmn) > 0 && fqmn[0] == '.' { - methFQN = fqmn[1:] - } else { - methFQN = fqmn - } + panic(fmt.Errorf("failed to resolve method FQN: '%s'", meth.FQMN())) } defName := methFQN + "Body" schema.Ref = fmt.Sprintf("#/definitions/%s", defName) @@ -2085,15 +2078,6 @@ func operationForMethod(httpMethod string) func(*openapiPathItemObject) *openapi // This function is called with a param which contains the entire definition of a method. func applyTemplate(p param) (*openapiSwaggerObject, error) { - // Clear the naming cache for this registry at the start of each file processing. - // This is necessary because when multiple files are processed, the cache may contain - // a filtered mapping from a previous file that doesn't include all names needed - // for the current file (e.g., method FQNs from the current file's services). - // The cache will be rebuilt with the appropriate filtered names later in this function. - registriesSeenMutex.Lock() - delete(registriesSeen, p.reg) - registriesSeenMutex.Unlock() - // Create the basic template object. This is the object that everything is // defined off of. s := openapiSwaggerObject{