1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
use std::{
    collections::HashMap,
    fmt::{Display, Formatter},
    io::Error,
};

use serde_json::Value;

const CRLF: &str = "\r\n";

/// The HTTP Method of a request.
///
/// See [RFC 7231](https://tools.ietf.org/html/rfc7231#section-4) for more information.
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub enum Method {
    /// HEAD method.
    Head,
    /// GET method.
    Get,
    /// POST method.
    Post,
    /// PUT method.
    Put,
    /// DELETE method.
    Delete,
}

impl From<String> for Method {
    fn from(val: String) -> Self {
        match val.as_str() {
            "HEAD" => Self::Head,
            "GET" => Self::Get,
            "POST" => Self::Post,
            "PUT" => Self::Put,
            "DELETE" => Self::Delete,
            _ => todo!(),
        }
    }
}

impl Display for Method {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Head => write!(f, "HEAD"),
            Self::Get => write!(f, "GET"),
            Self::Post => write!(f, "POST"),
            Self::Put => write!(f, "PUT"),
            Self::Delete => write!(f, "DELETE"),
        }
    }
}

/// The HTTP Body of a request.
///
/// See [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.3) for more information.
#[derive(Debug, Clone)]
pub enum Body {
    /// No body.
    None,
    /// A text/plain body.
    Text(String),
    /// A deserialized application/json body.
    Json(Value),
}

impl Body {
    /// # Panics
    ///
    /// Will panic if the content type is `application/json` and the body is not valid JSON.
    #[must_use]
    pub fn parse(body: String, content_type: Option<&String>) -> Self {
        match content_type {
            Some(content_type) => match content_type.as_str() {
                "application/json" => Self::Json(serde_json::from_str(&body).unwrap()),
                _ => Self::Text(body),
            },
            None => Self::Text(body),
        }
    }
}

impl Display for Body {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::None => write!(f, ""),
            Self::Text(body) => write!(f, "{body}"),
            Self::Json(body) => write!(f, "{body}"),
        }
    }
}

/// An HTTP 1.1 request.
///
/// See [RFC 7230](https://tools.ietf.org/html/rfc7230) for more information.
#[derive(Debug, Clone)]
pub struct Request {
    /// The HTTP method of the request.
    pub method: Method,
    /// The path of the request.
    pub path: String,
    /// The parsed query of the request.
    pub query: HashMap<String, String>,
    /// The parsed headers of the request.
    pub headers: HashMap<String, String>,
    /// The body of the request.
    pub body: Body,
}

/// Try to parse a request object from a buffer.
///
/// # Errors
///
/// Will return an error if the buffer is empty.
///
/// # Panics
///
/// Will panic if the request method is not valid.
impl TryFrom<&[u8; 1024]> for Request {
    type Error = Error;

    fn try_from(buf: &[u8; 1024]) -> Result<Self, Self::Error> {
        let mut body = Body::None;
        let mut headers = HashMap::new();

        if buf[0] == 0 {
            return Err(Error::new(std::io::ErrorKind::InvalidData, "Empty request"));
        }

        let mut buff_read: usize = 2;
        let mut lines = buf.split(|&byte| byte == b'\n');

        let request_line = lines.next().unwrap();
        buff_read += request_line.len() + 1;
        let mut request_line = request_line.split(|&byte| byte == b' ');

        let method: Method = String::from_utf8_lossy(request_line.next().unwrap())
            .to_string()
            .into();

        let uri = String::from_utf8_lossy(request_line.next().unwrap());
        let mut uri = uri.splitn(2, |byte| byte == '?');

        let path = uri.next().unwrap().trim().to_string();

        let query = uri.next().map_or_else(HashMap::new, |query| {
            query
                .trim()
                .split('&')
                .map(|pair| {
                    let mut pair = pair.split('=');
                    let key = pair.next().unwrap().trim().to_string();
                    let value = pair.next().unwrap().trim().to_string();

                    (key, value)
                })
                .collect::<HashMap<String, String>>()
        });

        for line in lines {
            if line == b"\r" {
                break;
            }

            let mut header = line.splitn(2, |&byte| byte == b':');
            let name = header.next().unwrap();
            let value = header.next().unwrap();

            let value = String::from_utf8_lossy(value).trim().to_string();
            let name = String::from_utf8_lossy(name).trim().to_string();

            headers.insert(name, value);
            buff_read += line.len() + 1;
        }

        if let Some(content_length) = headers.get("Content-Length") {
            let content_length = content_length.parse::<usize>().unwrap();

            body = Body::parse(
                String::from_utf8_lossy(&buf[buff_read..buff_read + content_length])
                    .trim()
                    .to_string(),
                headers.get("Content-Type"),
            );
        }

        Ok(Self {
            method,
            path,
            query,
            headers,
            body,
        })
    }
}

impl Display for Request {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let mut str_request = String::new();

        str_request.push_str(&format!("{} {} HTTP/1.1{CRLF}", self.method, self.path));
        for (name, value) in &self.headers {
            str_request.push_str(&format!("{name}: {value}{CRLF}"));
        }
        str_request.push_str(CRLF);
        str_request.push_str(&self.body.to_string());

        write!(f, "{str_request}")
    }
}