use reqwest::Response;
use serde::de::DeserializeOwned;
use crate::rest::error::AsterDexError;
use crate::rest::response::ApiResponse;
const ERROR_BODY_PREVIEW: usize = 512;
pub(crate) async fn parse_response<T: DeserializeOwned>(
response: Response,
) -> Result<ApiResponse<T>, AsterDexError> {
let used_weight = response
.headers()
.get("X-MBX-USED-WEIGHT-1MINUTE")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<u32>().ok());
let order_count = response
.headers()
.get("X-MBX-ORDER-COUNT-1MINUTE")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<u32>().ok());
let status = response.status();
if status.as_u16() == 429 {
let retry_after = response
.headers()
.get("Retry-After")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<u64>().ok())
.map(std::time::Duration::from_secs);
return Err(AsterDexError::RateLimited { retry_after });
}
if status.as_u16() == 418 {
let retry_after = response
.headers()
.get("Retry-After")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<u64>().ok())
.map(std::time::Duration::from_secs);
return Err(AsterDexError::IpBanned { retry_after });
}
let bytes = response.bytes().await.map_err(AsterDexError::from_reqwest)?;
if !status.is_success() {
if let Ok(api_err) = serde_json::from_slice::<serde_json::Value>(&bytes) {
if let (Some(code), Some(msg)) = (
api_err.get("code").and_then(|c| c.as_i64()),
api_err.get("msg").and_then(|m| m.as_str()),
) {
return Err(AsterDexError::ApiError {
code,
msg: msg.to_string(),
});
}
}
return Err(AsterDexError::HttpError {
status: status.as_u16(),
body: body_preview(&bytes),
});
}
tracing::debug!(body = %String::from_utf8_lossy(&bytes), "raw response body");
let data = serde_json::from_slice::<T>(&bytes).map_err(|e| AsterDexError::SerdeError {
message: format!(
"failed to deserialize response: {e} (body preview: {})",
body_preview(&bytes)
),
})?;
Ok(ApiResponse {
data,
used_weight,
order_count,
})
}
fn body_preview(bytes: &[u8]) -> String {
let slice = &bytes[..bytes.len().min(ERROR_BODY_PREVIEW)];
let mut s = String::from_utf8_lossy(slice).into_owned();
if bytes.len() > ERROR_BODY_PREVIEW {
s.push_str(&format!("...[+{} bytes truncated]", bytes.len() - ERROR_BODY_PREVIEW));
}
s
}