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: forms validation discrepency between underlying object and form type #2278

Closed euri10 closed 1 year ago

euri10 commented 1 year ago

Description

Given this little app that basically enables in a browser to post a form to 6 different routes: atrts/msgspec/pydantic times url-encoded/multi-part version, I have isues to understand why the multi-part pydantic vs attrs/msgspec different behaviour.

I would expect a 201 on all and get a 400 on multi-part msgspec and attrs (see logs), is that intended ?

Input I pass is purposedely 1 and 1 for name and amount and the same for all POST logs below.

URL to code causing the issue

No response

MCVE

from typing import Annotated

import msgspec
from attr import define, field
from attr.validators import ge, instance_of, lt
from jinja2 import Environment, DictLoader
from pydantic import BaseModel, ConfigDict, Field

from litestar import Litestar, Request, post, get
from litestar.contrib.jinja import JinjaTemplateEngine
from litestar.enums import RequestEncodingType
from litestar.params import Body
from litestar.response import Template
from litestar.template import TemplateConfig

MAX_INT_POSTGRES = 10

@define
class AddProductFormAttrs:
    name: str
    amount: int = field(validator=[instance_of(int), ge(1), lt(MAX_INT_POSTGRES)])

class AddProductFormPydantic(BaseModel):
    model_config = ConfigDict(arbitrary_types_allowed=True)
    name: str
    amount: int = Field(ge=1, lt=MAX_INT_POSTGRES)

class AddProductFormMsgspec(msgspec.Struct):
    name: str
    amount: Annotated[int, msgspec.Meta(lt=MAX_INT_POSTGRES, ge=1)]

@get(path="/")
async def form_index() -> Template:
    return Template(template_name="form.html", context={"templates": ["attrs", "pydantic", "msgspec"]})

@post(path="/form/attrs")
async def form_attrs(
        request: Request, data: Annotated[AddProductFormAttrs, Body(media_type=RequestEncodingType.URL_ENCODED)]
) -> int:
    print(type(data.name))
    return data.amount

@post(path="/form/pydantic")
async def form_pydantic(
        request: Request, data: Annotated[AddProductFormPydantic, Body(media_type=RequestEncodingType.URL_ENCODED)]
) -> int:
    print(type(data.name))
    return data.amount

@post(path="/form/msgspec")
async def form_msgspec(
        request: Request, data: Annotated[AddProductFormMsgspec, Body(media_type=RequestEncodingType.URL_ENCODED)]
) -> int:
    print(type(data.name))
    return data.amount

@post(path="/form/multipart/attrs")
async def form_multipart_attrs(
    request: Request, data: Annotated[AddProductFormAttrs, Body(media_type=RequestEncodingType.MULTI_PART)]
) -> int:
    print(type(data.name))
    return data.amount

@post(path="/form/multipart/pydantic")
async def form_multipart_pydantic(
    request: Request, data: Annotated[AddProductFormPydantic, Body(media_type=RequestEncodingType.MULTI_PART)]
) -> int:
    print(type(data.name))
    return data.amount

@post(path="/form/multipart/msgspec")
async def form_multipart_msgspec(
        request: Request, data: Annotated[AddProductFormMsgspec, Body(media_type=RequestEncodingType.MULTI_PART)]
) -> int:
    print(type(data.name))
    return data.amount

form_html = """
<html>
<body>
{% for t in templates %}
    <div>{{ t }}
    <form action="/form/{{ t }}" method="post">
    <input type="text" name="name"/>
    <input type="number" name="amount"/>
    <button type="submit">Submit</button>
    </form>
    </div>
    <div>{{ t }} multipart
    <form action="/form/multipart//{{ t }}" method="post" enctype="multipart/form-data">
    <input type="text" name="name"/>
    <input type="number" name="amount"/>
    <button type="submit">Submit</button>
    </form>
    </div>
{% endfor %}
</body>
</html>
"""

environment = Environment(loader=DictLoader({"form.html": form_html}))
app = Litestar(route_handlers=[form_index, form_attrs, form_pydantic, form_msgspec, form_multipart_attrs, form_multipart_pydantic, form_multipart_msgspec],
               template_config=TemplateConfig(JinjaTemplateEngine.from_environment(jinja_environment=environment)),
               debug=True)

Steps to reproduce

No response

Screenshots

