Skip to content

Commit d032f92

Browse files
authored
Merge pull request #10 from izo0x90/Hristo/Nested-routing
Hristo/nested routing
2 parents c16d345 + 937131b commit d032f92

File tree

6 files changed

+6169
-948
lines changed

6 files changed

+6169
-948
lines changed

lightbug.🔥

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,35 @@
1-
from lightbug_api import App
1+
from lightbug_api import (
2+
App,
3+
Router,
4+
)
25
from lightbug_http import HTTPRequest, HTTPResponse, OK
36

7+
48
@always_inline
59
fn printer(req: HTTPRequest) -> HTTPResponse:
6-
print("Got a request on ", req.uri.path, " with method ", req.method)
7-
return OK(req.body_raw)
10+
print("Got a request on ", req.uri.path, " with method ", req.method)
11+
return OK(req.body_raw)
12+
813

914
@always_inline
1015
fn hello(req: HTTPRequest) -> HTTPResponse:
11-
return OK("Hello 🔥!")
16+
return OK("Hello 🔥!")
17+
18+
19+
@always_inline
20+
fn nested(req: HTTPRequest) -> HTTPResponse:
21+
print("Handling route:", req.uri.path)
22+
return OK(req.uri.path)
23+
1224

1325
fn main() raises:
1426
var app = App()
1527

1628
app.get("/", hello)
1729
app.post("/", printer)
1830

31+
var nested_router = Router("nested")
32+
nested_router.get(path="all/echo/", handler=nested)
33+
app.add_router(nested_router^)
34+
1935
app.start_server()

lightbug_api/__init__.mojo

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,35 @@
1-
from lightbug_http import HTTPRequest, HTTPResponse, SysServer, NotFound
2-
from lightbug_api.routing import Router
1+
from lightbug_http import HTTPRequest, HTTPResponse, Server
2+
from lightbug_api.routing import (
3+
RootRouter,
4+
Router,
5+
HTTPHandler,
6+
)
37

48

59
@value
610
struct App:
7-
var router: Router
11+
var router: RootRouter
812

9-
fn __init__(inout self):
10-
self.router = Router()
13+
fn __init__(inout self) raises:
14+
self.router = RootRouter()
1115

12-
fn func(self, req: HTTPRequest) raises -> HTTPResponse:
13-
for route_ptr in self.router.routes:
14-
var route = route_ptr[]
15-
if route.path == req.uri.path and route.method == req.method:
16-
return route.handler(req)
17-
return NotFound(req.uri.path)
16+
fn get(
17+
inout self,
18+
path: String,
19+
handler: HTTPHandler,
20+
) raises:
21+
self.router.get(path, handler)
1822

19-
fn get(inout self, path: String, handler: fn (HTTPRequest) -> HTTPResponse):
20-
self.router.add_route(path, "GET", handler)
23+
fn post(
24+
inout self,
25+
path: String,
26+
handler: HTTPHandler,
27+
) raises:
28+
self.router.post(path, handler)
2129

22-
fn post(inout self, path: String, handler: fn (HTTPRequest) -> HTTPResponse):
23-
self.router.add_route(path, "POST", handler)
30+
fn add_router(inout self, owned router: Router) raises -> None:
31+
self.router.add_router(router)
2432

2533
fn start_server(inout self, address: StringLiteral = "0.0.0.0:8080") raises:
26-
var server = SysServer()
27-
server.listen_and_serve(address, self)
34+
var server = Server()
35+
server.listen_and_serve(address, self.router)

lightbug_api/routing.mojo

Lines changed: 133 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,141 @@
1-
from lightbug_http import HTTPRequest, HTTPResponse, NotFound
1+
from utils.variant import Variant
2+
from collections import Dict, Optional
3+
from collections.dict import _DictEntryIter
4+
5+
from lightbug_http import NotFound, OK, HTTPService, HTTPRequest, HTTPResponse
6+
from lightbug_http.strings import RequestMethod
7+
8+
alias MAX_SUB_ROUTER_DEPTH = 20
9+
10+
11+
struct RouterErrors:
12+
alias ROUTE_NOT_FOUND_ERROR = "ROUTE_NOT_FOUND_ERROR"
13+
alias INVALID_PATH_ERROR = "INVALID_PATH_ERROR"
14+
alias INVALID_PATH_FRAGMENT_ERROR = "INVALID_PATH_FRAGMENT_ERROR"
15+
16+
17+
alias HTTPHandler = fn (req: HTTPRequest) -> HTTPResponse
218

319

420
@value
5-
struct APIRoute:
6-
var path: String
7-
var method: String
8-
var handler: fn (HTTPRequest) -> HTTPResponse
21+
struct HandlerMeta:
22+
var handler: HTTPHandler
23+
24+
25+
alias HTTPHandlersMap = Dict[String, HandlerMeta]
926

1027

