use std::sync::Arc;
use async_trait::async_trait;
use futures_util::{Stream, TryStreamExt};
use crate::error::WxErrorException;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransportRequest {
pub method: TransportMethod,
pub url: String,
pub headers: Vec<(String, String)>,
pub body: TransportBody,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransportMethod {
Get,
Post,
PostJson(String),
PostXml(String),
PostForm(Vec<(String, String)>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransportBody {
None,
Text(String),
Bytes(Vec<u8>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransportResponse {
pub status: u16,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}
#[async_trait]
pub trait HttpTransport: Send + Sync {
async fn send(&self, req: TransportRequest) -> Result<TransportResponse, WxErrorException>;
}
pub struct ReqwestTransport {
client: reqwest::Client,
}
impl ReqwestTransport {
pub fn new(client: reqwest::Client) -> Self {
Self { client }
}
fn build_request(&self, req: &TransportRequest) -> reqwest::RequestBuilder {
let mut builder = match &req.method {
TransportMethod::Get => self.client.get(&req.url),
TransportMethod::Post => self.client.post(&req.url),
TransportMethod::PostJson(payload) => self
.client
.post(&req.url)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.body(payload.clone()),
TransportMethod::PostXml(payload) => self
.client
.post(&req.url)
.header(reqwest::header::CONTENT_TYPE, "text/xml")
.body(payload.clone()),
TransportMethod::PostForm(pairs) => {
let form_body = url::form_urlencoded::Serializer::new(String::new())
.extend_pairs(pairs)
.finish();
self.client
.post(&req.url)
.header(
reqwest::header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.body(form_body)
}
};
for (name, value) in &req.headers {
builder = builder.header(name.as_str(), value.as_str());
}
builder = match (&req.method, req.body.clone()) {
(TransportMethod::Get | TransportMethod::Post, TransportBody::Text(text)) => {
builder.body(text)
}
(TransportMethod::Get | TransportMethod::Post, TransportBody::Bytes(bytes)) => {
builder.body(bytes)
}
_ => builder,
};
builder
}
pub async fn send_stream(
&self,
req: TransportRequest,
) -> Result<
impl Stream<Item = Result<bytes::Bytes, WxErrorException>> + Send + use<>,
WxErrorException,
> {
let resp = self.build_request(&req).send().await?;
let status = resp.status();
if !status.is_success() {
let body = resp.bytes().await.unwrap_or_default();
return Err(WxErrorException::Http(format!(
"流式下载失败,HTTP状态码:{},响应内容:{}",
status.as_u16(),
String::from_utf8_lossy(&body)
)));
}
Ok(resp
.bytes_stream()
.map_err(|e| WxErrorException::Http(e.to_string())))
}
}
#[async_trait]
impl HttpTransport for ReqwestTransport {
async fn send(&self, req: TransportRequest) -> Result<TransportResponse, WxErrorException> {
let resp = self.build_request(&req).send().await?;
let status = resp.status().as_u16();
let headers = resp
.headers()
.iter()
.map(|(name, value)| {
(
name.as_str().to_string(),
String::from_utf8_lossy(value.as_bytes()).into_owned(),
)
})
.collect();
let body = resp.bytes().await?.to_vec();
Ok(TransportResponse {
status,
headers,
body,
})
}
}
type MockHandler =
Arc<dyn Fn(&TransportRequest) -> Result<TransportResponse, WxErrorException> + Send + Sync>;
pub struct MockTransport {
handler: MockHandler,
}
impl MockTransport {
pub fn new<F>(f: F) -> Self
where
F: Fn(&TransportRequest) -> Result<TransportResponse, WxErrorException>
+ Send
+ Sync
+ 'static,
{
Self {
handler: Arc::new(f),
}
}
pub fn ok_json(body: &str) -> Self {
let body = body.to_string();
Self::new(move |_| {
Ok(TransportResponse {
status: 200,
headers: vec![],
body: body.clone().into_bytes(),
})
})
}
}
#[async_trait]
impl HttpTransport for MockTransport {
async fn send(&self, req: TransportRequest) -> Result<TransportResponse, WxErrorException> {
(self.handler)(&req)
}
}