gotham/router/builder/
single.rs

1use futures_util::FutureExt;
2use hyper::Body;
3
4use std::future::Future;
5use std::panic::RefUnwindSafe;
6use std::pin::Pin;
7
8use crate::extractor::{PathExtractor, QueryStringExtractor};
9use crate::handler::{
10    DirHandler, FileHandler, FileOptions, FilePathExtractor, Handler, HandlerError, HandlerFuture,
11    HandlerResult, IntoResponse, NewHandler,
12};
13use crate::pipeline::PipelineHandleChain;
14use crate::router::builder::{
15    ExtendRouteMatcher, ReplacePathExtractor, ReplaceQueryStringExtractor, SingleRouteBuilder,
16};
17use crate::router::route::dispatch::DispatcherImpl;
18use crate::router::route::matcher::RouteMatcher;
19use crate::router::route::{Delegation, Extractors, RouteImpl};
20use crate::state::State;
21
22pub trait HandlerMarker {
23    fn call_and_wrap(self, state: State) -> Pin<Box<HandlerFuture>>;
24}
25
26pub trait AsyncHandlerFn<'a> {
27    type Res: IntoResponse + 'static;
28    type Fut: std::future::Future<Output = Result<Self::Res, HandlerError>> + Send + 'a;
29    fn call(self, arg: &'a mut State) -> Self::Fut;
30}
31
32impl<'a, Fut, R, F> AsyncHandlerFn<'a> for F
33where
34    F: FnOnce(&'a mut State) -> Fut,
35    R: IntoResponse + 'static,
36    Fut: std::future::Future<Output = Result<R, HandlerError>> + Send + 'a,
37{
38    type Res = R;
39    type Fut = Fut;
40    fn call(self, state: &'a mut State) -> Fut {
41        self(state)
42    }
43}
44
45impl<F, R> HandlerMarker for F
46where
47    R: IntoResponse + 'static,
48    for<'a> F: AsyncHandlerFn<'a, Res = R> + Send + 'static,
49{
50    fn call_and_wrap(self, mut state: State) -> Pin<Box<HandlerFuture>> {
51        async move {
52            let fut = self.call(&mut state);
53            let result = fut.await;
54            match result {
55                Ok(data) => {
56                    let response = data.into_response(&state);
57                    Ok((state, response))
58                }
59                Err(err) => Err((state, err)),
60            }
61        }
62        .boxed()
63    }
64}
65
66/// Describes the API for defining a single route, after determining which request paths will be
67/// dispatched here. The API here uses chained function calls to build and add the route into the
68/// `RouterBuilder` which created it.
69///
70/// # Examples
71///
72/// ```rust
73/// # use hyper::{Body, Response, StatusCode};
74/// # use gotham::state::State;
75/// # use gotham::router::Router;
76/// # use gotham::router::builder::*;
77/// # use gotham::pipeline::*;
78/// # use gotham::middleware::session::NewSessionMiddleware;
79/// # use gotham::test::TestServer;
80/// #
81/// fn my_handler(state: State) -> (State, Response<Body>) {
82///     // Handler implementation elided.
83/// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
84/// }
85/// #
86/// # fn router() -> Router {
87/// #   let (chain, pipelines) = single_pipeline(
88/// #       new_pipeline().add(NewSessionMiddleware::default()).build()
89/// #   );
90/// #
91/// build_router(chain, pipelines, |route| {
92///     route.get("/request/path") // <- This value implements `DefineSingleRoute`
93///          .to(my_handler);
94/// })
95/// # }
96/// #
97/// # fn main() {
98/// #   let test_server = TestServer::new(router()).unwrap();
99/// #   let response = test_server.client()
100/// #       .get("https://example.com/request/path")
101/// #       .perform()
102/// #       .unwrap();
103/// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
104/// # }
105/// ```
106pub trait DefineSingleRoute {
107    /// Directs the route to the given `Handler`, automatically creating a `NewHandler` which
108    /// copies the `Handler`. This is the easiest option for code which is using bare functions as
109    /// `Handler` functions.
110    ///
111    /// # Examples
112    ///
113    /// ```rust
114    /// # use hyper::{Body, Response, StatusCode};
115    /// # use gotham::state::State;
116    /// # use gotham::router::Router;
117    /// # use gotham::router::builder::*;
118    /// # use gotham::pipeline::*;
119    /// # use gotham::middleware::session::NewSessionMiddleware;
120    /// # use gotham::test::TestServer;
121    /// #
122    /// fn my_handler(state: State) -> (State, Response<Body>) {
123    ///     // Handler implementation elided.
124    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
125    /// }
126    /// #
127    /// # fn router() -> Router {
128    /// #   let (chain, pipelines) = single_pipeline(
129    /// #       new_pipeline().add(NewSessionMiddleware::default()).build()
130    /// #   );
131    ///
132    /// build_router(chain, pipelines, |route| {
133    ///     route.get("/request/path").to(my_handler);
134    /// })
135    /// #
136    /// # }
137    /// #
138    /// # fn main() {
139    /// #   let test_server = TestServer::new(router()).unwrap();
140    /// #   let response = test_server.client()
141    /// #       .get("https://example.com/request/path")
142    /// #       .perform()
143    /// #       .unwrap();
144    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
145    /// # }
146    /// ```
147    fn to<H>(self, handler: H)
148    where
149        H: Handler + RefUnwindSafe + Copy + Send + Sync + 'static;
150
151    /// Similar to `to`, but accepts an `async fn`
152    ///
153    /// # Examples
154    ///
155    /// ```rust
156    /// # use hyper::{Body, Response, StatusCode};
157    /// # use gotham::handler::HandlerResult;
158    /// # use gotham::state::State;
159    /// # use gotham::router::Router;
160    /// # use gotham::router::builder::*;
161    /// # use gotham::pipeline::*;
162    /// # use gotham::middleware::session::NewSessionMiddleware;
163    /// # use gotham::test::TestServer;
164    /// #
165    /// async fn my_handler(state: State) -> HandlerResult {
166    ///     // Handler implementation elided.
167    /// #   Ok((state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap()))
168    /// }
169    /// #
170    /// # fn router() -> Router {
171    /// #   let (chain, pipelines) = single_pipeline(
172    /// #       new_pipeline().add(NewSessionMiddleware::default()).build()
173    /// #   );
174    ///
175    /// build_router(chain, pipelines, |route| {
176    ///     route.get("/request/path").to_async(my_handler);
177    /// })
178    /// #
179    /// # }
180    /// #
181    /// # fn main() {
182    /// #   let test_server = TestServer::new(router()).unwrap();
183    /// #   let response = test_server.client()
184    /// #       .get("https://example.com/request/path")
185    /// #       .perform()
186    /// #       .unwrap();
187    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
188    /// # }
189    /// ```
190    fn to_async<H, Fut>(self, handler: H)
191    where
192        Self: Sized,
193        H: (FnOnce(State) -> Fut) + RefUnwindSafe + Copy + Send + Sync + 'static,
194        Fut: Future<Output = HandlerResult> + Send + 'static;
195
196    /// Directs the route to the given `async fn`, passing `State` to it by mutable reference.
197    ///
198    /// Note that, as of Rust 1.46.0, this does not work for closures due to
199    /// [rust-lang/rust#70263](https://github.com/rust-lang/rust/issues/70263).
200    ///
201    /// On the other hand, one can easily use the `?` operator for error handling
202    /// in these async functions.
203    ///
204    /// # Examples
205    ///
206    /// ```rust
207    /// # use hyper::StatusCode;
208    /// # use gotham::handler::{HandlerError, IntoResponse, MapHandlerError};
209    /// # use gotham::state::State;
210    /// # use gotham::router::Router;
211    /// # use gotham::router::builder::*;
212    /// # use gotham::pipeline::*;
213    /// # use gotham::middleware::session::NewSessionMiddleware;
214    /// # use gotham::test::TestServer;
215    /// #
216    /// async fn my_handler(_state: &mut State) -> Result<impl IntoResponse, HandlerError> {
217    ///     let flavors =
218    ///         std::fs::read("coffee-flavors.txt").map_err_with_status(StatusCode::IM_A_TEAPOT)?;
219    ///     Ok(flavors)
220    /// }
221    /// #
222    /// # fn router() -> Router {
223    /// #   let (chain, pipelines) = single_pipeline(
224    /// #       new_pipeline().add(NewSessionMiddleware::default()).build()
225    /// #   );
226    ///
227    /// build_router(chain, pipelines, |route| {
228    ///     route.get("/request/path").to_async_borrowing(my_handler);
229    /// })
230    /// #
231    /// # }
232    /// #
233    /// # fn main() {
234    /// #   let test_server = TestServer::new(router()).unwrap();
235    /// #   let response = test_server.client()
236    /// #       .get("https://example.com/request/path")
237    /// #       .perform()
238    /// #       .unwrap();
239    /// #   assert_eq!(response.status(), StatusCode::IM_A_TEAPOT);
240    /// # }
241    /// ```
242    fn to_async_borrowing<F>(self, handler: F)
243    where
244        Self: Sized,
245        F: HandlerMarker + Copy + Send + Sync + RefUnwindSafe + 'static;
246
247    /// Directs the route to the given `NewHandler`. This gives more control over how `Handler`
248    /// values are constructed.
249    ///
250    /// # Examples
251    ///
252    /// ```rust
253    /// # use std::pin::Pin;
254    /// #
255    /// # use futures_util::future::{self, FutureExt};
256    /// # use hyper::{Body, Response, StatusCode};
257    /// # use gotham::handler::{Handler, HandlerFuture, NewHandler};
258    /// # use gotham::state::State;
259    /// # use gotham::router::Router;
260    /// # use gotham::router::builder::*;
261    /// # use gotham::pipeline::*;
262    /// # use gotham::middleware::session::NewSessionMiddleware;
263    /// # use gotham::test::TestServer;
264    /// #
265    /// struct MyNewHandler;
266    /// struct MyHandler;
267    ///
268    /// impl NewHandler for MyNewHandler {
269    ///     type Instance = MyHandler;
270    ///
271    ///     fn new_handler(&self) -> anyhow::Result<Self::Instance> {
272    ///         Ok(MyHandler)
273    ///     }
274    /// }
275    ///
276    /// impl Handler for MyHandler {
277    ///     fn handle(self, state: State) -> Pin<Box<HandlerFuture>> {
278    ///         // Handler implementation elided.
279    /// #       let response = Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap();
280    /// #       future::ok((state, response)).boxed()
281    ///     }
282    /// }
283    /// #
284    /// # fn router() -> Router {
285    /// #   let (chain, pipelines) = single_pipeline(
286    /// #       new_pipeline().add(NewSessionMiddleware::default()).build()
287    /// #   );
288    ///
289    /// build_router(chain, pipelines, |route| {
290    ///     route.get("/request/path").to_new_handler(MyNewHandler);
291    /// })
292    /// # }
293    /// #
294    /// # fn main() {
295    /// #   let test_server = TestServer::new(router()).unwrap();
296    /// #   let response = test_server.client()
297    /// #       .get("https://example.com/request/path")
298    /// #       .perform()
299    /// #       .unwrap();
300    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
301    /// # }
302    /// ```
303    fn to_new_handler<NH>(self, new_handler: NH)
304    where
305        NH: NewHandler + 'static;
306
307    /// Directs the route to serve static files from the given root directory.
308    /// The route must contain a trailing glob segment, which will be used
309    /// to serve any matching names under the given path.
310    ///
311    /// # Examples
312    ///
313    /// ```rust
314    /// # use hyper::StatusCode;
315    /// # use gotham::router::Router;
316    /// # use gotham::router::builder::*;
317    /// # use gotham::pipeline::*;
318    /// # use gotham::middleware::session::NewSessionMiddleware;
319    /// # use gotham::test::TestServer;
320    /// #
321    /// #
322    /// # fn router() -> Router {
323    /// #   let (chain, pipelines) = single_pipeline(
324    /// #       new_pipeline().add(NewSessionMiddleware::default()).build()
325    /// #   );
326    ///
327    /// build_router(chain, pipelines, |route| {
328    ///     route.get("/*").to_dir("resources/test/assets");
329    /// })
330    /// # }
331    /// #
332    /// # fn main() {
333    /// #   let test_server = TestServer::new(router()).unwrap();
334    /// #   let response = test_server.client()
335    /// #       .get("https://example.com/doc.html")
336    /// #       .perform()
337    /// #       .unwrap();
338    /// #   assert_eq!(response.status(), StatusCode::OK);
339    /// # }
340    /// ```
341    fn to_dir<P>(self, options: P)
342    where
343        Self: ReplacePathExtractor<FilePathExtractor> + Sized,
344        Self::Output: DefineSingleRoute,
345        FileOptions: From<P>,
346    {
347        self.with_path_extractor::<FilePathExtractor>()
348            .to_new_handler(DirHandler::new(options));
349    }
350
351    /// Directs the route to serve a single static file from the given path.
352    ///
353    /// # Examples
354    ///
355    /// ```rust
356    /// # use hyper::StatusCode;
357    /// # use gotham::router::Router;
358    /// # use gotham::router::builder::*;
359    /// # use gotham::pipeline::*;
360    /// # use gotham::middleware::session::NewSessionMiddleware;
361    /// # use gotham::test::TestServer;
362    /// #
363    /// #
364    /// # fn router() -> Router {
365    /// #   let (chain, pipelines) = single_pipeline(
366    /// #       new_pipeline().add(NewSessionMiddleware::default()).build()
367    /// #   );
368    ///
369    /// build_router(chain, pipelines, |route| {
370    ///     route.get("/").to_file("resources/test/assets/doc.html");
371    /// })
372    /// # }
373    /// #
374    /// # fn main() {
375    /// #   let test_server = TestServer::new(router()).unwrap();
376    /// #   let response = test_server.client()
377    /// #       .get("https://example.com/")
378    /// #       .perform()
379    /// #       .unwrap();
380    /// #   assert_eq!(response.status(), StatusCode::OK);
381    /// # }
382    /// ```
383    fn to_file<P>(self, options: P)
384    where
385        Self: Sized,
386        FileOptions: From<P>,
387    {
388        self.to_new_handler(FileHandler::new(options));
389    }
390
391    /// Applies a `PathExtractor` type to the current route, to extract path parameters into
392    /// `State` with the given type.
393    ///
394    /// # Examples
395    ///
396    /// ```rust
397    /// # use hyper::{Body, Response, StatusCode};
398    /// # use gotham::state::State;
399    /// # use gotham::router::{build_router, Router};
400    /// # use gotham::prelude::*;
401    /// # use gotham::pipeline::*;
402    /// # use gotham::middleware::session::NewSessionMiddleware;
403    /// # use gotham::test::TestServer;
404    /// # use serde::Deserialize;
405    /// #
406    /// #[derive(Deserialize, StateData, StaticResponseExtender)]
407    /// struct MyPathParams {
408    /// #   #[allow(dead_code)]
409    ///     name: String,
410    /// }
411    ///
412    /// fn my_handler(state: State) -> (State, Response<Body>) {
413    /// #   {
414    ///     let params = MyPathParams::borrow_from(&state);
415    ///
416    ///     // Handler implementation elided.
417    /// #   assert_eq!(params.name, "world");
418    /// #   }
419    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
420    /// }
421    /// #
422    /// # fn router() -> Router {
423    /// #   let pipelines = new_pipeline_set();
424    /// #   let (pipelines, default) =
425    /// #       pipelines.add(new_pipeline().add(NewSessionMiddleware::default()).build());
426    /// #
427    /// #   let pipelines = finalize_pipeline_set(pipelines);
428    /// #
429    /// #   let default_pipeline_chain = (default, ());
430    ///
431    /// build_router(default_pipeline_chain, pipelines, |route| {
432    ///     route.get("/hello/:name")
433    ///          .with_path_extractor::<MyPathParams>()
434    ///          .to(my_handler);
435    /// })
436    /// # }
437    /// #
438    /// # fn main() {
439    /// #   let test_server = TestServer::new(router()).unwrap();
440    /// #   let response = test_server.client()
441    /// #       .get("https://example.com/hello/world")
442    /// #       .perform()
443    /// #       .unwrap();
444    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
445    /// # }
446    /// ```
447    fn with_path_extractor<NPE>(self) -> <Self as ReplacePathExtractor<NPE>>::Output
448    where
449        NPE: PathExtractor<Body> + Send + Sync + 'static,
450        Self: ReplacePathExtractor<NPE>,
451        Self::Output: DefineSingleRoute;
452
453    /// Applies a `QueryStringExtractor` type to the current route, to extract query parameters into
454    /// `State` with the given type.
455    ///
456    /// # Examples
457    ///
458    /// ```rust
459    /// # use hyper::{Body, Response, StatusCode};
460    /// # use gotham::state::State;
461    /// # use gotham::router::{build_router, Router};
462    /// # use gotham::prelude::*;
463    /// # use gotham::pipeline::*;
464    /// # use gotham::middleware::session::NewSessionMiddleware;
465    /// # use gotham::test::TestServer;
466    /// # use serde::Deserialize;
467    /// #
468    /// #[derive(StateData, Deserialize, StaticResponseExtender)]
469    /// struct MyQueryParams {
470    /// #   #[allow(dead_code)]
471    ///     id: u64,
472    /// }
473    ///
474    /// fn my_handler(state: State) -> (State, Response<Body>) {
475    ///     let id = MyQueryParams::borrow_from(&state).id;
476    ///
477    ///     // Handler implementation elided.
478    /// #   assert_eq!(id, 42);
479    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
480    /// }
481    /// #
482    /// # fn router() -> Router {
483    /// #   let pipelines = new_pipeline_set();
484    /// #   let (pipelines, default) =
485    /// #       pipelines.add(new_pipeline().add(NewSessionMiddleware::default()).build());
486    /// #
487    /// #   let pipelines = finalize_pipeline_set(pipelines);
488    /// #
489    /// #   let default_pipeline_chain = (default, ());
490    ///
491    /// build_router(default_pipeline_chain, pipelines, |route| {
492    ///     route.get("/request/path")
493    ///          .with_query_string_extractor::<MyQueryParams>()
494    ///          .to(my_handler);
495    /// })
496    /// # }
497    /// #
498    /// # fn main() {
499    /// #   let test_server = TestServer::new(router()).unwrap();
500    /// #   let response = test_server.client()
501    /// #       .get("https://example.com/request/path?id=42")
502    /// #       .perform()
503    /// #       .unwrap();
504    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
505    /// # }
506    /// ```
507    fn with_query_string_extractor<NQSE>(
508        self,
509    ) -> <Self as ReplaceQueryStringExtractor<NQSE>>::Output
510    where
511        NQSE: QueryStringExtractor<Body> + Send + Sync + 'static,
512        Self: ReplaceQueryStringExtractor<NQSE>,
513        Self::Output: DefineSingleRoute;
514
515    /// Adds additional `RouteMatcher` requirements to the current route.
516    ///
517    /// ```
518    /// # use hyper::{Body, Response, StatusCode};
519    /// # use hyper::header::ACCEPT;
520    /// # use gotham::state::State;
521    /// # use gotham::router::route::matcher::AcceptHeaderRouteMatcher;
522    /// # use gotham::router::Router;
523    /// # use gotham::router::builder::*;
524    /// # use gotham::test::TestServer;
525    /// #
526    /// # fn my_handler(state: State) -> (State, Response<Body>) {
527    /// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
528    /// # }
529    /// #
530    /// # fn router() -> Router {
531    /// build_simple_router(|route| {
532    ///     let matcher = AcceptHeaderRouteMatcher::new(vec![mime::APPLICATION_JSON]);
533    ///     route.get("/request/path")
534    ///          .add_route_matcher(matcher)
535    ///          .to(my_handler);
536    /// })
537    /// # }
538    /// #
539    /// # fn main() {
540    /// #   let test_server = TestServer::new(router()).unwrap();
541    /// #
542    /// #   let response = test_server.client()
543    /// #       .get("https://example.com/request/path")
544    /// #       .with_header(ACCEPT, mime::APPLICATION_JSON.to_string().parse().unwrap())
545    /// #       .perform()
546    /// #       .unwrap();
547    /// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
548    /// #
549    /// #   let response = test_server.client()
550    /// #       .get("https://example.com/request/path")
551    /// #       .with_header(ACCEPT, mime::TEXT_PLAIN.to_string().parse().unwrap())
552    /// #       .perform()
553    /// #       .unwrap();
554    /// #   assert_eq!(response.status(), StatusCode::NOT_ACCEPTABLE);
555    /// # }
556    /// ```
557    fn add_route_matcher<NRM>(self, matcher: NRM) -> <Self as ExtendRouteMatcher<NRM>>::Output
558    where
559        NRM: RouteMatcher + Send + Sync + 'static,
560        Self: ExtendRouteMatcher<NRM>,
561        Self::Output: DefineSingleRoute;
562}
563
564impl<'a, M, C, P, PE, QSE> DefineSingleRoute for SingleRouteBuilder<'a, M, C, P, PE, QSE>
565where
566    M: RouteMatcher + Send + Sync + 'static,
567    C: PipelineHandleChain<P> + Send + Sync + 'static,
568    P: RefUnwindSafe + Send + Sync + 'static,
569    PE: PathExtractor<Body> + Send + Sync + 'static,
570    QSE: QueryStringExtractor<Body> + Send + Sync + 'static,
571{
572    fn to<H>(self, handler: H)
573    where
574        H: Handler + RefUnwindSafe + Copy + Send + Sync + 'static,
575    {
576        self.to_new_handler(move || Ok(handler))
577    }
578
579    fn to_async<H, Fut>(self, handler: H)
580    where
581        Self: Sized,
582        H: (FnOnce(State) -> Fut) + RefUnwindSafe + Copy + Send + Sync + 'static,
583        Fut: Future<Output = HandlerResult> + Send + 'static,
584    {
585        self.to_new_handler(move || Ok(move |s: State| handler(s).boxed()))
586    }
587
588    fn to_async_borrowing<F>(self, handler: F)
589    where
590        Self: Sized,
591        F: HandlerMarker + Copy + Send + Sync + RefUnwindSafe + 'static,
592    {
593        self.to_new_handler(move || Ok(move |state: State| handler.call_and_wrap(state)))
594    }
595
596    fn to_new_handler<NH>(self, new_handler: NH)
597    where
598        NH: NewHandler + 'static,
599    {
600        let dispatcher = DispatcherImpl::new(new_handler, self.pipeline_chain, self.pipelines);
601        let route: RouteImpl<M, PE, QSE> = RouteImpl::new(
602            self.matcher,
603            Box::new(dispatcher),
604            Extractors::new(),
605            Delegation::Internal,
606        );
607        self.node_builder.add_route(Box::new(route));
608    }
609
610    fn with_path_extractor<NPE>(self) -> <Self as ReplacePathExtractor<NPE>>::Output
611    where
612        NPE: PathExtractor<Body> + Send + Sync + 'static,
613    {
614        self.replace_path_extractor()
615    }
616
617    fn with_query_string_extractor<NQSE>(
618        self,
619    ) -> <Self as ReplaceQueryStringExtractor<NQSE>>::Output
620    where
621        NQSE: QueryStringExtractor<Body> + Send + Sync + 'static,
622    {
623        self.replace_query_string_extractor()
624    }
625
626    fn add_route_matcher<NRM>(self, matcher: NRM) -> <Self as ExtendRouteMatcher<NRM>>::Output
627    where
628        NRM: RouteMatcher + Send + Sync + 'static,
629    {
630        self.extend_route_matcher(matcher)
631    }
632}