1128
@value
12-
struct Router:
13-
var routes: List[APIRoute]
29+
struct RouterBase[is_main_app: Bool = False](HTTPService):
30+
var path_fragment: String
31+
var sub_routers: Dict[String, RouterBase[False]]
32+
var routes: Dict[String, HTTPHandlersMap]
33+
34+
fn __init__(out self: Self) raises:
35+
if not is_main_app:
36+
raise Error("Sub-router requires url path fragment it will manage")
37+
self.__init__(path_fragment="/")
38+
39+
fn __init__(out self: Self, path_fragment: String) raises:
40+
self.path_fragment = path_fragment
41+
self.sub_routers = Dict[String, RouterBase[False]]()
42+
self.routes = Dict[String, HTTPHandlersMap]()
43+
44+
self.routes[RequestMethod.head.value] = HTTPHandlersMap()
45+
self.routes[RequestMethod.get.value] = HTTPHandlersMap()
46+
self.routes[RequestMethod.put.value] = HTTPHandlersMap()
47+
self.routes[RequestMethod.post.value] = HTTPHandlersMap()
48+
self.routes[RequestMethod.patch.value] = HTTPHandlersMap()
49+
self.routes[RequestMethod.delete.value] = HTTPHandlersMap()
50+
self.routes[RequestMethod.options.value] = HTTPHandlersMap()
51+
52+
if not self._validate_path_fragment(path_fragment):
53+
raise Error(RouterErrors.INVALID_PATH_FRAGMENT_ERROR)
54+
55+
fn _route(
56+
mut self, partial_path: String, method: String, depth: Int = 0
57+
) raises -> HandlerMeta:
58+
if depth > MAX_SUB_ROUTER_DEPTH:
59+
raise Error(RouterErrors.ROUTE_NOT_FOUND_ERROR)
60+
61+
var sub_router_name: String = ""
62+
var remaining_path: String = ""
63+
var handler_path = partial_path
64+
65+
if partial_path:
66+
# TODO: (Hrist) Update to lightbug_http.uri.URIDelimiters.PATH when available
67+
var fragments = partial_path.split("/", 1)
68+
69+
sub_router_name = fragments[0]
70+
if len(fragments) == 2:
71+
remaining_path = fragments[1]
72+
else:
73+
remaining_path = ""
74+
75+
else:
76+
# TODO: (Hrist) Update to lightbug_http.uri.URIDelimiters.PATH when available
77+
handler_path = "/"
78+
79+
if sub_router_name in self.sub_routers:
80+
return self.sub_routers[sub_router_name]._route(
81+
remaining_path, method, depth + 1
82+
)
83+
elif handler_path in self.routes[method]:
84+
return self.routes[method][handler_path]
85+
else:
86+
raise Error(RouterErrors.ROUTE_NOT_FOUND_ERROR)
87+
88+
fn func(mut self, req: HTTPRequest) raises -> HTTPResponse:
89+
var uri = req.uri
90+
# TODO: (Hrist) Update to lightbug_http.uri.URIDelimiters.PATH when available
91+
var path = uri.path.split("/", 1)[1]
92+
var route_handler_meta: HandlerMeta
93+
try:
94+
route_handler_meta = self._route(path, req.method)
95+
except e:
96+
if str(e) == RouterErrors.ROUTE_NOT_FOUND_ERROR:
97+
return NotFound(uri.path)
98+
raise e
99+
100+
return route_handler_meta.handler(req)
101+
102+
fn _validate_path_fragment(self, path_fragment: String) -> Bool:
103+
# TODO: Validate fragment
104+
return True
105+
106+
fn _validate_path(self, path: String) -> Bool:
107+
# TODO: Validate path
108+
return True
109+
110+
fn add_router(mut self, owned router: RouterBase[False]) raises -> None:
111+
self.sub_routers[router.path_fragment] = router
112+
113+
fn add_route(
114+
mut self,
115+
partial_path: String,
116+
handler: HTTPHandler,
117+
method: RequestMethod = RequestMethod.get,
118+
) raises -> None:
119+
if not self._validate_path(partial_path):
120+
raise Error(RouterErrors.INVALID_PATH_ERROR)
121+
var handler_meta = HandlerMeta(handler)
122+
123+
self.routes[method.value][partial_path] = handler_meta^
124+
125+
fn get(
126+
inout self,
127+
path: String,
128+
handler: HTTPHandler,
129+
) raises:
130+
self.add_route(path, handler, RequestMethod.get)
131+
132+
fn post(
133+
inout self,
134+
path: String,
135+
handler: HTTPHandler,
136+
) raises:
137+
self.add_route(path, handler, RequestMethod.post)
14138

15-
fn __init__(inout self):
16-
self.routes = List[APIRoute]()
17139

18-
fn add_route(inout self, path: String, method: String, handler: fn (HTTPRequest) -> HTTPResponse):
19-
self.routes.append(APIRoute(path, method, handler))
140+
alias RootRouter = RouterBase[True]
141+
alias Router = RouterBase[False]

0 commit comments

Comments
 (0)