gotham/router/builder/
mod.rs

1//! Defines a builder API for constructing a `Router`.
2
3mod associated;
4mod draw;
5mod modify;
6mod single;
7
8use std::marker::PhantomData;
9use std::panic::RefUnwindSafe;
10
11use hyper::{Body, StatusCode};
12
13use crate::extractor::{
14    NoopPathExtractor, NoopQueryStringExtractor, PathExtractor, QueryStringExtractor,
15};
16use crate::pipeline::{finalize_pipeline_set, new_pipeline_set, PipelineHandleChain, PipelineSet};
17use crate::router::response::{ResponseExtender, ResponseFinalizerBuilder};
18use crate::router::route::dispatch::DispatcherImpl;
19use crate::router::route::matcher::{AndRouteMatcher, RouteMatcher};
20use crate::router::route::{Delegation, Extractors, RouteImpl};
21use crate::router::tree::node::Node;
22use crate::router::tree::Tree;
23use crate::router::Router;
24
25pub use self::associated::{AssociatedRouteBuilder, AssociatedSingleRouteBuilder};
26pub use self::draw::DrawRoutes;
27pub use self::modify::{ExtendRouteMatcher, ReplacePathExtractor, ReplaceQueryStringExtractor};
28pub use self::single::DefineSingleRoute;
29
30/// Builds a `Router` using the provided closure. Routes are defined using the `RouterBuilder`
31/// value passed to the closure, and the `Router` is constructed before returning.
32///
33///
34/// **Important note**: The `path` passed to the `RouterBuilder` is decomposed
35/// in segments. This means that empty segments and trailing slashes are
36/// removed. Basically `route.get("/foo//bar/baz/")` will have the same effect as
37/// `route.get("/foo/bar/baz")`
38///
39/// ```rust
40/// # use hyper::{Body, Response, StatusCode};
41/// # use gotham::state::State;
42/// # use gotham::router::Router;
43/// # use gotham::router::builder::*;
44/// # use gotham::pipeline::*;
45/// # use gotham::middleware::session::{NewSessionMiddleware, SessionData};
46/// # use gotham::test::TestServer;
47/// # use serde::{Deserialize, Serialize};
48/// #
49/// # #[derive(Serialize, Deserialize, Default)]
50/// # struct Session;
51/// #
52/// # fn my_handler(state: State) -> (State, Response<Body>) {
53/// #   assert!(state.has::<SessionData<Session>>());
54/// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
55/// # }
56/// #
57/// fn router() -> Router {
58///     let (chain, pipelines) = single_pipeline(
59///         new_pipeline()
60///             .add(NewSessionMiddleware::default().with_session_type::<Session>())
61///             .build()
62///     );
63///
64///     build_router(chain, pipelines, |route| {
65///         route.get("/request/path").to(my_handler);
66///     })
67/// }
68/// #
69/// # fn main() {
70/// #   let test_server = TestServer::new(router()).unwrap();
71/// #   let response = test_server.client()
72/// #       .get("https://example.com/request/path")
73/// #       .perform()
74/// #       .unwrap();
75/// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
76/// # }
77/// ```
78pub fn build_router<C, P, F>(pipeline_chain: C, pipelines: PipelineSet<P>, f: F) -> Router
79where
80    C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
81    P: Send + Sync + 'static,
82    F: FnOnce(&mut RouterBuilder<'_, C, P>),
83{
84    let mut tree = Tree::new();
85
86    let response_finalizer = {
87        let mut builder = RouterBuilder {
88            node_builder: tree.borrow_root_mut(),
89            pipeline_chain,
90            pipelines,
91            response_finalizer_builder: ResponseFinalizerBuilder::new(),
92        };
93
94        f(&mut builder);
95
96        builder.response_finalizer_builder.finalize()
97    };
98
99    Router::new(tree, response_finalizer)
100}
101
102/// Builds a `Router` with **no** middleware using the provided closure. Routes are defined using
103/// the `RouterBuilder` value passed to the closure, and the `Router` is constructed before
104/// returning.
105///
106/// ```rust
107/// # extern crate gotham;
108/// # extern crate hyper;
109/// #
110/// # use hyper::{Body, Response, StatusCode};
111/// # use gotham::state::State;
112/// # use gotham::router::Router;
113/// # use gotham::router::builder::*;
114/// # use gotham::test::TestServer;
115/// #
116/// # fn my_handler(state: State) -> (State, Response<Body>) {
117/// #   (state, Response::builder().status(StatusCode::ACCEPTED).body(Body::empty()).unwrap())
118/// # }
119/// #
120/// fn router() -> Router {
121///     build_simple_router(|route| {
122///         route.get("/request/path").to(my_handler);
123///     })
124/// }
125/// #
126/// # fn main() {
127/// #   let test_server = TestServer::new(router()).unwrap();
128/// #   let response = test_server.client()
129/// #       .get("https://example.com/request/path")
130/// #       .perform()
131/// #       .unwrap();
132/// #   assert_eq!(response.status(), StatusCode::ACCEPTED);
133/// # }
134/// ```
135pub fn build_simple_router<F>(f: F) -> Router
136where
137    F: FnOnce(&mut RouterBuilder<'_, (), ()>),
138{
139    let pipelines = finalize_pipeline_set(new_pipeline_set());
140    build_router((), pipelines, f)
141}
142
143/// The top-level builder which is created by `build_router` and passed to the provided closure.
144/// See the `build_router` function and the `DrawRoutes` trait for usage.
145pub struct RouterBuilder<'a, C, P>
146where
147    C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
148    P: Send + Sync + 'static,
149{
150    node_builder: &'a mut Node,
151    pipeline_chain: C,
152    pipelines: PipelineSet<P>,
153    response_finalizer_builder: ResponseFinalizerBuilder,
154}
155
156impl<'a, C, P> RouterBuilder<'a, C, P>
157where
158    C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
159    P: Send + Sync + 'static,
160{
161    /// Adds a `ResponseExtender` to the `ResponseFinalizer` in the `Router`.
162    ///
163    /// ```rust
164    /// # use hyper::{Body, Response, StatusCode};
165    /// # use hyper::header::WARNING;
166    /// # use gotham::state::State;
167    /// # use gotham::router::Router;
168    /// # use gotham::router::response::ResponseExtender;
169    /// # use gotham::router::builder::*;
170    /// # use gotham::test::TestServer;
171    /// #
172    /// # fn my_handler(state: State) -> (State, Response<Body>) {
173    /// #   (state, Response::builder().status(StatusCode::INTERNAL_SERVER_ERROR).body(Body::empty()).unwrap())
174    /// # }
175    /// #
176    /// struct MyExtender;
177    ///
178    /// impl ResponseExtender<Body> for MyExtender {
179    ///     fn extend(&self, state: &mut State, response: &mut Response<Body>) {
180    ///         // Extender implementation omitted.
181    /// #       let _ = state;
182    /// #       response.headers_mut().insert(WARNING, "299 example.com Deprecated".parse().unwrap());
183    ///     }
184    /// }
185    ///
186    /// fn router() -> Router {
187    ///     build_simple_router(|route| {
188    ///         route.add_response_extender(StatusCode::INTERNAL_SERVER_ERROR, MyExtender);
189    /// #
190    /// #       route.get("/").to(my_handler);
191    ///     })
192    /// }
193    /// #
194    /// # fn main() {
195    /// #   let test_server = TestServer::new(router()).unwrap();
196    /// #   let response = test_server.client()
197    /// #       .get("https://example.com/")
198    /// #       .perform()
199    /// #       .unwrap();
200    /// #   assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
201    /// #
202    /// #   {
203    /// #       let warning = response.headers().get(WARNING).unwrap();
204    /// #       assert_eq!(warning, "299 example.com Deprecated");
205    /// #   }
206    /// # }
207    /// ```
208    pub fn add_response_extender<E>(&mut self, status_code: StatusCode, extender: E)
209    where
210        E: ResponseExtender<Body> + Send + Sync + 'static,
211    {
212        self.response_finalizer_builder
213            .add(status_code, Box::new(extender))
214    }
215}
216
217/// A scoped builder, which is created by `DrawRoutes::scope` and passed to the provided closure.
218/// The `DrawRoutes` trait has documentation for using this type.
219pub struct ScopeBuilder<'a, C, P>
220where
221    C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
222    P: Send + Sync + 'static,
223{
224    node_builder: &'a mut Node,
225    pipeline_chain: C,
226    pipelines: PipelineSet<P>,
227}
228
229/// A delegated builder, which is created by `DrawRoutes::delegate` and returned. The `DrawRoutes`
230/// trait has documentation for using this type.
231pub struct DelegateRouteBuilder<'a, M, C, P>
232where
233    M: RouteMatcher + Send + Sync + 'static,
234    C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
235    P: Send + Sync + 'static,
236{
237    matcher: M,
238    node_builder: &'a mut Node,
239    pipeline_chain: C,
240    pipelines: PipelineSet<P>,
241}
242
243type DelegatedRoute<M> = RouteImpl<M, NoopPathExtractor, NoopQueryStringExtractor>;
244
245impl<'a, M, C, P> DelegateRouteBuilder<'a, M, C, P>
246where
247    M: RouteMatcher + Send + Sync + 'static,
248    C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
249    P: RefUnwindSafe + Send + Sync + 'static,
250{
251    /// Directs the delegated route to the given `Router`.
252    pub fn to_router(self, router: Router) {
253        let dispatcher = DispatcherImpl::new(router, self.pipeline_chain, self.pipelines);
254        let route: DelegatedRoute<M> = DelegatedRoute::new(
255            self.matcher,
256            Box::new(dispatcher),
257            Extractors::new(),
258            Delegation::External,
259        );
260
261        self.node_builder.add_route(Box::new(route));
262    }
263
264    /// Adds additional `RouteMatcher` requirements to the current delegate.
265    pub fn add_route_matcher<NM: RouteMatcher + Send + Sync + 'static>(
266        self,
267        matcher: NM,
268    ) -> DelegateRouteBuilder<'a, AndRouteMatcher<M, NM>, C, P> {
269        DelegateRouteBuilder {
270            matcher: AndRouteMatcher::<M, NM>::new(self.matcher, matcher),
271            node_builder: self.node_builder,
272            pipeline_chain: self.pipeline_chain,
273            pipelines: self.pipelines,
274        }
275    }
276}
277
278/// Implements the traits required to define a single route, after determining which request paths
279/// will be dispatched here. The `DefineSingleRoute` trait has documentation for using this type.
280pub struct SingleRouteBuilder<'a, M, C, P, PE, QSE>
281where
282    M: RouteMatcher + Send + Sync + 'static,
283    C: PipelineHandleChain<P> + Send + Sync + 'static,
284    P: Send + Sync + 'static,
285    PE: PathExtractor<Body> + Send + Sync + 'static,
286    QSE: QueryStringExtractor<Body> + Send + Sync + 'static,
287{
288    node_builder: &'a mut Node,
289    matcher: M,
290    pipeline_chain: C,
291    pipelines: PipelineSet<P>,
292    phantom: PhantomData<(PE, QSE)>,
293}
294
295// Trait impls live with the traits.
296impl<'a, M, C, P, PE, QSE> SingleRouteBuilder<'a, M, C, P, PE, QSE>
297where
298    M: RouteMatcher + Send + Sync + 'static,
299    C: PipelineHandleChain<P> + Send + Sync + 'static,
300    P: Send + Sync + 'static,
301    PE: PathExtractor<Body> + Send + Sync + 'static,
302    QSE: QueryStringExtractor<Body> + Send + Sync + 'static,
303{
304    /// Coerces the type of the internal `PhantomData`, to replace an extractor by changing the
305    /// type parameter without changing anything else.
306    fn coerce<NPE, NQSE>(self) -> SingleRouteBuilder<'a, M, C, P, NPE, NQSE>
307    where
308        NPE: PathExtractor<Body> + Send + Sync + 'static,
309        NQSE: QueryStringExtractor<Body> + Send + Sync + 'static,
310    {
311        SingleRouteBuilder {
312            node_builder: self.node_builder,
313            matcher: self.matcher,
314            pipeline_chain: self.pipeline_chain,
315            pipelines: self.pipelines,
316            phantom: PhantomData,
317        }
318    }
319}
320
321#[cfg(test)]
322mod tests {
323    use super::*;
324
325    use hyper::service::Service;
326    use hyper::{body, Body, Request, Response, StatusCode};
327    use serde::Deserialize;
328
329    use crate::middleware::cookie::CookieParser;
330    use crate::pipeline::new_pipeline;
331    use crate::router::response::StaticResponseExtender;
332    use crate::service::GothamService;
333    use crate::state::{State, StateData};
334
335    #[derive(Deserialize)]
336    struct SalutationParams {
337        name: String,
338    }
339
340    impl StateData for SalutationParams {}
341
342    impl StaticResponseExtender for SalutationParams {
343        type ResBody = Body;
344        fn extend(_: &mut State, _: &mut Response<Body>) {}
345    }
346
347    #[derive(Deserialize)]
348    struct AddParams {
349        x: u64,
350        y: u64,
351    }
352
353    impl StateData for AddParams {}
354
355    impl StaticResponseExtender for AddParams {
356        type ResBody = Body;
357        fn extend(_: &mut State, _: &mut Response<Body>) {}
358    }
359
360    mod welcome {
361        use super::*;
362        pub(crate) fn index(state: State) -> (State, Response<Body>) {
363            (
364                state,
365                Response::builder()
366                    .status(StatusCode::OK)
367                    .body(Body::empty())
368                    .unwrap(),
369            )
370        }
371
372        pub(crate) fn literal(state: State) -> (State, Response<Body>) {
373            (
374                state,
375                Response::builder()
376                    .status(StatusCode::CREATED)
377                    .body(Body::empty())
378                    .unwrap(),
379            )
380        }
381
382        pub(crate) fn hello(mut state: State) -> (State, Response<Body>) {
383            let params = state.take::<SalutationParams>();
384            let response = Response::builder()
385                .status(StatusCode::OK)
386                .body(format!("Hello, {}!", params.name).into())
387                .unwrap();
388            (state, response)
389        }
390
391        pub(crate) fn globbed(state: State) -> (State, Response<Body>) {
392            let response = Response::builder()
393                .status(StatusCode::OK)
394                .body("Globbed".into())
395                .unwrap();
396            (state, response)
397        }
398
399        pub(crate) fn delegated(state: State) -> (State, Response<Body>) {
400            let response = Response::builder()
401                .status(StatusCode::OK)
402                .body("Delegated".into())
403                .unwrap();
404            (state, response)
405        }
406
407        pub(crate) fn goodbye(mut state: State) -> (State, Response<Body>) {
408            let params = state.take::<SalutationParams>();
409            let response = Response::builder()
410                .status(StatusCode::OK)
411                .body(format!("Goodbye, {}!", params.name).into())
412                .unwrap();
413            (state, response)
414        }
415
416        pub(crate) fn add(mut state: State) -> (State, Response<Body>) {
417            let params = state.take::<AddParams>();
418            let response = Response::builder()
419                .status(StatusCode::OK)
420                .body(format!("{} + {} = {}", params.x, params.y, params.x + params.y,).into())
421                .unwrap();
422            (state, response)
423        }
424
425        pub(crate) fn trailing_slash(state: State) -> (State, Response<Body>) {
426            let response = Response::builder()
427                .status(StatusCode::OK)
428                .body("Trailing slash!".into())
429                .unwrap();
430            (state, response)
431        }
432    }
433
434    mod resource {
435        use super::*;
436        pub(crate) fn create(state: State) -> (State, Response<Body>) {
437            let response = Response::builder()
438                .status(StatusCode::CREATED)
439                .body(Body::empty())
440                .unwrap();
441            (state, response)
442        }
443
444        pub(crate) fn destroy(state: State) -> (State, Response<Body>) {
445            let response = Response::builder()
446                .status(StatusCode::ACCEPTED)
447                .body(Body::empty())
448                .unwrap();
449            (state, response)
450        }
451
452        pub(crate) fn show(state: State) -> (State, Response<Body>) {
453            let response = Response::builder()
454                .status(StatusCode::OK)
455                .body("It's a resource.".into())
456                .unwrap();
457            (state, response)
458        }
459
460        pub(crate) fn update(state: State) -> (State, Response<Body>) {
461            let response = Response::builder()
462                .status(StatusCode::ACCEPTED)
463                .body(Body::empty())
464                .unwrap();
465            (state, response)
466        }
467    }
468
469    mod api {
470        use super::*;
471        pub(crate) fn submit(state: State) -> (State, Response<Body>) {
472            (
473                state,
474                Response::builder()
475                    .status(StatusCode::ACCEPTED)
476                    .body(Body::empty())
477                    .unwrap(),
478            )
479        }
480    }
481
482    #[test]
483    fn build_router_test() {
484        let pipelines = new_pipeline_set();
485        let (pipelines, default) = pipelines.add(new_pipeline().add(CookieParser).build());
486
487        let pipelines = finalize_pipeline_set(pipelines);
488
489        let default_pipeline_chain = (default, ());
490
491        let delegated_router = build_simple_router(|route| {
492            route.get("/b").to(welcome::delegated);
493        });
494
495        let router = build_router(default_pipeline_chain, pipelines, |route| {
496            route.get("/").to(welcome::index);
497
498            route
499                .get("/hello/:name")
500                .with_path_extractor::<SalutationParams>()
501                .to(welcome::hello);
502
503            route
504                .get("/hello/:name/*")
505                .with_path_extractor::<SalutationParams>()
506                .to(welcome::globbed);
507
508            route
509                .get("/goodbye/:name:[a-zA-Z]+")
510                .with_path_extractor::<SalutationParams>()
511                .to(welcome::goodbye);
512
513            route
514                .get("/add")
515                .with_query_string_extractor::<AddParams>()
516                .to(welcome::add);
517
518            route.get(r"/literal/\:param/\*").to(welcome::literal);
519
520            route.scope("/api", |route| {
521                route.post("/submit").to(api::submit);
522            });
523
524            route.associate("/resource", |route| {
525                route.post().to(resource::create);
526                route.patch().to(resource::update);
527                route.delete().to(resource::destroy);
528                route.get_or_head().to(resource::show);
529            });
530
531            route.delegate("/delegated").to_router(delegated_router);
532
533            route.get("/trailing-slash/").to(welcome::trailing_slash);
534        });
535
536        let new_service = GothamService::new(router);
537
538        let call = move |req| {
539            let mut service = new_service.connect("127.0.0.1:10000".parse().unwrap());
540            futures_executor::block_on(service.call(req)).unwrap()
541        };
542
543        let response = call(Request::get("/").body(Body::empty()).unwrap());
544        assert_eq!(response.status(), StatusCode::OK);
545
546        let response = call(Request::post("/api/submit").body(Body::empty()).unwrap());
547        assert_eq!(response.status(), StatusCode::ACCEPTED);
548
549        let response = call(Request::get("/hello/world").body(Body::empty()).unwrap());
550        assert_eq!(response.status(), StatusCode::OK);
551        let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
552            .unwrap()
553            .to_vec();
554        assert_eq!(&String::from_utf8(response_bytes).unwrap(), "Hello, world!");
555
556        let response = call(
557            Request::get("/hello/world/more/path/here/handled/by/glob")
558                .body(Body::empty())
559                .unwrap(),
560        );
561        assert_eq!(response.status(), StatusCode::OK);
562        let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
563            .unwrap()
564            .to_vec();
565        assert_eq!(&String::from_utf8(response_bytes).unwrap(), "Globbed");
566
567        let response = call(Request::get("/delegated/b").body(Body::empty()).unwrap());
568        assert_eq!(response.status(), StatusCode::OK);
569        let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
570            .unwrap()
571            .to_vec();
572        assert_eq!(&String::from_utf8(response_bytes).unwrap(), "Delegated");
573
574        let response = call(Request::get("/goodbye/world").body(Body::empty()).unwrap());
575        assert_eq!(response.status(), StatusCode::OK);
576        let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
577            .unwrap()
578            .to_vec();
579        assert_eq!(
580            &String::from_utf8(response_bytes).unwrap(),
581            "Goodbye, world!"
582        );
583
584        let response = call(Request::get("/goodbye/9875").body(Body::empty()).unwrap());
585        assert_eq!(response.status(), StatusCode::NOT_FOUND);
586
587        let response = call(
588            Request::get("/literal/:param/*")
589                .body(Body::empty())
590                .unwrap(),
591        );
592        assert_eq!(response.status(), StatusCode::CREATED);
593
594        let response = call(Request::get("/literal/a/b").body(Body::empty()).unwrap());
595        assert_eq!(response.status(), StatusCode::NOT_FOUND);
596
597        let response = call(Request::get("/add?x=16&y=71").body(Body::empty()).unwrap());
598        assert_eq!(response.status(), StatusCode::OK);
599        let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
600            .unwrap()
601            .to_vec();
602        assert_eq!(&String::from_utf8(response_bytes).unwrap(), "16 + 71 = 87");
603
604        let response = call(Request::post("/resource").body(Body::empty()).unwrap());
605        assert_eq!(response.status(), StatusCode::CREATED);
606
607        let response = call(Request::patch("/resource").body(Body::empty()).unwrap());
608        assert_eq!(response.status(), StatusCode::ACCEPTED);
609
610        let response = call(Request::delete("/resource").body(Body::empty()).unwrap());
611        assert_eq!(response.status(), StatusCode::ACCEPTED);
612
613        let response = call(Request::get("/resource").body(Body::empty()).unwrap());
614        assert_eq!(response.status(), StatusCode::OK);
615        let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
616            .unwrap()
617            .to_vec();
618        assert_eq!(&response_bytes[..], b"It's a resource.");
619
620        let response = call(
621            Request::get("/trailing-slash/")
622                .body(Body::empty())
623                .unwrap(),
624        );
625        assert_eq!(response.status(), StatusCode::OK);
626        let response = call(Request::get("/trailing-slash").body(Body::empty()).unwrap());
627        assert_eq!(response.status(), StatusCode::OK);
628    }
629}