Skip to content

Commit 1ca5686

Browse files
committed
Merge branch 'main' into docs-poc
2 parents 3f23be7 + d032f92 commit 1ca5686

File tree

6 files changed

+219
-6711
lines changed

6 files changed

+219
-6711
lines changed

lightbug.🔥

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
from lightbug_api.app 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:
610
"""Prints the request body and returns it.
@@ -28,12 +32,25 @@ fn hello(req: HTTPRequest) -> HTTPResponse:
2832
hello.
2933
"""
3034
return OK("Hello 🔥!", "text/plain; charset=utf-8")
35+
print("Got a request on ", req.uri.path, " with method ", req.method)
36+
return OK(req.body_raw)
37+
38+
39+
@always_inline
40+
fn nested(req: HTTPRequest) -> HTTPResponse:
41+
print("Handling route:", req.uri.path)
42+
return OK(req.uri.path)
43+
3144

3245
fn main() raises:
3346
var app = App[docs_enabled=True]()
3447

3548
app.get("/", hello, "hello")
3649
app.post("/printer", printer, "printer")
3750

51+
var nested_router = Router("nested")
52+
nested_router.get(path="all/echo/", handler=nested)
53+
app.add_router(nested_router^)
54+
3855
app.start_server()
3956

lightbug_api/__init__.mojo

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from lightbug_http import HTTPRequest, HTTPResponse, Server
2+
from lightbug_api.routing import (
3+
RootRouter,
4+
Router,
5+
HTTPHandler,
6+
)
7+
8+
9+
@value
10+
struct App:
11+
var router: RootRouter
12+
13+
fn __init__(inout self) raises:
14+
self.router = RootRouter()
15+
16+
fn get(
17+
inout self,
18+
path: String,
19+
handler: HTTPHandler,
20+
) raises:
21+
self.router.get(path, handler)
22+
23+
fn post(
24+
inout self,
25+
path: String,
26+
handler: HTTPHandler,
27+
) raises:
28+
self.router.post(path, handler)
29+
30+
fn add_router(inout self, owned router: Router) raises -> None:
31+
self.router.add_router(router)
32+
33+
fn start_server(inout self, address: StringLiteral = "0.0.0.0:8080") raises:
34+
var server = Server()
35+
server.listen_and_serve(address, self.router)

lightbug_api/routing.mojo

Lines changed: 137 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
1-
from lightbug_http import HTTPRequest, HTTPResponse, NotFound
21
from lightbug_api.service import not_found
2+
from utils.variant import Variant
3+
from collections import Dict, Optional
4+
from collections.dict import _DictEntryIter
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
18+
19+
20+
@value
21+
struct HandlerMeta:
22+
var handler: HTTPHandler
23+
24+
25+
alias HTTPHandlersMap = Dict[String, HandlerMeta]
26+
327

428

529
struct APIRoute(CollectionElement):
@@ -34,10 +58,11 @@ struct APIRoute(CollectionElement):
3458
self.handler = existing.handler
3559
self.operation_id = existing.operation_id^
3660

37-
3861
@value
39-
struct Router:
40-
var routes: List[APIRoute]
62+
struct RouterBase[is_main_app: Bool = False](HTTPService):
63+
var path_fragment: String
64+
var sub_routers: Dict[String, RouterBase[False]]
65+
var routes: Dict[String, HTTPHandlersMap]
4166

