Skip to main content

rust_web_server/extract/
mod.rs

1#[cfg(test)]
2mod tests;
3
4use std::collections::HashMap;
5
6use crate::header::Header;
7use crate::mime_type::MimeType;
8use crate::range::Range;
9use crate::request::Request;
10use crate::response::{Response, STATUS_CODE_REASON_PHRASE};
11use crate::url::URL;
12
13/// Types that can be extracted from a [`Request`].
14///
15/// Implement this trait to build reusable request-parsing logic that maps
16/// cleanly to an HTTP error response on failure.
17///
18/// # Example
19///
20/// ```rust,no_run
21/// use rust_web_server::extract::{Body, BodyText, Query, FromRequest};
22/// use rust_web_server::request::Request;
23///
24/// // inside a Controller::process implementation
25/// fn handle(request: &Request) {
26///     let body = Body::from_request(request).unwrap();
27///     let text = BodyText::from_request(request).unwrap();
28///     let params = Query::from_request(request).unwrap();
29///     let id = params.get("id").map(String::as_str).unwrap_or("");
30/// }
31/// ```
32pub trait FromRequest: Sized {
33    /// Extract `Self` from `request`, or return a ready-to-send error [`Response`].
34    fn from_request(request: &Request) -> Result<Self, Response>;
35}
36
37/// Raw request body bytes.
38///
39/// Never fails — an empty body produces an empty `Vec`.
40#[derive(Debug)]
41pub struct Body(pub Vec<u8>);
42
43impl FromRequest for Body {
44    fn from_request(request: &Request) -> Result<Self, Response> {
45        Ok(Body(request.body.clone()))
46    }
47}
48
49impl Body {
50    pub fn into_bytes(self) -> Vec<u8> {
51        self.0
52    }
53}
54
55/// Request body decoded as UTF-8 text.
56///
57/// Returns `400 Bad Request` if the body is not valid UTF-8.
58#[derive(Debug)]
59pub struct BodyText(pub String);
60
61impl FromRequest for BodyText {
62    fn from_request(request: &Request) -> Result<Self, Response> {
63        String::from_utf8(request.body.clone())
64            .map(BodyText)
65            .map_err(|_| bad_request(request, "request body is not valid UTF-8"))
66    }
67}
68
69impl BodyText {
70    pub fn as_str(&self) -> &str {
71        &self.0
72    }
73}
74
75/// Parsed query parameters from the request URI.
76///
77/// Never fails — a URI with no query string produces an empty map.
78#[derive(Debug)]
79///
80/// # Example
81///
82/// ```rust,no_run
83/// use rust_web_server::extract::{Query, FromRequest};
84/// use rust_web_server::request::Request;
85///
86/// fn handle(request: &Request) {
87///     let q = Query::from_request(request).unwrap();
88///     let page = q.get("page").map(String::as_str).unwrap_or("1");
89/// }
90/// ```
91pub struct Query(pub HashMap<String, String>);
92
93impl FromRequest for Query {
94    fn from_request(request: &Request) -> Result<Self, Response> {
95        let query_str = request.request_uri
96            .splitn(2, '?')
97            .nth(1)
98            .unwrap_or("");
99        Ok(Query(URL::parse_query(query_str)))
100    }
101}
102
103impl Query {
104    pub fn get(&self, key: &str) -> Option<&String> {
105        self.0.get(key)
106    }
107}
108
109/// Clone of all request headers.
110#[derive(Debug)]
111pub struct RequestHeaders(pub Vec<Header>);
112
113impl FromRequest for RequestHeaders {
114    fn from_request(request: &Request) -> Result<Self, Response> {
115        Ok(RequestHeaders(request.headers.clone()))
116    }
117}
118
119impl RequestHeaders {
120    /// Return the value of the first header matching `name` (case-insensitive).
121    pub fn get(&self, name: &str) -> Option<&str> {
122        let lower = name.to_lowercase();
123        self.0.iter()
124            .find(|h| h.name.to_lowercase() == lower)
125            .map(|h| h.value.as_str())
126    }
127}
128
129fn bad_request(request: &Request, msg: &str) -> Response {
130    let header_list = Header::get_header_list(request);
131    let cr = Range::get_content_range(msg.as_bytes().to_vec(), MimeType::TEXT_PLAIN.to_string());
132    Response::get_response(
133        STATUS_CODE_REASON_PHRASE.n400_bad_request,
134        Some(header_list),
135        Some(vec![cr]),
136    )
137}