Repository: litestar-org/litestar

Issue:
Bug: Description not set for UUID based path parameters in OpenAPI
### Description

The value set in `Parameter(description="UUID ID")` is not used for the actual description in the OpenAPI schema if the path parameter is of type `UUID`. I did some digging and found out this is due to the presence of the following code. https://github.com/litestar-org/litestar/blob/44809182b1b62a8da5b7c5bec3b22987581779d5/litestar/_openapi/schema_generation/schema.py#L525-L532

Since description for UUID is already set https://github.com/litestar-org/litestar/blob/44809182b1b62a8da5b7c5bec3b22987581779d5/litestar/_openapi/schema_generation/schema.py#L120 the code above makes it so that description from the user defined `Parameter` is not set again.

#### Proposed Fix 1 (Breaking / debatably breaking?)
Remove "description" in https://github.com/litestar-org/litestar/blob/44809182b1b62a8da5b7c5bec3b22987581779d5/litestar/_openapi/schema_generation/schema.py#L120 such that it appears like so
```suggestion py
UUID: Schema(type=OpenAPIType.STRING, format=OpenAPIFormat.UUID)
```
#### Proposed Fix 2 (Special Casing until next major release then replace with Proposed Fix 1)
Change https://github.com/litestar-org/litestar/blob/44809182b1b62a8da5b7c5bec3b22987581779d5/litestar/_openapi/schema_generation/schema.py#L531 such that it appears like so
```suggestion py
if schema_key == "description" or getattr(schema, schema_key, None) is None:
```

@peterschutt suggested special casing with comments explaining the behavior would be ideal. Thoughts?

### URL to code causing the issue

_No response_

### MCVE

```python
from uuid import UUID
from typing_extensions import Annotated
from litestar.params import Parameter
from litestar import get
from litestar.testing import create_test_client

@get("str/{id:str}")
async def str_path(id: Annotated[str, Parameter(description="String ID")]) -> str:
    return id

@get("uuid/{id:uuid}")
async def uuid_path(id: Annotated[UUID, Parameter(description="UUID ID")]) -> UUID:
    return id

with create_test_client([str_path, uuid_path]) as client:
    # correct, currently passes, should pass
    assert client.app.openapi_schema.paths["/str/{id}"].get.parameters[0].description == "String ID"

    # wrong, currently passes, should fail
    assert client.app.openapi_schema.paths["/uuid/{id}"].get.parameters[0].description == "Any UUID string"

    # expected, currently fails, should pass
    assert client.app.openapi_schema.paths["/uuid/{id}"].get.parameters[0].description == "UUID ID"
```


### Steps to reproduce

```bash
1. Save as "mcve.py"
2. Run `python mcve.py`
3. Second assert that currently passes, should fail.
4. Third assert that currently fails, should pass.
```


### Screenshots

```bash
"![SCREENSHOT_DESCRIPTION](SCREENSHOT_LINK.png)"
```


### Logs

_No response_

### Litestar Version

cc45c1132584210250dd725595612c9c95c4bf68 (main as of issue creation)

### Platform

- [ ] Linux
- [X] Mac
- [ ] Windows
- [ ] Other (Please specify in the description above)

<!-- POLAR PLEDGE BADGE START -->
---
> [!NOTE]  
> While we are open for sponsoring on [GitHub Sponsors](https://github.com/sponsors/litestar-org/) and 
> [OpenCollective](https://opencollective.com/litestar), we also utilize [Polar.sh](https://polar.sh/) to engage in pledge-based sponsorship.
>
> Check out all issues funded or available for funding [on our Polar.sh dashboard](https://polar.sh/litestar-org)
> * If you would like to see an issue prioritized, make a pledge towards it!
> * We receive the pledge once the issue is completed & verified
> * This, along with engagement in the community, helps us know which features are a priority to our users.

<a href="https://polar.sh/litestar-org/litestar/issues/2967">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/litestar-org/litestar/issues/2967/pledge.svg?darkmode=1">
  <img alt="Fund with Polar" src="https://polar.sh/api/github/litestar-org/litestar/issues/2967/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->



Discussion:
> Proposed Fix 1 (Breaking / debatably breaking?)

If we treat this as a bug and include the fix in a patch release, removing the default description will cause schema generation to change behavior between patch releases - this is why I'm in favor of option 2.
@Alc-Alc would you want to go ahead and implement a fix for this?

Resolving patch:
diff --git a/litestar/_openapi/schema_generation/schema.py b/litestar/_openapi/schema_generation/schema.py
--- a/litestar/_openapi/schema_generation/schema.py
+++ b/litestar/_openapi/schema_generation/schema.py
@@ -117,7 +117,7 @@
     Sequence: Schema(type=OpenAPIType.ARRAY),
     Set: Schema(type=OpenAPIType.ARRAY),
     Tuple: Schema(type=OpenAPIType.ARRAY),
-    UUID: Schema(type=OpenAPIType.STRING, format=OpenAPIFormat.UUID, description="Any UUID string"),
+    UUID: Schema(type=OpenAPIType.STRING, format=OpenAPIFormat.UUID),
     bool: Schema(type=OpenAPIType.BOOLEAN),
     bytearray: Schema(type=OpenAPIType.STRING),
     bytes: Schema(type=OpenAPIType.STRING),


Test patch:
diff --git a/tests/examples/test_openapi.py b/tests/examples/test_openapi.py
--- a/tests/examples/test_openapi.py
+++ b/tests/examples/test_openapi.py
@@ -28,7 +28,7 @@ def test_schema_generation() -> None:
             "components": {
                 "schemas": {
                     "IdModel": {
-                        "properties": {"id": {"type": "string", "format": "uuid", "description": "Any UUID string"}},
+                        "properties": {"id": {"type": "string", "format": "uuid"}},
                         "type": "object",
                         "required": ["id"],
                         "title": "IdContainer",
diff --git a/tests/unit/test_openapi/test_parameters.py b/tests/unit/test_openapi/test_parameters.py
--- a/tests/unit/test_openapi/test_parameters.py
+++ b/tests/unit/test_openapi/test_parameters.py
@@ -1,4 +1,5 @@
 from typing import TYPE_CHECKING, List, Optional, Type, cast
+from uuid import UUID
 
 import pytest
 from typing_extensions import Annotated
@@ -324,3 +325,21 @@ async def index(
         assert response.json()["paths"]["/"]["get"]["parameters"][0]["examples"] == {
             "text-example-1": {"summary": "example summary", "value": "example value"}
         }
+
+
+def test_uuid_path_description_generation() -> None:
+    # https://github.com/litestar-org/litestar/issues/2967
+    @get("str/{id:str}")
+    async def str_path(id: Annotated[str, Parameter(description="String ID")]) -> str:
+        return id
+
+    @get("uuid/{id:uuid}")
+    async def uuid_path(id: Annotated[UUID, Parameter(description="UUID ID")]) -> UUID:
+        return id
+
+    with create_test_client(
+        [str_path, uuid_path], openapi_config=OpenAPIConfig(title="Test API", version="1.0.0")
+    ) as client:
+        response = client.get("/schema/openapi.json")
+        assert response.json()["paths"]["/str/{id}"]["get"]["parameters"][0]["description"] == "String ID"
+        assert response.json()["paths"]["/uuid/{id}"]["get"]["parameters"][0]["description"] == "UUID ID"