Open
Description
Summary
- When performing validation on a request body that is a list, I'd like the validation error message's "extra" property to show the array index for the "key" property rather than showing "data". I'd like something similar for query parameters that are lists, where the array index is shown instead of the query parameter's name from the route handler's function signature.
- When the name for a query parameter, cookie, or header differs from the name used in the route handler's signature, the error message's "key" property should show the alternative name instead of the one in the function.
- Parameters in route handlers that are not the request body, but whose names start with "data", should not have their error message source set to "body".
- When an
ExtendedMsgSpecValidationError
is raised, instead of turning the elements ofloc
to strings and joining with"."
, the element should be wrapped in[]
if it's an integer and otherwise prefixed with"."
so that the error message "key" property better resembles the format that msgspec uses.
Basic Example
from typing import Annotated
from litestar import Litestar, post
from litestar.params import Parameter
@post("/")
async def foo(
data: list[int],
data_list: Annotated[list[int], Parameter(query="dataList")],
foo_header: Annotated[int, Parameter(header="X-HEADER")],
foo_cookie: Annotated[int, Parameter(cookie="my-cookie")],
) -> None: ...
app = Litestar(route_handlers=[foo])
Currently, curl -X POST 'http://localhost:8000?dataList=1&dataList=2&dataList=oops' -H "X-HEADER: oops" -b "my-cookie=oops" -d '[1, 2, "oops"]'
will show:
{
"status_code": 400,
"detail": "Validation failed for POST /?dataList=1&dataList=2&dataList=oops",
"extra": [
{
"message": "Expected `int`, got `str`",
"key": "data",
"source": "body"
},
{
"message": "Expected `int`, got `str`",
"key": "data_list",
"source": "body"
},
{
"message": "Expected `int`, got `str`",
"key": "foo_header",
"source": "header"
},
{
"message": "Expected `int`, got `str`",
"key": "foo_cookie",
"source": "cookie"
}
]
}
With the enhancement, the response will be:
{
"status_code": 400,
"detail": "Validation failed for POST /?dataList=1&dataList=2&dataList=oops",
"extra": [
{
"message": "Expected `int`, got `str`",
"key": "[2]",
"source": "body"
},
{
"message": "Expected `int`, got `str`",
"key": "dataList[2]",
"source": "query"
},
{
"message": "Expected `int`, got `str`",
"key": "X-HEADER",
"source": "header"
},
{
"message": "Expected `int`, got `str`",
"key": "my-cookie",
"source": "cookie"
}
]
}
Drawbacks and Impact
- Users who previously had a list query parameter might prefer the previous behavior when a parameter with only one element is passed.
- In the above example, just one query parameter like
?dataList=abc
will show"dataList[0]"
as the key instead of just"dataList"
- In the above example, just one query parameter like
- The following tests need to be modified to accommodate this change.
- In tests/unit/test_signature/test_validation.py:
test_invalid_input_attrs
test_invalid_input_dataclass
test_invalid_input_typed_dict - In tests/unit/test_plugins/test_pydantic/test_integration.py:
test_signature_model_invalid_input
- In tests/unit/test_signature/test_validation.py:
Unresolved questions
Should "data" be shown as the "key" in an error message when validation applies to the request body as a whole and not its parts?
- For example, if a POST request expects the request body to be an object, then sending an array will show "data" as the key in both the current implementation and this enhancement.
- Also note that even if you remove
"key": "data"
, "data" can still show up in error messages. For example, when a request body is required, sending no request body will show"message": "'data'"
.
Changing the ExtendedMsgSpecValidationError
's approach for building its error message is based on the loc_to_dot_sep()
example in Pydantic's documentation (https://docs.pydantic.dev/latest/errors/errors/#customize-error-messages). While this change will more closely resemble how error messages are built for ValidationError
, I don't know if users prefer "items[1].value"
over "items.1.value"
.