use serde::Serialize;
pub use axum::response::Response;
pub use axum::body::Body;
pub use bytes::Bytes;
pub use http::request::Parts as RequestParts;
pub use http::response::Builder as ResponseBuilder;
pub use http::Method;
pub use http::StatusCode;
pub use http::Uri;
pub use http::{HeaderMap, HeaderName, HeaderValue};
pub mod header {
pub use http::header::*;
}
pub fn bytes_response(status: StatusCode, content_type: &str, body: Bytes) -> Response {
let len = body.len();
Response::builder()
.status(status)
.header(header::CONTENT_TYPE, content_type)
.header(header::CONTENT_LENGTH, len)
.body(Body::from(body))
.expect("valid bytes response")
}
pub fn byte_range(content_type: &str, full: &Bytes, range: Option<(u64, u64)>) -> Response {
let total = full.len() as u64;
match range {
Some((start, end)) if start <= end && start < total => {
let end = end.min(total - 1);
let slice = full.slice(start as usize..=end as usize); Response::builder()
.status(StatusCode::PARTIAL_CONTENT)
.header(header::CONTENT_TYPE, content_type)
.header(header::ACCEPT_RANGES, "bytes")
.header(
header::CONTENT_RANGE,
format!("bytes {start}-{end}/{total}"),
)
.header(header::CONTENT_LENGTH, end - start + 1)
.body(Body::from(slice))
.expect("valid range response")
}
_ => bytes_response(StatusCode::OK, content_type, full.clone()),
}
}
pub trait IntoResponse: Sized {
fn into_response(self) -> Response;
}
impl<T: axum::response::IntoResponse> IntoResponse for T {
#[inline]
fn into_response(self) -> Response {
axum::response::IntoResponse::into_response(self)
}
}
pub struct Json<T>(pub T);
impl<T: Serialize> axum::response::IntoResponse for Json<T> {
#[inline]
fn into_response(self) -> axum::response::Response {
axum::response::IntoResponse::into_response(axum::Json(self.0))
}
}
impl<T> From<T> for Json<T> {
#[inline]
fn from(v: T) -> Self {
Json(v)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bytes_response_sets_type_and_length() {
let r = bytes_response(StatusCode::OK, "text/plain", Bytes::from_static(b"hello"));
assert_eq!(r.status(), StatusCode::OK);
assert_eq!(r.headers().get(header::CONTENT_TYPE).unwrap(), "text/plain");
assert_eq!(r.headers().get(header::CONTENT_LENGTH).unwrap(), "5");
}
#[test]
fn byte_range_partial_content() {
let full = Bytes::from_static(b"0123456789");
let r = byte_range("application/octet-stream", &full, Some((2, 5)));
assert_eq!(r.status(), StatusCode::PARTIAL_CONTENT);
assert_eq!(
r.headers().get(header::CONTENT_RANGE).unwrap(),
"bytes 2-5/10"
);
assert_eq!(r.headers().get(header::CONTENT_LENGTH).unwrap(), "4");
assert_eq!(r.headers().get(header::ACCEPT_RANGES).unwrap(), "bytes");
}
#[test]
fn byte_range_clamps_end_past_eof() {
let full = Bytes::from_static(b"0123456789");
let r = byte_range("application/octet-stream", &full, Some((8, 100)));
assert_eq!(r.status(), StatusCode::PARTIAL_CONTENT);
assert_eq!(
r.headers().get(header::CONTENT_RANGE).unwrap(),
"bytes 8-9/10"
);
assert_eq!(r.headers().get(header::CONTENT_LENGTH).unwrap(), "2");
}
#[test]
fn byte_range_no_range_is_full_200() {
let full = Bytes::from_static(b"0123456789");
let r = byte_range("application/octet-stream", &full, None);
assert_eq!(r.status(), StatusCode::OK);
assert_eq!(r.headers().get(header::CONTENT_LENGTH).unwrap(), "10");
}
#[test]
fn byte_range_start_past_eof_falls_back_to_full() {
let full = Bytes::from_static(b"0123456789");
let r = byte_range("application/octet-stream", &full, Some((50, 60)));
assert_eq!(r.status(), StatusCode::OK);
}
}