yao-dev-common 0.1.10

Common library
Documentation
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)?)
    }
}

/// GET 请求
///
/// # params
/// - `base_url`: request url
/// - `params`: request params, &[(&str, &str)]
/// - `headers`: request headers, Option<Headers>
///
/// # return
/// - `Option<T>`: for<'a> Deserialize<'a>
///
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>,
{
    // build request client and get response
    let response = request_build(base_url, Method::GET, params, "", headers, show_debug).await?;
    if response.status().is_success() {
        // let result = serde_json::from_str::<T>(&body_str)?;
        let result: T = parse_response(response).await?;
        tracing::info!("[do_get] http result: {:?}", result);
        return Ok(result);
    }
    Err(AppError::from(response.text().await?).into())
}

/// POST 请求 with params
///
/// # params
/// - `base_url`: request url
/// - `params`: request params, &[(&str, &str)]
/// - `headers`: request headers, Option<Headers>
///
/// # return
/// - `Option<T>`: for<'a> Deserialize<'a>
///
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>,
{
    // build request client and get response
    let response = request_build(base_url, Method::POST, params, "", headers, show_debug).await?;
    // parse response to json
    let result: T = parse_response(response).await?;
    //
    tracing::info!("[do_post_with_params] http result: {:?}", result);
    Ok(result)
}

/// POST 请求 with body
///
/// # params
/// - `base_url`: request url
/// - `params`: request params, &[(&str, &str)]
/// - `headers`: request headers, Option<Headers>
/// - `body`: request body, &str
///
/// # return
/// - `Option<T>`: for<'a> Deserialize<'a>
///
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>,
{
    // build request client and get response
    let response = request_build(base_url, Method::POST, params, body, headers, show_debug).await?;
    // parse response to json
    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>,
{
    // build request client and get response
    let response = request_build(base_url, method.clone(), params, body, headers, show_debug).await?;
    // parse response to json
    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> {
    // set url
    // let url = set_url(base_url, params).await;
    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())
    }
    // build request client
    let client = Client::builder().timeout(Duration::from_secs(60)).build()?;
    // get request
    let mut request_builder = client.request(method.clone(), base_url);
    // set query params
    if !params.is_none() && !params.clone().unwrap().is_empty() {
        request_builder = request_builder.query(&params);
    }
    // set headers
    if headers.is_some() {
        let headers = headers.unwrap();
        // header
        if headers.header.is_some() {
            request_builder = set_headers(headers.header.unwrap(), request_builder).await;
        }
        // auth
        if headers.auth.is_some() {
            request_builder = set_auth(headers.auth.unwrap(), request_builder).await;
        }
    }

    // set body
    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
}