Repository: litestar-org/litestar

Issue:
Bug: Wrong OpenAPI `examples` format and nesting
### Description

For examples to show up on SwaggerUI, the `examples` array should be an object and nested on the same level as `schema`, not under it.

Wrong definition:
```json
{
  "parameters": [
    {
      "schema": {
        "type": "string",
        "examples": [
          {
            "summary": "example summary",
            "value": "example value"
          }
        ]
      }
    }
  ]
}
````

Correct definition:
```json
{
  "parameters": [
    {
      "schema": {
        "type": "string"
      },
	  "examples": {
        "example1": {
          "summary": "example summary"
          "value": "example value"
        }
      }
    }
  ]
}
```

### MCVE

```python
from litestar import Litestar, get
from litestar.openapi import OpenAPIConfig
from litestar.openapi.spec.example import Example
from litestar.params import Parameter

from typing import Annotated


@get(path="/")
async def index(
    text: Annotated[
        str,
        Parameter(
            examples=[Example(value="example value", summary="example summary")]
        )
    ]
) -> str:
    return text

app = Litestar(
    route_handlers=[
        index
    ],
    openapi_config=OpenAPIConfig(
        title="Test API",
        version="1.0.0"
    )
)
```


### Steps to reproduce

```bash
1. Go to the SwaggerUI docs.
2. Click on the `index` GET method.
3. See that there are no examples under the `text` query parameter.
```


### Screenshots

![SwaggerUI query](https://github.com/litestar-org/litestar/assets/24798198/37dadcf0-7d5b-49c3-91e6-01c3bdd4137c)

### Litestar Version

2.1.1

### Platform

- [ ] Linux
- [ ] Mac
- [X] 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 Litestar 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/2494">
<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/litestar-org/litestar/issues/2494/pledge.svg?darkmode=1">
  <img alt="Fund with Polar" src="https://polar.sh/api/github/litestar-org/litestar/issues/2494/pledge.svg">
</picture>
</a>
<!-- POLAR PLEDGE BADGE END -->



Discussion:
Thanks for reporting @Teraskull!
https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-validation-00#section-9.5

> The value of this keyword MUST be an array.  There are no
   restrictions placed on the values within the array.  When multiple
   occurrences of this keyword are applicable to a single sub-instance,
   implementations MUST provide a flat array of all values rather than
   an array of arrays.

Maybe this is a swagger bug? https://github.com/swagger-api/swagger-ui/issues/9045
Elements:

![image](https://github.com/litestar-org/litestar/assets/20659309/1e64b9a2-aaa6-46de-9777-5390a9652f62)

Redoc:

![image](https://github.com/litestar-org/litestar/assets/20659309/f61e039b-62fd-47c5-8b3e-e2c9905af42c)

I think we are to spec.

The thing I think we have an issue with is `Parameter.examples` argument, e.g., it seems to work better if the examples are passed as strings, but our types don't allow it:

![image](https://github.com/litestar-org/litestar/assets/20659309/8b8118c0-06f7-4c7e-b306-1bfcc88ce348)

![image](https://github.com/litestar-org/litestar/assets/20659309/b4139fdd-95a7-4091-b0f9-4cc5cdc0e183)

![image](https://github.com/litestar-org/litestar/assets/20659309/8982e304-bc91-49e3-bb69-0bb55fac7f89)

But that is a different issue.

Closing this as an upstream issue.

Opened https://github.com/litestar-org/litestar/issues/2495 for the related issue.
I was too hasty to close this off and it does appear we have a bug in the parameter examples representations.

The above documentation that I've cited is specifically for schema examples, but parameters have their own structure for providing examples, which we aren't hitting.

From [here](https://github.com/litestar-org/litestar/issues/2495#issuecomment-1776287832):

Thanks @Teraskull - OK I'm slowly catching on.

This example:

```py
@get(path="/")
async def index(
    text: Annotated[
        str,
        Parameter(
            examples=[Example(value="example value", summary="example summary")]
        )
    ]
) -> str:
    return text
```
should look like this?

```py
@get(path="/")
async def index(
    text: Annotated[
        str,
        Parameter(
            examples={"an-example": Example(value="example value", summary="example summary")}
        )
    ]
) -> str:
    return text
```
And in the rendered schema, that mapping of example name to example object should exist outside of of the schema object, but we are rendering it as a schema example - which is why these are rendering funky or not at all? Does that sound like I've caught up?

If so, I'll reopen your other issue in favor of this one.

Resolving patch:
diff --git a/litestar/_openapi/parameters.py b/litestar/_openapi/parameters.py
--- a/litestar/_openapi/parameters.py
+++ b/litestar/_openapi/parameters.py
@@ -18,7 +18,7 @@
     from litestar._openapi.schema_generation import SchemaCreator
     from litestar.di import Provide
     from litestar.handlers.base import BaseRouteHandler
-    from litestar.openapi.spec import Reference
+    from litestar.openapi.spec import Example, Reference
     from litestar.types.internal_types import PathParameterDefinition
 
 
@@ -109,12 +109,17 @@ def create_parameter(
 
     schema = result if isinstance(result, Schema) else schema_creator.schemas[result.value]
 
+    examples: dict[str, Example | Reference] = {}
+    for i, example in enumerate(kwarg_definition.examples or [] if kwarg_definition else []):
+        examples[f"{field_definition.name}-example-{i}"] = example
+
     return Parameter(
         description=schema.description,
         name=parameter_name,
         param_in=param_in,
         required=is_required,
         schema=result,
+        examples=examples or None,
     )
 
 


Test patch:
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,6 +1,7 @@
 from typing import TYPE_CHECKING, List, Optional, Type, cast
 
 import pytest
+from typing_extensions import Annotated
 
 from litestar import Controller, Litestar, Router, get
 from litestar._openapi.parameters import create_parameter_for_handler
@@ -11,9 +12,11 @@
 from litestar.di import Provide
 from litestar.enums import ParamType
 from litestar.exceptions import ImproperlyConfiguredException
-from litestar.openapi.spec import OpenAPI
+from litestar.openapi import OpenAPIConfig
+from litestar.openapi.spec import Example, OpenAPI
 from litestar.openapi.spec.enums import OpenAPIType
 from litestar.params import Dependency, Parameter
+from litestar.testing import create_test_client
 from litestar.utils import find_index
 
 if TYPE_CHECKING:
@@ -306,3 +309,19 @@ def my_handler(
     assert local.schema.type == OpenAPIType.INTEGER  # type: ignore
     assert local.required
     assert local.schema.examples  # type: ignore
+
+
+def test_parameter_examples() -> None:
+    @get(path="/")
+    async def index(
+        text: Annotated[str, Parameter(examples=[Example(value="example value", summary="example summary")])]
+    ) -> str:
+        return text
+
+    with create_test_client(
+        route_handlers=[index], openapi_config=OpenAPIConfig(title="Test API", version="1.0.0")
+    ) as client:
+        response = client.get("/schema/openapi.json")
+        assert response.json()["paths"]["/"]["get"]["parameters"][0]["examples"] == {
+            "text-example-0": {"summary": "example summary", "value": "example value"}
+        }