use futures_util::StreamExt;
use http_kit::{
BodyError,
sse::SseStream,
utils::{ByteStr, Bytes},
};
pub trait ResponseExt {
fn into_json<T: serde::de::DeserializeOwned>(
self,
) -> impl Future<Output = Result<T, BodyError>> + Send;
fn into_sse(self) -> SseStream;
fn into_string(self) -> impl Future<Output = Result<ByteStr, BodyError>> + Send;
fn into_bytes(self) -> impl Future<Output = Result<Bytes, BodyError>> + Send;
fn into_bytes_with_limit(
self,
limit: usize,
) -> impl Future<Output = Result<Bytes, crate::Error>> + Send;
fn error_for_status(self) -> impl Future<Output = Result<Self, crate::Error>> + Send
where
Self: Sized;
}
impl ResponseExt for crate::Response {
async fn into_json<T: serde::de::DeserializeOwned>(self) -> Result<T, BodyError> {
self.into_body().into_json().await
}
fn into_sse(self) -> SseStream {
self.into_body().into_sse()
}
fn into_string(self) -> impl Future<Output = Result<ByteStr, BodyError>> + Send {
self.into_body().into_string()
}
fn into_bytes(self) -> impl Future<Output = Result<Bytes, BodyError>> + Send {
self.into_body().into_bytes()
}
async fn into_bytes_with_limit(self, limit: usize) -> Result<Bytes, crate::Error> {
let mut body = self.into_body();
let mut bytes = Vec::new();
while let Some(chunk) = body.next().await {
let chunk = chunk?;
let next_len = bytes
.len()
.checked_add(chunk.len())
.ok_or(crate::Error::ResponseBodyTooLarge { limit })?;
if next_len > limit {
return Err(crate::Error::ResponseBodyTooLarge { limit });
}
bytes.extend_from_slice(&chunk);
}
Ok(bytes.into())
}
async fn error_for_status(self) -> Result<Self, crate::Error> {
let status = self.status();
if status.is_success() {
return Ok(self);
}
let (parts, body) = self.into_parts();
let body_text = body.into_string().await.ok().map(|text| text.to_string());
let message = body_text.clone().unwrap_or_else(|| {
status
.canonical_reason()
.unwrap_or("Unknown error")
.to_string()
});
Err(crate::Error::Http {
status,
message,
response: Box::new(crate::error::HttpErrorResponse {
response: Self::from_parts(parts, http_kit::Body::empty()),
body_text,
}),
})
}
}
#[cfg(test)]
mod tests {
use super::ResponseExt;
use futures_executor::block_on;
use futures_util::stream;
use http_kit::{Body, Response, utils::Bytes};
#[test]
fn bounded_response_accepts_body_at_limit() {
let response = Response::new(Body::from("license"));
let body = block_on(response.into_bytes_with_limit(7)).unwrap();
assert_eq!(body.as_ref(), b"license");
}
#[test]
fn bounded_response_rejects_stream_when_limit_is_exceeded() {
let chunks = stream::iter([
Ok::<_, std::io::Error>(Bytes::from_static(b"license")),
Ok(Bytes::from_static(b"-response")),
]);
let response = Response::new(Body::from_stream(chunks));
let error = block_on(response.into_bytes_with_limit(8)).unwrap_err();
assert!(matches!(
error,
crate::Error::ResponseBodyTooLarge { limit: 8 }
));
}
}