gotham/extractor/
path.rs

1use hyper::body::HttpBody;
2use hyper::{Body, Response};
3use serde::{Deserialize, Deserializer};
4
5use crate::router::response::StaticResponseExtender;
6use crate::state::{State, StateData};
7
8/// Defines a binding for storing the dynamic segments of the `Request` path in `State`. On failure
9/// the `StaticResponseExtender` implementation extends the `Response` to indicate why the
10/// extraction process failed.
11///
12/// This trait is automatically implemented when the struct implements the `Deserialize`,
13/// `StateData` and `StaticResponseExtender` traits. These traits can be derived, or implemented
14/// manually for greater control.
15///
16/// The default behaviour given by deriving all three traits will use the automatically derived
17/// behaviour from Serde, and result in a `400 Bad Request` HTTP response if the path segments are
18/// not able to be deserialized.
19///
20/// # Examples
21///
22/// ```rust
23/// # use hyper::{Body, Response, StatusCode};
24/// # use gotham::state::State;
25/// # use gotham::helpers::http::response::create_response;
26/// # use gotham::router::{build_simple_router, Router};
27/// # use gotham::prelude::*;
28/// # use gotham::test::TestServer;
29/// # use serde::Deserialize;
30/// #
31/// #[derive(Deserialize, StateData, StaticResponseExtender)]
32/// struct MyPathParams {
33///     id: i32,
34///     slug: String,
35/// }
36///
37/// fn handler(mut state: State) -> (State, Response<Body>) {
38///     let MyPathParams { id, slug } = MyPathParams::take_from(&mut state);
39///     let body = format!("id = {}, slug = {}", id, slug);
40///
41///     let response = create_response(
42///         &state,
43///         StatusCode::OK,
44///         mime::TEXT_PLAIN,
45///         body,
46///     );
47///
48///     (state, response)
49/// }
50///
51/// fn router() -> Router {
52///     build_simple_router(|route| {
53///         route
54///             .get("/article/:id/:slug")
55///             .with_path_extractor::<MyPathParams>()
56///             .to(handler);
57///     })
58/// }
59/// #
60/// # fn main() {
61/// #   let test_server = TestServer::new(router()).unwrap();
62/// #   let response = test_server
63/// #       .client()
64/// #       .get("http://example.com/article/1551/ten-reasons-serde-is-amazing")
65/// #       .perform()
66/// #       .unwrap();
67/// #   assert_eq!(response.status(), StatusCode::OK);
68/// #   let body = response.read_utf8_body().unwrap();
69/// #   assert_eq!(body, "id = 1551, slug = ten-reasons-serde-is-amazing");
70/// # }
71pub trait PathExtractor<B>:
72    for<'de> Deserialize<'de> + StaticResponseExtender<ResBody = B> + StateData
73where
74    B: HttpBody,
75{
76}
77
78impl<T, B> PathExtractor<B> for T
79where
80    B: HttpBody,
81    for<'de> T: Deserialize<'de> + StaticResponseExtender<ResBody = B> + StateData,
82{
83}
84
85/// A `PathExtractor` that does not extract/store any data from the `Request` path.
86///
87/// This is the default `PathExtractor` which is applied to a route when no other `PathExtractor`
88/// is provided. It ignores any dynamic path segments, and always succeeds during deserialization.
89pub struct NoopPathExtractor;
90
91// This doesn't get derived correctly if we just `#[derive(Deserialize)]` above, because the
92// Deserializer expects to _ignore_ a value, not just do nothing. By filling in the impl ourselves,
93// we can explicitly do nothing.
94impl<'de> Deserialize<'de> for NoopPathExtractor {
95    fn deserialize<D>(_de: D) -> Result<Self, D::Error>
96    where
97        D: Deserializer<'de>,
98    {
99        Ok(NoopPathExtractor)
100    }
101}
102
103impl StateData for NoopPathExtractor {}
104
105impl StaticResponseExtender for NoopPathExtractor {
106    type ResBody = Body;
107    fn extend(_state: &mut State, _res: &mut Response<Body>) {}
108}