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: &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 /// merget two App instances into one.
335 ///
336 /// input App's state would be discarded in progress and Self's state would be preserved.
337 ///
338 /// only App instances without any middlewares enclosed can be merged.
339 ///
340 /// # Example
341 /// ```rust
342 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
343 /// # use xitca_unsafe_collection::futures::NowOrPanic;
344 /// # use xitca_web::{
345 /// # handler::{handler_service, state::StateRef},
346 /// # http::{Request, StatusCode},
347 /// # route::get,
348 /// # service::Service,
349 /// # App
350 /// # };
351 /// // define routes in separate App instances.
352 /// let api_v1 = App::new()
353 /// .at("/foo", get(handler_service(handler)))
354 /// .at("/bar", get(handler_service(handler)));
355 ///
356 /// let api_v2 = App::new()
357 /// .at("/baz", get(handler_service(handler)));
358 ///
359 /// // merge them into one App and attach state.
360 /// // note: any state set on api_v1 or api_v2 via with_state would be discarded;
361 /// // only the state on the final merged App is used.
362 /// let app = App::new()
363 /// .merge(api_v1)
364 /// .merge(api_v2)
365 /// .with_state(996usize);
366 ///
367 /// async fn handler(StateRef(state): StateRef<'_, usize>) -> StatusCode {
368 /// assert_eq!(996, *state);
369 /// StatusCode::OK
370 /// }
371 ///
372 /// let service = app.finish().call(()).now_or_panic().unwrap();
373 ///
374 /// // all merged routes are accessible.
375 /// let req = Request::builder().uri("/foo").body(Default::default())?;
376 /// let res = service.call(req).now_or_panic()?;
377 /// assert_eq!(res.status(), StatusCode::OK);
378 ///
379 /// let req = Request::builder().uri("/baz").body(Default::default())?;
380 /// let res = service.call(req).now_or_panic()?;
381 /// assert_eq!(res.status(), StatusCode::OK);
382 /// # Ok(())
383 /// # }
384 /// ```
385 ///
386 /// Middlewares enclosed on the input App change its type and prevent merging.
387 /// Only bare Apps (no `.enclosed()` / `.enclosed_fn()`) can be passed to `merge`:
388 /// ```compile_fail
389 /// # use xitca_web::{
390 /// # handler::handler_service,
391 /// # http::StatusCode,
392 /// # route::get,
393 /// # App, WebContext
394 /// # };
395 /// # async fn mw<S, C, B>(s: &S, req: WebContext<'_, C, B>) {}
396 /// let other = App::new()
397 /// .at("/foo", get(handler_service(|| async { StatusCode::OK })))
398 /// .enclosed_fn(mw); // adding middleware changes the type
399 ///
400 /// // this will not compile because `other` is no longer `App<AppRouter<_>, _>`.
401 /// App::new().merge(other);
402 /// ```
403 pub fn merge<CF2>(mut self, other: App<AppRouter<Obj>, CF2>) -> Self {
404 self.router = self.router.merge(other.router);
405 self
406 }
407}
408
409impl<R, CF> App<R, CF> {
410 /// Construct App with a thread safe state that will be shared among all tasks and worker threads.
411 ///
412 /// State accessing is based on generic type approach where the State type and it's typed fields are generally
413 /// opaque to middleware and routing services of the application. In order to cast concrete type from generic
414 /// state type [std::borrow::Borrow] trait is utilized. See example below for explanation.
415 ///
416 /// # Example
417 /// ```rust
418 /// # use xitca_web::{
419 /// # error::Error,
420 /// # handler::{handler_service, state::{BorrowState, StateRef}, FromRequest},
421 /// # service::Service,
422 /// # App, WebContext
423 /// # };
424 /// // our typed state.
425 /// #[derive(Clone, Default)]
426 /// struct State {
427 /// string: String,
428 /// usize: usize
429 /// }
430 ///
431 /// // implement Borrow trait to enable borrowing &String type from State.
432 /// impl BorrowState<String> for State {
433 /// fn borrow(&self) -> &String {
434 /// &self.string
435 /// }
436 /// }
437 ///
438 /// App::new()
439 /// .with_state(State::default())// construct app with state type.
440 /// .at("/", handler_service(index)) // a function service that have access to state.
441 /// # .at("/nah", handler_service(|_: &WebContext<'_, State>| async { "used for infer type" }))
442 /// .enclosed_fn(middleware_fn); // a function middleware that have access to state
443 ///
444 /// // the function service don't know what the real type of application state is.
445 /// // it only needs to know &String can be borrowed from it.
446 /// async fn index(_: StateRef<'_, String>) -> &'static str {
447 /// ""
448 /// }
449 ///
450 /// // similar to function service. the middleware does not need to know the real type of C.
451 /// // it only needs to know it implement according trait.
452 /// async fn middleware_fn<S, C, Res>(service: &S, ctx: WebContext<'_, C>) -> Result<Res, Error>
453 /// where
454 /// S: for<'r> Service<WebContext<'r, C>, Response = Res, Error = Error>,
455 /// C: BorrowState<String> // annotate we want to borrow &String from generic C state type.
456 /// {
457 /// // WebContext::state would return &C then we can call Borrow::borrow on it to get &String
458 /// let _string = ctx.state().borrow();
459 /// // or use extractor manually like in function service.
460 /// let _string = StateRef::<'_, String>::from_request(&ctx).await?;
461 /// service.call(ctx).await
462 /// }
463 /// ```
464 pub fn with_state<C>(self, state: C) -> App<R, CtxBuilder<C>>
465 where
466 C: Send + Sync + Clone + 'static,
467 {
468 self.with_async_state(move || ready(Ok::<_, Infallible>(state.clone())))
469 }
470
471 /// Construct App with async closure which it's output would be used as state.
472 /// async state is used to produce thread per core and/or non thread safe state copies.
473 /// The output state is not bound to `Send` and `Sync` auto traits.
474 pub fn with_async_state<CF1, Fut, C, E>(self, builder: CF1) -> App<R, CtxBuilder<C>>
475 where
476 CF1: Fn() -> Fut + Send + Sync + 'static,
477 Fut: Future<Output = Result<C, E>> + 'static,
478 E: fmt::Debug + 'static,
479 {
480 let ctx_builder = Box::new(move || {
481 let fut = builder();
482 Box::pin(async { fut.await.map_err(|e| Box::new(e) as Box<dyn fmt::Debug>) }) as _
483 });
484
485 App {
486 router: self.router,
487 ctx_builder,
488 }
489 }
490}
491
492impl<R, CF> App<R, CF>
493where
494 R: Service + Send + Sync,
495 R::Error: fmt::Debug + 'static,
496{
497 /// Enclose App with middleware type. Middleware must impl [Service] trait.
498 /// See [middleware](crate::middleware) for more.
499 pub fn enclosed<T>(self, transform: T) -> App<EnclosedBuilder<R, T>, CF>
500 where
501 T: Service<Result<R::Response, R::Error>>,
502 {
503 App {
504 router: self.router.enclosed(transform),
505 ctx_builder: self.ctx_builder,
506 }
507 }
508
509 /// Enclose App with function as middleware type.
510 /// See [middleware](crate::middleware) for more.
511 pub fn enclosed_fn<Req, T, O>(self, transform: T) -> App<EnclosedFnBuilder<R, T>, CF>
512 where
513 T: AsyncFn(&R::Response, Req) -> O + Clone,
514 {
515 App {
516 router: self.router.enclosed_fn(transform),
517 ctx_builder: self.ctx_builder,
518 }
519 }
520
521 /// Mutate `<<Self::Response as Service<Req>>::Future as Future>::Output` type with given
522 /// closure.
523 pub fn map<T, Res, ResMap>(self, mapper: T) -> App<MapBuilder<R, T>, CF>
524 where
525 T: Fn(Res) -> ResMap + Clone,
526 Self: Sized,
527 {
528 App {
529 router: self.router.map(mapper),
530 ctx_builder: self.ctx_builder,
531 }
532 }
533}
534
535impl<R, CF> App<R, CF>
536where
537 R: Service + Send + Sync,
538 R::Error: fmt::Debug + 'static,
539{
540 /// Finish App build. No other App method can be called afterwards.
541 pub fn finish<C, ResB, SE>(
542 self,
543 ) -> impl Service<
544 Response = impl ReadyService + Service<WebRequest, Response = WebResponse<EitherResBody<ResB>>, Error = Infallible>,
545 Error = impl fmt::Debug,
546 >
547 where
548 R::Response: ReadyService + for<'r> Service<WebContext<'r, C>, Response = WebResponse<ResB>, Error = SE>,
549 SE: for<'r> Service<WebContext<'r, C>, Response = WebResponse, Error = Infallible>,
550 CF: IntoCtx<Ctx = C>,
551 C: 'static,
552 {
553 let App { ctx_builder, router } = self;
554 router
555 .enclosed(crate::middleware::WebContext)
556 .enclosed(ContextBuilder::new(ctx_builder.into_ctx()))
557 }
558
559 /// Finish App build. No other App method can be called afterwards.
560 pub fn finish_boxed<C, ResB, SE>(
561 self,
562 ) -> AppObject<impl ReadyService + Service<WebRequest, Response = WebResponse, Error = Infallible>>
563 where
564 R: 'static,
565 R::Response:
566 ReadyService + for<'r> Service<WebContext<'r, C>, Response = WebResponse<ResB>, Error = SE> + 'static,
567 SE: for<'r> Service<WebContext<'r, C>, Response = WebResponse, Error = Infallible> + 'static,
568 ResB: Body<Data = Bytes> + 'static,
569 ResB::Error: error::Error + Send + Sync + 'static,
570 CF: IntoCtx<Ctx = C> + 'static,
571 C: 'static,
572 {
573 struct BoxApp<S>(S);
574
575 impl<S, Arg> Service<Arg> for BoxApp<S>
576 where
577 S: Service<Arg>,
578 S::Error: fmt::Debug + 'static,
579 {
580 type Response = S::Response;
581 type Error = Box<dyn fmt::Debug>;
582
583 async fn call(&self, arg: Arg) -> Result<Self::Response, Self::Error> {
584 self.0.call(arg).await.map_err(|e| Box::new(e) as _)
585 }
586 }
587
588 Box::new(BoxApp(self.finish().enclosed(TypeEraser::response_body())))
589 }
590
591 #[cfg(feature = "__server")]
592 /// Finish App build and serve is with [HttpServer]. No other App method can be called afterwards.
593 ///
594 /// [HttpServer]: crate::server::HttpServer
595 pub fn serve<C, ResB, SE>(
596 self,
597 ) -> crate::server::HttpServer<
598 impl Service<
599 Response = impl ReadyService
600 + Service<WebRequest, Response = WebResponse<EitherResBody<ResB>>, Error = Infallible>,
601 Error = impl fmt::Debug,
602 >,
603 >
604 where
605 R: 'static,
606 R::Response: ReadyService + for<'r> Service<WebContext<'r, C>, Response = WebResponse<ResB>, Error = SE>,
607 SE: for<'r> Service<WebContext<'r, C>, Response = WebResponse, Error = Infallible> + 'static,
608 ResB: 'static,
609 CF: IntoCtx<Ctx = C> + 'static,
610 C: 'static,
611 {
612 crate::server::HttpServer::serve(self.finish())
613 }
614}
615
616type EitherResBody<B> = Either<B, ResponseBody>;
617
618impl<R, F> PathGen for App<R, F>
619where
620 R: PathGen,
621{
622 fn path_gen(&mut self, prefix: &str) -> String {
623 self.router.path_gen(prefix)
624 }
625}
626
627impl<R, F> RouteGen for App<R, F>
628where
629 R: RouteGen,
630{
631 type Route<R1> = R::Route<R1>;
632
633 fn route_gen<R1>(route: R1) -> Self::Route<R1> {
634 R::route_gen(route)
635 }
636}
637
638impl<R, Arg> Service<Arg> for App<R>
639where
640 R: Service<Arg>,
641{
642 type Response = R::Response;
643 type Error = R::Error;
644
645 async fn call(&self, req: Arg) -> Result<Self::Response, Self::Error> {
646 self.router.call(req).await
647 }
648}
649
650impl<R, Arg, C> Service<Arg> for App<R, CtxBuilder<C>>
651where
652 R: Service<Arg>,
653{
654 type Response = NestAppService<C, R::Response>;
655 type Error = R::Error;
656
657 async fn call(&self, req: Arg) -> Result<Self::Response, Self::Error> {
658 let ctx = (self.ctx_builder)()
659 .await
660 .expect("fallible nested application state builder is not supported yet");
661 let service = self.router.call(req).await?;
662
663 Ok(NestAppService { ctx, service })
664 }
665}
666
667pub struct NestAppService<C, S> {
668 ctx: C,
669 service: S,
670}
671
672impl<'r, C1, C, S, SE> Service<WebContext<'r, C1>> for NestAppService<C, S>
673where
674 S: for<'r1> Service<WebContext<'r1, C>, Response = WebResponse, Error = SE>,
675 SE: Into<Error>,
676{
677 type Response = WebResponse;
678 type Error = Error;
679
680 async fn call(&self, ctx: WebContext<'r, C1>) -> Result<Self::Response, Self::Error> {
681 let WebContext { req, body, .. } = ctx;
682
683 self.service
684 .call(WebContext {
685 req,
686 body,
687 ctx: &self.ctx,
688 })
689 .await
690 .map_err(Into::into)
691 }
692}
693
694/// object safe [App] instance. used for case where naming [App]'s type is needed.
695pub type AppObject<S> =
696 Box<dyn xitca_service::object::ServiceObject<(), Response = S, Error = Box<dyn fmt::Debug>> + Send + Sync>;
697
698#[cfg(test)]
699mod test {
700 use xitca_unsafe_collection::futures::NowOrPanic;
701
702 use crate::{
703 handler::{
704 extension::ExtensionRef, extension::ExtensionsRef, handler_service, path::PathRef, state::StateRef,
705 uri::UriRef,
706 },
707 http::{Method, const_header_value::TEXT_UTF8, header::CONTENT_TYPE, request},
708 middleware::UncheckedReady,
709 route::get,
710 };
711
712 use super::*;
713
714 async fn middleware<S, C, B, Res, Err>(s: &S, req: WebContext<'_, C, B>) -> Result<Res, Err>
715 where
716 S: for<'r> Service<WebContext<'r, C, B>, Response = Res, Error = Err>,
717 {
718 s.call(req).await
719 }
720
721 #[allow(clippy::too_many_arguments)]
722 async fn handler(
723 _res: Result<UriRef<'_>, Error>,
724 _opt: Option<UriRef<'_>>,
725 _req: &WebRequest<()>,
726 StateRef(state): StateRef<'_, String>,
727 PathRef(path): PathRef<'_>,
728 UriRef(_): UriRef<'_>,
729 ExtensionRef(_): ExtensionRef<'_, Foo>,
730 ExtensionsRef(_): ExtensionsRef<'_>,
731 req: &WebContext<'_, String>,
732 ) -> String {
733 assert_eq!("state", state);
734 assert_eq!(state, req.state());
735 assert_eq!("/", path);
736 assert_eq!(path, req.req().uri().path());
737 state.to_string()
738 }
739
740 // Handler with no state extractor
741 async fn stateless_handler(_: PathRef<'_>) -> String {
742 String::from("debug")
743 }
744
745 #[derive(Clone)]
746 struct Middleware;
747
748 impl<S, E> Service<Result<S, E>> for Middleware {
749 type Response = MiddlewareService<S>;
750 type Error = E;
751
752 async fn call(&self, res: Result<S, E>) -> Result<Self::Response, Self::Error> {
753 res.map(MiddlewareService)
754 }
755 }
756
757 struct MiddlewareService<S>(S);
758
759 impl<'r, S, C, B, Res, Err> Service<WebContext<'r, C, B>> for MiddlewareService<S>
760 where
761 S: for<'r2> Service<WebContext<'r2, C, B>, Response = Res, Error = Err>,
762 C: 'r,
763 B: 'r,
764 {
765 type Response = Res;
766 type Error = Err;
767
768 async fn call(&self, mut req: WebContext<'r, C, B>) -> Result<Self::Response, Self::Error> {
769 self.0.call(req.reborrow()).await
770 }
771 }
772
773 #[allow(clippy::borrow_interior_mutable_const)]
774 #[test]
775 fn test_app() {
776 let state = String::from("state");
777
778 let service = App::new()
779 .at("/", get(handler_service(handler)))
780 .with_state(state)
781 .at(
782 "/stateless",
783 get(handler_service(stateless_handler)).head(handler_service(stateless_handler)),
784 )
785 .enclosed_fn(middleware)
786 .enclosed(Middleware)
787 .enclosed(UncheckedReady)
788 .finish()
789 .call(())
790 .now_or_panic()
791 .ok()
792 .unwrap();
793
794 let mut req = WebRequest::default();
795 req.extensions_mut().insert(Foo);
796
797 let res = service.call(req).now_or_panic().unwrap();
798
799 assert_eq!(res.status().as_u16(), 200);
800
801 assert_eq!(res.headers().get(CONTENT_TYPE).unwrap(), TEXT_UTF8);
802
803 let req = request::Builder::default()
804 .uri("/abc")
805 .body(Default::default())
806 .unwrap();
807
808 let res = service.call(req).now_or_panic().unwrap();
809
810 assert_eq!(res.status().as_u16(), 404);
811
812 let req = request::Builder::default()
813 .method(Method::POST)
814 .body(Default::default())
815 .unwrap();
816
817 let res = service.call(req).now_or_panic().unwrap();
818
819 assert_eq!(res.status().as_u16(), 405);
820 }
821
822 #[derive(Clone)]
823 struct Foo;
824
825 #[test]
826 fn app_nest_router() {
827 async fn handler(StateRef(state): StateRef<'_, String>, PathRef(path): PathRef<'_>) -> String {
828 assert_eq!("state", state);
829 assert_eq!("/scope/nest", path);
830 state.to_string()
831 }
832
833 fn app() -> NestApp<String> {
834 App::new().at("/nest", get(handler_service(handler)))
835 }
836
837 let state = String::from("state");
838 let service = App::new()
839 .with_state(state.clone())
840 .at("/root", get(handler_service(handler)))
841 .at("/scope", app())
842 .finish()
843 .call(())
844 .now_or_panic()
845 .ok()
846 .unwrap();
847
848 let req = request::Builder::default()
849 .uri("/scope/nest")
850 .body(Default::default())
851 .unwrap();
852
853 let res = service.call(req).now_or_panic().unwrap();
854
855 assert_eq!(res.status().as_u16(), 200);
856
857 async fn handler2(StateRef(state): StateRef<'_, usize>, PathRef(path): PathRef<'_>) -> String {
858 assert_eq!(996, *state);
859 assert_eq!("/scope/nest", path);
860 state.to_string()
861 }
862
863 let service = App::new()
864 .with_state(state)
865 .at("/root", get(handler_service(handler)))
866 .at(
867 "/scope",
868 App::new()
869 .with_state(996usize)
870 .at("/nest", get(handler_service(handler2))),
871 )
872 .finish()
873 .call(())
874 .now_or_panic()
875 .ok()
876 .unwrap();
877
878 let req = request::Builder::default()
879 .uri("/scope/nest")
880 .body(Default::default())
881 .unwrap();
882
883 let res = service.call(req).now_or_panic().unwrap();
884
885 assert_eq!(res.status().as_u16(), 200);
886 }
887}