Skip to content

Commit 1c67ca5

Browse files
authored
Merge pull request #11 from izo0x90/Hristo/Add-ser-deser-scaffolding
Adds the scaffold for serialization and de-serialization
2 parents d032f92 + 1e5e4e2 commit 1c67ca5

File tree

5 files changed

+185
-55
lines changed

5 files changed

+185
-55
lines changed

lightbug.🔥

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,71 @@
11
from lightbug_api import (
22
App,
3+
BaseRequest,
34
Router,
5+
HandlerResponse,
6+
JSONType
47
)
58
from lightbug_http import HTTPRequest, HTTPResponse, OK
69

710

811
@always_inline
9-
fn printer(req: HTTPRequest) -> HTTPResponse:
10-
print("Got a request on ", req.uri.path, " with method ", req.method)
11-
return OK(req.body_raw)
12-
12+
fn printer(req: BaseRequest) raises -> HandlerResponse:
13+
print("Got a request on ", req.request.uri.path, " with method ", req.request.method)
14+
return OK(req.request.body_raw)
1315

1416
@always_inline
15-
fn hello(req: HTTPRequest) -> HTTPResponse:
17+
fn hello(payload: BaseRequest) raises -> HandlerResponse:
1618
return OK("Hello 🔥!")
1719

1820

1921
@always_inline
20-
fn nested(req: HTTPRequest) -> HTTPResponse:
21-
print("Handling route:", req.uri.path)
22-
return OK(req.uri.path)
22+
fn nested(req: BaseRequest) raises -> HandlerResponse:
23+
print("Handling route:", req.request.uri.path)
24+
25+
# Returning a string will get marshaled to a proper `OK` response
26+
return req.request.uri.path
27+
28+
@value
29+
struct Payload:
30+
var request: HTTPRequest
31+
var json: JSONType
32+
var a: Int
33+
34+
fn __init__(out self, request: HTTPRequest, json: JSONType):
35+
self.a = 1
36+
self.request = request
37+
self.json = json
38+
39+
fn __str__(self) -> String:
40+
return str(self.a)
41+
42+
fn from_request(mut self, req: HTTPRequest) raises -> Self:
43+
self.a = 2
44+
return self
45+
46+
47+
@always_inline
48+
fn custom_request_payload(payload: Payload) raises -> HandlerResponse:
49+
print(payload.a)
50+
51+
# Returning a JSON as the response, this is a very limited placeholder for now
52+
var json_response = JSONType()
53+
json_response["a"] = str(payload.a)
54+
return json_response
2355

2456

2557
fn main() raises:
2658
var app = App()
2759

28-
app.get("/", hello)
60+
app.get[BaseRequest]("/", hello)
61+
62+
app.get[Payload]("custom/", custom_request_payload)
63+
64+
# We can skip specifying payload when using BaseRequest
2965
app.post("/", printer)
3066

3167
var nested_router = Router("nested")
3268
nested_router.get(path="all/echo/", handler=nested)
33-
app.add_router(nested_router^)
69+
app.add_router(nested_router)
3470

3571
app.start_server()

lightbug_api/__init__.mojo

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from lightbug_http import HTTPRequest, HTTPResponse, Server
22
from lightbug_api.routing import (
3+
BaseRequest,
4+
FromReq,
35
RootRouter,
46
Router,
5-
HTTPHandler,
7+
HandlerResponse,
8+
JSONType,
69
)
710

811

@@ -13,19 +16,23 @@ struct App:
1316
fn __init__(inout self) raises:
1417
self.router = RootRouter()
1518

16-
fn get(
19+
fn get[
20+
T: FromReq = BaseRequest
21+
](
1722
inout self,
1823
path: String,
19-
handler: HTTPHandler,
24+
handler: fn (T) raises -> HandlerResponse,
2025
) raises:
21-
self.router.get(path, handler)
26+
self.router.get[T](path, handler)
2227

23-
fn post(
28+
fn post[
29+
T: FromReq = BaseRequest
30+
](
2431
inout self,
2532
path: String,
26-
handler: HTTPHandler,
33+
handler: fn (T) raises -> HandlerResponse,
2734
) raises:
28-
self.router.post(path, handler)
35+
self.router.post[T](path, handler)
2936

3037
fn add_router(inout self, owned router: Router) raises -> None:
3138
self.router.add_router(router)

lightbug_api/routing.mojo

Lines changed: 112 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from utils.variant import Variant
2-
from collections import Dict, Optional
2+
from collections import Dict, List, Optional
33
from collections.dict import _DictEntryIter
44

55
from lightbug_http import NotFound, OK, HTTPService, HTTPRequest, HTTPResponse
6-
from lightbug_http.strings import RequestMethod
6+
from lightbug_http.http import RequestMethod
7+
from lightbug_http.uri import URIDelimiters
78

89
alias MAX_SUB_ROUTER_DEPTH = 20
910

@@ -14,15 +15,84 @@ struct RouterErrors:
1415
alias INVALID_PATH_FRAGMENT_ERROR = "INVALID_PATH_FRAGMENT_ERROR"
1516

1617

17-
alias HTTPHandler = fn (req: HTTPRequest) -> HTTPResponse
18+
alias HTTPHandlerWrapper = fn (req: HTTPRequest) raises escaping -> HTTPResponse
19+
20+
# TODO: Placeholder type, what can the JSON container look like
21+
alias JSONType = Dict[String, String]
22+
23+
alias HandlerResponse = Variant[HTTPResponse, String, JSONType]
24+
25+
26+
trait FromReq(Movable, Copyable):
27+
fn __init__(out self, request: HTTPRequest, json: JSONType):
28+
...
29+
30+
fn from_request(mut self, req: HTTPRequest) raises -> Self:
31+
...
32+
33+
fn __str__(self) -> String:
34+
...
1835

1936

2037
@value
21-
struct HandlerMeta:
22-
var handler: HTTPHandler
38+
struct BaseRequest:
39+
var request: HTTPRequest
40+
var json: JSONType
41+
42+
fn __init__(out self, request: HTTPRequest, json: JSONType):
43+
self.request = request
44+
self.json = json
2345

46+
fn __str__(self) -> String:
47+
return str("")
2448

25-
alias HTTPHandlersMap = Dict[String, HandlerMeta]
49+
fn from_request(mut self, req: HTTPRequest) raises -> Self:
50+
return self
51+
52+
53+
@value
54+
struct RouteHandler[T: FromReq](CollectionElement):
55+
var handler: fn (T) raises -> HandlerResponse
56+
57+
fn __init__(inout self, h: fn (T) raises -> HandlerResponse):
58+
self.handler = h
59+
60+
fn _encode_response(self, res: HandlerResponse) raises -> HTTPResponse:
61+
if res.isa[HTTPResponse]():
62+
return res[HTTPResponse]
63+
elif res.isa[String]():
64+
return OK(res[String])
65+
elif res.isa[JSONType]():
66+
return OK(self._serialize_json(res[JSONType]))
67+
else:
68+
raise Error("Unsupported response type")
69+
70+
fn _serialize_json(self, json: JSONType) raises -> String:
71+
# TODO: Placeholder json serialize implementation
72+
fn ser(j: JSONType) raises -> String:
73+
var str_frags = List[String]()
74+
for kv in j.items():
75+
str_frags.append(
76+
'"' + str(kv[].key) + '": "' + str(kv[].value) + '"'
77+
)
78+
79+
var str_res = str("{") + str(",").join(str_frags) + str("}")
80+
return str_res
81+
82+
return ser(json)
83+
84+
fn _deserialize_json(self, req: HTTPRequest) raises -> JSONType:
85+
# TODO: Placeholder json deserialize implementation
86+
return JSONType()
87+
88+
fn handle(self, req: HTTPRequest) raises -> HTTPResponse:
89+
var payload = T(request=req, json=self._deserialize_json(req))
90+
payload = payload.from_request(req)
91+
var handler_response = self.handler(payload)
92+
return self._encode_response(handler_response^)
93+
94+
95+
alias HTTPHandlersMap = Dict[String, HTTPHandlerWrapper]
2696

2797

2898
@value
@@ -54,7 +124,7 @@ struct RouterBase[is_main_app: Bool = False](HTTPService):
54124

55125
fn _route(
56126
mut self, partial_path: String, method: String, depth: Int = 0
57-
) raises -> HandlerMeta:
127+
) raises -> HTTPHandlerWrapper:
58128
if depth > MAX_SUB_ROUTER_DEPTH:
59129
raise Error(RouterErrors.ROUTE_NOT_FOUND_ERROR)
60130

@@ -63,8 +133,7 @@ struct RouterBase[is_main_app: Bool = False](HTTPService):
63133
var handler_path = partial_path
64134

65135
if partial_path:
66-
# TODO: (Hrist) Update to lightbug_http.uri.URIDelimiters.PATH when available
67-
var fragments = partial_path.split("/", 1)
136+
var fragments = partial_path.split(URIDelimiters.PATH, 1)
68137

69138
sub_router_name = fragments[0]
70139
if len(fragments) == 2:
@@ -73,8 +142,7 @@ struct RouterBase[is_main_app: Bool = False](HTTPService):
73142
remaining_path = ""
74143

75144
else:
76-
# TODO: (Hrist) Update to lightbug_http.uri.URIDelimiters.PATH when available
77-
handler_path = "/"
145+
handler_path = URIDelimiters.PATH
78146

79147
if sub_router_name in self.sub_routers:
80148
return self.sub_routers[sub_router_name]._route(
@@ -87,17 +155,16 @@ struct RouterBase[is_main_app: Bool = False](HTTPService):
87155

88156
fn func(mut self, req: HTTPRequest) raises -> HTTPResponse:
89157
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
158+
var path = uri.path.split(URIDelimiters.PATH, 1)[1]
159+
var route_handler_meta: HTTPHandlerWrapper
93160
try:
94161
route_handler_meta = self._route(path, req.method)
95162
except e:
96163
if str(e) == RouterErrors.ROUTE_NOT_FOUND_ERROR:
97164
return NotFound(uri.path)
98165
raise e
99166

100-
return route_handler_meta.handler(req)
167+
return route_handler_meta(req)
101168

102169
fn _validate_path_fragment(self, path_fragment: String) -> Bool:
103170
# TODO: Validate fragment
@@ -110,31 +177,51 @@ struct RouterBase[is_main_app: Bool = False](HTTPService):
110177
fn add_router(mut self, owned router: RouterBase[False]) raises -> None:
111178
self.sub_routers[router.path_fragment] = router
112179

113-
fn add_route(
180+
# fn register[T: FromReq](inout self, path: String, handler: fn(T) raises):
181+
#
182+
# fn handle(req: Request) raises:
183+
# RouteHandler[T](handler).handle(req)
184+
#
185+
# self.routes[path] = handle
186+
#
187+
# fn route(self, path: String, req: Request) raises:
188+
# if path in self.routes:
189+
# self.routes[path](req)
190+
# else:
191+
192+
fn add_route[
193+
T: FromReq
194+
](
114195
mut self,
115196
partial_path: String,
116-
handler: HTTPHandler,
197+
handler: fn (T) raises -> HandlerResponse,
117198
method: RequestMethod = RequestMethod.get,
118199
) raises -> None:
119200
if not self._validate_path(partial_path):
120201
raise Error(RouterErrors.INVALID_PATH_ERROR)
121-
var handler_meta = HandlerMeta(handler)
122202

123-
self.routes[method.value][partial_path] = handler_meta^
203+
fn handle(req: HTTPRequest) raises -> HTTPResponse:
204+
return RouteHandler[T](handler).handle(req)
205+
206+
self.routes[method.value][partial_path] = handle^
124207

125-
fn get(
208+
fn get[
209+
T: FromReq = BaseRequest
210+
](
126211
inout self,
127212
path: String,
128-
handler: HTTPHandler,
213+
handler: fn (T) raises -> HandlerResponse,
129214
) raises:
130-
self.add_route(path, handler, RequestMethod.get)
215+
self.add_route[T](path, handler, RequestMethod.get)
131216

132-
fn post(
217+
fn post[
218+
T: FromReq = BaseRequest
219+
](
133220
inout self,
134221
path: String,
135-
handler: HTTPHandler,
222+
handler: fn (T) raises -> HandlerResponse,
136223
) raises:
137-
self.add_route(path, handler, RequestMethod.post)
224+
self.add_route[T](path, handler, RequestMethod.post)
138225

139226

140227
alias RootRouter = RouterBase[True]

0 commit comments

Comments
 (0)