add http response struct
This commit is contained in:
		
							parent
							
								
									57dcb801e8
								
							
						
					
					
						commit
						fb164ba137
					
				| @ -1,6 +1,6 @@ | |||||||
| [package] | [package] | ||||||
| name = "http" | name = "http" | ||||||
| version = "0.1.1" | version = "0.1.2" | ||||||
| edition = "2021" | edition = "2021" | ||||||
| 
 | 
 | ||||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||||
|  | |||||||
| @ -8,11 +8,17 @@ | |||||||
| //! * HTTP Headers not parsed
 | //! * HTTP Headers not parsed
 | ||||||
| 
 | 
 | ||||||
| mod body; | mod body; | ||||||
|  | mod message; | ||||||
| mod request; | mod request; | ||||||
|  | mod response; | ||||||
| mod start_line; | mod start_line; | ||||||
|  | mod status; | ||||||
| mod version; | mod version; | ||||||
| 
 | 
 | ||||||
| pub use body::HTTPBody; | pub use body::HTTPBody; | ||||||
|  | pub use message::JSONMessage; | ||||||
| pub use request::HTTPRequest; | pub use request::HTTPRequest; | ||||||
|  | pub use response::HTTPResponse; | ||||||
| pub use start_line::HTTPStartLine; | pub use start_line::HTTPStartLine; | ||||||
|  | pub use status::{HTTPStatusCode, HTTPStatusLine}; | ||||||
| pub use version::HTTPVersion; | pub use version::HTTPVersion; | ||||||
|  | |||||||
							
								
								
									
										94
									
								
								src/message.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/message.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,94 @@ | |||||||
|  | use json::JsonValue; | ||||||
|  | use std::collections::HashMap; | ||||||
|  | 
 | ||||||
|  | const JSON_DELIMITER: &'static str = ","; | ||||||
|  | 
 | ||||||
|  | /// JSONMessage aims to build a JSON object from an `HashMap`
 | ||||||
