diff --git a/LICENSE-MIT b/LICENSE similarity index 100% rename from LICENSE-MIT rename to LICENSE diff --git a/ohkami/Cargo.toml b/ohkami/Cargo.toml index d350fb66..a4b53382 100644 --- a/ohkami/Cargo.toml +++ b/ohkami/Cargo.toml @@ -32,4 +32,4 @@ rt_async-std = ["dep:async-std"] test = ["tokio?/macros", "async-std?/attributes"] ##### DEBUG ##### -# default = ["rt_tokio", "serde/derive"] \ No newline at end of file +# default = ["rt_tokio", "test", "serde/derive"] \ No newline at end of file diff --git a/ohkami/src/layer3_fang_handler/handler/into_handler.rs b/ohkami/src/layer3_fang_handler/handler/into_handler.rs index 19911d6d..fd503ff2 100644 --- a/ohkami/src/layer3_fang_handler/handler/into_handler.rs +++ b/ohkami/src/layer3_fang_handler/handler/into_handler.rs @@ -18,12 +18,12 @@ const _: (/* only Context */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |_, c, _| + Handler::new(move |_, c, _| Box::pin({ let res = self(c); async {res.await} }) - )) + ) } } }; @@ -37,7 +37,7 @@ const _: (/* PathParam */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |_, c, params| + Handler::new(move |_, c, params| match <$param_type as PathParam>::parse(unsafe {params.assume_init_first().as_bytes()}) { Ok(p1) => Box::pin({ let res = self(c, p1); @@ -48,7 +48,7 @@ const _: (/* PathParam */) = { async {res} }), } - )) + ) } } )*}; @@ -62,7 +62,7 @@ const _: (/* PathParam */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |_, c, params| + Handler::new(move |_, c, params| // SAFETY: Due to the architecture of `Router`, // `params` has already `append`ed once before this code match ::parse(unsafe {params.assume_init_first().as_bytes()}) { @@ -75,7 +75,7 @@ const _: (/* PathParam */) = { async {res} }), } - )) + ) } } @@ -85,7 +85,7 @@ const _: (/* PathParam */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |_, c, params| { + Handler::new(move |_, c, params| { let (p1_range, p2_range) = params.assume_init_extract(); // SAFETY: Due to the architecture of `Router`, // `params` has already `append`ed twice before this code @@ -105,7 +105,7 @@ const _: (/* PathParam */) = { async {res} }), } - })) + }) } } }; @@ -117,7 +117,7 @@ const _: (/* FromRequest items */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |req, c, _| + Handler::new(move |req, c, _| match Item1::parse(&req) { Ok(item1) => Box::pin({ let res = self(c, item1); @@ -128,7 +128,7 @@ const _: (/* FromRequest items */) = { async {res} }) } - )) + ) } } @@ -138,7 +138,7 @@ const _: (/* FromRequest items */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |req, c, _| + Handler::new(move |req, c, _| match Item1::parse(&req) { Ok(item1) => match Item2::parse(&req) { Ok(item2) => Box::pin({ @@ -155,7 +155,7 @@ const _: (/* FromRequest items */) = { async {res} }) } - )) + ) } } @@ -165,7 +165,7 @@ const _: (/* FromRequest items */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |req, c, _| + Handler::new(move |req, c, _| match Item1::parse(&req) { Ok(item1) => match Item2::parse(&req) { Ok(item2) => match Item3::parse(&req) { @@ -188,7 +188,7 @@ const _: (/* FromRequest items */) = { async {res} }) } - )) + ) } } }; @@ -202,7 +202,7 @@ const _: (/* single PathParam and FromRequest items */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |req, c, params| + Handler::new(move |req, c, params| // SAFETY: Due to the architecture of `Router`, // `params` has already `append`ed once before this code match <$param_type as PathParam>::parse(unsafe {params.assume_init_first().as_bytes()}) { @@ -221,7 +221,7 @@ const _: (/* single PathParam and FromRequest items */) = { async {res} }), } - )) + ) } } @@ -231,7 +231,7 @@ const _: (/* single PathParam and FromRequest items */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |req, c, params| + Handler::new(move |req, c, params| // SAFETY: Due to the architecture of `Router`, // `params` has already `append`ed once before this code match <$param_type as PathParam>::parse(unsafe {params.assume_init_first().as_bytes()}) { @@ -256,7 +256,7 @@ const _: (/* single PathParam and FromRequest items */) = { async {res} }), } - )) + ) } } @@ -266,7 +266,7 @@ const _: (/* single PathParam and FromRequest items */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |req, c, params| + Handler::new(move |req, c, params| // SAFETY: Due to the architecture of `Router`, // `params` has already `append`ed once before this code match <$param_type as PathParam>::parse(unsafe {params.assume_init_first().as_bytes()}) { @@ -297,7 +297,7 @@ const _: (/* single PathParam and FromRequest items */) = { async {res} }), } - )) + ) } } )*}; @@ -313,7 +313,7 @@ const _: (/* one PathParam and FromRequest items */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |req, c, params| + Handler::new(move |req, c, params| // SAFETY: Due to the architecture of `Router`, // `params` has already `append`ed once before this code match P1::parse(unsafe {params.assume_init_first().as_bytes()}) { @@ -332,7 +332,7 @@ const _: (/* one PathParam and FromRequest items */) = { async {res} }), } - )) + ) } } @@ -342,7 +342,7 @@ const _: (/* one PathParam and FromRequest items */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |req, c, params| + Handler::new(move |req, c, params| // SAFETY: Due to the architecture of `Router`, // `params` has already `append`ed once before this code match P1::parse(unsafe {params.assume_init_first().as_bytes()}) { @@ -367,7 +367,7 @@ const _: (/* one PathParam and FromRequest items */) = { async {res} }), } - )) + ) } } @@ -377,7 +377,7 @@ const _: (/* one PathParam and FromRequest items */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |req, c, params| + Handler::new(move |req, c, params| // SAFETY: Due to the architecture of `Router`, // `params` has already `append`ed once before this code match P1::parse(unsafe {params.assume_init_first().as_bytes()}) { @@ -408,7 +408,7 @@ const _: (/* one PathParam and FromRequest items */) = { async {res} }), } - )) + ) } } }; @@ -420,7 +420,7 @@ const _: (/* two PathParams and FromRequest items */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |req, c, params| { + Handler::new(move |req, c, params| { let (p1_range, p2_range) = params.assume_init_extract(); // SAFETY: Due to the architecture of `Router`, @@ -447,7 +447,7 @@ const _: (/* two PathParams and FromRequest items */) = { async {res} }), } - })) + }) } } @@ -457,7 +457,7 @@ const _: (/* two PathParams and FromRequest items */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |req, c, params| { + Handler::new(move |req, c, params| { let (p1_range, p2_range) = params.assume_init_extract(); // SAFETY: Due to the architecture of `Router`, @@ -490,7 +490,7 @@ const _: (/* two PathParams and FromRequest items */) = { async {res} }), } - })) + }) } } @@ -500,7 +500,7 @@ const _: (/* two PathParams and FromRequest items */) = { Fut: Future + Send + Sync + 'static, { fn into_handler(self) -> Handler { - Handler(Box::new(move |req, c, params| { + Handler::new(move |req, c, params| { let (p1_range, p2_range) = params.assume_init_extract(); // SAFETY: Due to the architecture of `Router`, @@ -539,7 +539,7 @@ const _: (/* two PathParams and FromRequest items */) = { async {res} }), } - })) + }) } } }; diff --git a/ohkami/src/layer3_fang_handler/handler/mod.rs b/ohkami/src/layer3_fang_handler/handler/mod.rs index 9f99b477..01a4cde1 100644 --- a/ohkami/src/layer3_fang_handler/handler/mod.rs +++ b/ohkami/src/layer3_fang_handler/handler/mod.rs @@ -1,7 +1,12 @@ -mod handlers; pub use handlers::{Handlers, ByAnother, Route}; +mod handlers; pub use handlers::{Handlers, ByAnother, Route}; mod into_handler; pub use into_handler::{IntoHandler}; -use std::{pin::Pin, future::Future}; +#[cfg(test)] use std::sync::Arc; + +use std::{ + pin::Pin, + future::Future, +}; use crate::{ Context, Request, layer0_lib::{List, Slice}, @@ -12,6 +17,7 @@ pub(crate) const PATH_PARAMS_LIMIT: usize = 2; pub(crate) type PathParams = List; +#[cfg(not(test))] pub struct Handler( pub(crate) Box Pin< @@ -22,3 +28,32 @@ pub struct Handler( > + Send + Sync + 'static > ); + +#[cfg(test)] +#[derive(Clone)] +pub struct Handler( + pub(crate) Arc Pin< + Box + + Send + 'static + > + > + Send + Sync + 'static + > +); + + +impl Handler { + fn new(proc: (impl + Fn(&mut Request, Context, PathParams) -> Pin< + Box + + Send + 'static + > + > + Send + Sync + 'static + ) + ) -> Self { + #[cfg(not(test))] {Self(Box::new(proc))} + #[cfg(test)] {Self(Arc::new(proc))} + } +} diff --git a/ohkami/src/layer4_router/trie.rs b/ohkami/src/layer4_router/trie.rs index 20ac30e5..c193f9db 100644 --- a/ohkami/src/layer4_router/trie.rs +++ b/ohkami/src/layer4_router/trie.rs @@ -16,6 +16,7 @@ const _: () = { /*===== defs =====*/ +#[cfg_attr(test, derive(Clone))] pub struct TrieRouter { pub(super/* for test */) GET: Node, pub(super/* for test */) PUT: Node, @@ -26,6 +27,7 @@ pub struct TrieRouter { pub(super) OPTIONSfangs: Vec, } +#[cfg_attr(test, derive(Clone))] pub(super/* for test */) struct Node { /// Why Option: root node doesn't have pattern pub(super/* for test */) pattern: Option, diff --git a/ohkami/src/layer6_testing/mod.rs b/ohkami/src/layer6_testing/mod.rs new file mode 100644 index 00000000..b1a0ecf6 --- /dev/null +++ b/ohkami/src/layer6_testing/mod.rs @@ -0,0 +1,34 @@ +use std::{pin::Pin, future::Future}; +use crate::{Response, Request}; + +#[cfg(test)] use crate::{Ohkami, Context}; + +pub trait Testing { + fn oneshot<'test>(&self, req: &'test mut Request) -> TestResponse<'test>; +} + +pub struct TestResponse<'test>( + Box + 'test> +); impl<'test> Future for TestResponse<'test> { + type Output = Response; + fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll { + unsafe {self.map_unchecked_mut(|tr| tr.0.as_mut())} + .poll(cx) + } +} + +#[cfg(test)] +impl Testing for Ohkami { + fn oneshot<'test>(&self, req: &'test mut Request) -> TestResponse<'test> { + let router = { + let mut router = self.routes.clone(); + for (methods, fang) in &self.fangs { + router = router.apply_fang(methods, fang.clone()) + } + router.into_radix() + }; + + let res = async move {router.handle(Context::new(), req).await}; + TestResponse(Box::new(res)) + } +} diff --git a/ohkami/src/lib.rs b/ohkami/src/lib.rs index 29026dd1..5a63beb3 100644 --- a/ohkami/src/lib.rs +++ b/ohkami/src/lib.rs @@ -2,6 +2,7 @@ /*===== crate features =====*/ + #[cfg(any( all(feature="rt_tokio", feature="rt_async-std") ))] compile_error!(" @@ -19,6 +20,7 @@ /*===== runtime dependency injection layer =====*/ + mod __rt__ { #[cfg(feature="rt_tokio")] pub(crate) use tokio::sync::Mutex; @@ -49,15 +51,18 @@ mod __rt__ { /*===== modules =====*/ + mod layer0_lib; mod layer1_req_res; mod layer2_context; mod layer3_fang_handler; mod layer4_router; mod layer5_ohkami; +mod layer6_testing; /*===== visibility managements =====*/ + pub use layer0_lib ::{Status, Method, ContentType}; pub use layer1_req_res ::{Request, Response, FromRequest}; pub use layer2_context ::{Context}; @@ -71,6 +76,7 @@ pub mod prelude { pub mod utils { pub use crate::layer1_req_res ::{File}; pub use crate::layer3_fang_handler::{builtin::*}; + pub use crate::layer6_testing ::{Testing}; pub use ohkami_macros ::{Query, Payload}; } @@ -86,6 +92,7 @@ pub mod __internal__ { /*===== usavility =====*/ + #[cfg(test)] #[allow(unused)] async fn __() { // fangs struct AppendHeader;