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}