1use std::{collections::HashMap, str::FromStr};
5
6use crate::method::HttpMethod;
7
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct Request {
11 pub method: HttpMethod,
13 pub path: String,
15 pub headers: HashMap<String, String>,
16 pub body: Option<String>,
17}
18
19impl FromStr for Request {
20 type Err = std::io::Error;
21
22 fn from_str(s: &str) -> Result<Self, Self::Err> {
23 let first_line = s
24 .lines()
25 .next()
26 .ok_or({
27 std::io::Error::new(
28 std::io::ErrorKind::InvalidInput,
29 "failed getting first http line",
30 )
31 })?
32 .split_whitespace()
33 .collect::<Vec<&str>>();
34 let (method, path): (HttpMethod, String) = match first_line.as_slice() {
35 [method_str, path, _version] => {
36 (HttpMethod::from_str_val(method_str), (*path).to_string())
37 }
38 _ => {
39 return Err(std::io::Error::new(
40 std::io::ErrorKind::InvalidInput,
41 "invalid http top header",
42 ));
43 }
44 };
45
46 let headers: HashMap<String, String> = s
47 .lines()
48 .take_while(|line| !line.is_empty())
49 .skip(1)
50 .filter_map(|line| {
51 line.split_once(':')
52 .map(|(k, v)| (k.trim().to_string(), v.trim().to_string()))
53 })
54 .collect();
55
56 let body = if method == HttpMethod::Get {
57 None
58 } else {
59 s.split_once("\r\n\r\n")
60 .map(|v| v.1.to_owned())
61 .filter(|v| !v.is_empty())
62 };
63
64 let req: Request = Request {
65 method,
66 path,
67 headers,
68 body,
69 };
70 Ok(req)
71 }
72}