Repository: litestar-org/litestar

Issue:
Enhancement: Add more types to the default `Response` serializer.
It looks like most of the project templates we have floating around implement a custom serializer for Responses.  We should consider enhancing the built in to reduce the need for this.

For instance, here is the current `Response.serializer`:
```python
    @staticmethod
    def serializer(value: Any) -> Dict[str, Any]:
        """Serializer hook for orjson to handle pydantic models.

        This method can be overridden to extend json serialization.

        Args:
            value: The value to be serialized

        Returns:
            A string keyed dictionary of json compatible values
        """
        if isinstance(value, BaseModel):
            return value.dict()
        raise TypeError  # pragma: no cover
```

and here is one that's used on another project:
```python
@staticmethod
    def serializer(value: Any) -> Dict[str, Any]:
        """Serializer hook for orjson to handle pydantic models.

        Args:
            value: The value to be serialized

        Returns:
            A string keyed dictionary of json compatible values
        """
        if isinstance(value, Enum):
            return value.value
        if isinstance(value, EnumMeta):
            return None
        if isinstance(value, SecretStr):
            return value.get_secret_value()
        if isinstance(value, UUID):
            return str(value)
        return starlite.Response.serializer(value)
```

Thoughts?




Discussion:
@cofin - This is a good idea. Please make sure to add tests and verify that all of these are in fact needed. I would assume that `UUID` is json serializable because it implements `__str__`, and thus it wouldnt be required. I am also not sure about `Enum` in the above. 

I would suggest we move the default serializer out of the response class and into a `serializers` package or into file in util. It can be reused in other places
Excellent.  Are there others that we should add in?
I would assume that all non-json serializable builtins not handled by `orjson`. You can checkout this list of types handled by `pydantic-factories` and supported by pydantic as a reference: https://github.com/Goldziher/pydantic-factories/blob/main/pydantic_factories/factory.py#L242

Another note-> please make sure to put the pydantic serializer as the first `if` clause -> it will ensure performance is pretty good.

Resolving patch:
diff --git a/starlite/response.py b/starlite/response.py
--- a/starlite/response.py
+++ b/starlite/response.py
@@ -1,3 +1,4 @@
+from pathlib import PurePath, PurePosixPath
 from typing import (
     TYPE_CHECKING,
     Any,
@@ -12,7 +13,7 @@
 
 import yaml
 from orjson import OPT_INDENT_2, OPT_OMIT_MICROSECONDS, OPT_SERIALIZE_NUMPY, dumps
-from pydantic import BaseModel
+from pydantic import BaseModel, SecretStr
 from pydantic_openapi_schema.v3_1_0.open_api import OpenAPI
 from starlette.responses import Response as StarletteResponse
 from starlette.status import HTTP_204_NO_CONTENT, HTTP_304_NOT_MODIFIED
@@ -61,7 +62,7 @@ def __init__(
         self.cookies = cookies or []
 
     @staticmethod
-    def serializer(value: Any) -> Dict[str, Any]:
+    def serializer(value: Any) -> Union[Dict[str, Any], str]:
         """Serializer hook for orjson to handle pydantic models.
 
         This method can be overridden to extend json serialization.
@@ -74,6 +75,10 @@ def serializer(value: Any) -> Dict[str, Any]:
         """
         if isinstance(value, BaseModel):
             return value.dict()
+        if isinstance(value, SecretStr):
+            return value.get_secret_value()
+        if isinstance(value, (PurePath, PurePosixPath)):
+            return str(value)
         raise TypeError  # pragma: no cover
 
     def render(self, content: Any) -> bytes:


Test patch:
diff --git a/tests/response/test_serialization.py b/tests/response/test_serialization.py
--- a/tests/response/test_serialization.py
+++ b/tests/response/test_serialization.py
@@ -1,13 +1,23 @@
+import enum
 from json import loads
+from pathlib import PurePath
 from typing import Any, Dict, List
 
 import pytest
+from pydantic import SecretStr
 from starlette.status import HTTP_200_OK
 
 from starlite import MediaType, Response
 from tests import Person, PersonFactory, PydanticDataClassPerson, VanillaDataClassPerson
 
 person = PersonFactory.build()
+secret = SecretStr("secret_text")
+pure_path = PurePath("/path/to/file")
+
+
+class TestEnum(enum.Enum):
+    A = "alpha"
+    B = "beta"
 
 
 @pytest.mark.parametrize(
@@ -20,13 +30,23 @@
         [PydanticDataClassPerson(**person.dict()), PydanticDataClassPerson, MediaType.JSON],
         ["abcdefg", str, MediaType.TEXT],
         ["<div/>", str, MediaType.HTML],
+        [{"enum": TestEnum.A}, Dict[str, TestEnum], MediaType.JSON],
+        [{"secret": secret}, Dict[str, SecretStr], MediaType.JSON],
+        [{"pure_path": pure_path}, Dict[str, PurePath], MediaType.JSON],
     ],
 )
 def test_response_serialization(content: Any, response_type: Any, media_type: MediaType) -> None:
     response = Response[response_type](content, media_type=media_type, status_code=HTTP_200_OK)  # type: ignore[valid-type]
     if media_type == MediaType.JSON:
         value = loads(response.body)
-        if isinstance(value, dict):
+        if isinstance(value, dict) and "enum" in value:
+            assert content.__class__(**value)["enum"] == content["enum"].value
+        elif isinstance(value, dict) and "secret" in value:
+            assert content.__class__(**value)["secret"] == content["secret"].get_secret_value()
+        elif isinstance(value, dict) and "pure_path" in value:
+            assert content.__class__(**value)["pure_path"] == str(content["pure_path"])
+        elif isinstance(value, dict):
+
             assert content.__class__(**value) == content
         else:
             assert [content[0].__class__(**value[0])] == content