use crate::errors::*;
use hyper::body::Body as HyperBody;
use hyper::http::Request as HyperRequest;
use hyper::{header::HeaderValue, HeaderMap, Method, Uri, Version};
#[derive(Debug, PartialEq, Eq)]
pub enum BodyProcessor {
URLENCODED,
XML,
JSON,
MULTIPART,
Other,
}
#[derive(Debug)]
pub struct RhodRequest {
req: Option<HyperRequest<HyperBody>>, }
impl RhodRequest {
pub fn new(req: HyperRequest<HyperBody>) -> RhodRequest {
RhodRequest { req: Some(req) }
}
pub fn uri(&self) -> &Uri {
self.req.as_ref().unwrap().uri()
}
pub fn uri_mut(&mut self) -> &mut Uri {
self.req.as_mut().unwrap().uri_mut()
}
pub fn method(&self) -> &Method {
self.req.as_ref().unwrap().method()
}
pub fn is_post(&self) -> bool {
self.method() == Method::POST
}
pub fn version(&self) -> Version {
self.req.as_ref().unwrap().version()
}
pub fn version_mut(&mut self) -> &mut Version {
self.req.as_mut().unwrap().version_mut()
}
pub fn headers(&self) -> &HeaderMap<HeaderValue> {
self.req.as_ref().unwrap().headers()
}
pub fn headers_mut(&mut self) -> &mut HeaderMap<HeaderValue> {
self.req.as_mut().unwrap().headers_mut()
}
pub async fn body(&mut self) -> RhodResult<Vec<u8>> {
let r = self.req.take().unwrap();
let (header, body) = r.into_parts();
match hyper::body::to_bytes(body).await {
Ok(b) => {
let cloned = b.clone();
self.req = Some(HyperRequest::from_parts(header, HyperBody::from(b)));
Ok(cloned.to_vec())
}
Err(e) => {
self.req = Some(HyperRequest::from_parts(header, HyperBody::empty()));
Err(RhodError::from_string(
format!("Cant parse request body to bytes. {}", e),
RhodErrorLevel::Error,
))
}
}
}
pub fn body_processor(&self) -> Option<BodyProcessor> {
match self.headers().get("Content-Type") {
Some(c) => {
let value = c.to_str();
if let Ok(value) = value {
let value = value.to_lowercase();
if value.contains("application/x-www-form-urlencoded") {
Some(BodyProcessor::URLENCODED)
} else if value.contains("application/xml") {
Some(BodyProcessor::XML)
} else if value.contains("application/json") {
Some(BodyProcessor::JSON)
} else if value.contains("multipart/form-data") {
Some(BodyProcessor::MULTIPART)
} else {
Some(BodyProcessor::Other)
}
} else {
None
}
}
None => None,
}
}
pub fn method_str(&self) -> &str {
self.method().as_str()
}
pub fn version_string(&self) -> String {
format!("{:?}", self.version())
}
pub fn request_line(&self) -> String {
let method = self.method_str();
let path = self.req.as_ref().unwrap().uri().path();
let version = self.version_string();
format!("{} {} {}", method, path, &version)
}
pub fn into_hyper_request(self) -> HyperRequest<HyperBody> {
self.req.unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_uri() {
let query = "par=value&par=value";
let fragment = "2";
let uri = format!("https://www.rust-lang.org/token?{}#{}", query, fragment);
let mut request = RhodRequest::new(
HyperRequest::builder()
.uri(uri)
.body(HyperBody::empty())
.unwrap(),
);
assert_eq!(request.uri().query(), Some(query));
assert_eq!(request.uri().host(), Some("www.rust-lang.org"));
assert_eq!(request.uri_mut().query(), Some(query));
assert_eq!(request.uri_mut().host(), Some("www.rust-lang.org"));
}
#[test]
fn test_method() {
let request = RhodRequest::new(
HyperRequest::builder()
.uri("https://www.rust-lang.org/")
.body(HyperBody::empty())
.unwrap(),
);
assert_eq!(request.method(), Method::GET);
assert!(!request.is_post());
let request = RhodRequest::new(
HyperRequest::post("https://www.rust-lang.org/")
.body(HyperBody::empty())
.unwrap(),
);
assert_eq!(request.method(), Method::POST);
assert!(request.is_post());
}
#[test]
fn test_version() {
let mut request = RhodRequest::new(
HyperRequest::builder()
.version(Version::HTTP_2)
.uri("https://www.rust-lang.org/")
.body(HyperBody::empty())
.unwrap(),
);
assert_eq!(request.version(), Version::HTTP_2);
*request.version_mut() = Version::HTTP_11;
assert_eq!(request.version(), Version::HTTP_11);
}
#[test]
fn test_headers() {
let mut request = RhodRequest::new(
HyperRequest::builder()
.uri("https://www.rust.rs/")
.header("User-Agent", "my-awesome-agent/1.0")
.body(HyperBody::empty())
.unwrap(),
);
assert_eq!(
request.headers().get("User-Agent").unwrap(),
"my-awesome-agent/1.0"
);
assert!(request.headers().get("Accept").is_none());
request
.headers_mut()
.insert("Accept", "text/html".parse().unwrap());
assert_eq!(request.headers().get("Accept").unwrap(), "text/html");
}
#[tokio::test]
async fn test_body() {
let mut request = RhodRequest::new(
HyperRequest::builder()
.uri("https://www.rust.rs/")
.header("User-Agent", "my-awesome-agent/1.0")
.body(HyperBody::from("key1=value1&key2=value2"))
.unwrap(),
);
assert_eq!(
request.body().await.unwrap(),
"key1=value1&key2=value2".as_bytes().to_vec()
);
let mut request = RhodRequest::new(
HyperRequest::builder()
.uri("https://www.rust.rs/")
.header("User-Agent", "my-awesome-agent/1.0")
.body(HyperBody::empty())
.unwrap(),
);
assert_eq!(request.body().await.unwrap(), [])
}
#[test]
fn test_body_processor() {
let mut request = RhodRequest::new(
HyperRequest::builder()
.uri("https://www.rust.rs/")
.header("User-Agent", "my-awesome-agent/1.0")
.body(HyperBody::empty())
.unwrap(),
);
assert!(request.body_processor().is_none());
request.headers_mut().insert(
"Content-Type",
"application/X-WWW-Form-Urlencoded".parse().unwrap(),
);
assert_eq!(request.body_processor().unwrap(), BodyProcessor::URLENCODED);
request
.headers_mut()
.insert("content-type", "application/xml".parse().unwrap());
assert_eq!(request.body_processor().unwrap(), BodyProcessor::XML);
request
.headers_mut()
.insert("content-type", "application/json".parse().unwrap());
assert_eq!(request.body_processor().unwrap(), BodyProcessor::JSON);
request
.headers_mut()
.insert("content-type", "multipart/form-data".parse().unwrap());
assert_eq!(request.body_processor().unwrap(), BodyProcessor::MULTIPART);
request
.headers_mut()
.insert("content-type", "idk".parse().unwrap());
assert_eq!(request.body_processor().unwrap(), BodyProcessor::Other);
}
#[test]
fn test_request_line() {
let request = RhodRequest::new(
HyperRequest::builder()
.version(Version::HTTP_2)
.uri("https://www.rust-lang.org/folder/file.txt?query=val#2")
.body(HyperBody::empty())
.unwrap(),
);
assert_eq!(request.method_str(), "GET");
assert_eq!(request.version_string(), "HTTP/2.0".to_string());
assert_eq!(
request.request_line(),
"GET /folder/file.txt HTTP/2.0".to_string()
);
}
}