use serde::Deserialize;
use serde_json::Value;
use crate::error::{ErrorResponse, WxPayError, WxPayResult};
use crate::http::client::HttpResponse;
#[derive(Debug, Clone)]
pub struct ResponseHandler;
impl ResponseHandler {
pub fn handle(response: &HttpResponse) -> WxPayResult<&str> {
if response.is_success() {
Ok(&response.body)
} else {
Self::handle_error(response)
}
}
fn extract_error_payload(response: &HttpResponse) -> Option<(String, String)> {
if let Ok(error) = serde_json::from_str::<ErrorResponse>(&response.body) {
return Some((error.code, error.message));
}
let value: Value = serde_json::from_str(&response.body).ok()?;
let code = value
.get("code")
.and_then(Value::as_str)
.or_else(|| value.get("error_code").and_then(Value::as_str))
.or_else(|| value.get("errcode").and_then(Value::as_str))
.map(ToOwned::to_owned);
let message = value
.get("message")
.and_then(Value::as_str)
.or_else(|| value.get("error_message").and_then(Value::as_str))
.or_else(|| value.get("errmsg").and_then(Value::as_str))
.or_else(|| value.get("error_description").and_then(Value::as_str))
.map(ToOwned::to_owned)
.or_else(|| {
value
.get("errors")
.and_then(Value::as_array)
.and_then(|items| items.first())
.and_then(|item| {
item.get("message")
.and_then(Value::as_str)
.map(ToOwned::to_owned)
.or_else(|| {
item.get("error")
.and_then(Value::as_str)
.map(ToOwned::to_owned)
})
})
});
match (code, message) {
(Some(code), Some(message)) => Some((code, message)),
_ => None,
}
}
pub fn handle_error(response: &HttpResponse) -> WxPayResult<&str> {
if let Some((code, message)) = Self::extract_error_payload(response) {
return Err(WxPayError::api(code, message));
}
Err(WxPayError::UnexpectedStatusCode(response.status))
}
pub fn parse<T: serde::de::DeserializeOwned>(response: &HttpResponse) -> WxPayResult<T> {
let body = Self::handle(response)?;
serde_json::from_str(body).map_err(WxPayError::from)
}
pub fn get_header<'a>(response: &'a HttpResponse, name: &str) -> Option<&'a str> {
response.get_header(name)
}
pub fn get_request_id(response: &HttpResponse) -> Option<&str> {
response.get_header("Request-ID")
}
pub fn get_signature(response: &HttpResponse) -> Option<&str> {
response.get_header("Wechatpay-Signature")
}
pub fn get_serial_number(response: &HttpResponse) -> Option<&str> {
response.get_header("Wechatpay-Serial")
}
pub fn get_timestamp(response: &HttpResponse) -> Option<&str> {
response.get_header("Wechatpay-Timestamp")
}
pub fn get_nonce(response: &HttpResponse) -> Option<&str> {
response.get_header("Wechatpay-Nonce")
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct WxPayResponse<T> {
#[serde(flatten)]
pub data: T,
#[serde(skip)]
pub request_id: Option<String>,
#[serde(skip)]
pub signature: Option<String>,
#[serde(skip)]
pub serial_number: Option<String>,
#[serde(skip)]
pub timestamp: Option<String>,
#[serde(skip)]
pub nonce: Option<String>,
}
impl<T> WxPayResponse<T> {
pub fn new(data: T) -> Self {
Self {
data,
request_id: None,
signature: None,
serial_number: None,
timestamp: None,
nonce: None,
}
}
pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
self.request_id = Some(request_id.into());
self
}
pub fn with_signature(mut self, signature: impl Into<String>) -> Self {
self.signature = Some(signature.into());
self
}
pub fn with_serial_number(mut self, serial_number: impl Into<String>) -> Self {
self.serial_number = Some(serial_number.into());
self
}
pub fn with_timestamp(mut self, timestamp: impl Into<String>) -> Self {
self.timestamp = Some(timestamp.into());
self
}
pub fn with_nonce(mut self, nonce: impl Into<String>) -> Self {
self.nonce = Some(nonce.into());
self
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct EmptyResponse {}
#[derive(Debug, Clone, Deserialize)]
pub struct PaginatedResponse<T> {
pub data: Vec<T>,
pub total: Option<u64>,
pub limit: Option<u64>,
pub offset: Option<u64>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct WxPayErrorResponse {
pub code: String,
pub message: String,
#[serde(default)]
pub detail: Option<Value>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_response_handler_success() {
let response = HttpResponse::new(200, vec![], r#"{"code":"SUCCESS"}"#.to_string());
let body = ResponseHandler::handle(&response).unwrap();
assert_eq!(body, r#"{"code":"SUCCESS"}"#);
}
#[test]
fn test_response_handler_error() {
let response = HttpResponse::new(
400,
vec![],
r#"{"code":"PARAM_ERROR","message":"参数错误"}"#.to_string(),
);
let result = ResponseHandler::handle(&response);
assert!(result.is_err());
match result.unwrap_err() {
WxPayError::ApiError { code, message } => {
assert_eq!(code, "PARAM_ERROR");
assert_eq!(message, "参数错误");
}
_ => panic!("Expected ApiError"),
}
}
#[test]
fn test_response_handler_unexpected_status() {
let response = HttpResponse::new(500, vec![], "Internal Server Error".to_string());
let result = ResponseHandler::handle(&response);
assert!(result.is_err());
match result.unwrap_err() {
WxPayError::UnexpectedStatusCode(status) => {
assert_eq!(status, 500);
}
_ => panic!("Expected UnexpectedStatusCode"),
}
}
#[test]
fn test_response_handler_parse() {
#[derive(Debug, Deserialize)]
struct TestResponse {
code: String,
}
let response = HttpResponse::new(200, vec![], r#"{"code":"SUCCESS"}"#.to_string());
let parsed: TestResponse = ResponseHandler::parse(&response).unwrap();
assert_eq!(parsed.code, "SUCCESS");
}
#[test]
fn test_wxpay_response() {
#[derive(Debug, Deserialize)]
struct TestData {
name: String,
}
let response = WxPayResponse::new(TestData {
name: "test".to_string(),
})
.with_request_id("12345")
.with_signature("test_signature")
.with_serial_number("CERT123")
.with_timestamp("1609459200")
.with_nonce("test_nonce");
assert_eq!(response.data.name, "test");
assert_eq!(response.request_id, Some("12345".to_string()));
assert_eq!(response.signature, Some("test_signature".to_string()));
assert_eq!(response.serial_number, Some("CERT123".to_string()));
assert_eq!(response.timestamp, Some("1609459200".to_string()));
assert_eq!(response.nonce, Some("test_nonce".to_string()));
}
}