Skip to content
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

feat: add support for async handlers #192

Merged
merged 4 commits into from
Sep 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 43 additions & 29 deletions crates/shared/src/core/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use http::Request;
use http_body_util::Full;
use std::sync::Arc;

use super::{Handler, RouteHandle};
use super::RouteHandler;
use crate::{
server::{context::AppState, Method, Middlewares, NgynContext, NgynResponse, Routes},
traits::{NgynController, NgynInterpreter, NgynMiddleware, NgynModule},
Expand Down Expand Up @@ -33,7 +33,7 @@ impl PlatformData {
let mut res = NgynResponse::default();

if let Some(state) = &self.state {
cx.state = Some(state.clone().into());
cx.state = Some(state.into());
}

let route_handler = self
Expand All @@ -53,7 +53,12 @@ impl PlatformData {

// execute controlled route if it is handled
if let Some(route_handler) = route_handler {
route_handler(&mut cx, &mut res);
match route_handler.as_ref() {
RouteHandler::Sync(handler) => handler(&mut cx, &mut res),
RouteHandler::Async(async_handler) => {
async_handler(&mut cx, &mut res).await;
}
}
cx.execute(&mut res).await;
// if the request method is HEAD, we should not return a body
// even if the route handler has set a body
Expand All @@ -77,7 +82,12 @@ impl PlatformData {
/// * `path` - The path of the route.
/// * `method` - The HTTP method of the route.
/// * `handler` - The handler function for the route.
pub(self) fn add_route(&mut self, path: String, method: Option<Method>, handler: Box<Handler>) {
pub(self) fn add_route(
&mut self,
path: String,
method: Option<Method>,
handler: Box<RouteHandler>,
) {
self.routes.push((path, method, handler));
}

Expand Down Expand Up @@ -125,43 +135,43 @@ pub trait NgynEngine: NgynPlatform {
/// let mut engine = MyEngine::default();
/// engine.route('/', Method::GET, Box::new(|_, _| {}));
/// ```
fn route(&mut self, path: &str, method: Method, handler: Box<Handler>) {
fn route(&mut self, path: &str, method: Method, handler: impl Into<RouteHandler>) {
self.data_mut()
.add_route(path.to_string(), Some(method), handler);
.add_route(path.to_string(), Some(method), Box::new(handler.into()));
}

fn any(&mut self, path: &str, handler: impl RouteHandle) {
fn any(&mut self, path: &str, handler: impl Into<RouteHandler>) {
self.data_mut()
.add_route(path.to_string(), None, handler.into());
.add_route(path.to_string(), None, Box::new(handler.into()));
}

/// Adds a new route to the `NgynApplication` with the `Method::Get`.
fn get(&mut self, path: &str, handler: impl RouteHandle) {
fn get(&mut self, path: &str, handler: impl Into<RouteHandler>) {
self.route(path, Method::GET, handler.into())
}

/// Adds a new route to the `NgynApplication` with the `Method::Post`.
fn post(&mut self, path: &str, handler: impl RouteHandle) {
fn post(&mut self, path: &str, handler: impl Into<RouteHandler>) {
self.route(path, Method::POST, handler.into())
}

/// Adds a new route to the `NgynApplication` with the `Method::Put`.
fn put(&mut self, path: &str, handler: impl RouteHandle) {
fn put(&mut self, path: &str, handler: impl Into<RouteHandler>) {
self.route(path, Method::PUT, handler.into())
}

/// Adds a new route to the `NgynApplication` with the `Method::Delete`.
fn delete(&mut self, path: &str, handler: impl RouteHandle) {
fn delete(&mut self, path: &str, handler: impl Into<RouteHandler>) {
self.route(path, Method::DELETE, handler.into())
}

/// Adds a new route to the `NgynApplication` with the `Method::Patch`.
fn patch(&mut self, path: &str, handler: impl RouteHandle) {
fn patch(&mut self, path: &str, handler: impl Into<RouteHandler>) {
self.route(path, Method::PATCH, handler.into())
}

/// Adds a new route to the `NgynApplication` with the `Method::Head`.
fn head(&mut self, path: &str, handler: impl RouteHandle) {
fn head(&mut self, path: &str, handler: impl Into<RouteHandler>) {
self.route(path, Method::HEAD, handler.into())
}

Expand Down Expand Up @@ -210,15 +220,13 @@ pub trait NgynEngine: NgynPlatform {
/// * `controller` - The arc'd controller to load.
fn load_controller(&mut self, controller: Arc<Box<dyn NgynController + 'static>>) {
for (path, http_method, handler) in controller.routes() {
let controller = controller.clone();
self.route(
path.as_str(),
Method::from_bytes(http_method.as_bytes()).unwrap_or_default(),
Box::new({
Box::new(move |cx: &mut NgynContext, _res: &mut NgynResponse| {
let controller = controller.clone();
move |cx: &mut NgynContext, _res: &mut NgynResponse| {
let controller = controller.clone();
cx.prepare(controller, handler.clone());
}
cx.prepare(controller, handler.clone());
}),
);
}
Expand All @@ -235,7 +243,7 @@ pub trait NgynEngine: NgynPlatform {

#[cfg(test)]
mod tests {
use crate::traits::NgynInjectable;
use crate::{core::Handler, traits::NgynInjectable};
use std::any::Any;

use super::*;
Expand Down Expand Up @@ -378,9 +386,11 @@ mod tests {
async fn test_respond_with_route_handler() {
let mut engine = MockEngine::default();
let handler: Box<Handler> = Box::new(|_, _| {});
engine
.data_mut()
.add_route("/test".to_string(), Some(Method::GET), handler);
engine.data_mut().add_route(
"/test".to_string(),
Some(Method::GET),
Box::new(RouteHandler::Sync(handler)),
);

let req = Request::builder()
.method(Method::GET)
Expand Down Expand Up @@ -430,9 +440,11 @@ mod tests {
async fn test_respond_with_head_method() {
let mut engine = MockEngine::default();
let handler: Box<Handler> = Box::new(|_, _| {});
engine
.data_mut()
.add_route("/test".to_string(), Some(Method::GET), handler);
engine.data_mut().add_route(
"/test".to_string(),
Some(Method::GET),
Box::new(RouteHandler::Sync(handler)),
);

let req = Request::builder()
.method(Method::HEAD)
Expand All @@ -449,9 +461,11 @@ mod tests {
async fn test_add_route() {
let mut engine = MockEngine::default();
let handler: Box<Handler> = Box::new(|_, _| {});
engine
.data_mut()
.add_route("/test".to_string(), Some(Method::GET), handler);
engine.data_mut().add_route(
"/test".to_string(),
Some(Method::GET),
Box::new(RouteHandler::Sync(handler)),
);

assert_eq!(engine.data.routes.len(), 1);
}
Expand Down
79 changes: 67 additions & 12 deletions crates/shared/src/core/handler.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
use crate::server::{NgynContext, NgynResponse, ToBytes};
use std::{future::Future, pin::Pin};

use crate::{
server::{NgynContext, NgynResponse, ToBytes},
traits::NgynMiddleware,
};

/// Represents a handler function that takes in a mutable reference to `NgynContext` and `NgynResponse`.
pub type Handler = dyn Fn(&mut NgynContext, &mut NgynResponse) + Send + Sync + 'static;

/// Represents a trait for converting a type into a `Handler` trait object.
pub trait RouteHandle: Send + Sync {
/// Converts the implementing type into a `Handler` trait object.
fn into(self) -> Box<Handler>;
pub type AsyncHandler = Box<
dyn for<'a, 'b> Fn(
&'a mut NgynContext,
&'b mut NgynResponse,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'b>>
+ Send
+ Sync,
>;

pub enum RouteHandler {
Sync(Box<Handler>),
Async(AsyncHandler),
}

impl<F: Fn(&mut NgynContext, &mut NgynResponse) + Send + Sync + 'static> From<F> for RouteHandler {
fn from(f: F) -> Self {
RouteHandler::Sync(Box::new(f))
}
}

impl<F> RouteHandle for F
where
F: Fn(&mut NgynContext, &mut NgynResponse) + Send + Sync + 'static,
{
/// Converts the implementing function into a `Handler` trait object.
fn into(self) -> Box<Handler> {
Box::new(self)
impl From<AsyncHandler> for RouteHandler {
fn from(f: AsyncHandler) -> Self {
RouteHandler::Async(Box::new(f))
}
}

Expand All @@ -39,3 +54,43 @@ pub fn handler<S: ToBytes + 'static>(
*res.body_mut() = body.into();
})
}

/// Creates a `AsyncHandler` trait object from an async function that takes in a mutable reference to `NgynContext` and returns a future with output that implements `ToBytes`.
///
/// ### Example
/// ```rust ignore
/// use ngyn::server::{async_handler, NgynContext, ToBytes};
///
/// app.get("/hello", async_handler(async |ctx: &mut NgynContext| {
/// "Hello, World!"
/// }));
/// ```
pub fn async_handler<S: ToBytes + 'static, Fut: Future<Output = S> + Send + 'static>(
f: impl Fn(&mut NgynContext) -> Fut + Send + Sync + 'static,
) -> AsyncHandler {
Box::new(move |ctx: &mut NgynContext, res: &mut NgynResponse| {
let fut = f(ctx);
Box::pin(async move {
let body = fut.await.to_bytes();
*res.body_mut() = body.into();
})
})
}

pub fn middleware<M: NgynMiddleware + 'static>(middleware: M) -> Box<Handler> {
Box::new(move |ctx: &mut NgynContext, res: &mut NgynResponse| {
middleware.handle(ctx, res);
})
}

pub fn with_middlewares<M: NgynMiddleware + 'static>(
middleware: Vec<M>,
handler: Box<Handler>,
) -> impl Into<RouteHandler> {
Box::new(move |ctx: &mut NgynContext, res: &mut NgynResponse| {
for m in &middleware {
m.handle(ctx, res);
}
handler(ctx, res);
})
}
10 changes: 5 additions & 5 deletions crates/shared/src/server/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ impl<T: AppState> AppState for Box<T> {
}
}

impl From<Arc<Box<dyn AppState>>> for Box<dyn AppState> {
fn from(value: Arc<Box<dyn AppState>>) -> Self {
impl From<&Arc<Box<dyn AppState>>> for Box<dyn AppState> {
fn from(value: &Arc<Box<dyn AppState>>) -> Self {
let arc_clone = value.clone();
let controller_ref: &dyn AppState = &**arc_clone;
let state_ref: &dyn AppState = &**arc_clone;

let controller_ptr: *const dyn AppState = controller_ref as *const dyn AppState;
let state_ptr: *const dyn AppState = state_ref as *const dyn AppState;

let nn_ptr = std::ptr::NonNull::new(controller_ptr as *mut dyn AppState).unwrap();
let nn_ptr = std::ptr::NonNull::new(state_ptr as *mut dyn AppState).unwrap();
let raw_ptr = nn_ptr.as_ptr();

unsafe { Box::from_raw(raw_ptr) }
Expand Down
2 changes: 1 addition & 1 deletion crates/shared/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ pub use transformer::{Body, Param, Query, Transducer, Transformer};
pub type NgynRequest = http::Request<Vec<u8>>;
pub type NgynResponse = http::Response<Full<Bytes>>;

pub(crate) type Routes = Vec<(String, Option<Method>, Box<crate::core::Handler>)>;
pub(crate) type Routes = Vec<(String, Option<Method>, Box<crate::core::RouteHandler>)>;
pub(crate) type Middlewares = Vec<Box<dyn crate::traits::NgynMiddleware>>;
9 changes: 9 additions & 0 deletions crates/shared/src/server/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,16 @@ mod tests {
};

response.peek_bytes(peek_fn).await;
assert_eq!(bytes, body);
}

#[tokio::test]
async fn test_read_bytes() {
let mut response = NgynResponse::default();
let body = Bytes::from("Hello, world!");
*response.body_mut() = body.clone().into();

let bytes = response.read_bytes().await.unwrap();
assert_eq!(bytes, body);
}
}