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}