Bug: msgspec.Meta getting lost silently if field in Struct typed as union type of 2 and more types where one with Meta
Description
While I've researching Issue #5025 and experimenting with types, I got unexpected (by logic) behavior.
If we write unioned type for field in request's struct where one type uses msgspec.Meta and the second any type, excluded None, we silently getting lost msgspec.Meta object from type.
Litestar allowing to me send string with any length in text field, while msgspec will raise ValidationError:
>>> import msgspec
>>> from typing import Union, Annotated
>>> T = Union[Annotated[str, msgspec.Meta(description="only-str", min_length=5)], int]
>>> msgspec.json.decode(b'1', type=T)
1
>>> msgspec.json.decode(b'"12345"', type=T)
'12345'
>>> msgspec.json.decode(b'"12"', type=T)
Traceback (most recent call last):
File "<input>", line 1, in <module>
msgspec.ValidationError: Expected `str` of length >= 5I found the merged PR #4815 and _unwrap_optional helper. There is the test_msgspec_union_with_metadata_arm_is_not_unwrapped test case that tests the OpenAPI schema. Test creates the same type and checks that we no more have description and conditions for our type that we declared in msgspec.Meta class earlier. But this test case also applies to DTO (this issue).
- litestar/_openapi/schema_generation/plugins/struct.py:36 calling kwarg_definition_from_field
- and the litestar/dto/msgspec_dto.py:53 is also calling kwarg_definition_from_field
Error in OpenAPI schema may be less serious, but DTO silently skips validation and accepts invalid incoming data.
So what is that: mistake or an expected behavior? I'm ready to try fixing it =)
URL to code causing the issue
No response
MCVE
from typing import Annotated, Union
from litestar import Litestar, post
from litestar.dto import MsgspecDTO
from msgspec import Struct, Meta
class TestStruct(Struct):
text: Union[Annotated[str, Meta(description="only-str", min_length=5)], int]
@post('/hello', dto=MsgspecDTO[TestStruct])
async def hello(data: TestStruct) -> T:
print(f"incoming text: {data.text}")
return data.text
app = Litestar(route_handlers=[hello])Steps to reproduce
- Run the MCVE with uvicorn
- Send request with 1 as integer value in text field
curl --header "Content-Type: application/json" --request POST --data '{"text": 1}' http://localhost:8000/hello - See "incoming text: 1" in app logs and 201 HTTP code. Thats expected behavior: we allow send any integer to
textfield - Send request with string value that shorter than 5
curl --header "Content-Type: application/json" --request POST --data '{"text": "123"}' http://localhost:8000/hello - See "incoming text: 123" in app logs and 201 HTTP code again. Thats not expected behavior: we do not allow to send string in
textfield shorter than 5
Screenshots
No response
Logs
Litestar Version
The main branch. But I think it also appears at 2.24.0
Platform
- Linux
- Mac
- Windows
- Other (Please specify in the description above)
Source: litestar-org/litestar