stackforge_core/layer/http/
builder.rs1#[derive(Debug, Clone)]
45pub struct HttpRequestBuilder {
46 method: String,
47 uri: String,
48 version: String,
49 headers: Vec<(String, String)>,
50 body: Vec<u8>,
51}
52
53impl Default for HttpRequestBuilder {
54 fn default() -> Self {
55 Self {
56 method: "GET".to_owned(),
57 uri: "/".to_owned(),
58 version: "HTTP/1.1".to_owned(),
59 headers: Vec::new(),
60 body: Vec::new(),
61 }
62 }
63}
64
65impl HttpRequestBuilder {
66 #[must_use]
68 pub fn new() -> Self {
69 Self::default()
70 }
71
72 #[must_use]
74 pub fn method(mut self, method: &str) -> Self {
75 self.method = method.to_owned();
76 self
77 }
78
79 #[must_use]
81 pub fn uri(mut self, uri: &str) -> Self {
82 self.uri = uri.to_owned();
83 self
84 }
85
86 #[must_use]
88 pub fn version(mut self, version: &str) -> Self {
89 self.version = version.to_owned();
90 self
91 }
92
93 #[must_use]
98 pub fn header(mut self, name: &str, value: &str) -> Self {
99 self.headers.push((name.to_owned(), value.to_owned()));
100 self
101 }
102
103 #[must_use]
108 pub fn body(mut self, body: Vec<u8>) -> Self {
109 self.body = body;
110 self
111 }
112
113 #[must_use]
126 pub fn build(&self) -> Vec<u8> {
127 let mut out = Vec::new();
128
129 out.extend_from_slice(self.method.as_bytes());
131 out.push(b' ');
132 out.extend_from_slice(self.uri.as_bytes());
133 out.push(b' ');
134 out.extend_from_slice(self.version.as_bytes());
135 out.extend_from_slice(b"\r\n");
136
137 for (name, value) in &self.headers {
139 out.extend_from_slice(name.as_bytes());
140 out.extend_from_slice(b": ");
141 out.extend_from_slice(value.as_bytes());
142 out.extend_from_slice(b"\r\n");
143 }
144
145 out.extend_from_slice(b"\r\n");
147
148 out.extend_from_slice(&self.body);
150
151 out
152 }
153}
154
155#[derive(Debug, Clone)]
168pub struct HttpResponseBuilder {
169 version: String,
170 status_code: u16,
171 reason: String,
172 headers: Vec<(String, String)>,
173 body: Vec<u8>,
174}
175
176impl Default for HttpResponseBuilder {
177 fn default() -> Self {
178 Self {
179 version: "HTTP/1.1".to_owned(),
180 status_code: 200,
181 reason: "OK".to_owned(),
182 headers: Vec::new(),
183 body: Vec::new(),
184 }
185 }
186}
187
188impl HttpResponseBuilder {
189 #[must_use]
191 pub fn new() -> Self {
192 Self::default()
193 }
194
195 #[must_use]
197 pub fn version(mut self, version: &str) -> Self {
198 self.version = version.to_owned();
199 self
200 }
201
202 #[must_use]
216 pub fn status(mut self, code: u16, reason: &str) -> Self {
217 self.status_code = code;
218 self.reason = reason.to_owned();
219 self
220 }
221
222 #[must_use]
227 pub fn header(mut self, name: &str, value: &str) -> Self {
228 self.headers.push((name.to_owned(), value.to_owned()));
229 self
230 }
231
232 #[must_use]
237 pub fn body(mut self, body: Vec<u8>) -> Self {
238 self.body = body;
239 self
240 }
241
242 #[must_use]
255 pub fn build(&self) -> Vec<u8> {
256 let mut out = Vec::new();
257
258 out.extend_from_slice(self.version.as_bytes());
260 out.push(b' ');
261 out.extend_from_slice(self.status_code.to_string().as_bytes());
262 out.push(b' ');
263 out.extend_from_slice(self.reason.as_bytes());
264 out.extend_from_slice(b"\r\n");
265
266 for (name, value) in &self.headers {
268 out.extend_from_slice(name.as_bytes());
269 out.extend_from_slice(b": ");
270 out.extend_from_slice(value.as_bytes());
271 out.extend_from_slice(b"\r\n");
272 }
273
274 out.extend_from_slice(b"\r\n");
276
277 out.extend_from_slice(&self.body);
279
280 out
281 }
282}
283
284#[cfg(test)]
289mod tests {
290 use super::*;
291 use crate::layer::http::request::HttpRequest;
292 use crate::layer::http::response::HttpResponse;
293
294 #[test]
295 fn test_request_builder_defaults() {
296 let bytes = HttpRequestBuilder::new().build();
297 assert!(bytes.starts_with(b"GET / HTTP/1.1\r\n"));
298 assert!(bytes.ends_with(b"\r\n\r\n"));
299 }
300
301 #[test]
302 fn test_request_builder_full() {
303 let body = b"key=value".to_vec();
304 let bytes = HttpRequestBuilder::new()
305 .method("POST")
306 .uri("/submit")
307 .version("HTTP/1.1")
308 .header("Host", "example.com")
309 .header("Content-Type", "application/x-www-form-urlencoded")
310 .header("Content-Length", &body.len().to_string())
311 .body(body.clone())
312 .build();
313
314 let req = HttpRequest::parse(&bytes).expect("should parse");
316 assert_eq!(req.method, "POST");
317 assert_eq!(req.uri, "/submit");
318 assert_eq!(req.version, "HTTP/1.1");
319 assert_eq!(req.headers.len(), 3);
320 let parsed_body = &bytes[req.body_offset..];
321 assert_eq!(parsed_body, body.as_slice());
322 }
323
324 #[test]
325 fn test_response_builder_defaults() {
326 let bytes = HttpResponseBuilder::new().build();
327 assert!(bytes.starts_with(b"HTTP/1.1 200 OK\r\n"));
328 assert!(bytes.ends_with(b"\r\n\r\n"));
329 }
330
331 #[test]
332 fn test_response_builder_full() {
333 let body = b"Hello, World!".to_vec();
334 let bytes = HttpResponseBuilder::new()
335 .status(200, "OK")
336 .header("Content-Type", "text/plain")
337 .header("Content-Length", &body.len().to_string())
338 .body(body.clone())
339 .build();
340
341 let resp = HttpResponse::parse(&bytes).expect("should parse");
343 assert_eq!(resp.status_code, 200);
344 assert_eq!(resp.reason, "OK");
345 assert_eq!(resp.headers.len(), 2);
346 let parsed_body = &bytes[resp.body_offset..];
347 assert_eq!(parsed_body, body.as_slice());
348 }
349
350 #[test]
351 fn test_response_builder_404() {
352 let bytes = HttpResponseBuilder::new().status(404, "Not Found").build();
353
354 assert!(bytes.starts_with(b"HTTP/1.1 404 Not Found\r\n"));
355 let resp = HttpResponse::parse(&bytes).unwrap();
356 assert_eq!(resp.status_code, 404);
357 assert_eq!(resp.reason, "Not Found");
358 }
359
360 #[test]
361 fn test_request_builder_http10() {
362 let bytes = HttpRequestBuilder::new()
363 .version("HTTP/1.0")
364 .uri("/old")
365 .build();
366 assert!(bytes.starts_with(b"GET /old HTTP/1.0\r\n"));
367 }
368
369 #[test]
370 fn test_multiple_headers_ordering() {
371 let bytes = HttpRequestBuilder::new()
372 .header("A", "1")
373 .header("B", "2")
374 .header("C", "3")
375 .build();
376
377 let req = HttpRequest::parse(&bytes).unwrap();
378 assert_eq!(req.headers[0], ("A", "1"));
379 assert_eq!(req.headers[1], ("B", "2"));
380 assert_eq!(req.headers[2], ("C", "3"));
381 }
382}