4267
fn __init__(out self):
4368
self.routes = List[APIRoute]()
@@ -52,3 +77,111 @@ struct Router:
5277
out self, path: String, method: String, handler: fn (HTTPRequest) -> HTTPResponse, operation_id: String
5378
):
5479
self.routes.append(APIRoute(path, method, handler, operation_id))
80+
fn __init__(out self: Self) raises:
81+
if not is_main_app:
82+
raise Error("Sub-router requires url path fragment it will manage")
83+
self.__init__(path_fragment="/")
84+
85+
fn __init__(out self: Self, path_fragment: String) raises:
86+
self.path_fragment = path_fragment
87+
self.sub_routers = Dict[String, RouterBase[False]]()
88+
self.routes = Dict[String, HTTPHandlersMap]()
89+
90+
self.routes[RequestMethod.head.value] = HTTPHandlersMap()
91+
self.routes[RequestMethod.get.value] = HTTPHandlersMap()
92+
self.routes[RequestMethod.put.value] = HTTPHandlersMap()
93+
self.routes[RequestMethod.post.value] = HTTPHandlersMap()
94+
self.routes[RequestMethod.patch.value] = HTTPHandlersMap()
95+
self.routes[RequestMethod.delete.value] = HTTPHandlersMap()
96+
self.routes[RequestMethod.options.value] = HTTPHandlersMap()
97+
98+
if not self._validate_path_fragment(path_fragment):
99+
raise Error(RouterErrors.INVALID_PATH_FRAGMENT_ERROR)
100+
101+
fn _route(
102+
mut self, partial_path: String, method: String, depth: Int = 0
103+
) raises -> HandlerMeta:
104+
if depth > MAX_SUB_ROUTER_DEPTH:
105+
raise Error(RouterErrors.ROUTE_NOT_FOUND_ERROR)
106+
107+
var sub_router_name: String = ""
108+
var remaining_path: String = ""
109+
var handler_path = partial_path
110+
111+
if partial_path:
112+
# TODO: (Hrist) Update to lightbug_http.uri.URIDelimiters.PATH when available
113+
var fragments = partial_path.split("/", 1)
114+
115+
sub_router_name = fragments[0]
116+
if len(fragments) == 2:
117+
remaining_path = fragments[1]
118+
else:
119+
remaining_path = ""
120+
121+
else:
122+
# TODO: (Hrist) Update to lightbug_http.uri.URIDelimiters.PATH when available
123+
handler_path = "/"
124+
125+
if sub_router_name in self.sub_routers:
126+
return self.sub_routers[sub_router_name]._route(
127+
remaining_path, method, depth + 1
128+
)
129+
elif handler_path in self.routes[method]:
130+
return self.routes[method][handler_path]
131+
else:
132+
raise Error(RouterErrors.ROUTE_NOT_FOUND_ERROR)
133+
134+
fn func(mut self, req: HTTPRequest) raises -> HTTPResponse:
135+
var uri = req.uri
136+
# TODO: (Hrist) Update to lightbug_http.uri.URIDelimiters.PATH when available
137+
var path = uri.path.split("/", 1)[1]
138+
var route_handler_meta: HandlerMeta
139+
try:
140+
route_handler_meta = self._route(path, req.method)
141+
except e:
142+
if str(e) == RouterErrors.ROUTE_NOT_FOUND_ERROR:
143+
return NotFound(uri.path)
144+
raise e
145+
146+
return route_handler_meta.handler(req)
147+
148+
fn _validate_path_fragment(self, path_fragment: String) -> Bool:
149+
# TODO: Validate fragment
150+
return True
151+
152+
fn _validate_path(self, path: String) -> Bool:
153+
# TODO: Validate path
154+
return True
155+
156+
fn add_router(mut self, owned router: RouterBase[False]) raises -> None:
157+
self.sub_routers[router.path_fragment] = router
158+
159+
fn add_route(
160+
mut self,
161+
partial_path: String,
162+
handler: HTTPHandler,
163+
method: RequestMethod = RequestMethod.get,
164+
) raises -> None:
165+
if not self._validate_path(partial_path):
166+
raise Error(RouterErrors.INVALID_PATH_ERROR)
167+
var handler_meta = HandlerMeta(handler)
168+
169+
self.routes[method.value][partial_path] = handler_meta^
170+
171+
fn get(
172+
inout self,
173+
path: String,
174+
handler: HTTPHandler,
175+
) raises:
176+
self.add_route(path, handler, RequestMethod.get)
177+
178+
fn post(
179+
inout self,
180+
path: String,
181+
handler: HTTPHandler,
182+
) raises:
183+
self.add_route(path, handler, RequestMethod.post)
184+
185+
186+
alias RootRouter = RouterBase[True]
187+
alias Router = RouterBase[False]

0 commit comments

Comments
 (0)