Neoteroi / BlackSheep

Fast ASGI web framework for Python
https://www.neoteroi.dev/blacksheep/
MIT License
1.8k stars 75 forks source link

Cryptic error message when a list is expected and an object is received #341

Closed RobertoPrevato closed 1 year ago

RobertoPrevato commented 1 year ago

Consider the following example:

from dataclasses import dataclass

from blacksheep import Application, pretty_json

app = Application()

@dataclass
class Access:
    id: int
    name: str
    permissions: list[str]

@app.router.post("/")
def set_access(data: list[Access]):
    # Just an example...
    return pretty_json(data)

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, port=44555, lifespan="on")

The server endpoint expects a list of objects. If the client sends a dictionary, the server produces a cryptic error message.

curl -X POST http://127.0.0.1:44555 -H "Content-Type: application/json" -d '{"id": 1, "name": "foo", "permissions": []}'

Bad Request: invalid parameter in request payload, caused by type Access or one of its subproperties. Error: __main__.Access() argument after ** must be a mapping, not str

"Bad Request: invalid parameter in request payload, caused by type Access or one of its subproperties. Error: main.Access() argument after ** must be a mapping, not str".

This happens because the function _get_default_converter_for_iterable does not handle properly this case.

Improve to raise a clearer exception:

    def _get_default_converter_for_iterable(self, expected_type):
        generic_type = self.get_type_for_generic_iterable(expected_type)
        item_type = self.generic_iterable_annotation_item_type(expected_type)

        if isinstance(item_type, ForwardRef):  # pragma: no cover
            from blacksheep.server.normalization import (
                UnsupportedForwardRefInSignatureError,
            )

            raise UnsupportedForwardRefInSignatureError(expected_type)

        item_converter = self._get_default_converter_single(item_type)

        def list_converter(values):
            if not isinstance(values, list):
                raise BadRequest("Invalid input: expected a list of objects.")

            return generic_type(item_converter(value) for value in values)

        return list_converter
RobertoPrevato commented 1 year ago

Reported in https://github.com/Neoteroi/BlackSheep/discussions/339