use crate::{
Result, constants,
error::{Error, ErrorCode},
};
use http::{HeaderValue, Method, Request, Response, header};
use serde::{Deserialize, de::DeserializeOwned};
use serde_json::{Map, Value};
use tracing::{debug, error};
use url::Url;
pub fn parse_url(url: impl Into<String>) -> Result<Url> {
Ok(Url::parse(&url.into())?)
}
pub fn parse_query(url: impl Into<String>) -> Result<Vec<(String, String)>> {
let url = parse_url(url)?;
let paris = url
.query_pairs()
.into_iter()
.map(|s| (s.0.to_string(), s.1.to_string()))
.collect();
Ok(paris)
}
pub fn build_request(
url: &str,
method: Method,
headers: Option<Value>,
query: Option<Value>,
body: Option<Value>,
) -> Result<Request<Vec<u8>>> {
let mut req_url = Url::parse(url)?;
let default_map = Map::new();
if let Some(value) = query
&& value.is_object()
{
debug!("req query value:{:?}", value);
value
.as_object()
.unwrap_or(&default_map)
.iter()
.for_each(|item| {
debug!("req query item:{:?}", item);
let value_str = match item.1 {
Value::String(s) => s,
_ => &value.to_string(),
};
req_url.query_pairs_mut().append_pair(item.0, value_str);
});
}
let req_builder = Request::builder()
.uri(req_url.to_string())
.header(header::USER_AGENT, constants::HTTP_CLIENT_USER_AGENT)
.method(method);
let req_builder_with_headers = if let Some(headers_value) = headers
&& headers_value.is_object()
{
headers_value
.as_object()
.unwrap_or(&default_map)
.iter()
.fold(req_builder, |current_builder, (key, value)| {
let value_str = value.as_str().unwrap_or("");
let header_value =
HeaderValue::from_str(value_str).unwrap_or(HeaderValue::from_static(""));
current_builder.header(key.as_str(), header_value)
})
} else {
req_builder
};
if let Some(value) = body
&& value.is_object()
{
debug!("builder body {:?}", &value);
let body = serde_json::to_vec(&value)?;
Ok(req_builder_with_headers.body(body)?)
} else {
Ok(req_builder_with_headers.body(Vec::new())?)
}
}
#[derive(Debug)]
pub struct RequestBuilder {
url: String,
method: Method,
headers: Option<Value>,
query: Option<Value>,
body: Option<Value>,
}
impl RequestBuilder {
pub fn new(url: impl Into<String>) -> Self {
RequestBuilder {
url: url.into(),
method: Method::POST,
headers: None,
query: None,
body: None,
}
}
pub fn method(mut self, method: Method) -> Self {
self.method = method;
self
}
pub fn headers(mut self, headers: Value) -> Self {
self.headers = Some(headers);
self
}
pub fn query(mut self, query: Value) -> Self {
self.query = Some(query);
self
}
pub fn body(mut self, body: Value) -> Self {
self.body = Some(body);
self
}
pub fn build(self) -> Result<Request<Vec<u8>>> {
build_request(&self.url, self.method, self.headers, self.query, self.body)
}
}
pub trait ResponseExt {
fn to_json<T>(self) -> Result<T>
where
T: DeserializeOwned + std::fmt::Debug;
fn to_raw(self) -> Result<Vec<u8>>;
}
impl ResponseExt for Response<Vec<u8>> {
fn to_json<T>(self) -> Result<T>
where
T: DeserializeOwned + std::fmt::Debug,
{
if self.status().is_success() {
let (_parts, body) = self.into_parts();
let json = serde_json::from_slice::<MpResponse<T>>(&body.to_vec())?;
debug!("msg_sec_check result: {:#?}", json);
#[cfg(test)]
eprintln!("msg_sec_check result: {:#?}", json);
Ok(json.extract()?)
} else {
let (_parts, body) = self.into_parts();
let message = String::from_utf8_lossy(&body.to_vec()).to_string();
Err(Error::InternalServer(message))
}
}
fn to_raw(self) -> Result<Vec<u8>> {
if self.status().is_success() {
Ok(self.into_body())
} else {
let (_parts, body) = self.into_parts();
let message = String::from_utf8_lossy(&body.to_vec()).to_string();
Err(Error::InternalServer(message))
}
}
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum MpResponse<T> {
Success {
#[serde(flatten)]
data: T,
},
Error {
#[serde(rename = "errcode")]
code: ErrorCode,
#[serde(rename = "errmsg")]
message: String,
},
}
impl<T> MpResponse<T> {
pub(crate) fn extract(self) -> Result<T> {
match self {
Self::Success { data } => Ok(data),
Self::Error { code, message } => {
error!("微信小程序返回错误: code={}, message={}", code, message);
Err((code, message).into())
}
}
}
}