Skip to main content

xitca_web/app/
mod.rs

1mod object;
2mod router;
3
4use core::{
5    convert::Infallible,
6    fmt,
7    future::{Future, ready},
8    pin::Pin,
9};
10
11use std::error;
12
13use xitca_http::util::{
14    middleware::context::ContextBuilder,
15    service::router::{IntoObject, PathGen, RouteGen, RouteObject, TypedRoute},
16};
17
18use crate::{
19    body::{Body, Either, RequestBody, ResponseBody},
20    bytes::Bytes,
21    context::WebContext,
22    error::{Error, RouterError},
23    http::{WebRequest, WebResponse},
24    middleware::eraser::TypeEraser,
25    service::{EnclosedBuilder, EnclosedFnBuilder, MapBuilder, Service, ServiceExt, ready::ReadyService},
26};
27
28use self::{object::WebObject, router::AppRouter};
29
30/// composed application type with router, stateful context and default middlewares.
31pub struct App<R = (), CF = ()> {
32    router: R,
33    ctx_builder: CF,
34}
35
36type BoxFuture<C> = Pin<Box<dyn Future<Output = Result<C, Box<dyn fmt::Debug>>>>>;
37type CtxBuilder<C> = Box<dyn Fn() -> BoxFuture<C> + Send + Sync>;
38type DefaultWebObject<C> = WebObject<C, RequestBody, WebResponse, RouterError<Error>>;
39type DefaultAppRouter<C> = AppRouter<RouteObject<(), DefaultWebObject<C>, Infallible>>;
40
41// helper trait to poly between () and Box<dyn Fn()> as application state.
42pub trait IntoCtx {
43    type Ctx;
44
45    fn into_ctx(self) -> impl Fn() -> BoxFuture<Self::Ctx> + Send + Sync;
46}
47
48impl IntoCtx for () {
49    type Ctx = ();
50
51    fn into_ctx(self) -> impl Fn() -> BoxFuture<Self::Ctx> + Send + Sync {
52        || Box::pin(ready(Ok(())))
53    }
54}
55
56impl<C> IntoCtx for CtxBuilder<C> {
57    type Ctx = C;
58
59    fn into_ctx(self) -> impl Fn() -> BoxFuture<Self::Ctx> + Send + Sync {
60        self
61    }
62}
63
64/// type alias for concrete type of nested App.
65///
66/// # Example
67/// ```rust
68/// # use xitca_web::{handler::handler_service, App, NestApp, WebContext};
69/// // a function return an App instance.
70/// fn app() -> NestApp<usize> {
71///     App::new().at("/index", handler_service(|_: &WebContext<'_, usize>| async { "" }))
72/// }
73///
74/// // nest app would be registered with /v2 as prefix therefore "/v2/index" become accessible.
75/// App::new().at("/v2", app()).with_state(996usize);
76/// ```
77pub type NestApp<C> = App<DefaultAppRouter<C>>;
78
79impl App {
80    /// Construct a new application instance.
81    pub fn new<Obj>() -> App<AppRouter<Obj>> {
82        App {
83            router: AppRouter::new(),
84            ctx_builder: (),
85        }
86    }
87}
88
89impl<Obj, CF> App<AppRouter<Obj>, CF> {
90    /// insert routed service with given string literal as route path to application. services will be routed with following rules:
91    ///
92    /// # Static route
93    /// string literal matched against http request's uri path.
94    /// ```rust
95    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
96    /// # use xitca_unsafe_collection::futures::NowOrPanic;
97    /// # use xitca_web::{
98    /// #   handler::{handler_service, path::PathRef},
99    /// #   http::{Request, StatusCode},
100    /// #   route::get,
101    /// #   service::Service,
102    /// #   App
103    /// # };
104    /// // register string path to handler service.
105    /// let app = App::new().at("/users", get(handler_service(handler)));
106    ///
107    /// // handler function extract request's uri path.
108    /// async fn handler(PathRef(path): PathRef<'_>) -> StatusCode {
109    ///     assert_eq!(path, "/users");
110    ///     StatusCode::OK
111    /// }
112    ///
113    /// // boilerplate for starting application service in test. in real world this should be achieved
114    /// // through App::serve API
115    /// let app_service = app.finish().call(()).now_or_panic().unwrap();
116    ///
117    /// // get request with uri can be matched against registered route.
118    /// let req = Request::builder().uri("/users").body(Default::default())?;
119    ///
120    /// // execute application service where the request would match handler function
121    /// let res = app_service.call(req).now_or_panic()?;
122    /// assert_eq!(res.status(), StatusCode::OK);
123    ///
124    /// // http query is not included in the path matching.
125    /// let req = Request::builder().uri("/users?foo=bar").body(Default::default())?;
126    /// let res = app_service.call(req).now_or_panic()?;
127    /// assert_eq!(res.status(), StatusCode::OK);
128    ///
129    /// // any change on uri path would result in no match of route.
130    /// let req = Request::builder().uri("/users/").body(Default::default())?;
131    /// let res = app_service.call(req).now_or_panic()?;
132    /// assert_eq!(res.status(), StatusCode::NOT_FOUND);
133    ///
134    /// # Ok(())
135    /// # }
136    /// ```
137    ///
138    /// # Dynamic route
139    /// Along with static routes, the router also supports dynamic route segments. These can either be named or catch-all parameters:
140    ///
141    /// ## Named Parameters
142    /// Named parameters like `/{id}` match anything until the next `/` or the end of the path:
143    /// ```rust
144    /// # fn main() {
145    /// #   #[cfg(feature = "params")]
146    /// #   _main();
147    /// # }
148    /// #
149    /// # #[cfg(feature = "params")]
150    /// # fn _main() -> Result<(), Box<dyn std::error::Error>> {
151    /// # use xitca_unsafe_collection::futures::NowOrPanic;
152    /// # use xitca_web::{
153    /// #   handler::{handler_service, params::Params},
154    /// #   http::{Request, StatusCode},
155    /// #   route::get,
156    /// #   service::Service,
157    /// #   App
158    /// # };
159    /// // register named param pattern to handler service.
160    /// let app = App::new().at("/users/{id}", get(handler_service(handler)));
161    ///
162    /// // handler function try to extract a single key/value string pair from url params.
163    /// async fn handler(Params(val): Params<u32>) -> StatusCode {
164    ///     // the value matches the string literal and it's routing rule registered in App::at.
165    ///     assert_eq!(val, 996);
166    ///     StatusCode::OK
167    /// }
168    ///
169    /// // boilerplate for starting application service in test. in real world this should be achieved
170    /// // through App::serve API
171    /// let app_service = app.finish().call(()).now_or_panic().unwrap();
172    ///
173    /// // get request with uri can be matched against registered route.
174    /// let req = Request::builder().uri("/users/996").body(Default::default())?;
175    ///
176    /// // execute application service where the request would match handler function
177    /// let res = app_service.call(req).now_or_panic()?;
178    /// assert_eq!(res.status(), StatusCode::OK);
179    ///
180    /// // :x pattern only match till next /. and in following request's case it will not find a matching route.
181    /// let req = Request::builder().uri("/users/996/fool").body(Default::default())?;
182    /// let res = app_service.call(req).now_or_panic()?;
183    /// assert_eq!(res.status(), StatusCode::NOT_FOUND);
184    /// # Ok(())
185    /// # }
186    /// ```
187    ///
188    /// ## Catch-all Parameters
189    /// Catch-all parameters start with `*` and match everything after the `/`.
190    /// They must always be at the **end** of the route:
191    /// ```rust
192    /// # fn main() {
193    /// #   #[cfg(feature = "params")]
194    /// #   _main();
195    /// # }
196    /// #
197    /// # #[cfg(feature = "params")]
198    /// # fn _main() -> Result<(), Box<dyn std::error::Error>> {
199    /// # use xitca_unsafe_collection::futures::NowOrPanic;
200    /// # use xitca_web::{
201    /// #   handler::{handler_service, params::Params},
202    /// #   http::{Request, StatusCode},
203    /// #   route::get,
204    /// #   service::Service,
205    /// #   App
206    /// # };
207    /// // register named param pattern to handler service.
208    /// let app = App::new().at("/{*path}", get(handler_service(handler)));
209    ///
210    /// // handler function try to extract a single key/value string pair from url params.
211    /// async fn handler(Params(path): Params<String>) -> StatusCode {
212    ///     assert!(path.ends_with(".css"));
213    ///     StatusCode::OK
214    /// }
215    ///
216    /// // boilerplate for starting application service in test. in real world this should be achieved
217    /// // through App::serve API
218    /// let app_service = app.finish().call(()).now_or_panic().unwrap();
219    ///
220    /// // get request with uri can be matched against registered route.
221    /// let req = Request::builder().uri("/foo/bar.css").body(Default::default())?;
222    ///
223    /// // execute application service where the request would match handler function
224    /// let res = app_service.call(req).now_or_panic()?;
225    /// assert_eq!(res.status(), StatusCode::OK);
226    ///
227    /// // *x pattern match till the end of uri path. in following request's case it will match against handler
228    /// let req = Request::builder().uri("/foo/bar/baz.css").body(Default::default())?;
229    /// let res = app_service.call(req).now_or_panic()?;
230    /// assert_eq!(res.status(), StatusCode::OK);
231    /// # Ok(())
232    /// # }
233    /// ```
234    ///
235    /// ## Implicit catch-all parameters
236    /// Built in http services require catch-all params would implicitly utilize them to reduce user input.
237    /// ```rust
238    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
239    /// # use xitca_unsafe_collection::futures::NowOrPanic;
240    /// # use xitca_web::{
241    /// #   handler::{handler_service, path::PathRef},
242    /// #   http::{Request, StatusCode},
243    /// #   route::get,
244    /// #   service::Service,
245    /// #   App
246    /// # };
247    /// // when nesting App is used as a route service it will silently register it as a catch-call param.
248    /// let app = App::new().at("/users", App::new()
249    ///     .at("/", get(handler_service(handler)))
250    ///     .at("/996", get(handler_service(handler)))
251    /// );
252    ///
253    /// // handler function.
254    /// async fn handler() -> StatusCode {
255    ///     StatusCode::OK
256    /// }
257    ///
258    /// // boilerplate for starting application service in test. in real world this should be achieved
259    /// // through App::serve API
260    /// let app_service = app.finish().call(()).now_or_panic().unwrap();
261    ///
262    /// // get request with uri can be matched against registered route.
263    /// let req = Request::builder().uri("/users/").body(Default::default())?;
264    ///
265    /// // execute application service where the request would match handler function
266    /// let res = app_service.call(req).now_or_panic()?;
267    /// assert_eq!(res.status(), StatusCode::OK);
268    ///
269    /// let req = Request::builder().uri("/users/996").body(Default::default())?;
270    /// let res = app_service.call(req).now_or_panic()?;
271    /// assert_eq!(res.status(), StatusCode::OK);
272    /// # Ok(())
273    /// # }
274    /// ```
275    ///
276    /// ## Character literal escaping
277    /// The literal characters `{` and `}` may be included in a static route by escaping them with the same character.
278    /// For example, the `{` character is escaped with `{{`, and the `}` character is escaped with `}}`.
279    ///
280    /// ## Conflict Rules
281    /// Static and dynamic route segments are allowed to overlap. If they do, static segments will be given higher priority:
282    /// ```rust
283    /// # use xitca_web::{
284    /// #   handler::{html::Html, redirect::Redirect, handler_service},
285    /// #   route::get,
286    /// #   App
287    /// # };
288    /// let app = App::new()
289    ///     .at("/", Redirect::see_other("/index.html"))        // high priority
290    ///     .at("/index.html", Html("<h1>Hello,World!</h1>"))   // high priority
291    ///     .at("/{*path}", get(handler_service(handler)))        // low priority
292    ///     .serve();
293    ///
294    /// async fn handler() -> &'static str {
295    ///     "todo"
296    /// }
297    /// ```
298    /// Formally, a route consists of a list of segments separated by `/`, with an optional leading and trailing slash: `(/)<segment_1>/.../<segment_n>(/)`.
299    ///
300    /// Given set of routes, their overlapping segments may include, in order of priority:
301    ///
302    /// - Any number of static segments (`/a`, `/b`, ...).
303    /// - *One* of the following:
304    ///   - Any number of route parameters with a suffix (`/{x}a`, `/{x}b`, ...), prioritizing the longest suffix.
305    ///   - Any number of route parameters with a prefix (`/a{x}`, `/b{x}`, ...), prioritizing the longest prefix.
306    ///   - A single route parameter with both a prefix and a suffix (`/a{x}b`).
307    /// - *One* of the following;
308    ///   - A single standalone parameter (`/{x}`).
309    ///   - A single standalone catch-all parameter (`/{*rest}`). Note this only applies to the final route segment.
310    ///
311    /// Any other combination of route segments is considered ambiguous, and attempting to insert such a route will result in an error.
312    ///
313    /// The one exception to the above set of rules is that catch-all parameters are always considered to conflict with suffixed route parameters, i.e. that `/{*rest}`
314    /// and `/{x}suffix` are overlapping. This is due to an implementation detail of the routing tree that may be relaxed in the future.
315    pub fn at<F, C, B>(mut self, path: &'static str, builder: F) -> Self
316    where
317        F: RouteGen + Service + Send + Sync,
318        F::Response: for<'r> Service<WebContext<'r, C, B>>,
319        for<'r> WebContext<'r, C, B>: IntoObject<F::Route<F>, (), Object = Obj>,
320    {
321        self.router = self.router.insert(path, builder);
322        self
323    }
324
325    /// insert typed route service with given path to application.
326    pub fn at_typed<T, C>(mut self, typed: T) -> Self
327    where
328        T: TypedRoute<C, Route = Obj>,
329    {
330        self.router = self.router.insert_typed(typed);
331        self
332    }
333}
334
335impl<R, CF> App<R, CF> {
336    /// Construct App with a thread safe state that will be shared among all tasks and worker threads.
337    ///
338    /// State accessing is based on generic type approach where the State type and it's typed fields are generally
339    /// opaque to middleware and routing services of the application. In order to cast concrete type from generic
340    /// state type [std::borrow::Borrow] trait is utilized. See example below for explanation.
341    ///
342    /// # Example
343    /// ```rust
344    /// # use xitca_web::{
345    /// #   error::Error,
346    /// #   handler::{handler_service, state::{BorrowState, StateRef}, FromRequest},
347    /// #   service::Service,
348    /// #   App, WebContext
349    /// # };
350    /// // our typed state.
351    /// #[derive(Clone, Default)]
352    /// struct State {
353    ///     string: String,
354    ///     usize: usize
355    /// }
356    ///
357    /// // implement Borrow trait to enable borrowing &String type from State.
358    /// impl BorrowState<String> for State {
359    ///     fn borrow(&self) -> &String {
360    ///         &self.string
361    ///     }
362    /// }
363    ///
364    /// App::new()
365    ///     .with_state(State::default())// construct app with state type.
366    ///     .at("/", handler_service(index)) // a function service that have access to state.
367    ///     # .at("/nah", handler_service(|_: &WebContext<'_, State>| async { "used for infer type" }))
368    ///     .enclosed_fn(middleware_fn); // a function middleware that have access to state
369    ///
370    /// // the function service don't know what the real type of application state is.
371    /// // it only needs to know &String can be borrowed from it.
372    /// async fn index(_: StateRef<'_, String>) -> &'static str {
373    ///     ""
374    /// }
375    ///
376    /// // similar to function service. the middleware does not need to know the real type of C.
377    /// // it only needs to know it implement according trait.
378    /// async fn middleware_fn<S, C, Res>(service: &S, ctx: WebContext<'_, C>) -> Result<Res, Error>
379    /// where
380    ///     S: for<'r> Service<WebContext<'r, C>, Response = Res, Error = Error>,
381    ///     C: BorrowState<String> // annotate we want to borrow &String from generic C state type.
382    /// {
383    ///     // WebContext::state would return &C then we can call Borrow::borrow on it to get &String
384    ///     let _string = ctx.state().borrow();
385    ///     // or use extractor manually like in function service.
386    ///     let _string = StateRef::<'_, String>::from_request(&ctx).await?;
387    ///     service.call(ctx).await
388    /// }
389    /// ```
390    pub fn with_state<C>(self, state: C) -> App<R, CtxBuilder<C>>
391    where
392        C: Send + Sync + Clone + 'static,
393    {
394        self.with_async_state(move || ready(Ok::<_, Infallible>(state.clone())))
395    }
396
397    /// Construct App with async closure which it's output would be used as state.
398    /// async state is used to produce thread per core and/or non thread safe state copies.
399    /// The output state is not bound to `Send` and `Sync` auto traits.
400    pub fn with_async_state<CF1, Fut, C, E>(self, builder: CF1) -> App<R, CtxBuilder<C>>
401    where
402        CF1: Fn() -> Fut + Send + Sync + 'static,
403        Fut: Future<Output = Result<C, E>> + 'static,
404        E: fmt::Debug + 'static,
405    {
406        let ctx_builder = Box::new(move || {
407            let fut = builder();
408            Box::pin(async { fut.await.map_err(|e| Box::new(e) as Box<dyn fmt::Debug>) }) as _
409        });
410
411        App {
412            router: self.router,
413            ctx_builder,
414        }
415    }
416}
417
418impl<R, CF> App<R, CF>
419where
420    R: Service + Send + Sync,
421    R::Error: fmt::Debug + 'static,
422{
423    /// Enclose App with middleware type. Middleware must impl [Service] trait.
424    /// See [middleware](crate::middleware) for more.
425    pub fn enclosed<T>(self, transform: T) -> App<EnclosedBuilder<R, T>, CF>
426    where
427        T: Service<Result<R::Response, R::Error>>,
428    {
429        App {
430            router: self.router.enclosed(transform),
431            ctx_builder: self.ctx_builder,
432        }
433    }
434
435    /// Enclose App with function as middleware type.
436    /// See [middleware](crate::middleware) for more.
437    pub fn enclosed_fn<Req, T, O>(self, transform: T) -> App<EnclosedFnBuilder<R, T>, CF>
438    where
439        T: AsyncFn(&R::Response, Req) -> O + Clone,
440    {
441        App {
442            router: self.router.enclosed_fn(transform),
443            ctx_builder: self.ctx_builder,
444        }
445    }
446
447    /// Mutate `<<Self::Response as Service<Req>>::Future as Future>::Output` type with given
448    /// closure.
449    pub fn map<T, Res, ResMap>(self, mapper: T) -> App<MapBuilder<R, T>, CF>
450    where
451        T: Fn(Res) -> ResMap + Clone,
452        Self: Sized,
453    {
454        App {
455            router: self.router.map(mapper),
456            ctx_builder: self.ctx_builder,
457        }
458    }
459}
460
461impl<R, CF> App<R, CF>
462where
463    R: Service + Send + Sync,
464    R::Error: fmt::Debug + 'static,
465{
466    /// Finish App build. No other App method can be called afterwards.
467    pub fn finish<C, ResB, SE>(
468        self,
469    ) -> impl Service<
470        Response = impl ReadyService + Service<WebRequest, Response = WebResponse<EitherResBody<ResB>>, Error = Infallible>,
471        Error = impl fmt::Debug,
472    >
473    where
474        R::Response: ReadyService + for<'r> Service<WebContext<'r, C>, Response = WebResponse<ResB>, Error = SE>,
475        SE: for<'r> Service<WebContext<'r, C>, Response = WebResponse, Error = Infallible>,
476        CF: IntoCtx<Ctx = C>,
477        C: 'static,
478    {
479        let App { ctx_builder, router } = self;
480        router
481            .enclosed(crate::middleware::WebContext)
482            .enclosed(ContextBuilder::new(ctx_builder.into_ctx()))
483    }
484
485    /// Finish App build. No other App method can be called afterwards.
486    pub fn finish_boxed<C, ResB, SE>(
487        self,
488    ) -> AppObject<impl ReadyService + Service<WebRequest, Response = WebResponse, Error = Infallible>>
489    where
490        R: 'static,
491        R::Response:
492            ReadyService + for<'r> Service<WebContext<'r, C>, Response = WebResponse<ResB>, Error = SE> + 'static,
493        SE: for<'r> Service<WebContext<'r, C>, Response = WebResponse, Error = Infallible> + 'static,
494        ResB: Body<Data = Bytes> + 'static,
495        ResB::Error: error::Error + Send + Sync + 'static,
496        CF: IntoCtx<Ctx = C> + 'static,
497        C: 'static,
498    {
499        struct BoxApp<S>(S);
500
501        impl<S, Arg> Service<Arg> for BoxApp<S>
502        where
503            S: Service<Arg>,
504            S::Error: fmt::Debug + 'static,
505        {
506            type Response = S::Response;
507            type Error = Box<dyn fmt::Debug>;
508
509            async fn call(&self, arg: Arg) -> Result<Self::Response, Self::Error> {
510                self.0.call(arg).await.map_err(|e| Box::new(e) as _)
511            }
512        }
513
514        Box::new(BoxApp(self.finish().enclosed(TypeEraser::response_body())))
515    }
516
517    #[cfg(feature = "__server")]
518    /// Finish App build and serve is with [HttpServer]. No other App method can be called afterwards.
519    ///
520    /// [HttpServer]: crate::server::HttpServer
521    pub fn serve<C, ResB, SE>(
522        self,
523    ) -> crate::server::HttpServer<
524        impl Service<
525            Response = impl ReadyService
526                       + Service<WebRequest, Response = WebResponse<EitherResBody<ResB>>, Error = Infallible>,
527            Error = impl fmt::Debug,
528        >,
529    >
530    where
531        R: 'static,
532        R::Response: ReadyService + for<'r> Service<WebContext<'r, C>, Response = WebResponse<ResB>, Error = SE>,
533        SE: for<'r> Service<WebContext<'r, C>, Response = WebResponse, Error = Infallible> + 'static,
534        ResB: 'static,
535        CF: IntoCtx<Ctx = C> + 'static,
536        C: 'static,
537    {
538        crate::server::HttpServer::serve(self.finish())
539    }
540}
541
542type EitherResBody<B> = Either<B, ResponseBody>;
543
544impl<R, F> PathGen for App<R, F>
545where
546    R: PathGen,
547{
548    fn path_gen(&mut self, prefix: &str) -> String {
549        self.router.path_gen(prefix)
550    }
551}
552
553impl<R, F> RouteGen for App<R, F>
554where
555    R: RouteGen,
556{
557    type Route<R1> = R::Route<R1>;
558
559    fn route_gen<R1>(route: R1) -> Self::Route<R1> {
560        R::route_gen(route)
561    }
562}
563
564impl<R, Arg> Service<Arg> for App<R>
565where
566    R: Service<Arg>,
567{
568    type Response = R::Response;
569    type Error = R::Error;
570
571    async fn call(&self, req: Arg) -> Result<Self::Response, Self::Error> {
572        self.router.call(req).await
573    }
574}
575
576impl<R, Arg, C> Service<Arg> for App<R, CtxBuilder<C>>
577where
578    R: Service<Arg>,
579{
580    type Response = NestAppService<C, R::Response>;
581    type Error = R::Error;
582
583    async fn call(&self, req: Arg) -> Result<Self::Response, Self::Error> {
584        let ctx = (self.ctx_builder)()
585            .await
586            .expect("fallible nested application state builder is not supported yet");
587        let service = self.router.call(req).await?;
588
589        Ok(NestAppService { ctx, service })
590    }
591}
592
593pub struct NestAppService<C, S> {
594    ctx: C,
595    service: S,
596}
597
598impl<'r, C1, C, S, SE> Service<WebContext<'r, C1>> for NestAppService<C, S>
599where
600    S: for<'r1> Service<WebContext<'r1, C>, Response = WebResponse, Error = SE>,
601    SE: Into<Error>,
602{
603    type Response = WebResponse;
604    type Error = Error;
605
606    async fn call(&self, ctx: WebContext<'r, C1>) -> Result<Self::Response, Self::Error> {
607        let WebContext { req, body, .. } = ctx;
608
609        self.service
610            .call(WebContext {
611                req,
612                body,
613                ctx: &self.ctx,
614            })
615            .await
616            .map_err(Into::into)
617    }
618}
619
620/// object safe [App] instance. used for case where naming [App]'s type is needed.
621pub type AppObject<S> =
622    Box<dyn xitca_service::object::ServiceObject<(), Response = S, Error = Box<dyn fmt::Debug>> + Send + Sync>;
623
624#[cfg(test)]
625mod test {
626    use xitca_unsafe_collection::futures::NowOrPanic;
627
628    use crate::{
629        handler::{
630            extension::ExtensionRef, extension::ExtensionsRef, handler_service, path::PathRef, state::StateRef,
631            uri::UriRef,
632        },
633        http::{Method, const_header_value::TEXT_UTF8, header::CONTENT_TYPE, request},
634        middleware::UncheckedReady,
635        route::get,
636    };
637
638    use super::*;
639
640    async fn middleware<S, C, B, Res, Err>(s: &S, req: WebContext<'_, C, B>) -> Result<Res, Err>
641    where
642        S: for<'r> Service<WebContext<'r, C, B>, Response = Res, Error = Err>,
643    {
644        s.call(req).await
645    }
646
647    #[allow(clippy::too_many_arguments)]
648    async fn handler(
649        _res: Result<UriRef<'_>, Error>,
650        _opt: Option<UriRef<'_>>,
651        _req: &WebRequest<()>,
652        StateRef(state): StateRef<'_, String>,
653        PathRef(path): PathRef<'_>,
654        UriRef(_): UriRef<'_>,
655        ExtensionRef(_): ExtensionRef<'_, Foo>,
656        ExtensionsRef(_): ExtensionsRef<'_>,
657        req: &WebContext<'_, String>,
658    ) -> String {
659        assert_eq!("state", state);
660        assert_eq!(state, req.state());
661        assert_eq!("/", path);
662        assert_eq!(path, req.req().uri().path());
663        state.to_string()
664    }
665
666    // Handler with no state extractor
667    async fn stateless_handler(_: PathRef<'_>) -> String {
668        String::from("debug")
669    }
670
671    #[derive(Clone)]
672    struct Middleware;
673
674    impl<S, E> Service<Result<S, E>> for Middleware {
675        type Response = MiddlewareService<S>;
676        type Error = E;
677
678        async fn call(&self, res: Result<S, E>) -> Result<Self::Response, Self::Error> {
679            res.map(MiddlewareService)
680        }
681    }
682
683    struct MiddlewareService<S>(S);
684
685    impl<'r, S, C, B, Res, Err> Service<WebContext<'r, C, B>> for MiddlewareService<S>
686    where
687        S: for<'r2> Service<WebContext<'r2, C, B>, Response = Res, Error = Err>,
688        C: 'r,
689        B: 'r,
690    {
691        type Response = Res;
692        type Error = Err;
693
694        async fn call(&self, mut req: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
695            self.0.call(req.reborrow()).await
696        }
697    }
698
699    #[allow(clippy::borrow_interior_mutable_const)]
700    #[test]
701    fn test_app() {
702        let state = String::from("state");
703
704        let service = App::new()
705            .at("/", get(handler_service(handler)))
706            .with_state(state)
707            .at(
708                "/stateless",
709                get(handler_service(stateless_handler)).head(handler_service(stateless_handler)),
710            )
711            .enclosed_fn(middleware)
712            .enclosed(Middleware)
713            .enclosed(UncheckedReady)
714            .finish()
715            .call(())
716            .now_or_panic()
717            .ok()
718            .unwrap();
719
720        let mut req = WebRequest::default();
721        req.extensions_mut().insert(Foo);
722
723        let res = service.call(req).now_or_panic().unwrap();
724
725        assert_eq!(res.status().as_u16(), 200);
726
727        assert_eq!(res.headers().get(CONTENT_TYPE).unwrap(), TEXT_UTF8);
728
729        let req = request::Builder::default()
730            .uri("/abc")
731            .body(Default::default())
732            .unwrap();
733
734        let res = service.call(req).now_or_panic().unwrap();
735
736        assert_eq!(res.status().as_u16(), 404);
737
738        let req = request::Builder::default()
739            .method(Method::POST)
740            .body(Default::default())
741            .unwrap();
742
743        let res = service.call(req).now_or_panic().unwrap();
744
745        assert_eq!(res.status().as_u16(), 405);
746    }
747
748    #[derive(Clone)]
749    struct Foo;
750
751    #[test]
752    fn app_nest_router() {
753        async fn handler(StateRef(state): StateRef<'_, String>, PathRef(path): PathRef<'_>) -> String {
754            assert_eq!("state", state);
755            assert_eq!("/scope/nest", path);
756            state.to_string()
757        }
758
759        fn app() -> NestApp<String> {
760            App::new().at("/nest", get(handler_service(handler)))
761        }
762
763        let state = String::from("state");
764        let service = App::new()
765            .with_state(state.clone())
766            .at("/root", get(handler_service(handler)))
767            .at("/scope", app())
768            .finish()
769            .call(())
770            .now_or_panic()
771            .ok()
772            .unwrap();
773
774        let req = request::Builder::default()
775            .uri("/scope/nest")
776            .body(Default::default())
777            .unwrap();
778
779        let res = service.call(req).now_or_panic().unwrap();
780
781        assert_eq!(res.status().as_u16(), 200);
782
783        async fn handler2(StateRef(state): StateRef<'_, usize>, PathRef(path): PathRef<'_>) -> String {
784            assert_eq!(996, *state);
785            assert_eq!("/scope/nest", path);
786            state.to_string()
787        }
788
789        let service = App::new()
790            .with_state(state)
791            .at("/root", get(handler_service(handler)))
792            .at(
793                "/scope",
794                App::new()
795                    .with_state(996usize)
796                    .at("/nest", get(handler_service(handler2))),
797            )
798            .finish()
799            .call(())
800            .now_or_panic()
801            .ok()
802            .unwrap();
803
804        let req = request::Builder::default()
805            .uri("/scope/nest")
806            .body(Default::default())
807            .unwrap();
808
809        let res = service.call(req).now_or_panic().unwrap();
810
811        assert_eq!(res.status().as_u16(), 200);
812    }
813}