"![SCREENSHOT_DESCRIPTION](SCREENSHOT_LINK.png)"

Logs

INFO:     Shutting down
INFO:     Waiting for application shutdown.
INFO:     Application shutdown complete.
INFO:     Finished server process [29797]
INFO:     Started server process [29878]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     127.0.0.1:48782 - "GET / HTTP/1.1" 200 OK
<class 'str'>
INFO:     127.0.0.1:48782 - "POST /form/multipart/pydantic HTTP/1.1" 201 Created
INFO:     127.0.0.1:55766 - "GET / HTTP/1.1" 200 OK
<class 'str'>
INFO:     127.0.0.1:59238 - "POST /form/attrs HTTP/1.1" 201 Created
INFO:     127.0.0.1:56858 - "POST /form/multipart/attrs HTTP/1.1" 400 Bad Request
ERROR - 2023-09-04 14:24:31,1613157331 - litestar - middleware - exception raised on http connection to route /form/multipart/attrs

Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 190, in parse_values_from_connection_kwargs
    return convert(kwargs, cls, strict=False, dec_hook=deserializer, str_keys=True).to_dict()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
msgspec.ValidationError: Expected `str`, got `int` - at `$.data.name`

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/middleware/exceptions/middleware.py", line 191, in __call__
    await self.app(scope, receive, send)
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 79, in handle
    response = await self._get_response_for_request(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 131, in _get_response_for_request
    response = await self._call_handler_function(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 160, in _call_handler_function
    response_data, cleanup_group = await self._get_response_data(
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 194, in _get_response_data
    parsed_kwargs = route_handler.signature_model.parse_values_from_connection_kwargs(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 203, in parse_values_from_connection_kwargs
    raise cls._create_exception(messages=messages, connection=connection) from e
litestar.exceptions.http_exceptions.ValidationException: 400: Validation failed for POST http://localhost:8000/form/multipart/attrs
Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 190, in parse_values_from_connection_kwargs
    return convert(kwargs, cls, strict=False, dec_hook=deserializer, str_keys=True).to_dict()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
msgspec.ValidationError: Expected `str`, got `int` - at `$.data.name`

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/middleware/exceptions/middleware.py", line 191, in __call__
    await self.app(scope, receive, send)
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 79, in handle
    response = await self._get_response_for_request(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 131, in _get_response_for_request
    response = await self._call_handler_function(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 160, in _call_handler_function
    response_data, cleanup_group = await self._get_response_data(
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 194, in _get_response_data
    parsed_kwargs = route_handler.signature_model.parse_values_from_connection_kwargs(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 203, in parse_values_from_connection_kwargs
    raise cls._create_exception(messages=messages, connection=connection) from e
litestar.exceptions.http_exceptions.ValidationException: 400: Validation failed for POST http://localhost:8000/form/multipart/attrs
<class 'str'>
INFO:     127.0.0.1:56870 - "POST /form/pydantic HTTP/1.1" 201 Created
<class 'str'>
INFO:     127.0.0.1:52932 - "POST /form/multipart/pydantic HTTP/1.1" 201 Created
<class 'str'>
INFO:     127.0.0.1:56944 - "POST /form/msgspec HTTP/1.1" 201 Created
INFO:     127.0.0.1:56944 - "POST /form/multipart/msgspec HTTP/1.1" 400 Bad Request
ERROR - 2023-09-04 14:24:57,1613183344 - litestar - middleware - exception raised on http connection to route /form/multipart/msgspec

Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 190, in parse_values_from_connection_kwargs
    return convert(kwargs, cls, strict=False, dec_hook=deserializer, str_keys=True).to_dict()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
msgspec.ValidationError: Expected `str`, got `int` - at `$.data.name`

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/middleware/exceptions/middleware.py", line 191, in __call__
    await self.app(scope, receive, send)
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 79, in handle
    response = await self._get_response_for_request(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 131, in _get_response_for_request
    response = await self._call_handler_function(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 160, in _call_handler_function
    response_data, cleanup_group = await self._get_response_data(
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 194, in _get_response_data
    parsed_kwargs = route_handler.signature_model.parse_values_from_connection_kwargs(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 203, in parse_values_from_connection_kwargs
    raise cls._create_exception(messages=messages, connection=connection) from e
litestar.exceptions.http_exceptions.ValidationException: 400: Validation failed for POST http://localhost:8000/form/multipart/msgspec
Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 190, in parse_values_from_connection_kwargs
    return convert(kwargs, cls, strict=False, dec_hook=deserializer, str_keys=True).to_dict()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
msgspec.ValidationError: Expected `str`, got `int` - at `$.data.name`

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/middleware/exceptions/middleware.py", line 191, in __call__
    await self.app(scope, receive, send)
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 79, in handle
    response = await self._get_response_for_request(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 131, in _get_response_for_request
    response = await self._call_handler_function(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 160, in _call_handler_function
    response_data, cleanup_group = await self._get_response_data(
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 194, in _get_response_data
    parsed_kwargs = route_handler.signature_model.parse_values_from_connection_kwargs(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 203, in parse_values_from_connection_kwargs
    raise cls._create_exception(messages=messages, connection=connection) from e
litestar.exceptions.http_exceptions.ValidationException: 400: Validation failed for POST http://localhost:8000/form/multipart/msgspec
WARNING:  StatReload detected changes in 'te.py'. Reloading...
INFO:     Shutting down
INFO:     Waiting for application shutdown.
INFO:     Application shutdown complete.
INFO:     Finished server process [29878]
INFO:     Started server process [30406]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     127.0.0.1:50626 - "GET / HTTP/1.1" 200 OK
<class 'str'>
INFO:     127.0.0.1:48508 - "POST /form/attrs HTTP/1.1" 201 Created
<class 'str'>
INFO:     127.0.0.1:48508 - "POST /form/pydantic HTTP/1.1" 201 Created
<class 'str'>
INFO:     127.0.0.1:48508 - "POST /form/msgspec HTTP/1.1" 201 Created
INFO:     127.0.0.1:35918 - "POST /form/multipart/attrs HTTP/1.1" 400 Bad Request
ERROR - 2023-09-04 14:26:37,1613283298 - litestar - middleware - exception raised on http connection to route /form/multipart/attrs

Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 190, in parse_values_from_connection_kwargs
    return convert(kwargs, cls, strict=False, dec_hook=deserializer, str_keys=True).to_dict()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
msgspec.ValidationError: Expected `str`, got `int` - at `$.data.name`

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/middleware/exceptions/middleware.py", line 191, in __call__
    await self.app(scope, receive, send)
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 79, in handle
    response = await self._get_response_for_request(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 131, in _get_response_for_request
    response = await self._call_handler_function(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 160, in _call_handler_function
    response_data, cleanup_group = await self._get_response_data(
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 194, in _get_response_data
    parsed_kwargs = route_handler.signature_model.parse_values_from_connection_kwargs(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 203, in parse_values_from_connection_kwargs
    raise cls._create_exception(messages=messages, connection=connection) from e
litestar.exceptions.http_exceptions.ValidationException: 400: Validation failed for POST http://localhost:8000/form/multipart/attrs
Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 190, in parse_values_from_connection_kwargs
    return convert(kwargs, cls, strict=False, dec_hook=deserializer, str_keys=True).to_dict()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
msgspec.ValidationError: Expected `str`, got `int` - at `$.data.name`

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/middleware/exceptions/middleware.py", line 191, in __call__
    await self.app(scope, receive, send)
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 79, in handle
    response = await self._get_response_for_request(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 131, in _get_response_for_request
    response = await self._call_handler_function(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 160, in _call_handler_function
    response_data, cleanup_group = await self._get_response_data(
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 194, in _get_response_data
    parsed_kwargs = route_handler.signature_model.parse_values_from_connection_kwargs(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 203, in parse_values_from_connection_kwargs
    raise cls._create_exception(messages=messages, connection=connection) from e
litestar.exceptions.http_exceptions.ValidationException: 400: Validation failed for POST http://localhost:8000/form/multipart/attrs
<class 'str'>
INFO:     127.0.0.1:55018 - "POST /form/multipart/pydantic HTTP/1.1" 201 Created
INFO:     127.0.0.1:51360 - "POST /form/multipart/msgspec HTTP/1.1" 400 Bad Request
ERROR - 2023-09-04 14:26:58,1613303675 - litestar - middleware - exception raised on http connection to route /form/multipart/msgspec

Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 190, in parse_values_from_connection_kwargs
    return convert(kwargs, cls, strict=False, dec_hook=deserializer, str_keys=True).to_dict()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
msgspec.ValidationError: Expected `str`, got `int` - at `$.data.name`

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/middleware/exceptions/middleware.py", line 191, in __call__
    await self.app(scope, receive, send)
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 79, in handle
    response = await self._get_response_for_request(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 131, in _get_response_for_request
    response = await self._call_handler_function(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 160, in _call_handler_function
    response_data, cleanup_group = await self._get_response_data(
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 194, in _get_response_data
    parsed_kwargs = route_handler.signature_model.parse_values_from_connection_kwargs(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 203, in parse_values_from_connection_kwargs
    raise cls._create_exception(messages=messages, connection=connection) from e
litestar.exceptions.http_exceptions.ValidationException: 400: Validation failed for POST http://localhost:8000/form/multipart/msgspec
Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 190, in parse_values_from_connection_kwargs
    return convert(kwargs, cls, strict=False, dec_hook=deserializer, str_keys=True).to_dict()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
msgspec.ValidationError: Expected `str`, got `int` - at `$.data.name`

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/lotso/PycharmProjects/litestar/litestar/middleware/exceptions/middleware.py", line 191, in __call__
    await self.app(scope, receive, send)
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 79, in handle
    response = await self._get_response_for_request(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 131, in _get_response_for_request
    response = await self._call_handler_function(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 160, in _call_handler_function
    response_data, cleanup_group = await self._get_response_data(
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/routes/http.py", line 194, in _get_response_data
    parsed_kwargs = route_handler.signature_model.parse_values_from_connection_kwargs(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lotso/PycharmProjects/litestar/litestar/_signature/model.py", line 203, in parse_values_from_connection_kwargs
    raise cls._create_exception(messages=messages, connection=connection) from e
litestar.exceptions.http_exceptions.ValidationException: 400: Validation failed for POST http://localhost:8000/form/multipart/msgspec
WARNING:  StatReload detected changes in 'te.py'. Reloading...

Litestar Version

❯ litestar version
2.0.0beta4

Platform


Funding

Fund with Polar

Goldziher commented 1 year ago

whats the input you are sending?

euri10 commented 1 year ago

whats the input you are sending?

the short version of the log above is that I'm entering 1 for both the input name and input amount in the form.

what is strange is that for url-encoded forms I get a 201 on all 3 endpoints, the data.name is a string as expected.

but as soon as you have multi-part forms, for the same inputs, the pydantic is the only one returning a 201, others return a 400 complaining data.name is not a string

euri10 commented 1 year ago

if I take as an example the multipart vs url-encoded using the msgspec Struct only, by the time we reach this line: https://github.com/litestar-org/litestar/blob/c36dba249337062266fe2175a256ed7de099fa0e/litestar/_signature/model.py#L190

what we have when entering both times 1 and 1 in the browser.

  1. in the url-encoded case is {'request': <litestar.connection.request.Request object at 0x7f955cf165c0>, 'data': {'amount': '1', 'name': '1'}}
  2. vs for the multi-part case {'request': <litestar.connection.request.Request object at 0x7f955cf16fc0>, 'data': {'name': 1, 'amount': 1}}
Goldziher commented 1 year ago

Well, the difference between url-encoded and multipart is that for url encoded we parse the raw request to a dict of string/string key value pairs and then let msgspec, attrs, pydantic etc. to handle validation and parsing. For multipart we use regex, and I think it's probable we do some conversion there.

We might need to ensure everything remains a string as well.

euri10 commented 1 year ago

from briefly looking at it, I think this might be due to this line in https://github.com/litestar-org/litestar/blob/c36dba249337062266fe2175a256ed7de099fa0e/litestar/_multipart.py#L160

if you replace it with fields[field_name].append(post_data.decode()) then a 201 occurs.

I think the decoding using msgspec is too early,

will try a PR with this and see how it afffects the tests :)

Goldziher commented 1 year ago

from briefly looking at it, I think this might be due to this line in https://github.com/litestar-org/litestar/blob/c36dba249337062266fe2175a256ed7de099fa0e/litestar/_multipart.py#L160

if you replace it with fields[field_name].append(post_data.decode()) then a 201 occurs.

I think the decoding using msgspec is too early,

will try a PR with this and see how it afffects the tests :)

Great 👍