Repository: litestar-org/litestar

Issue:
OpenAPI schema generation includes `status_code` but default exception handler never outputs that attribute
I was doing some endpoint schema validation for my project and ran into what seems like an inconsistency between the generated OpenAPI schema and reality.

Specifically, `HTTPException` has an (optional) `status_code` attribute which is typically set to the response status code when something exceptional happens.
The fact that this field should always be present in the output is documented in the generated schema, but in the code it is omitted in all cases in the default exception handler:
```py
if isinstance(exc, HTTPException):
    content = {"detail": exc.detail, "extra": exc.extra}
```

This currently makes my tests fail because the schema says status_code is required, but starlite will never output it.

There are two possible fixes for this:
1. Fix the OpenAPI schema generator not to include the field as required (or at all)
2. Update the default exception handler to include the field when it exists


Discussion:
So, whats your suggestion for a fix?

I would go with updating the error handler. 

If you want, add a PR
Personally I would go with option 2 because:
- The status codes are already tracked and it's documented in all schemas that Starlite has generated up until now, so this would break the "API" less in the sense that the OpenAPI schema just becomes more truthful instead of changing.
- The status code is always given in `HTTPException` (500 by default) so we can simply add the field in the default exception handler and fix this very easily. It will be 500 when not otherwise specified and if it *is* specified, it'll be whatever the developer put in, which is expected behavior based on the rest of Starlite.

Resolving patch:
diff --git a/starlite/app.py b/starlite/app.py
--- a/starlite/app.py
+++ b/starlite/app.py
@@ -283,7 +283,7 @@ def default_http_exception_handler(self, request: Request, exc: Exception) -> St
             server_middleware = ServerErrorMiddleware(app=self)
             return server_middleware.debug_response(request=request, exc=exc)
         if isinstance(exc, HTTPException):
-            content = {"detail": exc.detail, "extra": exc.extra}
+            content = {"detail": exc.detail, "extra": exc.extra, "status_code": exc.status_code}
         elif isinstance(exc, StarletteHTTPException):
             content = {"detail": exc.detail}
         else:
diff --git a/starlite/openapi/responses.py b/starlite/openapi/responses.py
--- a/starlite/openapi/responses.py
+++ b/starlite/openapi/responses.py
@@ -125,7 +125,9 @@ def create_error_responses(exceptions: List[Type[HTTPException]]) -> Iterator[Tu
                 properties=dict(
                     status_code=Schema(type=OpenAPIType.INTEGER),
                     detail=Schema(type=OpenAPIType.STRING),
-                    extra=Schema(type=OpenAPIType.OBJECT, additionalProperties=Schema()),
+                    extra=Schema(
+                        type=[OpenAPIType.NULL, OpenAPIType.OBJECT, OpenAPIType.ARRAY], additionalProperties=Schema()
+                    ),
                 ),
                 description=pascal_case_to_text(get_name(exc)),
                 examples=[{"status_code": status_code, "detail": HTTPStatus(status_code).phrase, "extra": {}}],


Test patch:
diff --git a/tests/app/test_error_handling.py b/tests/app/test_error_handling.py
--- a/tests/app/test_error_handling.py
+++ b/tests/app/test_error_handling.py
@@ -27,6 +27,40 @@ def test_default_handle_http_exception_handling() -> None:
     assert json.loads(response.body) == {
         "detail": "starlite_exception",
         "extra": {"key": "value"},
+        "status_code": 500,
+    }
+
+    response = Starlite(route_handlers=[]).default_http_exception_handler(
+        Request(scope={"type": "http", "method": "GET"}),
+        HTTPException(detail="starlite_exception"),
+    )
+    assert response.status_code == HTTP_500_INTERNAL_SERVER_ERROR
+    assert json.loads(response.body) == {
+        "detail": "starlite_exception",
+        "extra": None,
+        "status_code": 500,
+    }
+
+    response = Starlite(route_handlers=[]).default_http_exception_handler(
+        Request(scope={"type": "http", "method": "GET"}),
+        HTTPException(detail="starlite_exception", extra=None),
+    )
+    assert response.status_code == HTTP_500_INTERNAL_SERVER_ERROR
+    assert json.loads(response.body) == {
+        "detail": "starlite_exception",
+        "extra": None,
+        "status_code": 500,
+    }
+
+    response = Starlite(route_handlers=[]).default_http_exception_handler(
+        Request(scope={"type": "http", "method": "GET"}),
+        HTTPException(detail="starlite_exception", extra=["extra-1", "extra-2"]),
+    )
+    assert response.status_code == HTTP_500_INTERNAL_SERVER_ERROR
+    assert json.loads(response.body) == {
+        "detail": "starlite_exception",
+        "extra": ["extra-1", "extra-2"],
+        "status_code": 500,
     }
 
     response = Starlite(route_handlers=[]).default_http_exception_handler(
diff --git a/tests/test_template.py b/tests/test_template.py
--- a/tests/test_template.py
+++ b/tests/test_template.py
@@ -52,7 +52,7 @@ def invalid_path() -> Template:
     ) as client:
         response = client.request("GET", "/")
         assert response.status_code == 500
-        assert response.json() == {"detail": "Template invalid.html not found.", "extra": None}
+        assert response.json() == {"detail": "Template invalid.html not found.", "extra": None, "status_code": 500}
 
 
 def test_mako_template(tmpdir: Any) -> None:
@@ -101,7 +101,7 @@ def invalid_path() -> Template:
     ) as client:
         response = client.request("GET", "/")
         assert response.status_code == 500
-        assert response.json() == {"detail": "Template invalid.html not found.", "extra": None}
+        assert response.json() == {"detail": "Template invalid.html not found.", "extra": None, "status_code": 500}
 
 
 def test_handler_raise_for_no_template_engine() -> None:
@@ -112,7 +112,7 @@ def invalid_path() -> Template:
     with create_test_client(route_handlers=[invalid_path]) as client:
         response = client.request("GET", "/")
         assert response.status_code == 500
-        assert response.json() == {"detail": "Template engine is not configured", "extra": None}
+        assert response.json() == {"detail": "Template engine is not configured", "extra": None, "status_code": 500}
 
 
 def test_template_with_no_context(tmpdir: Any) -> None: