-
Notifications
You must be signed in to change notification settings - Fork 7
Adds the scaffold for serialization and de-serialization #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,71 @@ | ||
from lightbug_api import ( | ||
App, | ||
BaseRequest, | ||
Router, | ||
HandlerResponse, | ||
JSONType | ||
) | ||
from lightbug_http import HTTPRequest, HTTPResponse, OK | ||
|
||
|
||
@always_inline | ||
fn printer(req: HTTPRequest) -> HTTPResponse: | ||
print("Got a request on ", req.uri.path, " with method ", req.method) | ||
return OK(req.body_raw) | ||
|
||
fn printer(req: BaseRequest) raises -> HandlerResponse: | ||
print("Got a request on ", req.request.uri.path, " with method ", req.request.method) | ||
return OK(req.request.body_raw) | ||
|
||
@always_inline | ||
fn hello(req: HTTPRequest) -> HTTPResponse: | ||
fn hello(payload: BaseRequest) raises -> HandlerResponse: | ||
return OK("Hello 🔥!") | ||
|
||
|
||
@always_inline | ||
fn nested(req: HTTPRequest) -> HTTPResponse: | ||
print("Handling route:", req.uri.path) | ||
return OK(req.uri.path) | ||
fn nested(req: BaseRequest) raises -> HandlerResponse: | ||
print("Handling route:", req.request.uri.path) | ||
|
||
# Returning a string will get marshaled to a proper `OK` response | ||
return req.request.uri.path | ||
|
||
@value | ||
struct Payload: | ||
var request: HTTPRequest | ||
var json: JSONType | ||
var a: Int | ||
|
||
fn __init__(out self, request: HTTPRequest, json: JSONType): | ||
self.a = 1 | ||
self.request = request | ||
self.json = json | ||
|
||
fn __str__(self) -> String: | ||
return str(self.a) | ||
|
||
fn from_request(mut self, req: HTTPRequest) raises -> Self: | ||
self.a = 2 | ||
return self | ||
|
||
|
||
@always_inline | ||
fn custom_request_payload(payload: Payload) raises -> HandlerResponse: | ||
print(payload.a) | ||
|
||
# Returning a JSON as the response, this is a very limited placeholder for now | ||
var json_response = JSONType() | ||
json_response["a"] = str(payload.a) | ||
return json_response | ||
|
||
|
||
fn main() raises: | ||
var app = App() | ||
|
||
app.get("/", hello) | ||
app.get[BaseRequest]("/", hello) | ||
|
||
app.get[Payload]("custom/", custom_request_payload) | ||
saviorand marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# We can skip specifying payload when using BaseRequest | ||
app.post("/", printer) | ||
|
||
var nested_router = Router("nested") | ||
nested_router.get(path="all/echo/", handler=nested) | ||
app.add_router(nested_router^) | ||
app.add_router(nested_router) | ||
|
||
app.start_server() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,10 @@ | ||
from utils.variant import Variant | ||
from collections import Dict, Optional | ||
from collections import Dict, List, Optional | ||
from collections.dict import _DictEntryIter | ||
|
||
from lightbug_http import NotFound, OK, HTTPService, HTTPRequest, HTTPResponse | ||
from lightbug_http.strings import RequestMethod | ||
from lightbug_http.http import RequestMethod | ||
from lightbug_http.uri import URIDelimiters | ||
|
||
alias MAX_SUB_ROUTER_DEPTH = 20 | ||
|
||
|
@@ -14,15 +15,84 @@ struct RouterErrors: | |
alias INVALID_PATH_FRAGMENT_ERROR = "INVALID_PATH_FRAGMENT_ERROR" | ||
|
||
|
||
alias HTTPHandler = fn (req: HTTPRequest) -> HTTPResponse | ||
alias HTTPHandlerWrapper = fn (req: HTTPRequest) raises escaping -> HTTPResponse | ||
|
||
# TODO: Placeholder type, what can the JSON container look like | ||
saviorand marked this conversation as resolved.
Show resolved
Hide resolved
|
||
alias JSONType = Dict[String, String] | ||
|
||
alias HandlerResponse = Variant[HTTPResponse, String, JSONType] | ||
|
||
|
||
trait FromReq(Movable, Copyable): | ||
fn __init__(out self, request: HTTPRequest, json: JSONType): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this we can prob omit from the trait as not all will have json payloads. either that, or the payload can just be passed inside There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the reason that is required on the trait is that since we don't have struct inheritance or reflection we have to have the user deal with deconstructing the data on custom payloads so we want to make sure their type is able to handle this ... we could make them add a method that tells us if we they need json |
||
... | ||
|
||
fn from_request(mut self, req: HTTPRequest) raises -> Self: | ||
... | ||
|
||
fn __str__(self) -> String: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this a required method? |
||
... | ||
|
||
|
||
@value | ||
struct HandlerMeta: | ||
var handler: HTTPHandler | ||
struct BaseRequest: | ||
var request: HTTPRequest | ||
var json: JSONType | ||
|
||
fn __init__(out self, request: HTTPRequest, json: JSONType): | ||
self.request = request | ||
self.json = json | ||
|
||
fn __str__(self) -> String: | ||
return str("") | ||
|
||
alias HTTPHandlersMap = Dict[String, HandlerMeta] | ||
fn from_request(mut self, req: HTTPRequest) raises -> Self: | ||
return self | ||
|
||
|
||
@value | ||
struct RouteHandler[T: FromReq](CollectionElement): | ||
var handler: fn (T) raises -> HandlerResponse | ||
|
||
fn __init__(inout self, h: fn (T) raises -> HandlerResponse): | ||
self.handler = h | ||
|
||
fn _encode_response(self, res: HandlerResponse) raises -> HTTPResponse: | ||
if res.isa[HTTPResponse](): | ||
return res[HTTPResponse] | ||
elif res.isa[String](): | ||
return OK(res[String]) | ||
elif res.isa[JSONType](): | ||
return OK(self._serialize_json(res[JSONType])) | ||
else: | ||
raise Error("Unsupported response type") | ||
|
||
fn _serialize_json(self, json: JSONType) raises -> String: | ||
# TODO: Placeholder json serialize implementation | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this then calls either EmberJSON |
||
fn ser(j: JSONType) raises -> String: | ||
var str_frags = List[String]() | ||
for kv in j.items(): | ||
str_frags.append( | ||
'"' + str(kv[].key) + '": "' + str(kv[].value) + '"' | ||
) | ||
|
||
var str_res = str("{") + str(",").join(str_frags) + str("}") | ||
return str_res | ||
|
||
return ser(json) | ||
|
||
fn _deserialize_json(self, req: HTTPRequest) raises -> JSONType: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this could either call emberJSON |
||
# TODO: Placeholder json deserialize implementation | ||
return JSONType() | ||
|
||
fn handle(self, req: HTTPRequest) raises -> HTTPResponse: | ||
var payload = T(request=req, json=self._deserialize_json(req)) | ||
payload = payload.from_request(req) | ||
var handler_response = self.handler(payload) | ||
return self._encode_response(handler_response^) | ||
|
||
|
||
alias HTTPHandlersMap = Dict[String, HTTPHandlerWrapper] | ||
saviorand marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
@value | ||
|
@@ -54,7 +124,7 @@ struct RouterBase[is_main_app: Bool = False](HTTPService): | |
|
||
fn _route( | ||
mut self, partial_path: String, method: String, depth: Int = 0 | ||
) raises -> HandlerMeta: | ||
) raises -> HTTPHandlerWrapper: | ||
if depth > MAX_SUB_ROUTER_DEPTH: | ||
raise Error(RouterErrors.ROUTE_NOT_FOUND_ERROR) | ||
|
||
|
@@ -63,8 +133,7 @@ struct RouterBase[is_main_app: Bool = False](HTTPService): | |
var handler_path = partial_path | ||
|
||
if partial_path: | ||
# TODO: (Hrist) Update to lightbug_http.uri.URIDelimiters.PATH when available | ||
var fragments = partial_path.split("/", 1) | ||
var fragments = partial_path.split(URIDelimiters.PATH, 1) | ||
|
||
sub_router_name = fragments[0] | ||
if len(fragments) == 2: | ||
|
@@ -73,8 +142,7 @@ struct RouterBase[is_main_app: Bool = False](HTTPService): | |
remaining_path = "" | ||
|
||
else: | ||
# TODO: (Hrist) Update to lightbug_http.uri.URIDelimiters.PATH when available | ||
handler_path = "/" | ||
handler_path = URIDelimiters.PATH | ||
|
||
if sub_router_name in self.sub_routers: | ||
return self.sub_routers[sub_router_name]._route( | ||
|
@@ -87,17 +155,16 @@ struct RouterBase[is_main_app: Bool = False](HTTPService): | |
|
||
fn func(mut self, req: HTTPRequest) raises -> HTTPResponse: | ||
var uri = req.uri | ||
# TODO: (Hrist) Update to lightbug_http.uri.URIDelimiters.PATH when available | ||
var path = uri.path.split("/", 1)[1] | ||
var route_handler_meta: HandlerMeta | ||
var path = uri.path.split(URIDelimiters.PATH, 1)[1] | ||
var route_handler_meta: HTTPHandlerWrapper | ||
try: | ||
route_handler_meta = self._route(path, req.method) | ||
except e: | ||
if str(e) == RouterErrors.ROUTE_NOT_FOUND_ERROR: | ||
return NotFound(uri.path) | ||
raise e | ||
|
||
return route_handler_meta.handler(req) | ||
return route_handler_meta(req) | ||
|
||
fn _validate_path_fragment(self, path_fragment: String) -> Bool: | ||
# TODO: Validate fragment | ||
|
@@ -110,31 +177,51 @@ struct RouterBase[is_main_app: Bool = False](HTTPService): | |
fn add_router(mut self, owned router: RouterBase[False]) raises -> None: | ||
self.sub_routers[router.path_fragment] = router | ||
|
||
fn add_route( | ||
# fn register[T: FromReq](inout self, path: String, handler: fn(T) raises): | ||
# | ||
# fn handle(req: Request) raises: | ||
# RouteHandler[T](handler).handle(req) | ||
# | ||
# self.routes[path] = handle | ||
# | ||
# fn route(self, path: String, req: Request) raises: | ||
# if path in self.routes: | ||
# self.routes[path](req) | ||
# else: | ||
|
||
fn add_route[ | ||
T: FromReq | ||
]( | ||
mut self, | ||
partial_path: String, | ||
handler: HTTPHandler, | ||
handler: fn (T) raises -> HandlerResponse, | ||
method: RequestMethod = RequestMethod.get, | ||
) raises -> None: | ||
if not self._validate_path(partial_path): | ||
raise Error(RouterErrors.INVALID_PATH_ERROR) | ||
var handler_meta = HandlerMeta(handler) | ||
|
||
self.routes[method.value][partial_path] = handler_meta^ | ||
fn handle(req: HTTPRequest) raises -> HTTPResponse: | ||
return RouteHandler[T](handler).handle(req) | ||
|
||
self.routes[method.value][partial_path] = handle^ | ||
|
||
fn get( | ||
fn get[ | ||
T: FromReq = BaseRequest | ||
]( | ||
inout self, | ||
path: String, | ||
handler: HTTPHandler, | ||
handler: fn (T) raises -> HandlerResponse, | ||
) raises: | ||
self.add_route(path, handler, RequestMethod.get) | ||
self.add_route[T](path, handler, RequestMethod.get) | ||
|
||
fn post( | ||
fn post[ | ||
T: FromReq = BaseRequest | ||
]( | ||
inout self, | ||
path: String, | ||
handler: HTTPHandler, | ||
handler: fn (T) raises -> HandlerResponse, | ||
) raises: | ||
self.add_route(path, handler, RequestMethod.post) | ||
self.add_route[T](path, handler, RequestMethod.post) | ||
|
||
|
||
alias RootRouter = RouterBase[True] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll probably add convenience methods on
BaseRequest
likeuri()
that would returnrequest.uri.path
, just so that the user doesn't have to write the wholereq.request.uri.path
etc.Also,
request
onBaseRequest
should probably be renamed to_inner
or similarThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would have been nice if we had struct inheritance so people could extend
BaseRequest
struct and automatically get all the convenience methods on their custom struct for free.But on this post in the forum from Dec Chris says they don't plan to add this feature in Mojo. I wonder if we can work around this limitation somehow