use crate::error::app_error::AppError;
use crate::http::parser::{JsonParser, ResponseParser, StringParser};
use reqwest::{Client, Error, Method, RequestBuilder, Response};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::any::TypeId;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::time::Duration;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Headers {
pub header: Option<HashMap<String, String>>,
pub auth: Option<Auth>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Auth {
pub username: String,
pub password: String,
pub token: String,
}
#[derive(Debug)]
pub enum HttpMethod {
GET,
POST,
PUT,
DELETE,
HEAD,
}
async fn parse_response<T>(response: Response) -> Result<T, AppError>
where
T: for<'a> Deserialize<'a> + std::fmt::Debug + 'static + From<String>,
{
let body_str = response.text().await?;
if TypeId::of::<T>() == TypeId::of::<String>() {
Ok(<StringParser as ResponseParser<T>>::parse(&body_str)?)
} else {
Ok(<JsonParser<T> as ResponseParser<T>>::parse(&body_str)?)
}
}
pub async fn do_get<T, E>(
base_url: &str,
params: Option<HashMap<String, String>>,
headers: Option<Headers>,
show_debug: Option<bool>,
) -> Result<T, E>
where
T: for<'a> Deserialize<'a> + std::fmt::Debug + 'static + From<String>,
E: std::fmt::Debug
+ std::fmt::Display
+ From<AppError>
+ From<reqwest::Error>
+ From<serde_json::Error>,
{
let response = request_build(base_url, Method::GET, params, "", headers, show_debug).await?;
if response.status().is_success() {
let result: T = parse_response(response).await?;
tracing::info!("[do_get] http result: {:?}", result);
return Ok(result);
}
Err(AppError::from(response.text().await?).into())
}
pub async fn do_post_with_params<T, E>(
base_url: &str,
params: Option<HashMap<String, String>>,
headers: Option<Headers>,
show_debug: Option<bool>,
) -> Result<T, E>
where
T: for<'a> Deserialize<'a> + std::fmt::Debug + 'static + From<String>,
E: std::fmt::Debug
+ std::fmt::Display
+ From<AppError>
+ From<reqwest::Error>
+ From<serde_json::Error>,
{
let response = request_build(base_url, Method::POST, params, "", headers, show_debug).await?;
let result: T = parse_response(response).await?;
tracing::info!("[do_post_with_params] http result: {:?}", result);
Ok(result)
}
pub async fn do_post_with_body<T, E>(
base_url: &str,
params: Option<HashMap<String, String>>,
body: &str,
headers: Option<Headers>,
show_debug: Option<bool>,
) -> Result<T, E>
where
T: for<'a> Deserialize<'a> + std::fmt::Debug + 'static + From<String>,
E: std::fmt::Debug
+ std::fmt::Display
+ From<AppError>
+ From<reqwest::Error>
+ From<serde_json::Error>,
{
let response = request_build(base_url, Method::POST, params, body, headers, show_debug).await?;
let result: T = parse_response(response).await?;
tracing::info!("[do_post_with_body] http result: {:?}", result);
Ok(result)
}
pub async fn do_something<T, E>(
base_url: &str,
method: Method,
params: Option<HashMap<String, String>>,
body: &str,
headers: Option<Headers>,
show_debug: Option<bool>,
) -> Result<T, E>
where
T: for<'a> Deserialize<'a> + std::fmt::Debug + 'static + From<String>,
E: std::fmt::Debug
+ std::fmt::Display
+ From<AppError>
+ From<reqwest::Error>
+ From<serde_json::Error>,
{
let response = request_build(base_url, method.clone(), params, body, headers, show_debug).await?;
let result: T = parse_response(response).await?;
tracing::info!(
"[do_something_with_{:?}] http result: {:?}",
method.clone(),
result
);
Ok(result)
}
async fn request_build(
base_url: &str,
method: Method,
params: Option<HashMap<String, String>>,
body: &str,
headers: Option<Headers>,
show_debug: Option<bool>,
) -> Result<Response, AppError> {
tracing::info!(
"request url: {}, method: {}, params: {:?}, body ignored",
base_url,
method.clone(),
params.clone()
);
if show_debug.is_some() && show_debug.unwrap() {
tracing::info!("request body: {:?}", body.clone());
tracing::info!("request header: {:?}", headers.clone())
}
let client = Client::builder().timeout(Duration::from_secs(60)).build()?;
let mut request_builder = client.request(method.clone(), base_url);
if !params.is_none() && !params.clone().unwrap().is_empty() {
request_builder = request_builder.query(¶ms);
}
if headers.is_some() {
let headers = headers.unwrap();
if headers.header.is_some() {
request_builder = set_headers(headers.header.unwrap(), request_builder).await;
}
if headers.auth.is_some() {
request_builder = set_auth(headers.auth.unwrap(), request_builder).await;
}
}
if !body.is_empty() {
request_builder = request_builder.body(body.to_string());
}
Ok(request_builder.send().await?)
}
async fn set_url(base_url: &str, params: &[(&str, &str)]) -> String {
let mut url = base_url.to_string();
if !params.is_empty() {
let str_params = query_to_string(params).await;
url = format!("{}?{}", url, str_params);
}
url
}
async fn query_to_string(params: &[(&str, &str)]) -> String {
let mut query_string = String::new();
for (key, value) in params {
query_string.push_str(&format!("{}={}&", key, value));
}
query_string.pop();
query_string
}
async fn set_headers(
headers: HashMap<String, String>,
mut request_builder: RequestBuilder,
) -> RequestBuilder {
let owned_map: Vec<(String, String)> = headers
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
for (key, value) in owned_map {
request_builder = request_builder.header(key, value);
}
request_builder
}
async fn set_auth(auth: Auth, mut request_builder: RequestBuilder) -> RequestBuilder {
let username = auth.username;
let password = auth.password;
let token = auth.token;
if !username.is_empty() {
if !password.is_empty() {
request_builder = request_builder.basic_auth(username, Some(password));
} else if !token.is_empty() {
request_builder = request_builder.bearer_auth(token);
}
}
request_builder
}