mirror of
https://github.com/tokio-rs/axum.git
synced 2026-09-07 00:00:12 +02:00
Move FromRequest and IntoResponse into new axum-core crate (#564)
* Move `IntoResponse` to axum-core * Move `FromRequest` to axum-core * some clean up * Remove hyper dependency from axum-core * Fix docs reference * Use default * Update changelog * Remove mention of default type
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
use super::IntoResponse;
|
||||
use crate::body::{boxed, BoxBody};
|
||||
use bytes::Bytes;
|
||||
use http::{
|
||||
header::{HeaderMap, HeaderName, HeaderValue},
|
||||
Response, StatusCode,
|
||||
};
|
||||
use http_body::{Empty, Full};
|
||||
use std::{convert::TryInto, fmt};
|
||||
|
||||
/// A response with headers.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// Router,
|
||||
/// response::{IntoResponse, Headers},
|
||||
/// routing::get,
|
||||
/// };
|
||||
/// use http::header::{HeaderName, HeaderValue};
|
||||
///
|
||||
/// // It works with any `IntoIterator<Item = (Key, Value)>` where `Key` can be
|
||||
/// // turned into a `HeaderName` and `Value` can be turned into a `HeaderValue`
|
||||
/// //
|
||||
/// // Such as `Vec<(HeaderName, HeaderValue)>`
|
||||
/// async fn just_headers() -> impl IntoResponse {
|
||||
/// Headers(vec![
|
||||
/// (HeaderName::from_static("X-Foo"), HeaderValue::from_static("foo")),
|
||||
/// ])
|
||||
/// }
|
||||
///
|
||||
/// // Or `Vec<(&str, &str)>`
|
||||
/// async fn from_strings() -> impl IntoResponse {
|
||||
/// Headers(vec![("X-Foo", "foo")])
|
||||
/// }
|
||||
///
|
||||
/// // Or `[(&str, &str)]` if you're on Rust 1.53+
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// .route("/just-headers", get(just_headers))
|
||||
/// .route("/from-strings", get(from_strings));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// If a conversion to `HeaderName` or `HeaderValue` fails a `500 Internal
|
||||
/// Server Error` response will be returned.
|
||||
///
|
||||
/// You can also return `(Headers, impl IntoResponse)` to customize the headers
|
||||
/// of a response, or `(StatusCode, Headeres, impl IntoResponse)` to customize
|
||||
/// the status code and headers.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Headers<H>(pub H);
|
||||
|
||||
impl<H> Headers<H> {
|
||||
fn try_into_header_map<K, V>(self) -> Result<HeaderMap, Response<BoxBody>>
|
||||
where
|
||||
H: IntoIterator<Item = (K, V)>,
|
||||
K: TryInto<HeaderName>,
|
||||
K::Error: fmt::Display,
|
||||
V: TryInto<HeaderValue>,
|
||||
V::Error: fmt::Display,
|
||||
{
|
||||
self.0
|
||||
.into_iter()
|
||||
.map(|(key, value)| {
|
||||
let key = key.try_into().map_err(Either::A)?;
|
||||
let value = value.try_into().map_err(Either::B)?;
|
||||
Ok((key, value))
|
||||
})
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|err| {
|
||||
let err = match err {
|
||||
Either::A(err) => err.to_string(),
|
||||
Either::B(err) => err.to_string(),
|
||||
};
|
||||
|
||||
let body = boxed(Full::new(Bytes::copy_from_slice(err.as_bytes())));
|
||||
let mut res = Response::new(body);
|
||||
*res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
res
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, K, V> IntoResponse for Headers<H>
|
||||
where
|
||||
H: IntoIterator<Item = (K, V)>,
|
||||
K: TryInto<HeaderName>,
|
||||
K::Error: fmt::Display,
|
||||
V: TryInto<HeaderValue>,
|
||||
V::Error: fmt::Display,
|
||||
{
|
||||
fn into_response(self) -> http::Response<BoxBody> {
|
||||
let headers = self.try_into_header_map();
|
||||
|
||||
match headers {
|
||||
Ok(headers) => {
|
||||
let mut res = Response::new(boxed(Empty::new()));
|
||||
*res.headers_mut() = headers;
|
||||
res
|
||||
}
|
||||
Err(err) => err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T, K, V> IntoResponse for (Headers<H>, T)
|
||||
where
|
||||
T: IntoResponse,
|
||||
H: IntoIterator<Item = (K, V)>,
|
||||
K: TryInto<HeaderName>,
|
||||
K::Error: fmt::Display,
|
||||
V: TryInto<HeaderValue>,
|
||||
V::Error: fmt::Display,
|
||||
{
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
let headers = match self.0.try_into_header_map() {
|
||||
Ok(headers) => headers,
|
||||
Err(res) => return res,
|
||||
};
|
||||
|
||||
(headers, self.1).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T, K, V> IntoResponse for (StatusCode, Headers<H>, T)
|
||||
where
|
||||
T: IntoResponse,
|
||||
H: IntoIterator<Item = (K, V)>,
|
||||
K: TryInto<HeaderName>,
|
||||
K::Error: fmt::Display,
|
||||
V: TryInto<HeaderValue>,
|
||||
V::Error: fmt::Display,
|
||||
{
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
let headers = match self.1.try_into_header_map() {
|
||||
Ok(headers) => headers,
|
||||
Err(res) => return res,
|
||||
};
|
||||
|
||||
(self.0, headers, self.2).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
enum Either<A, B> {
|
||||
A(A),
|
||||
B(B),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures_util::FutureExt;
|
||||
use http::header::USER_AGENT;
|
||||
|
||||
#[test]
|
||||
fn vec_of_header_name_and_value() {
|
||||
let res = Headers(vec![(USER_AGENT, HeaderValue::from_static("axum"))]).into_response();
|
||||
|
||||
assert_eq!(res.headers()["user-agent"], "axum");
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec_of_strings() {
|
||||
let res = Headers(vec![("user-agent", "axum")]).into_response();
|
||||
|
||||
assert_eq!(res.headers()["user-agent"], "axum");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_body() {
|
||||
let res = (Headers(vec![("user-agent", "axum")]), "foo").into_response();
|
||||
|
||||
assert_eq!(res.headers()["user-agent"], "axum");
|
||||
let body = crate::body::to_bytes(res.into_body())
|
||||
.now_or_never()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(&body[..], b"foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_status_and_body() {
|
||||
let res = (
|
||||
StatusCode::NOT_FOUND,
|
||||
Headers(vec![("user-agent", "axum")]),
|
||||
"foo",
|
||||
)
|
||||
.into_response();
|
||||
|
||||
assert_eq!(res.headers()["user-agent"], "axum");
|
||||
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||
let body = crate::body::to_bytes(res.into_body())
|
||||
.now_or_never()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(&body[..], b"foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_header_name() {
|
||||
let bytes: &[u8] = &[0, 159, 146, 150]; // invalid utf-8
|
||||
let res = Headers(vec![(bytes, "axum")]).into_response();
|
||||
|
||||
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_header_value() {
|
||||
let bytes: &[u8] = &[0, 159, 146, 150]; // invalid utf-8
|
||||
let res = Headers(vec![("user-agent", bytes)]).into_response();
|
||||
|
||||
assert!(res.headers().get("user-agent").is_none());
|
||||
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
//! Types and traits for generating responses.
|
||||
//!
|
||||
//! See [`axum::response`] for more details.
|
||||
//!
|
||||
//! [`axum::response`]: https://docs.rs/axum/latest/axum/response/index.html
|
||||
|
||||
use crate::{
|
||||
body::{boxed, BoxBody},
|
||||
BoxError,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use http::{
|
||||
header::{self, HeaderMap, HeaderValue},
|
||||
Response, StatusCode,
|
||||
};
|
||||
use http_body::{
|
||||
combinators::{MapData, MapErr},
|
||||
Empty, Full,
|
||||
};
|
||||
use std::{borrow::Cow, convert::Infallible};
|
||||
|
||||
mod headers;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use self::headers::Headers;
|
||||
|
||||
/// Trait for generating responses.
|
||||
///
|
||||
/// Types that implement `IntoResponse` can be returned from handlers.
|
||||
///
|
||||
/// # Implementing `IntoResponse`
|
||||
///
|
||||
/// You generally shouldn't have to implement `IntoResponse` manually, as axum
|
||||
/// provides implementations for many common types.
|
||||
///
|
||||
/// However it might be necessary if you have a custom error type that you want
|
||||
/// to return from handlers:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// Router,
|
||||
/// body::{self, BoxBody, Bytes},
|
||||
/// routing::get,
|
||||
/// http::{Response, StatusCode},
|
||||
/// response::IntoResponse,
|
||||
/// };
|
||||
///
|
||||
/// enum MyError {
|
||||
/// SomethingWentWrong,
|
||||
/// SomethingElseWentWrong,
|
||||
/// }
|
||||
///
|
||||
/// impl IntoResponse for MyError {
|
||||
/// fn into_response(self) -> Response<BoxBody> {
|
||||
/// let body = match self {
|
||||
/// MyError::SomethingWentWrong => {
|
||||
/// body::boxed(body::Full::from("something went wrong"))
|
||||
/// },
|
||||
/// MyError::SomethingElseWentWrong => {
|
||||
/// body::boxed(body::Full::from("something else went wrong"))
|
||||
/// },
|
||||
/// };
|
||||
///
|
||||
/// Response::builder()
|
||||
/// .status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
/// .body(body)
|
||||
/// .unwrap()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // `Result<impl IntoResponse, MyError>` can now be returned from handlers
|
||||
/// let app = Router::new().route("/", get(handler));
|
||||
///
|
||||
/// async fn handler() -> Result<(), MyError> {
|
||||
/// Err(MyError::SomethingWentWrong)
|
||||
/// }
|
||||
/// # async {
|
||||
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// Or if you have a custom body type you'll also need to implement
|
||||
/// `IntoResponse` for it:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// body::{self, BoxBody},
|
||||
/// routing::get,
|
||||
/// response::IntoResponse,
|
||||
/// Router,
|
||||
/// };
|
||||
/// use http_body::Body;
|
||||
/// use http::{Response, HeaderMap};
|
||||
/// use bytes::Bytes;
|
||||
/// use std::{
|
||||
/// convert::Infallible,
|
||||
/// task::{Poll, Context},
|
||||
/// pin::Pin,
|
||||
/// };
|
||||
///
|
||||
/// struct MyBody;
|
||||
///
|
||||
/// // First implement `Body` for `MyBody`. This could for example use
|
||||
/// // some custom streaming protocol.
|
||||
/// impl Body for MyBody {
|
||||
/// type Data = Bytes;
|
||||
/// type Error = Infallible;
|
||||
///
|
||||
/// fn poll_data(
|
||||
/// self: Pin<&mut Self>,
|
||||
/// cx: &mut Context<'_>
|
||||
/// ) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||
/// # unimplemented!()
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// fn poll_trailers(
|
||||
/// self: Pin<&mut Self>,
|
||||
/// cx: &mut Context<'_>
|
||||
/// ) -> Poll<Result<Option<HeaderMap>, Self::Error>> {
|
||||
/// # unimplemented!()
|
||||
/// // ...
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Now we can implement `IntoResponse` directly for `MyBody`
|
||||
/// impl IntoResponse for MyBody {
|
||||
/// fn into_response(self) -> Response<BoxBody> {
|
||||
/// Response::new(body::boxed(self))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // We don't need to implement `IntoResponse for Response<MyBody>` as that is
|
||||
/// // covered by a blanket implementation in axum.
|
||||
///
|
||||
/// // `MyBody` can now be returned from handlers.
|
||||
/// let app = Router::new().route("/", get(|| async { MyBody }));
|
||||
/// # async {
|
||||
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
pub trait IntoResponse {
|
||||
/// Create a response.
|
||||
fn into_response(self) -> Response<BoxBody>;
|
||||
}
|
||||
|
||||
impl IntoResponse for () {
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
Response::new(boxed(Empty::new()))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Infallible {
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
match self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> IntoResponse for Result<T, E>
|
||||
where
|
||||
T: IntoResponse,
|
||||
E: IntoResponse,
|
||||
{
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
match self {
|
||||
Ok(value) => value.into_response(),
|
||||
Err(err) => err.into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> IntoResponse for Response<B>
|
||||
where
|
||||
B: http_body::Body<Data = Bytes> + Send + 'static,
|
||||
B::Error: Into<BoxError>,
|
||||
{
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
self.map(boxed)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_into_response_for_body {
|
||||
($body:ty) => {
|
||||
impl IntoResponse for $body {
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
Response::new(boxed(self))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_into_response_for_body!(Full<Bytes>);
|
||||
impl_into_response_for_body!(Empty<Bytes>);
|
||||
|
||||
impl IntoResponse for http::response::Parts {
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
Response::from_parts(self, boxed(Empty::new()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> IntoResponse for http_body::combinators::BoxBody<Bytes, E>
|
||||
where
|
||||
E: Into<BoxError> + 'static,
|
||||
{
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
Response::new(boxed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> IntoResponse for http_body::combinators::UnsyncBoxBody<Bytes, E>
|
||||
where
|
||||
E: Into<BoxError> + 'static,
|
||||
{
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
Response::new(boxed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, F> IntoResponse for MapData<B, F>
|
||||
where
|
||||
B: http_body::Body + Send + 'static,
|
||||
F: FnMut(B::Data) -> Bytes + Send + 'static,
|
||||
B::Error: Into<BoxError>,
|
||||
{
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
Response::new(boxed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, F, E> IntoResponse for MapErr<B, F>
|
||||
where
|
||||
B: http_body::Body<Data = Bytes> + Send + 'static,
|
||||
F: FnMut(B::Error) -> E + Send + 'static,
|
||||
E: Into<BoxError>,
|
||||
{
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
Response::new(boxed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for &'static str {
|
||||
#[inline]
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
Cow::Borrowed(self).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for String {
|
||||
#[inline]
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
Cow::<'static, str>::Owned(self).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Cow<'static, str> {
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
let mut res = Response::new(boxed(Full::from(self)));
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Bytes {
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
let mut res = Response::new(boxed(Full::from(self)));
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for &'static [u8] {
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
let mut res = Response::new(boxed(Full::from(self)));
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Vec<u8> {
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
let mut res = Response::new(boxed(Full::from(self)));
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Cow<'static, [u8]> {
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
let mut res = Response::new(boxed(Full::from(self)));
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for StatusCode {
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
Response::builder()
|
||||
.status(self)
|
||||
.body(boxed(Empty::new()))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoResponse for (StatusCode, T)
|
||||
where
|
||||
T: IntoResponse,
|
||||
{
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
let mut res = self.1.into_response();
|
||||
*res.status_mut() = self.0;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoResponse for (HeaderMap, T)
|
||||
where
|
||||
T: IntoResponse,
|
||||
{
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
let mut res = self.1.into_response();
|
||||
res.headers_mut().extend(self.0);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoResponse for (StatusCode, HeaderMap, T)
|
||||
where
|
||||
T: IntoResponse,
|
||||
{
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
let mut res = self.2.into_response();
|
||||
*res.status_mut() = self.0;
|
||||
res.headers_mut().extend(self.1);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for HeaderMap {
|
||||
fn into_response(self) -> Response<BoxBody> {
|
||||
let mut res = Response::new(boxed(Empty::new()));
|
||||
*res.headers_mut() = self;
|
||||
res
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user