diff --git a/protoc-gen-openapiv2/internal/genopenapi/template.go b/protoc-gen-openapiv2/internal/genopenapi/template.go index 62cfdaf5206..a1635f5dce2 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/template.go +++ b/protoc-gen-openapiv2/internal/genopenapi/template.go @@ -709,8 +709,13 @@ func renderFieldAsDefinition(f *descriptor.Field, reg *descriptor.Registry, refs if len(comments) > 0 { // Use title and description from field instead of nested message if present. paragraphs := strings.Split(comments, paragraphDeliminator) - schema.Title = strings.TrimSpace(paragraphs[0]) - schema.Description = strings.TrimSpace(strings.Join(paragraphs[1:], paragraphDeliminator)) + firstParagraph := strings.TrimSpace(paragraphs[0]) + if !strings.Contains(firstParagraph, "\n") { + schema.Title = firstParagraph + schema.Description = strings.TrimSpace(strings.Join(paragraphs[1:], paragraphDeliminator)) + } else { + schema.Description = strings.TrimSpace(comments) + } } // to handle case where path param is present inside the field of descriptorpb.FieldDescriptorProto_TYPE_MESSAGE type diff --git a/protoc-gen-openapiv2/internal/genopenapi/template_test.go b/protoc-gen-openapiv2/internal/genopenapi/template_test.go index 7ab82e69752..783a3957ec8 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/template_test.go +++ b/protoc-gen-openapiv2/internal/genopenapi/template_test.go @@ -12025,6 +12025,7 @@ func Test_updateSwaggerObjectFromFieldBehavior(t *testing.T) { }) } } + // TestBodyParameterRequiredFieldBug tests the bug where the body parameter name // is incorrectly added to the schema's required array when using body: "field_name" func TestBodyParameterRequiredFieldBug(t *testing.T) { @@ -12724,3 +12725,61 @@ func TestBodyParameterSelfReferentialBug(t *testing.T) { bodyParam.Schema.Required, expectedRequired, cmp.Diff(expectedRequired, bodyParam.Schema.Required)) } } + +func TestFieldCommentsWithNewlines(t *testing.T) { + tests := []struct { + name string + comments string + expectedTitle string + expectedDesc string + }{ + { + name: "single line comment goes to title", + comments: "Short comment without newlines", + expectedTitle: "Short comment without newlines", + expectedDesc: "", + }, + { + name: "two paragraphs - first goes to title", + comments: "Short title\n\nLonger description here", + expectedTitle: "Short title", + expectedDesc: "Longer description here", + }, + { + name: "multi-line first paragraph goes to description only", + comments: "a comment about my body parameter that is quite long\nand may have multiple lines\nthat should become the description", + expectedTitle: "", + expectedDesc: "a comment about my body parameter that is quite long\nand may have multiple lines\nthat should become the description", + }, + { + name: "multi-line first paragraph with second paragraph", + comments: "first line\nstill first paragraph\n\nsecond paragraph", + expectedTitle: "", + expectedDesc: "first line\nstill first paragraph\n\nsecond paragraph", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var title, desc string + comments := tt.comments + if len(comments) > 0 { + paragraphs := strings.Split(comments, paragraphDeliminator) + firstParagraph := strings.TrimSpace(paragraphs[0]) + if !strings.Contains(firstParagraph, "\n") { + title = firstParagraph + desc = strings.TrimSpace(strings.Join(paragraphs[1:], paragraphDeliminator)) + } else { + desc = strings.TrimSpace(comments) + } + } + + if title != tt.expectedTitle { + t.Errorf("Title mismatch: got %q, want %q", title, tt.expectedTitle) + } + if desc != tt.expectedDesc { + t.Errorf("Description mismatch: got %q, want %q", desc, tt.expectedDesc) + } + }) + } +}