use core::fmt;
use serde::de::DeserializeOwned;
use crate::error::HttpError;
pub type Response<R, E = HttpError> = Result<R, E>;
pub struct RawResponse {
status: u16,
pub(crate) body: Vec<u8>,
}
impl fmt::Debug for RawResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RawResponse")
.field("status", &self.status)
.field("body_len", &self.body.len())
.finish()
}
}
impl RawResponse {
pub fn new(status: u16, body: Vec<u8>) -> Self {
Self { status, body }
}
pub fn status(&self) -> u16 {
self.status
}
pub fn is_success(&self) -> bool {
(200..300).contains(&self.status)
}
pub fn is_client_error(&self) -> bool {
(400..500).contains(&self.status)
}
pub fn is_server_error(&self) -> bool {
(500..600).contains(&self.status)
}
pub fn body_lossy(&self) -> String {
String::from_utf8_lossy(&self.body).into_owned()
}
pub fn json_or_status_error<T: DeserializeOwned>(self) -> Response<T> {
if !self.is_success() {
return Err(HttpError::Status {
status: self.status,
message: self.body_lossy(),
});
}
serde_json::from_slice(&self.body).map_err(HttpError::from)
}
pub async fn text(self) -> Response<String> {
String::from_utf8(self.body).map_err(|e| HttpError::Other(e.to_string()))
}
pub async fn json<T: DeserializeOwned>(self) -> Response<T> {
serde_json::from_slice(&self.body).map_err(HttpError::from)
}
pub async fn bytes(self) -> Response<Vec<u8>> {
Ok(self.body)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_response_type_is_result() {
let success: Response<i32> = Ok(42);
assert!(success.is_ok());
assert!(matches!(success, Ok(42)));
let error: Response<i32> = Err(HttpError::Timeout);
assert!(error.is_err());
assert!(matches!(error, Err(HttpError::Timeout)));
}
#[test]
fn raw_response_debug_redacts_body_and_reports_length() {
let secret = "response-body-secret";
let response = RawResponse::new(200, secret.as_bytes().to_vec());
let debug = format!("{response:?}");
assert!(debug.contains("status: 200"));
assert!(debug.contains(&format!("body_len: {}", secret.len())));
assert!(!debug.contains(secret));
}
}