use std::fmt::Display;
use std::fmt::Formatter;
use std::ops::Deref;
use crate::network::HeaderMap;
use crate::network::NetAuth;
use crate::network::RequestBody;
use crate::network::RequestMethod;
use crate::network::ResponseType;
use super::NetRequestHeaders;
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
pub struct NetUrl(String);
impl NetUrl {
pub const fn new(url: String) -> Self {
Self(url)
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Deref for NetUrl {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Display for NetUrl {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum NetRequestLogging {
None,
Request,
Response,
Both,
}
impl Default for NetRequestLogging {
fn default() -> Self {
Self::None
}
}
#[derive(Debug)]
pub struct NetRequest {
method: RequestMethod,
url: NetUrl,
body: RequestBody,
response_type: ResponseType,
auth: Option<NetAuth>,
headers: NetRequestHeaders,
log: NetRequestLogging,
}
impl NetRequest {
pub fn get(net_url: NetUrl) -> NetRequestBuilder {
NetRequestBuilder::default()
.url(net_url)
.header("Accept", "application/json")
}
pub fn post(net_url: NetUrl) -> NetRequestBuilder {
NetRequestBuilder::default()
.method(RequestMethod::Post)
.url(net_url)
}
pub fn put(net_url: NetUrl) -> NetRequestBuilder {
NetRequestBuilder::default()
.method(RequestMethod::Put)
.url(net_url)
}
pub fn patch(net_url: NetUrl) -> NetRequestBuilder {
NetRequestBuilder::default()
.method(RequestMethod::Patch)
.url(net_url)
}
pub fn delete(net_url: NetUrl) -> NetRequestBuilder {
NetRequestBuilder::default()
.method(RequestMethod::Delete)
.response_type(ResponseType::None)
.url(net_url)
}
pub fn propfind(net_url: NetUrl) -> NetRequestBuilder {
NetRequestBuilder::default()
.method(RequestMethod::Propfind)
.response_type(ResponseType::None)
.url(net_url)
}
pub const fn new(
method: RequestMethod,
url: NetUrl,
headers: NetRequestHeaders,
body: RequestBody,
response_type: ResponseType,
auth: Option<NetAuth>,
log: NetRequestLogging,
) -> Self {
Self {
method,
url,
headers,
body,
response_type,
auth,
log,
}
}
pub fn as_trace(&self) -> String {
format!(
"{} {}",
self.method,
self.url.as_str().chars().take(90).collect::<String>()
)
}
pub const fn method(&self) -> RequestMethod {
self.method
}
pub const fn url(&self) -> &NetUrl {
&self.url
}
pub const fn body(&self) -> &RequestBody {
&self.body
}
pub const fn response_type(&self) -> ResponseType {
self.response_type
}
pub const fn log(&self) -> NetRequestLogging {
self.log
}
pub fn auth(&self) -> Option<NetAuth> {
self.auth.clone()
}
pub fn headers(&self) -> HeaderMap {
self.headers
.clone()
.try_into()
.unwrap_or_else(|_| HeaderMap::new())
}
pub fn as_curl(&self) -> Result<String, serde_json::Error> {
let mut curl = format!("curl -X {} {}", self.method, *self.url);
if let Some(accept) = &self.response_type.accept() {
if self.headers.get("Accept").is_none() {
curl.push_str(&format!(" -H 'Accept: {}'", accept));
}
}
let mut headers = vec![];
for (key, value) in self.headers.iter() {
headers.push(format!(" -H '{}: {}'", key, value));
}
headers.sort();
for header in headers {
curl.push_str(&header);
}
if let Some(auth) = &self.auth() {
curl.push_str(&format!(
" -u {}:{}",
auth.username(),
auth.password().expose_password()
));
}
if self.method == RequestMethod::Post || self.method == RequestMethod::Put {
let body = String::try_from(&self.body)?;
curl.push_str(&format!(" -d '{}'", body));
}
Ok(curl)
}
}
#[derive(Default)]
pub struct NetRequestBuilder {
method: RequestMethod,
url: NetUrl,
headers: NetRequestHeaders,
body: RequestBody,
response_type: ResponseType,
auth: Option<NetAuth>,
log: NetRequestLogging,
}
impl NetRequestBuilder {
pub fn build(self) -> NetRequest {
NetRequest::new(
self.method,
self.url,
self.headers,
self.body,
self.response_type,
self.auth,
self.log,
)
}
pub fn method(mut self, method: RequestMethod) -> Self {
assert_ne!(method, RequestMethod::default());
self.method = method;
if self.method == RequestMethod::Get || self.method == RequestMethod::Delete {
self.body = RequestBody::None;
}
self
}
fn url(mut self, url: NetUrl) -> Self {
self.url = url;
self
}
pub fn header(mut self, key: &str, value: &str) -> Self {
self.headers = self.headers.with(key, value);
self
}
pub fn headers(mut self, new_headers: NetRequestHeaders) -> Self {
assert_ne!(new_headers, NetRequestHeaders::default());
for (key, value) in new_headers.iter() {
self.headers = self.headers.with(key, value);
}
self
}
pub fn body(mut self, body: RequestBody) -> Self {
match body {
RequestBody::Json(_) => {
self.headers = self.headers.with("Content-Type", "application/json")
}
RequestBody::Xml(_) => {
self.headers = self.headers.with("Content-Type", "application/xml")
}
_ => (),
}
self.body = body;
self
}
pub fn string_body(mut self, body: String) -> Self {
self.body = RequestBody::String(body);
self.header("Content-Type", "text/plain")
.response_type(ResponseType::Text)
}
pub fn json_body<T: serde::Serialize>(mut self, body: T) -> Result<Self, serde_json::Error> {
self.body = RequestBody::json(body)?;
Ok(self.header("Content-Type", "application/json"))
}
pub fn xml_body(mut self, body: &str) -> Self {
self.body = RequestBody::Xml(body.to_string());
self.header("Content-Type", "application/xml")
}
pub fn response_type(mut self, response_type: ResponseType) -> Self {
assert_ne!(response_type, ResponseType::default());
self.response_type = response_type;
match response_type.accept() {
Some(accept) => self.header("Accept", accept.as_str()),
None => {
self.headers.remove("Accept");
self
}
}
}
pub fn auth(mut self, auth: NetAuth) -> Self {
self.auth = Some(auth);
self
}
pub const fn log(mut self, log: NetRequestLogging) -> Self {
self.log = log;
self
}
}
#[cfg(test)]
mod tests {
use assert2::let_assert;
use serde_json::json;
use crate::network::net_auth::{NetAuthPassword, NetAuthUsername};
use super::*;
#[test_log::test]
fn test_as_curl_no_auth() {
let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string())).build();
let_assert!(Ok(curl) = request.as_curl());
assert_eq!(
curl,
"curl -X GET https://httpbin.org/get -H 'Accept: application/json'".to_string()
);
}
#[test_log::test]
fn test_as_curl_auth() {
let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string()))
.auth(NetAuth::new(
NetAuthUsername::new("user".into()),
NetAuthPassword::new("pass".into()),
))
.build();
let_assert!(Ok(curl) = request.as_curl());
assert_eq!(
curl,
"curl -X GET https://httpbin.org/get -H 'Accept: application/json' -u user:pass"
.to_string()
);
}
#[test_log::test]
fn test_as_curl_no_auth_post_json() -> Result<(), serde_json::Error> {
let request = NetRequest::post(NetUrl("https://httpbin.org/post".to_string()))
.json_body(json!({
"args": {},
"url": "https://httpbin.org/post",
}))?
.build();
let_assert!(Ok(curl) = request.as_curl());
assert_eq!(
curl,
"curl -X POST https://httpbin.org/post -H 'Accept: application/json' -H 'Content-Type: application/json' -d '{\"args\":{},\"url\":\"https://httpbin.org/post\"}'".to_string()
);
Ok(())
}
#[test_log::test]
fn test_as_curl_no_auth_post_text() {
let request = NetRequest::post(NetUrl("https://httpbin.org/post".to_string()))
.string_body("body".to_string())
.build();
let_assert!(Ok(curl) = request.as_curl());
assert_eq!(
curl,
"curl -X POST https://httpbin.org/post -H 'Accept: text/plain' -H 'Content-Type: text/plain' -d 'body'".to_string()
);
}
#[test_log::test]
fn test_as_curl_no_auth_put() -> Result<(), serde_json::Error> {
let request = NetRequest::put(NetUrl("https://httpbin.org/put".to_string()))
.json_body(json!({
"args": {},
"url": "https://httpbin.org/put",
}))?
.build();
let_assert!(Ok(curl) = request.as_curl());
assert_eq!(
curl,
"curl -X PUT https://httpbin.org/put -H 'Accept: application/json' -H 'Content-Type: application/json' -d '{\"args\":{},\"url\":\"https://httpbin.org/put\"}'".to_string()
);
Ok(())
}
#[test_log::test]
fn test_as_curl_no_auth_delete() {
let request = NetRequest::delete(NetUrl("https://httpbin.org/delete".to_string())).build();
let_assert!(Ok(curl) = request.as_curl());
assert_eq!(
curl,
"curl -X DELETE https://httpbin.org/delete".to_string()
);
}
#[test_log::test]
fn test_as_curl_no_auth_put_xml() {
let request = NetRequest::put(NetUrl("https://httpbin.org/put".to_string()))
.xml_body("<xml/>")
.build();
let_assert!(Ok(curl) = request.as_curl());
assert_eq!(
curl,
"curl -X PUT https://httpbin.org/put -H 'Accept: application/json' -H 'Content-Type: application/xml' -d '<xml/>'"
.to_string()
);
}
#[test_log::test]
fn test_as_curl_accept_json() {
let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string())).build();
let_assert!(Ok(curl) = request.as_curl());
assert_eq!(
curl,
"curl -X GET https://httpbin.org/get -H 'Accept: application/json'".to_string()
);
}
#[test_log::test]
fn test_as_curl_accept_xml() {
let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string()))
.response_type(ResponseType::Xml)
.build();
let_assert!(Ok(curl) = request.as_curl());
assert_eq!(
curl,
"curl -X GET https://httpbin.org/get -H 'Accept: application/xml'".to_string()
);
}
#[test_log::test]
fn test_as_curl_accept_text() {
let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string()))
.response_type(ResponseType::Text)
.build();
let_assert!(Ok(curl) = request.as_curl());
assert_eq!(
curl,
"curl -X GET https://httpbin.org/get -H 'Accept: text/plain'".to_string()
);
}
#[test_log::test]
fn text_as_curl_accept_none() {
let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string()))
.response_type(ResponseType::None)
.build();
let_assert!(Ok(curl) = request.as_curl());
assert_eq!(curl, "curl -X GET https://httpbin.org/get".to_string());
}
#[test_log::test]
fn test_as_curl_with_headers() {
let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string()))
.header("header1", "value1")
.header("header2", "value2")
.build();
let_assert!(Ok(curl) = request.as_curl());
assert!(curl.contains("-H 'header1: value1'"));
assert!(curl.contains("-H 'header2: value2'"));
assert!(curl.starts_with("curl -X GET https://httpbin.org/get"));
}
#[test_log::test]
fn test_net_request_headers() {
let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string()))
.header("header1", "value1")
.header("header2", "value2")
.build();
let headers = request.headers();
assert!(headers.len() >= 2);
let_assert!(Some(value1) = headers.get("header1"));
assert_eq!(value1, "value1");
let_assert!(Some(value2) = headers.get("header2"));
assert_eq!(value2, "value2");
}
#[test_log::test]
fn test_as_curl_propfind() {
let request =
NetRequest::propfind(NetUrl("https://httpbin.org/propfind".to_string())).build();
let_assert!(Ok(curl) = request.as_curl());
assert_eq!(
curl,
"curl -X PROPFIND https://httpbin.org/propfind".to_string()
);
}
#[test_log::test]
fn test_as_trace() {
let request = NetRequest::get(NetUrl("https://httpbin.org/get".to_string())).build();
assert_eq!(request.as_trace(), "GET https://httpbin.org/get");
}
}