mod stream;
pub use stream::{SseEvent, SseResponse, StreamResponse};
use bytes::Bytes;
use http::{Response, StatusCode, header::CONTENT_TYPE};
use http_body_util::{BodyExt, Full, combinators::UnsyncBoxBody};
pub(crate) const APPLICATION_JSON: &str = "application/json";
pub(crate) const APPLICATION_PROBLEM_JSON: &str = "application/problem+json";
pub(crate) const FORM_CONTENT_TYPE: &str = "application/x-www-form-urlencoded";
#[cfg(feature = "metrics")]
pub(crate) const PROMETHEUS_TEXT_FORMAT: &str = "text/plain; version=0.0.4; charset=utf-8";
const TEXT_PLAIN_UTF8: &str = "text/plain; charset=utf-8";
pub type BodyError = Box<dyn std::error::Error + Send + Sync>;
pub type BoxBody = UnsyncBoxBody<Bytes, BodyError>;
pub fn full(bytes: impl Into<Bytes>) -> BoxBody {
Full::new(bytes.into())
.map_err(|never| match never {})
.boxed_unsync()
}
pub fn empty() -> BoxBody {
full(Bytes::new())
}
pub trait IntoResponse {
fn into_response(self) -> Response<BoxBody>;
}
impl IntoResponse for Response<BoxBody> {
fn into_response(self) -> Response<BoxBody> {
self
}
}
impl IntoResponse for &str {
fn into_response(self) -> Response<BoxBody> {
Response::builder()
.status(StatusCode::OK)
.header(CONTENT_TYPE, TEXT_PLAIN_UTF8)
.body(full(self.to_owned()))
.unwrap()
}
}
impl IntoResponse for String {
fn into_response(self) -> Response<BoxBody> {
Response::builder()
.status(StatusCode::OK)
.header(CONTENT_TYPE, TEXT_PLAIN_UTF8)
.body(full(self))
.unwrap()
}
}
pub struct StaticStr(pub &'static str);
impl IntoResponse for StaticStr {
fn into_response(self) -> Response<BoxBody> {
Response::builder()
.status(StatusCode::OK)
.header(CONTENT_TYPE, TEXT_PLAIN_UTF8)
.body(full(Bytes::from_static(self.0.as_bytes())))
.unwrap()
}
}
impl IntoResponse for StatusCode {
fn into_response(self) -> Response<BoxBody> {
Response::builder().status(self).body(empty()).unwrap()
}
}
impl IntoResponse for (StatusCode, String) {
fn into_response(self) -> Response<BoxBody> {
Response::builder()
.status(self.0)
.header(CONTENT_TYPE, TEXT_PLAIN_UTF8)
.body(full(self.1))
.unwrap()
}
}
impl<T: IntoResponse, E: IntoResponse> IntoResponse for std::result::Result<T, E> {
fn into_response(self) -> Response<BoxBody> {
match self {
Ok(v) => v.into_response(),
Err(e) => e.into_response(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use http_body_util::BodyExt;
use hyper::body::Body;
#[tokio::test]
async fn test_str_into_response() {
let response = "hello".into_response();
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(
response.headers().get("content-type").unwrap(),
"text/plain; charset=utf-8"
);
let body = response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(&body[..], b"hello");
}
#[tokio::test]
async fn test_string_into_response() {
let response = "world".to_string().into_response();
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(
response.headers().get("content-type").unwrap(),
"text/plain; charset=utf-8"
);
let body = response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(&body[..], b"world");
}
#[tokio::test]
async fn test_status_code_into_response() {
let response = StatusCode::NOT_FOUND.into_response();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
let body = response.into_body().collect().await.unwrap().to_bytes();
assert!(body.is_empty());
}
#[tokio::test]
async fn test_status_code_ok() {
let response = StatusCode::OK.into_response();
assert_eq!(response.status(), StatusCode::OK);
}
#[tokio::test]
async fn test_status_code_created() {
let response = StatusCode::CREATED.into_response();
assert_eq!(response.status(), StatusCode::CREATED);
}
#[tokio::test]
async fn test_status_code_no_content() {
let response = StatusCode::NO_CONTENT.into_response();
assert_eq!(response.status(), StatusCode::NO_CONTENT);
}
#[tokio::test]
async fn test_tuple_into_response() {
let response = (StatusCode::CREATED, "created".to_string()).into_response();
assert_eq!(response.status(), StatusCode::CREATED);
assert_eq!(
response.headers().get("content-type").unwrap(),
"text/plain; charset=utf-8"
);
let body = response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(&body[..], b"created");
}
#[tokio::test]
async fn test_tuple_with_error_status() {
let response = (StatusCode::BAD_REQUEST, "bad request".to_string()).into_response();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn test_response_into_response_identity() {
let original = Response::builder()
.status(StatusCode::ACCEPTED)
.body(full("test"))
.unwrap();
let response = original.into_response();
assert_eq!(response.status(), StatusCode::ACCEPTED);
}
#[tokio::test]
async fn test_result_ok_into_response() {
let result: std::result::Result<&str, StatusCode> = Ok("success");
let response = result.into_response();
assert_eq!(response.status(), StatusCode::OK);
let body = response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(&body[..], b"success");
}
#[test]
fn test_result_err_into_response() {
let result: std::result::Result<&str, StatusCode> = Err(StatusCode::INTERNAL_SERVER_ERROR);
let response = result.into_response();
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[tokio::test]
async fn test_static_str_into_response() {
let response = StaticStr("Hello, World!").into_response();
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(
response.headers().get("content-type").unwrap(),
"text/plain; charset=utf-8"
);
let body = response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(&body[..], b"Hello, World!");
}
#[tokio::test]
async fn test_static_str_empty() {
let response = StaticStr("").into_response();
assert_eq!(response.status(), StatusCode::OK);
let body = response.into_body().collect().await.unwrap().to_bytes();
assert!(body.is_empty());
}
#[tokio::test]
async fn test_static_str_is_zero_copy() {
let response = StaticStr("static content").into_response();
let body = response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(&body[..], b"static content");
}
#[test]
fn test_buffered_body_reports_exact_size_hint() {
let body = full("hello");
assert_eq!(body.size_hint().exact(), Some(5));
}
#[test]
fn test_empty_body_reports_zero_size_hint() {
let body = empty();
assert_eq!(body.size_hint().exact(), Some(0));
}
}