@@ -3,6 +3,7 @@ from std.utils import Variant
33
44from lightbug_http import HTTPRequest, HTTPResponse, HTTPService, NotFound, OK
55from lightbug_http.http import RequestMethod
6+ from lightbug_http.http.common_response import InternalError
67from lightbug_http.uri import URIDelimiters
78
89from lightbug_api.context import Context
@@ -30,6 +31,51 @@ comptime HandlerResponse = Variant[HTTPResponse, String]
3031# Use Context to access the request, path params, query params, headers, body.
3132comptime Handler = fn (Context) raises - > HandlerResponse
3233
34+ # ------------------------------------------------------------ middleware types
35+
36+ # Middleware return type:
37+ # HTTPResponse — short-circuit: send this response immediately, skip handler
38+ # Bool — continue to the next middleware / handler (value is ignored)
39+ #
40+ # Use the helpers ``next()`` and ``abort(response)`` to return these cleanly.
41+ comptime MiddlewareResult = Variant[HTTPResponse, Bool]
42+
43+ # Every middleware shares this non-capturing function-pointer signature.
44+ comptime Middleware = fn (Context) raises - > MiddlewareResult
45+
46+
47+ fn next () -> MiddlewareResult:
48+ """ Signal that processing should continue to the next middleware / handler."""
49+ return MiddlewareResult(True )
50+
51+
52+ fn abort (var response : HTTPResponse) -> MiddlewareResult:
53+ """ Short-circuit the request with *response*, skipping all further processing."""
54+ return MiddlewareResult(response^ )
55+
56+
57+ # Wrapper so Middleware function pointers can live in a List[MiddlewareEntry].
58+ struct MiddlewareEntry (Copyable ):
59+ var handler : Middleware
60+
61+ fn __init__ (out self , handler : Middleware):
62+ self .handler = handler
63+
64+ fn __init__ (out self , * , copy : Self):
65+ self .handler = copy.handler
66+
67+
68+ # ------------------------------------------------------------ error handler
69+
70+ # Called when a route handler raises an unhandled error.
71+ # Return an appropriate HTTPResponse; the exception is consumed.
72+ comptime ErrorHandler = fn (Context, Error) raises - > HTTPResponse
73+
74+
75+ fn _default_error_handler (ctx : Context, e : Error) raises -> HTTPResponse:
76+ print (" lightbug_api error:" , String(e))
77+ return InternalError()
78+
3379
3480# --------------------------------------------------------------- path matching
3581
@@ -229,6 +275,8 @@ struct RouterBase[is_main_app: Bool = False](HTTPService, Copyable):
229275 var path_fragment : String
230276 var sub_routers : List[RouterBase[False ]]
231277 var routes : List[RouteEntry]
278+ var middleware : List[MiddlewareEntry]
279+ var error_handler : ErrorHandler
232280
233281 # ------------------------------------------------------------------ init
234282
@@ -238,11 +286,15 @@ struct RouterBase[is_main_app: Bool = False](HTTPService, Copyable):
238286 self .path_fragment = " /"
239287 self .sub_routers = List[RouterBase[False ]]()
240288 self .routes = List[RouteEntry]()
289+ self .middleware = List[MiddlewareEntry]()
290+ self .error_handler = _default_error_handler
241291
242292 def __init__ (out self : Self, path_fragment : String) raises :
243293 self .path_fragment = path_fragment
244294 self .sub_routers = List[RouterBase[False ]]()
245295 self .routes = List[RouteEntry]()
296+ self .middleware = List[MiddlewareEntry]()
297+ self .error_handler = _default_error_handler
246298
247299 if not self ._validate_path_fragment(path_fragment):
248300 raise Error(RouterErrors.INVALID_PATH_FRAGMENT_ERROR )
@@ -251,6 +303,8 @@ struct RouterBase[is_main_app: Bool = False](HTTPService, Copyable):
251303 self .path_fragment = copy.path_fragment
252304 self .sub_routers = copy.sub_routers.copy()
253305 self .routes = copy.routes.copy()
306+ self .middleware = copy.middleware.copy()
307+ self .error_handler = copy.error_handler
254308
255309 # -------------------------------------------------------- route registration
256310
@@ -296,6 +350,23 @@ struct RouterBase[is_main_app: Bool = False](HTTPService, Copyable):
296350 """ Mount a sub-router, nesting all its routes under its path fragment."""
297351 self .sub_routers.append(router^ )
298352
353+ def use (mut self , middleware : Middleware) -> None :
354+ """ Add a middleware function that runs before every handler on this router.
355+
356+ Middleware runs in registration order. Return ``next()`` to continue,
357+ or ``abort(response)`` to short-circuit.
358+
359+ Example::
360+
361+ fn require_auth(ctx: Context) raises -> MiddlewareResult:
362+ if not ctx.header("Authorization"):
363+ return abort(Response.unauthorized())
364+ return next()
365+
366+ app.use(require_auth)
367+ """
368+ self .middleware.append(MiddlewareEntry(middleware))
369+
299370 # ---------------------------------------------------------------- dispatch
300371
301372 def func (mut self , req : HTTPRequest) raises -> HTTPResponse:
@@ -310,7 +381,21 @@ struct RouterBase[is_main_app: Bool = False](HTTPService, Copyable):
310381 raise e^
311382
312383 var ctx = Context(req.copy(), route_match.path_params.copy())
313- var res = route_match.handler(ctx)
384+
385+ # Run middleware chain — any middleware may short-circuit with a response.
386+ for i in range (len (self .middleware)):
387+ var mw_result = self .middleware[i].handler(ctx)
388+ if mw_result.isa[HTTPResponse]():
389+ return mw_result.unsafe_take[HTTPResponse]()
390+ # else Bool → continue to next middleware / handler
391+
392+ # Dispatch to the matched handler; convert unhandled errors to responses.
393+ var res : HandlerResponse
394+ try :
395+ res = route_match.handler(ctx)
396+ except e:
397+ return self .error_handler(ctx, e)
398+
314399 return self ._encode_response(res^ )
315400
316401 # --------------------------------------------------------------- internals
0 commit comments