Repository: pydantic/pydantic

Issue:
Whitespace of docstrings is not correctly trimmed according to PEP-257
### Initial Checks

- [X] I confirm that I'm using Pydantic V2

### Description

The pydantic model in the example code below creates incorrect swagger description texts. Either the full block or the second (and subsequent) paragraph is treated as pre-formatted text. **This makes it impossible to add markup to multi-paragraph descriptions**. The example code contains a self-contained unit-test which illustrates the issue well.

Python's builtin `inspect.getdoc` function returns the correct output. Could this be a solution to the issue?

It is caused by incorrect whitespace trimming. Following [PEP-257#handling-docstring-indentation](https://peps.python.org/pep-0257/#handling-docstring-indentation), the following two docstrings should be identical:

```python
def foo():
    """A multi-line
    docstring.
    """

def bar():
    """
    A multi-line
    docstring.
    """
```

The issue is even apparent in the official Pydantic documentation (the screenshots below are taken from [the pydantic v2.2 docs](https://docs.pydantic.dev/2.2/usage/json_schema/)):

![image](https://github.com/pydantic/pydantic/assets/65717/07438020-8af1-40c1-be9d-789b43a1764d)
![image](https://github.com/pydantic/pydantic/assets/65717/6ddc7e23-888d-4de7-a29f-b13b2088ceff)

### Example Code

```Python
import inspect
import unittest

from pydantic import BaseModel


class PayloadOne(BaseModel):
    """
    Leading newline

    Second paragraph
    """

    example: str


class PayloadTwo(BaseModel):
    """No leading newline

    Second paragraph
    """

    example: str


class Tester(unittest.TestCase):
    def test_one(self):
        result = PayloadOne.model_json_schema()["description"]
        expected = inspect.getdoc(PayloadOne)
        self.assertEqual(result, expected)

    def test_two(self):
        result = PayloadTwo.model_json_schema()["description"]
        expected = inspect.getdoc(PayloadTwo)
        self.assertEqual(result, expected)


unittest.main()
```


### Python, Pydantic & OS Version

```Text
pydantic version: 2.0.2
        pydantic-core version: 2.1.2 release build profile
                 install path: /workspaces/backend/env/lib/python3.11/site-packages/pydantic
               python version: 3.11.4 (main, Jul 28 2023, 05:20:14) [GCC 10.2.1 20210110]
                     platform: Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.31
     optional deps. installed: ['typing-extensions']
```


Selected Assignee: @lig


Discussion:

Resolving patch:
diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py
--- a/pydantic/_internal/_generate_schema.py
+++ b/pydantic/_internal/_generate_schema.py
@@ -219,7 +219,7 @@ def modify_model_json_schema(
         original_schema['title'] = cls.__name__
     docstring = cls.__doc__
     if docstring and 'description' not in original_schema:
-        original_schema['description'] = docstring
+        original_schema['description'] = inspect.cleandoc(docstring)
     return json_schema
 
 


Test patch:
diff --git a/tests/test_json_schema.py b/tests/test_json_schema.py
--- a/tests/test_json_schema.py
+++ b/tests/test_json_schema.py
@@ -1695,6 +1695,25 @@ class B(A):
     assert B.model_json_schema() == {'title': 'B', 'type': 'object', 'properties': {}}
 
 
+@pytest.mark.parametrize(
+    'docstring,description',
+    [
+        ('foobar', 'foobar'),
+        ('\n     foobar\n    ', 'foobar'),
+        ('foobar\n    ', 'foobar\n    '),
+        ('foo\n    bar\n    ', 'foo\nbar'),
+        ('\n    foo\n    bar\n    ', 'foo\nbar'),
+    ],
+)
+def test_docstring(docstring, description):
+    class A(BaseModel):
+        x: int
+
+    A.__doc__ = docstring
+
+    assert A.model_json_schema()['description'] == description
+
+
 @pytest.mark.parametrize(
     'kwargs,type_,expected_extra',
     [