Repository: ngyn-rs/ngyn (language: rust)

Issue:
Platform Stability Checklist
- [x] - Fix broken platform macro
- [x] - Fix broken compat of Vercel platform
- [x] - Fix Controller Generics
- [x] - Update examples



Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 64d89ebe..aa6ce3f5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -364,9 +364,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
 [[package]]
 name = "aws_lambda_events"
-version = "0.10.0"
+version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65991dbc3bfb586939ba1527eefdc99bc21157b6ec891f180fb1e16e2dddc7a9"
+checksum = "03611508dd1e514e311caec235b581c99a4cb66fa1771bd502819eed69894f12"
 dependencies = [
  "base64 0.21.4",
  "bytes",
@@ -411,6 +411,12 @@ version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
+[[package]]
+name = "base64"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
+
 [[package]]
 name = "base64"
 version = "0.21.4"
@@ -1367,9 +1373,9 @@ dependencies = [
 
 [[package]]
 name = "lambda_http"
-version = "0.8.1"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b15e8ab48a5d8eab3110567008caad4d191924d1470b74c5c8b802b904b5a34"
+checksum = "2505c4a24f5a8d8ac66a87691215ec1f79736c5bc6e62bb921788dca9753f650"
 dependencies = [
  "aws_lambda_events",
  "base64 0.21.4",
@@ -1385,19 +1391,22 @@ dependencies = [
  "serde",
  "serde_json",
  "serde_urlencoded",
+ "tokio-stream",
  "url",
 ]
 
 [[package]]
 name = "lambda_runtime"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8300e2e10ab2a49016584d9f248736a9da5ea819648df4c9d6c82fa2231fb510"
+checksum = "deca8f65d7ce9a8bfddebb49d7d91b22e788a59ca0c5190f26794ab80ed7a702"
 dependencies = [
  "async-stream",
+ "base64 0.20.0",
  "bytes",
  "futures",
  "http 0.2.9",
+ "http-body 0.4.5",
  "http-serde",
  "hyper 0.14.27",
  "lambda_runtime_api_client",
@@ -1528,7 +1537,6 @@ dependencies = [
  "ngyn_shared 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "nject",
  "tide",
- "vercel_runtime",
 ]
 
 [[package]]
@@ -1802,9 +1810,9 @@ dependencies = [
 
 [[package]]
 name = "query_map"
-version = "0.6.0"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4465aacac3bebc9484cf7a56dc8b2d7feacb657da6002a9198b4f7af4247a204"
+checksum = "5eab6b8b1074ef3359a863758dae650c7c0c6027927a085b7af911c8e0bf3a15"
 dependencies = [
  "form_urlencoded",
  "serde",
@@ -2055,18 +2063,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
 
 [[package]]
 name = "serde"
-version = "1.0.188"
+version = "1.0.193"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.188"
+version = "1.0.193"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2777,7 +2785,7 @@ dependencies = [
 name = "vercel_app"
 version = "0.2.8"
 dependencies = [
- "ngyn 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ngyn 0.3.1",
  "nject",
  "tokio",
  "vercel_runtime",
@@ -2785,9 +2793,9 @@ dependencies = [
 
 [[package]]
 name = "vercel_runtime"
-version = "1.1.0"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9cf51c114b81ef5b4831b2e3b8d7c98b6e15ffe02eec03bae972b8aa2f2d01b"
+checksum = "b08a53b5050450ac9a287b2e308b35478af816730cee0c5d19b73128475268a5"
 dependencies = [
  "async-trait",
  "base64 0.21.4",
@@ -2808,9 +2816,9 @@ dependencies = [
 
 [[package]]
 name = "vercel_runtime_macro"
-version = "1.1.0"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f5825fd955017e6dd1984a561344bbf340cdbfb1a436b9b639c543a1dc63f98b"
+checksum = "0daaf5bc7ed28af6645e82b2edfb8ab4ecc40d7cd65901350e808c259829956c"
 dependencies = [
  "glob",
  "quote",
@@ -2820,9 +2828,9 @@ dependencies = [
 
 [[package]]
 name = "vercel_runtime_router"
-version = "1.1.0"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1f5769df66e81052dc294e2dbb5a45214ad965ca2db3be09c2707db1c581a97"
+checksum = "3afac9b3d8d97c41c948769da85c241375b3eb7d806f91956d6e8866fbc7cb2b"
 dependencies = [
  "glob",
  "lazy_static",
diff --git a/crates/core/src/app/factory.rs b/crates/core/src/app/factory.rs
index 62a69ea0..d431cdc0 100644
--- a/crates/core/src/app/factory.rs
+++ b/crates/core/src/app/factory.rs
@@ -25,7 +25,7 @@ impl<Application: NgynEngine> NgynFactory<Application> {
     /// ```
     pub fn create<AppModule: NgynModule>() -> Application {
         let mut module = AppModule::new(vec![]);
-        let mut server = Application::new();
+        let mut server = Application::default();
         for controller in module.get_controllers() {
             for (path, http_method, handler) in controller.routes() {
                 server.route(
diff --git a/crates/core/src/platforms/hyper.rs b/crates/core/src/platforms/hyper.rs
index 36281ee4..24f6f4af 100644
--- a/crates/core/src/platforms/hyper.rs
+++ b/crates/core/src/platforms/hyper.rs
@@ -1,21 +1,19 @@
-use http_body_util::Full;
+use http_body_util::{BodyExt, Full};
 use hyper::body::{Bytes, Incoming};
 use hyper::server::conn::http1;
 use hyper::{service::service_fn, Request, Response};
 use hyper_util::rt::TokioIo;
+use ngyn_macros::Platform;
 use ngyn_shared::{FullResponse, Handler, Method, NgynContext, NgynEngine};
 use std::sync::Arc;
 use tokio::net::TcpListener;
 
+#[derive(Default, Platform)]
 pub struct HyperApplication {
     routes: Vec<(String, Method, Option<Box<Handler>>)>,
 }
 
 impl NgynEngine for HyperApplication {
-    fn new() -> Self {
-        HyperApplication { routes: vec![] }
-    }
-
     fn route(&mut self, path: &str, method: Method, handler: Box<Handler>) -> &mut Self {
         self.routes.push((path.to_string(), method, Some(handler)));
         self
@@ -33,7 +31,16 @@ impl HyperApplication {
         let service = service_fn(move |req: Request<Incoming>| {
             let routes = Arc::clone(&routes_copy);
             async move {
-                let mut cx = NgynContext::from_request(req);
+                let mut cx = NgynContext::from_request(req.map(|b| {
+                    let mut new_body = vec![];
+                    b.map_frame(|mut f| {
+                        if let Some(d) = f.data_mut() {
+                            new_body.append(&mut d.to_vec());
+                        }
+                        f
+                    });
+                    new_body
+                }));
                 let mut res = Response::new(Full::new(Bytes::default()));
 
                 let handler = routes
diff --git a/crates/core/src/platforms/vercel.rs b/crates/core/src/platforms/vercel.rs
index faed083b..14982788 100644
--- a/crates/core/src/platforms/vercel.rs
+++ b/crates/core/src/platforms/vercel.rs
@@ -1,18 +1,17 @@
-use http_body_util::Full;
-use hyper::{body::Incoming, Request, Response};
-use ngyn_macros::platform;
+use std::str::FromStr;
+
+use http_body_util::{BodyExt, Full};
+use hyper::{header::HeaderName, Response, Uri};
+use ngyn_macros::Platform;
 use ngyn_shared::{Bytes, FullResponse, Handler, Method, NgynContext, NgynEngine};
-use vercel_runtime::{Body, Error, Response as VercelResponse};
+use vercel_runtime::{Body, Error, Request, Response as VercelResponse};
 
+#[derive(Default, Platform)]
 pub struct VercelApplication {
     routes: Vec<(String, Method, Box<Handler>)>,
 }
 
 impl NgynEngine for VercelApplication {
-    fn new() -> Self {
-        Self { routes: Vec::new() }
-    }
-
     fn route(&mut self, path: &str, method: Method, handler: Box<Handler>) -> &mut Self {
         self.routes.push((path.to_string(), method, handler));
         self
@@ -20,14 +19,46 @@ impl NgynEngine for VercelApplication {
 }
 
 impl VercelApplication {
-    pub async fn handle(self, request: Request<Incoming>) -> Result<VercelResponse<Body>, Error> {
+    pub async fn handle(self, request: Request) -> Result<VercelResponse<Body>, Error> {
+        let request = request.map(|b| b.to_vec());
+        let (parts, body) = request.into_parts();
+
+        // TODO: Once vercel_runtime supports http v1, we can remove this
+        let request = {
+            let mut hyper_request = hyper::Request::new(body);
+
+            let method = parts.method.to_string();
+            *hyper_request.method_mut() = Method::from_bytes(method.as_bytes()).unwrap();
+
+            let uri = parts.uri.clone().to_string();
+            *hyper_request.uri_mut() = Uri::from_str(&uri).unwrap();
+
+            let headers = hyper_request.headers_mut();
+            let raw_headers = parts.headers.clone();
+            raw_headers.into_iter().for_each(|(key, value)| {
+                if let Some(key) = key {
+                    headers.insert(
+                        HeaderName::from_str(key.as_str()).unwrap(),
+                        value.clone().to_str().unwrap().parse().unwrap(),
+                    );
+                }
+            });
+
+            hyper_request
+        };
         let mut cx = NgynContext::from_request(request);
 
         let handler = self
             .routes
             .iter()
-            .find(|(path, method, _)| cx.with(path, method).is_some())
-            .map(|(_, _, handler)| handler);
+            .filter_map(|(path, method, handler)| {
+                if cx.with(path, method).is_some() {
+                    Some(handler)
+                } else {
+                    None
+                }
+            })
+            .next();
 
         let mut res = Response::new(Full::new(Bytes::default()));
 
@@ -39,9 +70,25 @@ impl VercelApplication {
             res.peek("Not Found".to_string());
         }
 
+        // TODO: Once vercel_runtime supports http v1, we can remove this
+        let (parts, body) = {
+            let (parts, mut r_body) = res.into_parts();
+            (
+                parts,
+                r_body
+                    .frame()
+                    .await
+                    .unwrap()
+                    .unwrap()
+                    .into_data()
+                    .unwrap()
+                    .to_vec(),
+            )
+        };
+
         Ok(VercelResponse::builder()
-            .status(res.status().as_u16())
-            .body(String::new().into())
+            .status(parts.status.as_u16())
+            .body(body.into())
             .unwrap())
     }
 }
diff --git a/crates/macros/src/common/controller_macro.rs b/crates/macros/src/common/controller_macro.rs
index 9b7e3540..03f74d70 100644
--- a/crates/macros/src/common/controller_macro.rs
+++ b/crates/macros/src/common/controller_macro.rs
@@ -51,7 +51,7 @@ pub fn controller_macro(args: TokenStream, input: TokenStream) -> TokenStream {
         ident,
         data,
         vis,
-        ..
+        generics,
     } = syn::parse_macro_input!(input as syn::DeriveInput);
     let args = syn::parse_macro_input!(args as ControllerArgs);
     let (types, keys) = parse_macro_data(data);
@@ -80,13 +80,13 @@ pub fn controller_macro(args: TokenStream, input: TokenStream) -> TokenStream {
     let expanded = quote! {
         #(#attrs)*
         #[ngyn::macros::dependency]
-        #vis struct #ident {
+        #vis struct #ident #generics {
             middlewares: Vec<std::sync::Arc<dyn ngyn::prelude::NgynMiddleware>>,
             #(#fields),*
         }
 
         #[ngyn::prelude::async_trait]
-        impl ngyn::prelude::NgynControllerRoutePlaceholder for #ident {
+        impl #generics ngyn::prelude::NgynControllerRoutePlaceholder for #ident #generics {
             #[allow(non_upper_case_globals)]
             const routes: &'static [(&'static str, &'static str, &'static str)] = &[];
 
@@ -101,7 +101,7 @@ pub fn controller_macro(args: TokenStream, input: TokenStream) -> TokenStream {
         }
 
         #[ngyn::prelude::async_trait]
-        impl ngyn::prelude::NgynController for #ident {
+        impl #generics ngyn::prelude::NgynController for #ident #generics {
             fn new(middlewares: Vec<std::sync::Arc<dyn ngyn::prelude::NgynMiddleware>>) -> Self {
                 #(#add_middlewares)*
                 let mut controller = #ident {
diff --git a/crates/macros/src/core/platform_macro.rs b/crates/macros/src/core/platform_macro.rs
index 15197ce4..ed783e6a 100644
--- a/crates/macros/src/core/platform_macro.rs
+++ b/crates/macros/src/core/platform_macro.rs
@@ -2,42 +2,40 @@ use proc_macro::TokenStream;
 use quote::quote;
 use syn::{parse_macro_input, DeriveInput};
 
-pub fn platform_macro(_args: TokenStream, input: TokenStream) -> TokenStream {
+pub fn platform_macro(input: TokenStream) -> TokenStream {
     let input = parse_macro_input!(input as DeriveInput);
     let ident = &input.ident;
 
     let expanded = quote! {
-        #input
-
         impl #ident {
             /// Adds a new route to the `NgynApplication` with the `Method::Get`.
-            pub fn get<F>(&mut self, path: &str, handler: F) -> &mut Self {
-                self.route(path, Method::Get, Box::new(handler))
+            pub fn get(&mut self, path: &str, handler: impl ngyn_shared::RouteHandle) -> &mut Self {
+                self.route(path, Method::GET, handler.into())
             }
 
             /// Adds a new route to the `NgynApplication` with the `Method::Post`.
-            pub fn post<F>(&mut self, path: &str, handler: F) -> &mut Self {
-                self.route(path, Method::Get, Box::new(handler))
+            pub fn post(&mut self, path: &str, handler: impl ngyn_shared::RouteHandle) -> &mut Self {
+                self.route(path, Method::POST, handler.into())
             }
 
             /// Adds a new route to the `NgynApplication` with the `Method::Put`.
-            pub fn put<F>(&mut self, path: &str, handler: F) -> &mut Self {
-                self.route(path, Method::Get, Box::new(handler))
+            pub fn put(&mut self, path: &str, handler: impl ngyn_shared::RouteHandle) -> &mut Self {
+                self.route(path, Method::PUT, handler.into())
             }
 
             /// Adds a new route to the `NgynApplication` with the `Method::Delete`.
-            pub fn delete<F>(&mut self, path: &str, handler: F) -> &mut Self {
-                self.route(path, Method::Get, Box::new(handler))
+            pub fn delete(&mut self, path: &str, handler: impl ngyn_shared::RouteHandle) -> &mut Self {
+                self.route(path, Method::DELETE, handler.into())
             }
 
             /// Adds a new route to the `NgynApplication` with the `Method::Patch`.
-            pub fn patch<F>(&mut self, path: &str, handler: F) -> &mut Self {
-                self.route(path, Method::Get, Box::new(handler))
+            pub fn patch(&mut self, path: &str, handler: impl ngyn_shared::RouteHandle) -> &mut Self {
+                self.route(path, Method::PATCH, handler.into())
             }
 
             /// Adds a new route to the `NgynApplication` with the `Method::Head`.
-            pub fn head<F>(&mut self, path: &str, handler: F) -> &mut Self {
-                self.route(path, Method::Get, Box::new(handler))
+            pub fn head(&mut self, path: &str, handler: impl ngyn_shared::RouteHandle) -> &mut Self {
+                self.route(path, Method::HEAD, handler.into())
             }
         }
     };
diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs
index 6de95b81..b9ff2120 100644
--- a/crates/macros/src/lib.rs
+++ b/crates/macros/src/lib.rs
@@ -12,10 +12,10 @@ use crate::core::platform_macro::platform_macro;
 use common::check_macro::check_impl_macro;
 use proc_macro::TokenStream;
 
-#[proc_macro_attribute]
+#[proc_macro_derive(Platform)]
 /// `platform` is used to mark a struct as a platform engine.
-pub fn platform(args: TokenStream, input: TokenStream) -> TokenStream {
-    platform_macro(args, input)
+pub fn platform(input: TokenStream) -> TokenStream {
+    platform_macro(input)
 }
 
 #[proc_macro_attribute]
diff --git a/crates/shared/src/core/engine.rs b/crates/shared/src/core/engine.rs
index 93f4e803..d79312a4 100644
--- a/crates/shared/src/core/engine.rs
+++ b/crates/shared/src/core/engine.rs
@@ -1,10 +1,7 @@
 use super::Handler;
 use crate::Method;
 
-pub trait NgynEngine {
-    /// Creates a new instance of the `App` with a new `Server`
-    fn new() -> Self;
-
+pub trait NgynEngine: Default {
     /// Adds a new route to the `App`.
     /// This function is chainable.
     ///
diff --git a/crates/shared/src/core/handler.rs b/crates/shared/src/core/handler.rs
index 8b5acae4..825a2c49 100644
--- a/crates/shared/src/core/handler.rs
+++ b/crates/shared/src/core/handler.rs
@@ -1,3 +1,16 @@
 use crate::{NgynContext, NgynResponse};
 
 pub type Handler = dyn Fn(&mut NgynContext, &mut NgynResponse) + Send + Sync + 'static;
+
+pub trait RouteHandle: Send + Sync {
+    fn into(self) -> Box<Handler>;
+}
+
+impl<F> RouteHandle for F
+where
+    F: Fn(&mut NgynContext, &mut NgynResponse) + Send + Sync + 'static,
+{
+    fn into(self) -> Box<Handler> {
+        Box::new(self)
+    }
+}
diff --git a/crates/shared/src/server/context.rs b/crates/shared/src/server/context.rs
index ceac5aba..2ac2ba36 100644
--- a/crates/shared/src/server/context.rs
+++ b/crates/shared/src/server/context.rs
@@ -1,7 +1,7 @@
 // a context extends hashmap to provide some extra functionality
 //
 
-use hyper::{body::Incoming, Request};
+use hyper::Request;
 use std::{collections::HashMap, sync::Arc};
 
 use crate::{Method, NgynController, NgynResponse, ToParts};
@@ -108,7 +108,7 @@ impl From<NgynContextValue> for String {
 }
 
 pub struct NgynContext {
-    pub request: Request<Incoming>,
+    pub request: Request<Vec<u8>>,
     pub params: Vec<(String, String)>,
     route_info: Option<(String, Arc<dyn NgynController>)>,
     store: HashMap<String, NgynContextValue>,
@@ -152,7 +152,7 @@ impl NgynContext {
         !stored_value.is_empty() || stored_value.clone().into()
     }
 
-    pub fn from_request(request: Request<Incoming>) -> Self {
+    pub fn from_request(request: Request<Vec<u8>>) -> Self {
         NgynContext {
             request,
             store: HashMap::new(),
diff --git a/crates/shared/src/server/transformer.rs b/crates/shared/src/server/transformer.rs
index 18925bba..eb28f728 100644
--- a/crates/shared/src/server/transformer.rs
+++ b/crates/shared/src/server/transformer.rs
@@ -1,8 +1,5 @@
 use std::borrow::Cow;
 
-use http_body_util::BodyExt;
-use hyper::body::Bytes;
-
 use crate::{context::NgynContext, NgynResponse};
 
 pub trait Transformer {
@@ -86,17 +83,7 @@ impl Dto {
 
 impl Transformer for Dto {
     fn transform(cx: &mut NgynContext, _res: &mut NgynResponse) -> Dto {
-        let mut data = String::new();
-        cx.request.body_mut().map_frame(|frame| {
-            let frame = if let Ok(data) = frame.into_data() {
-                data
-            } else {
-                Bytes::new()
-            };
-            data.push_str(&String::from_utf8(frame.clone().into()).unwrap());
-            hyper::body::Frame::data(frame)
-        });
-
+        let data = String::from_utf8_lossy(cx.request.body()).to_string();
         Dto { data }
     }
 }
diff --git a/examples/vercel_app/Cargo.toml b/examples/vercel_app/Cargo.toml
index 37e7cff0..b47c771f 100644
--- a/examples/vercel_app/Cargo.toml
+++ b/examples/vercel_app/Cargo.toml
@@ -10,10 +10,8 @@ path = "api/[[...all]].rs"
 [lib]
 path = "src/lib.rs"
 
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
 [dependencies]
 nject = "0.3.0"
-ngyn = { version = "0.3.1", features = ["vercel"] }
+ngyn = { version = "0.3.1", features = ["vercel"], path = "../../crates/core" }
 tokio = { version = "1.32.0", features = ["full"] }
-vercel_runtime = { version = "1.1.0" }
+vercel_runtime = { version = "1.1.1" }
diff --git a/examples/vercel_app/src/modules/sample/sample_controller.rs b/examples/vercel_app/src/modules/sample/sample_controller.rs
index 695d96cf..e1ada5ef 100644
--- a/examples/vercel_app/src/modules/sample/sample_controller.rs
+++ b/examples/vercel_app/src/modules/sample/sample_controller.rs
@@ -10,9 +10,9 @@ pub struct SampleController {
 #[routes]
 impl SampleController {
     #[get("/hello")]
-    fn say_hello(&self) {
+    fn say_hello(&self) -> String {
         self.sample_service.say_hello();
-        "Hello, Ngyn from Vercel!".to_string();
+        "Hello, Ngyn from Vercel!".to_string()
     }
 
     #[get(["/bye", "/goodbye"])]


Test patch: