protorbit/http/
request.rs1use anyhow::{anyhow, Result};
2use std::collections::HashMap;
3
4use crate::http::Method;
5
6use super::Version;
7
8#[derive(Debug, PartialEq, Clone)]
9pub struct Request {
10 pub method: super::method::Method,
11 pub path: String,
12 pub version: super::version::Version,
13 pub headers: HashMap<String, String>,
14 pub body: String,
15}
16
17impl Request {
18 pub fn new(
19 method: super::method::Method,
20 path: String,
21 version: super::version::Version,
22 headers: HashMap<String, String>,
23 body: String,
24 ) -> Self {
25 Self {
26 method,
27 path,
28 version,
29 headers,
30 body,
31 }
32 }
33
34 fn parse(data: impl Into<String>) -> Result<Self> {
35 let data = Into::<String>::into(data);
36 let mut lines = data.lines();
37 let req_line = match lines.next() {
38 Some(line) => line,
39 None => return Err(anyhow!("No request line")),
40 };
41
42 let (method, rest) = match req_line.split_once(" ") {
43 Some(kv) => kv,
44 None => return Err(anyhow!("Invalid request line")),
45 };
46 let method = Method::try_from(Into::<String>::into(method))?;
47 let (uri, version) = match rest.split_once(" ") {
48 Some(kv) => kv,
49 None => return Err(anyhow!("Invalid request line")),
50 };
51 let version = Version::try_from(Into::<String>::into(version))?;
52 let mut headers: HashMap<String, String> = HashMap::new();
53
54 while let Some(header) = lines.next() {
55 if header == "" {
57 break;
58 }
59 if let Some((k, v)) = header.split_once(':') {
60 let _ = headers.insert(k.into(), v.into());
61 }
62 }
63
64 let mut body = String::new();
65 while let Some(line) = lines.next() {
66 body.push_str(line);
67 body.push('\n'); }
69
70 Ok(Self {
71 method,
72 path: uri.into(),
73 version,
74 headers,
75 body,
76 })
77 }
78}
79
80impl TryFrom<String> for Request {
81 type Error = anyhow::Error;
82
83 fn try_from(value: String) -> Result<Self, Self::Error> {
84 Request::parse(Into::<String>::into(value))
85 }
86}
87
88impl Into<String> for Request {
89 fn into(self) -> String {
90 let mut buffer = String::new();
91 let s = format!(
92 "{} {} {}\r\n",
93 Into::<String>::into(self.method),
94 self.path,
95 Into::<String>::into(self.version),
96 );
97 buffer.push_str(s.as_str());
98 for (h, v) in self.headers.iter() {
99 buffer.push_str(&format!("{}: {}\r\n", h, v));
100 }
101 buffer.push_str("\r\n");
102 buffer.push_str(&self.body);
103 buffer
104 }
105}
106
107#[cfg(test)]
108mod test {
109 #[test]
110 fn test() {
111 let raw_req = "GET / HTTP/1.1\r\nContent-Type: application/json\r\n\r\n{}";
112 let req = super::Request::try_from(Into::<String>::into(raw_req)).unwrap();
113 let expected_req = super::Request::new(
114 crate::http::method::Method::GET,
115 "/".to_string(),
116 crate::http::version::Version::HTTP1_1,
117 std::collections::HashMap::from([(
118 "Content-Type".to_string(),
119 "application/json".to_string(),
120 )]),
121 "{}".to_string(),
122 );
123 assert_eq!(req, expected_req);
124 let request_string: String = req.into();
125 assert_eq!(raw_req, request_string);
126 }
127}