Skip to content

Commit 18bc992

Browse files
committed
Added ExceptionMiddleware
1 parent 0dac42c commit 18bc992

File tree

3 files changed

+87
-24
lines changed

3 files changed

+87
-24
lines changed

ellar/core/factory.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
from ellar.constants import MODULE_METADATA, MODULE_WATERMARK
99
from ellar.core import Config
10+
from ellar.core.exceptions.interfaces import IExceptionMiddlewareService
11+
from ellar.core.exceptions.service import ExceptionMiddlewareService
1012
from ellar.core.main import App
1113
from ellar.core.modules import ModuleBase
1214
from ellar.core.modules.ref import create_module_ref_factor
@@ -95,6 +97,9 @@ def _create_app(
9597

9698
config = Config(app_configured=True, config_module=config_module)
9799
injector = EllarInjector(auto_bind=config.INJECTOR_AUTO_BIND)
100+
injector.container.register(
101+
IExceptionMiddlewareService, ExceptionMiddlewareService
102+
)
98103
injector.container.register_instance(config, concrete_type=Config)
99104
cls._build_modules(app_module=module, injector=injector, config=config)
100105

ellar/core/main.py

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
from ellar.core.context import ExecutionContext, IExecutionContext
99
from ellar.core.datastructures import State, URLPath
1010
from ellar.core.events import EventHandler, RouterEventManager
11+
from ellar.core.exceptions.interfaces import (
12+
IExceptionHandler,
13+
IExceptionMiddlewareService,
14+
)
1115
from ellar.core.guard import GuardCanActivate
1216
from ellar.core.middleware import (
1317
CORSMiddleware,
@@ -57,7 +61,6 @@ def __init__(
5761
self._injector: EllarInjector = injector
5862

5963
self._global_guards = [] if global_guards is None else list(global_guards)
60-
self._exception_handlers = dict(t.cast(dict, self.config.EXCEPTION_HANDLERS))
6164
self._user_middleware = list(t.cast(list, self.config.MIDDLEWARE))
6265

6366
self.on_startup = RouterEventManager(
@@ -173,14 +176,11 @@ def config(self) -> Config: # type: ignore
173176
return self._config
174177

175178
def build_middleware_stack(self) -> ASGIApp:
176-
error_handler = None
177-
exception_handlers = {}
178-
179-
for key, value in self._exception_handlers.items():
180-
if key in (500, Exception):
181-
error_handler = value
182-
else:
183-
exception_handlers[key] = value
179+
service_middleware = self.injector.get(
180+
IExceptionMiddlewareService # type:ignore
181+
)
182+
service_middleware.build_exception_handlers()
183+
error_handler = service_middleware.get_500_error_handler()
184184

185185
middleware = (
186186
[
@@ -212,7 +212,9 @@ def build_middleware_stack(self) -> ASGIApp:
212212
+ self._user_middleware
213213
+ [
214214
Middleware(
215-
ExceptionMiddleware, handlers=exception_handlers, debug=self.debug
215+
ExceptionMiddleware,
216+
exception_middleware_service=service_middleware,
217+
debug=self.debug,
216218
),
217219
]
218220
)
@@ -265,20 +267,11 @@ def _finalize_app_initialization(self) -> None:
265267

266268
def add_exception_handler(
267269
self,
268-
exc_class_or_status_code: t.Union[int, t.Type[Exception]],
269-
handler: t.Callable,
270-
) -> None: # pragma: no cover
271-
self._exception_handlers[exc_class_or_status_code] = handler
272-
self.middleware_stack = self.build_middleware_stack()
273-
274-
def exception_handler(
275-
self, exc_class_or_status_code: t.Union[int, t.Type[Exception]]
276-
) -> t.Callable: # pragma: nocover
277-
def decorator(func: t.Callable) -> t.Callable:
278-
self.add_exception_handler(exc_class_or_status_code, func)
279-
return func
280-
281-
return decorator
270+
exception_handler: IExceptionHandler,
271+
) -> None:
272+
if exception_handler not in self.config.EXCEPTION_HANDLERS:
273+
self.config.EXCEPTION_HANDLERS.append(exception_handler)
274+
self.rebuild_middleware_stack()
282275

283276
def rebuild_middleware_stack(self) -> None:
284277
self.middleware_stack = self.build_middleware_stack()

ellar/core/middleware/exceptions.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import typing as t
2+
3+
from starlette.exceptions import HTTPException
4+
5+
from ellar.constants import SCOPE_EXECUTION_CONTEXT_PROVIDER
6+
from ellar.types import ASGIApp, TMessage, TReceive, TScope, TSend
7+
8+
if t.TYPE_CHECKING: # pragma: no cover
9+
from ellar.core.exceptions.service import ExceptionMiddlewareService
10+
11+
12+
class ExceptionMiddleware:
13+
def __init__(
14+
self,
15+
app: ASGIApp,
16+
exception_middleware_service: "ExceptionMiddlewareService",
17+
debug: bool = False,
18+
) -> None:
19+
self.app = app
20+
self.debug = debug # TODO: We ought to handle 404 cases if debug is set.
21+
self._exception_middleware_service = exception_middleware_service
22+
23+
async def __call__(self, scope: TScope, receive: TReceive, send: TSend) -> None:
24+
if scope["type"] not in ("http", "websocket"):
25+
await self.app(scope, receive, send)
26+
return
27+
28+
response_started = False
29+
30+
async def sender(message: TMessage) -> None:
31+
nonlocal response_started
32+
33+
if message["type"] == "http.response.start":
34+
response_started = True
35+
await send(message)
36+
37+
try:
38+
await self.app(scope, receive, sender)
39+
except Exception as exc:
40+
handler = None
41+
42+
if isinstance(exc, HTTPException):
43+
handler = self._exception_middleware_service.lookup_status_code_exception_handler(
44+
exc.status_code
45+
)
46+
47+
if handler is None:
48+
handler = self._exception_middleware_service.lookup_exception_handler(
49+
exc
50+
)
51+
52+
if handler is None:
53+
raise exc
54+
55+
if response_started:
56+
msg = "Caught handled exception, but response already started."
57+
raise RuntimeError(msg) from exc
58+
59+
if scope["type"] == "http":
60+
response = await handler.catch(
61+
scope[SCOPE_EXECUTION_CONTEXT_PROVIDER], exc
62+
)
63+
await response(scope, receive, sender)
64+
elif scope["type"] == "websocket":
65+
await handler.catch(scope[SCOPE_EXECUTION_CONTEXT_PROVIDER], exc)

0 commit comments

Comments
 (0)