|  | pub struct JSONMessage { | ||||||
|  |     message: HashMap<String, String>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for JSONMessage { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         JSONMessage { | ||||||
|  |             message: HashMap::new(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// try to convert `JSONMessage` into `json::JsonValue`
 | ||||||
|  | impl TryInto<JsonValue> for JSONMessage { | ||||||
|  |     type Error = String; | ||||||
|  |     fn try_into(self) -> Result<JsonValue, Self::Error> { | ||||||
|  |         let message = format!(r#"{{{}}}"#, self.build_json()); | ||||||
|  |         match json::parse(&message) { | ||||||
|  |             Ok(r) => Ok(r), | ||||||
|  |             Err(e) => Err(format!( | ||||||
|  |                 "unable to parse the HTTPMessage correctly: {}, err={}", | ||||||
|  |                 message, e | ||||||
|  |             )), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl JSONMessage { | ||||||
|  |     pub fn put(&mut self, key: &str, value: &str) { | ||||||
|  |         self.message.insert(key.to_string(), value.to_string()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// associated function to build an HTTPMessage error
 | ||||||
|  |     pub fn error(message: &str) -> Option<JsonValue> { | ||||||
|  |         let mut http_message = JSONMessage::default(); | ||||||
|  |         http_message.put("error", message); | ||||||
|  | 
 | ||||||
|  |         match message.try_into() { | ||||||
|  |             Ok(m) => Some(m), | ||||||
|  |             Err(e) => { | ||||||
|  |                 log::error!( | ||||||
|  |                     "unable to parse the message: {} into JSON, err={}", | ||||||
|  |                     message, | ||||||
|  |                     e | ||||||
|  |                 ); | ||||||
|  |                 return None; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// loops over all the HashMap keys, builds a JSON key value for each one and join them with `JSON_DELIMITER`
 | ||||||
|  |     fn build_json(self) -> String { | ||||||
|  |         let unstruct: Vec<String> = self | ||||||
|  |             .message | ||||||
|  |             .keys() | ||||||
|  |             .map(|k| format!(r#""{}":{:?}"#, k, self.message.get(k).unwrap())) | ||||||
|  |             .collect(); | ||||||
|  |         let joined = unstruct.join(JSON_DELIMITER); | ||||||
|  |         joined | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn test_message() { | ||||||
|  |     let mut message = JSONMessage::default(); | ||||||
|  |     message.put("email", "toto@toto.fr"); | ||||||
|  |     message.put("password", "tata"); | ||||||
|  | 
 | ||||||
|  |     let mut json_result: Result<JsonValue, String> = message.try_into(); | ||||||
|  |     assert!(json_result.is_ok()); | ||||||
|  | 
 | ||||||
|  |     let mut json = json_result.unwrap(); | ||||||
|  |     assert!(json.has_key("email")); | ||||||
|  |     assert!(json.has_key("password")); | ||||||
|  | 
 | ||||||
|  |     let empty_message = JSONMessage::default(); | ||||||
|  |     json_result = empty_message.try_into(); | ||||||
|  |     assert!(json_result.is_ok()); | ||||||
|  | 
 | ||||||
|  |     json = json_result.unwrap(); | ||||||
|  |     assert_eq!("{}", json.dump().to_string()); | ||||||
|  | 
 | ||||||
|  |     let mut bad_message = JSONMessage::default(); | ||||||
|  |     bad_message.put("\"", ""); | ||||||
|  | 
 | ||||||
|  |     json_result = bad_message.try_into(); | ||||||
|  |     assert!(json_result.is_err()); | ||||||
|  | } | ||||||
							
								
								
									
										112
									
								
								src/response.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/response.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,112 @@ | |||||||
|  | use json::JsonValue; | ||||||
|  | 
 | ||||||
|  | use crate::{HTTPStatusCode, HTTPStatusLine}; | ||||||
|  | 
 | ||||||
|  | const UNEXPECTED_ERROR: &'static str = r#"{"error":"unexpected error occurred"}"#; | ||||||
|  | const NOT_FOUND_ERROR: &'static str = r#"{"error":"url not found"}"#; | ||||||
|  | const FORBIDDEN_ERROR: &'static str = r#"{"error":"url forbidden"}"#; | ||||||
|  | const BAD_REQUEST_ERROR: &'static str = r#"{"error":"bad request"}"#; | ||||||
|  | 
 | ||||||
|  | const STATUS_OK: &'static str = r#"{"status":"ok"}"#; | ||||||
|  | 
 | ||||||
|  | /// HTTPResponse represents an HTTP response (headers are not parsed)
 | ||||||
|  | /// NOTE: for simplicity, only JSON body are allowed
 | ||||||
|  | pub struct HTTPResponse { | ||||||
|  |     status_line: HTTPStatusLine, | ||||||
|  |     body: JsonValue, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Into<String> for HTTPResponse { | ||||||
|  |     fn into(self) -> String { | ||||||
|  |         let body: String = json::stringify(self.body.clone()); | ||||||
|  |         let status_line: String = self.status_line.into(); | ||||||
|  |         format!( | ||||||
|  |             "{}\r\nContent-Type: application/json\r\nContent-Length:{}\r\n\r\n{}", | ||||||
|  |             status_line, | ||||||
|  |             body.len(), | ||||||
|  |             body | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for HTTPResponse { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         HTTPResponse { | ||||||
|  |             status_line: HTTPStatusLine::default(), | ||||||
|  |             body: json::parse(r#"{}"#).unwrap(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl HTTPResponse { | ||||||
|  |     pub fn as_500(message: Option<json::JsonValue>) -> Self { | ||||||
|  |         let mut response = Self::default(); | ||||||
|  | 
 | ||||||
|  |         response | ||||||
|  |             .status_line | ||||||
|  |             .set_status_code(HTTPStatusCode::Http500); | ||||||
|  | 
 | ||||||
|  |         response.body = { | ||||||
|  |             match message { | ||||||
|  |                 Some(m) => m, | ||||||
|  |                 None => json::parse(UNEXPECTED_ERROR).unwrap(), | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         response | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn as_404() -> Self { | ||||||
|  |         let mut response = Self::default(); | ||||||
|  | 
 | ||||||
|  |         response | ||||||
|  |             .status_line | ||||||
|  |             .set_status_code(HTTPStatusCode::Http404); | ||||||
|  |         response.body = json::parse(NOT_FOUND_ERROR).unwrap(); | ||||||
|  | 
 | ||||||
|  |         response | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn as_403() -> Self { | ||||||
|  |         let mut response = HTTPResponse { | ||||||
|  |             status_line: HTTPStatusLine::default(), | ||||||
|  |             body: json::parse(FORBIDDEN_ERROR).unwrap(), | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         response | ||||||
|  |             .status_line | ||||||
|  |             .set_status_code(HTTPStatusCode::Http403); | ||||||
|  | 
 | ||||||
|  |         response | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn as_400() -> Self { | ||||||
|  |         let mut response = HTTPResponse { | ||||||
|  |             status_line: HTTPStatusLine::default(), | ||||||
|  |             body: json::parse(BAD_REQUEST_ERROR).unwrap(), | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         response | ||||||
|  |             .status_line | ||||||
|  |             .set_status_code(HTTPStatusCode::Http400); | ||||||
|  | 
 | ||||||
|  |         response | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn as_200(message: Option<json::JsonValue>) -> Self { | ||||||
|  |         let mut response = Self::default(); | ||||||
|  | 
 | ||||||
|  |         response | ||||||
|  |             .status_line | ||||||
|  |             .set_status_code(HTTPStatusCode::Http200); | ||||||
|  | 
 | ||||||
|  |         response.body = { | ||||||
|  |             match message { | ||||||
|  |                 Some(m) => m, | ||||||
|  |                 None => json::parse(STATUS_OK).unwrap(), | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         response | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										50
									
								
								src/status.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/status.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | |||||||
|  | use crate::HTTPVersion; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, PartialEq)] | ||||||
|  | pub enum HTTPStatusCode { | ||||||
|  |     Http200, | ||||||
|  |     Http400, | ||||||
|  |     Http403, | ||||||
|  |     Http404, | ||||||
|  |     Http500, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Into<String> for HTTPStatusCode { | ||||||
|  |     fn into(self) -> String { | ||||||
|  |         match self { | ||||||
|  |             Self::Http200 => "200".to_string(), | ||||||
|  |             Self::Http400 => "400".to_string(), | ||||||
|  |             Self::Http404 => "404".to_string(), | ||||||
|  |             Self::Http403 => "403".to_string(), | ||||||
|  |             Self::Http500 => "500".to_string(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub struct HTTPStatusLine { | ||||||
|  |     version: HTTPVersion, | ||||||
|  |     status_code: HTTPStatusCode, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for HTTPStatusLine { | ||||||
|  |     fn default() -> HTTPStatusLine { | ||||||
|  |         HTTPStatusLine { | ||||||
|  |             version: HTTPVersion::Http1_1, | ||||||
|  |             status_code: HTTPStatusCode::Http400, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Into<String> for HTTPStatusLine { | ||||||
|  |     fn into(self) -> String { | ||||||
|  |         let version: String = self.version.into(); | ||||||
|  |         let status_code: String = self.status_code.into(); | ||||||
|  |         format! {"{} {}", version, status_code} | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl HTTPStatusLine { | ||||||
|  |     pub fn set_status_code(&mut self, code: HTTPStatusCode) { | ||||||
|  |         self.status_code = code; | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user