litestar-org / litestar

Production-ready, Light, Flexible and Extensible ASGI API framework | Effortlessly Build Performant APIs
https://litestar.dev/
MIT License
5.65k stars 384 forks source link

Bug: Incorrectly serialized examples in OpenAPI schema #2272

Closed floxay closed 1 year ago

floxay commented 1 year ago

Description

The provided example values are serialized incorrectly into the OpenAPI schema. image

The issue seem to be that due to inheritance simply the BaseSchemaObject.to_schema() method is called for Example instances during the schema serialization, because of this all of the fields of Example ends up being serialized under the example "section" instead of just the value of the Example.value field.

URL to code causing the issue

https://github.com/litestar-org/litestar/blob/3b3ed502de26bb945857fa8b94af45afdde7e075/litestar/openapi/spec/base.py#L22-L23

MCVE

from typing import Annotated
from msgspec import Struct, Meta
from litestar import Litestar, post

class TestBody(Struct):
    field1: Annotated[str, Meta(examples=["the answer"])]
    field2: Annotated[int, Meta(examples=[42])]

@post(sync_to_thread=False)
def test(data: TestBody) -> None:
    ...

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app=Litestar(route_handlers=[test]), port=8080)

Steps to reproduce

1. Run the MCVE
2. Open any of the auto-generated docs (redoc, swagger, elements)
3. See the request or schema example (depending on docs)

Screenshots

No response

Logs

No response

Litestar Version

2.0.1

Platform


Funding

Fund with Polar

abdulhaq-e commented 1 year ago

Hello

Sorry that I don't have much experience with OpenAPI yet. I would like to clarify that the correct serialization according to the OpenAPI specs is:

{
    "field1": "the answer",
    "field2": 42
}

Is that correct?

Goldziher commented 1 year ago

Hello

Sorry that I don't have much experience with OpenAPI yet. I would like to clarify that the correct serialization according to the OpenAPI specs is:

{
    "field1": "the answer",
    "field2": 42
}

Is that correct?

you can see the openapi spec: https://spec.openapis.org/oas/latest.html#requestBodyObject

Goldziher commented 1 year ago

I am actually not certain this bug report is correct- https://spec.openapis.org/oas/latest.html#exampleObject. We need a reproduction of this with openapi 3.1 schema that fails an up to date validator.

floxay commented 1 year ago

Yes, this is a little bit confusing but I believe this is incorrect:

image image

image

Edit: This seems to work and to me makes the most sense image

Edit 2: Maybe it's enough to just not make Example instances for per field examples as those are not supposed to be OpenAPI Example Objects? Not sure where the logic for this is or what it looks like, just seems like a reasonable approach.

floxay commented 1 year ago

Based on "Edit 2": https://github.com/litestar-org/litestar/compare/main...floxay:litestar:exp/openapi-example-serialization Seems to work but unsure about the Pydantic v2 json_schema_extra callable stuff.

openapi.json ![image](https://github.com/litestar-org/litestar/assets/57007485/cd3698f8-4b2c-4a8a-876c-6ad4c6735f6a)
Pydantic v2 with `json_schema_extra` ```py class TestBody(BaseModel): field1: str = Field(examples=["the answer", "the other answer"]) field2: int = Field(examples=[42]) model_config = { "json_schema_extra": { "examples": [ { "field1": "the answer", "field2": 42 } ] } } ``` ![image](https://github.com/litestar-org/litestar/assets/57007485/41c9112a-0b36-4de6-b7d3-713e75a367af) ![image](https://github.com/litestar-org/litestar/assets/57007485/6499912e-3170-4427-98fa-1c614f89d4bd) ~~Here I think it would make sense to create an Example Object from the `json_schema_extra` value as this is supposed to be a complete example?~~ Schema Objects don't seem to allow Example Objects nor Reference Objects...
Goldziher commented 1 year ago

there should be a field called examples: [...], I think. So this is a bug.

PR with a fix and proper tests is highly welcome.

Goldziher commented 1 year ago

@floxay do you want to take this over?

floxay commented 1 year ago

Sure, I think I can do it later today.

floxay commented 1 year ago

Unfortunately I still have no clue how to resolve the JsonSchemaExtraCallable in Pydantic's ConfigDict.json_schema_extra.

Additionally, while I were correcting some tests I've also noticed that the examples field of OpenAPI Parameter Objects is supposed to be a string to Example Object | Reference Object mapping, meaning the list litestar.Parameter takes is somewhat problematic as example names cannot be provided to build the map. Plus, currently these examples provided as Example objects are serialized into ParamObj.schema.examples rather than ParamObj.examples:

Due to the above tests/unit/test_openapi/test_parameters.py::test_create_parameters still fails in #2299