diff --git a/http/src/util/mod.rs b/http/src/util/mod.rs index ee6522708..7722b923d 100644 --- a/http/src/util/mod.rs +++ b/http/src/util/mod.rs @@ -9,3 +9,106 @@ pub(crate) mod futures; #[cfg(feature = "http1")] pub(crate) mod hint; pub(crate) mod keep_alive; + +pub mod cursed { + use std::{boxed::Box, marker::PhantomData}; + + use xitca_service::{ + fn_build, + object::{ + helpers::{ServiceObject, Wrapper}, + ObjectConstructor, + }, + BuildService, BuildServiceExt, Service, + }; + + /// A trait that serves a type-level function to convert a type from one lifetime to another. + /// + /// For example: `for<'a> <&'static str as Cursed>::Type<'a> = &'a str` + pub trait Cursed { + /// A projection that takes `Self` as type and a given lifetime `'a` + type Type<'a>: 'a; + } + + /// Same as [Cursed] but works in value-level, rather than type-level. + /// + /// Cannot be merged with [Cursed], because of _rust issue number_. + pub trait CursedMap: Cursed { + /// A map that takes `Self` as a value and given lifetime, `'a`, + /// and outputs a value of type `Self::Type<'a>`. + fn map<'a>(self) -> Self::Type<'a> + where + Self: 'a; + } + + impl Cursed for http::Request { + type Type<'a> = Self; + } + impl CursedMap for http::Request { + fn map<'a>(self) -> Self::Type<'a> { + self + } + } + + impl Cursed for crate::request::Request { + type Type<'a> = Self; + } + impl CursedMap for crate::request::Request { + fn map<'a>(self) -> Self::Type<'a> { + self + } + } + + impl Cursed for String { + type Type<'a> = Self; + } + impl CursedMap for String { + fn map<'a>(self) -> Self::Type<'a> { + self + } + } + + /// An [object constructor](ObjectConstructor) for service with [cursed](Cursed) + /// request types. + pub struct CursedObjectConstructor(PhantomData); + + pub type CursedFactoryObject = + impl BuildService>; + + pub type CursedServiceObject = + impl for<'r> Service, Response = Res, Error = Err>; + + impl ObjectConstructor for CursedObjectConstructor + where + I: BuildService + 'static, + Svc: for<'r> Service, Response = Res, Error = Err> + 'static, + + Req: Cursed, + + // This bound is necessary to guide type inference to infer `Req`. + // Because we can't provide a static guarantee to exclude + // such rogue implementations: + // ``` + // impl Cursed for &'_ str { + // type Type<'a> = &'a u8; + // } + // ``` + Svc: Service, + { + type Object = CursedFactoryObject; + + fn into_object(inner: I) -> Self::Object { + let factory = fn_build(move |_arg: ()| { + let fut = inner.build(()); + async move { + let boxed_service = Box::new(Wrapper(fut.await?)) + as Box ServiceObject, Response = _, Error = _>>; + Ok(Wrapper(boxed_service)) + } + }) + .boxed_future(); + + Box::new(factory) as Box> + } + } +} diff --git a/http/src/util/service/context.rs b/http/src/util/service/context.rs index 9a0a0e36f..4f5d0099d 100644 --- a/http/src/util/service/context.rs +++ b/http/src/util/service/context.rs @@ -3,6 +3,7 @@ use std::{future::Future, marker::PhantomData}; use xitca_service::{pipeline::PipelineE, ready::ReadyService, BuildService, Service}; use crate::request::{BorrowReq, BorrowReqMut}; +use crate::util::cursed::{Cursed, CursedMap}; /// ServiceFactory type for constructing compile time checked stateful service. /// @@ -99,6 +100,29 @@ impl<'a, Req, C> Context<'a, Req, C> { } } +impl Cursed for Context<'_, Req, C> +where + Req: Cursed, + C: 'static, +{ + type Type<'a> = Context<'a, Req::Type<'a>, C>; +} +impl CursedMap for Context<'_, Req, C> +where + Req: CursedMap, + C: 'static, +{ + fn map<'a>(self) -> Self::Type<'a> + where + Self: 'a, + { + Context { + req: self.req.map(), + state: self.state, + } + } +} + impl BorrowReq for Context<'_, Req, C> where Req: BorrowReq, @@ -151,7 +175,8 @@ pub struct ContextService { impl Service for ContextService where - S: for<'c> Service, Response = Res, Error = Err>, + S: for<'c> Service, C>, Response = Res, Error = Err>, + Req: CursedMap, { type Response = Res; type Error = Err; @@ -161,7 +186,7 @@ where async move { self.service .call(Context { - req, + req: req.map(), state: &self.state, }) .await @@ -169,11 +194,12 @@ where } } -impl ReadyService for ContextService +impl ReadyService for ContextService where - S: for<'c> ReadyService, Response = Res, Error = Err, Ready = R>, + Self: Service, + S: ReadyService, { - type Ready = R; + type Ready = S::Ready; type ReadyFuture<'f> = impl Future where Self: 'f; #[inline] @@ -182,51 +208,6 @@ where } } -pub mod object { - use super::*; - - use std::{boxed::Box, marker::PhantomData}; - - use xitca_service::{ - fn_build, - object::{ - helpers::{ServiceObject, Wrapper}, - ObjectConstructor, - }, - BuildService, BuildServiceExt, Service, - }; - - pub struct ContextObjectConstructor(PhantomData<(Req, C)>); - - pub type ContextFactoryObject = - impl BuildService>; - - pub type ContextServiceObject = - impl for<'c> Service, Response = Res, Error = Err>; - - impl ObjectConstructor for ContextObjectConstructor - where - I: BuildService + 'static, - Svc: for<'c> Service, Response = Res, Error = Err> + 'static, - { - type Object = ContextFactoryObject; - - fn into_object(inner: I) -> Self::Object { - let factory = fn_build(move |_arg: ()| { - let fut = inner.build(()); - async move { - let boxed_service = Box::new(Wrapper(fut.await?)) - as Box ServiceObject, Response = _, Error = _>>; - Ok(Wrapper(boxed_service)) - } - }) - .boxed_future(); - - Box::new(factory) as Box> - } - } -} - #[cfg(test)] mod test { use std::convert::Infallible; @@ -289,7 +270,8 @@ mod test { service.call(req).await } - let router = GenericRouter::with_custom_object::>() + let router = GenericRouter::with_cursed_object() + //let router = GenericRouter::with_custom_object::>() .insert("/", get(fn_service(handler))) .enclosed_fn(enclosed); @@ -306,4 +288,50 @@ mod test { assert_eq!(res.status().as_u16(), 200); } + + #[tokio::test] + async fn nested_lifetime_request() { + struct Req<'a> { + _r: &'a str, + } + + impl Cursed for Req<'_> { + type Type<'a> = Req<'a>; + } + impl CursedMap for Req<'_> { + fn map<'a>(self) -> Self::Type<'a> + where + Self: 'a, + { + self + } + } + + impl BorrowReq for Req<'_> { + fn borrow(&self) -> &http::Uri { + Box::leak(Box::new(http::Uri::from_static("http://host.com/"))) + } + } + + async fn handler(req: Context<'_, Req<'_>, String>) -> Result, Infallible> { + let (_, state) = req.into_parts(); + assert_eq!(state, "string_state"); + Ok(Response::new(())) + } + + let router = GenericRouter::with_cursed_object().insert("/", fn_service(handler)); + + let service = ContextBuilder::new(|| async { Ok::<_, Infallible>(String::from("string_state")) }) + .service(router) + .build(()) + .await + .ok() + .unwrap(); + + let r = String::new(); + + let res = service.call(Req { _r: r.as_str() }).await.unwrap(); + + assert_eq!(res.status().as_u16(), 200); + } } diff --git a/http/src/util/service/router.rs b/http/src/util/service/router.rs index cfbfa7c5c..a91bdd799 100644 --- a/http/src/util/service/router.rs +++ b/http/src/util/service/router.rs @@ -8,7 +8,11 @@ use xitca_service::{ BuildService, Service, }; -use crate::{http, request::BorrowReq}; +use crate::{ + http, + request::BorrowReq, + util::cursed::{Cursed, CursedObjectConstructor}, +}; /// A [GenericRouter] specialized with [DefaultObjectConstructor] pub type Router = @@ -38,6 +42,11 @@ impl GenericRouter<(), SF> { GenericRouter::new() } + /// Creates a new router with a [cursed object constructor](CursedObjectConstructor). + pub fn with_cursed_object() -> GenericRouter, SF> { + GenericRouter::new() + } + /// Creates a new router with a custom [object constructor](ObjectConstructor). pub fn with_custom_object() -> GenericRouter { GenericRouter::new() diff --git a/web/src/app/mod.rs b/web/src/app/mod.rs index 56406ea77..a4236bdb2 100644 --- a/web/src/app/mod.rs +++ b/web/src/app/mod.rs @@ -265,7 +265,7 @@ mod test { let mut req = Request::default(); req.extensions_mut().insert(Foo); - let res = service.call(req).await.unwrap(); + let res = Service::>::call(&service, req).await.unwrap(); assert_eq!(res.status().as_u16(), 200); assert_eq!(res.headers().get(CONTENT_TYPE).unwrap(), TEXT_UTF8); @@ -273,14 +273,14 @@ mod test { let mut req = Request::default(); *req.uri_mut() = Uri::from_static("/abc"); - let res = service.call(req).await.unwrap(); + let res = Service::>::call(&service, req).await.unwrap(); assert_eq!(res.status().as_u16(), 404); let mut req = Request::default(); *req.method_mut() = Method::POST; - let res = service.call(req).await.unwrap(); + let res = Service::>::call(&service, req).await.unwrap(); assert_eq!(res.status().as_u16(), 405); }