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