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:
David Pedersen
2021-11-30 13:46:13 +00:00
committed by GitHub
parent f95c974406
commit 254d8fde17
41 changed files with 1437 additions and 977 deletions
+92
View File
@@ -0,0 +1,92 @@
//! HTTP body utilities.
use crate::{BoxError, Error};
use bytes::Bytes;
use bytes::{Buf, BufMut};
use http_body::Body;
/// A boxed [`Body`] trait object.
///
/// This is used in axum as the response body type for applications. It's
/// necessary to unify multiple response bodies types into one.
pub type BoxBody = http_body::combinators::UnsyncBoxBody<Bytes, Error>;
/// Convert a [`http_body::Body`] into a [`BoxBody`].
pub fn boxed<B>(body: B) -> BoxBody
where
B: http_body::Body<Data = Bytes> + Send + 'static,
B::Error: Into<BoxError>,
{
try_downcast(body).unwrap_or_else(|body| body.map_err(Error::new).boxed_unsync())
}
pub(crate) fn try_downcast<T, K>(k: K) -> Result<T, K>
where
T: 'static,
K: Send + 'static,
{
let mut k = Some(k);
if let Some(k) = <dyn std::any::Any>::downcast_mut::<Option<T>>(&mut k) {
Ok(k.take().unwrap())
} else {
Err(k.unwrap())
}
}
// copied from hyper under the following license:
// Copyright (c) 2014-2021 Sean McArthur
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
pub(crate) async fn to_bytes<T>(body: T) -> Result<Bytes, T::Error>
where
T: Body,
{
futures_util::pin_mut!(body);
// If there's only 1 chunk, we can just return Buf::to_bytes()
let mut first = if let Some(buf) = body.data().await {
buf?
} else {
return Ok(Bytes::new());
};
let second = if let Some(buf) = body.data().await {
buf?
} else {
return Ok(first.copy_to_bytes(first.remaining()));
};
// With more than 1 buf, we gotta flatten into a Vec first.
let cap = first.remaining() + second.remaining() + body.size_hint().lower() as usize;
let mut vec = Vec::with_capacity(cap);
vec.put(first);
vec.put(second);
while let Some(buf) = body.data().await {
vec.put(buf?);
}
Ok(vec.into())
}
#[test]
fn test_try_downcast() {
assert_eq!(try_downcast::<i32, _>(5_u32), Err(5_u32));
assert_eq!(try_downcast::<i32, _>(5_i32), Ok(5_i32));
}
+39
View File
@@ -0,0 +1,39 @@
use crate::BoxError;
use std::{error::Error as StdError, fmt};
/// Errors that can happen when using axum.
#[derive(Debug)]
pub struct Error {
inner: BoxError,
}
impl Error {
/// Create a new `Error` from a boxable error.
pub fn new(error: impl Into<BoxError>) -> Self {
Self {
inner: error.into(),
}
}
pub(crate) fn downcast<T>(self) -> Result<T, Self>
where
T: StdError + 'static,
{
match self.inner.downcast::<T>() {
Ok(t) => Ok(*t),
Err(err) => Err(*err.downcast().unwrap()),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(&*self.inner)
}
}
+284
View File
@@ -0,0 +1,284 @@
//! Types and traits for extracting data from requests.
//!
//! See [`axum::extract`] for more details.
//!
//! [`axum::extract`]: https://docs.rs/axum/latest/axum/extract/index.html
use self::rejection::*;
use crate::response::IntoResponse;
use crate::Error;
use async_trait::async_trait;
use http::{Extensions, HeaderMap, Method, Request, Uri, Version};
use std::convert::Infallible;
pub mod rejection;
mod request_parts;
mod tuple;
/// Types that can be created from requests.
///
/// See [`axum::extract`] for more details.
///
/// # What is the `B` type parameter?
///
/// `FromRequest` is generic over the request body (the `B` in
/// [`http::Request<B>`]). This is to allow `FromRequest` to be usable with any
/// type of request body. This is necessary because some middleware change the
/// request body, for example to add timeouts.
///
/// If you're writing your own `FromRequest` that wont be used outside your
/// application, and not using any middleware that changes the request body, you
/// can most likely use `axum::body::Body`.
///
/// If you're writing a library that's intended for others to use, it's recommended
/// to keep the generic type parameter:
///
/// ```rust
/// use axum::{
/// async_trait,
/// extract::{FromRequest, RequestParts},
/// };
///
/// struct MyExtractor;
///
/// #[async_trait]
/// impl<B> FromRequest<B> for MyExtractor
/// where
/// B: Send, // required by `async_trait`
/// {
/// type Rejection = http::StatusCode;
///
/// async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
/// // ...
/// # unimplemented!()
/// }
/// }
/// ```
///
/// This ensures your extractor is as flexible as possible.
///
/// [`http::Request<B>`]: http::Request
/// [`axum::extract`]: https://docs.rs/axum/latest/axum/extract/index.html
#[async_trait]
pub trait FromRequest<B>: Sized {
/// If the extractor fails it'll use this "rejection" type. A rejection is
/// a kind of error that can be converted into a response.
type Rejection: IntoResponse;
/// Perform the extraction.
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection>;
}
/// The type used with [`FromRequest`] to extract data from requests.
///
/// Has several convenience methods for getting owned parts of the request.
#[derive(Debug)]
pub struct RequestParts<B> {
method: Method,
uri: Uri,
version: Version,
headers: Option<HeaderMap>,
extensions: Option<Extensions>,
body: Option<B>,
}
impl<B> RequestParts<B> {
/// Create a new `RequestParts`.
///
/// You generally shouldn't need to construct this type yourself, unless
/// using extractors outside of axum for example to implement a
/// [`tower::Service`].
///
/// [`tower::Service`]: https://docs.rs/tower/lastest/tower/trait.Service.html
pub fn new(req: Request<B>) -> Self {
let (
http::request::Parts {
method,
uri,
version,
headers,
extensions,
..
},
body,
) = req.into_parts();
RequestParts {
method,
uri,
version,
headers: Some(headers),
extensions: Some(extensions),
body: Some(body),
}
}
/// Convert this `RequestParts` back into a [`Request`].
///
/// Fails if
///
/// - The full [`HeaderMap`] has been extracted, that is [`take_headers`]
/// have been called.
/// - The full [`Extensions`] has been extracted, that is
/// [`take_extensions`] have been called.
/// - The request body has been extracted, that is [`take_body`] have been
/// called.
///
/// [`take_headers`]: RequestParts::take_headers
/// [`take_extensions`]: RequestParts::take_extensions
/// [`take_body`]: RequestParts::take_body
pub fn try_into_request(self) -> Result<Request<B>, Error> {
let Self {
method,
uri,
version,
mut headers,
mut extensions,
mut body,
} = self;
let mut req = if let Some(body) = body.take() {
Request::new(body)
} else {
return Err(Error::new(RequestAlreadyExtracted::BodyAlreadyExtracted(
BodyAlreadyExtracted,
)));
};
*req.method_mut() = method;
*req.uri_mut() = uri;
*req.version_mut() = version;
if let Some(headers) = headers.take() {
*req.headers_mut() = headers;
} else {
return Err(Error::new(
RequestAlreadyExtracted::HeadersAlreadyExtracted(HeadersAlreadyExtracted),
));
}
if let Some(extensions) = extensions.take() {
*req.extensions_mut() = extensions;
} else {
return Err(Error::new(
RequestAlreadyExtracted::ExtensionsAlreadyExtracted(ExtensionsAlreadyExtracted),
));
}
Ok(req)
}
/// Gets a reference the request method.
pub fn method(&self) -> &Method {
&self.method
}
/// Gets a mutable reference to the request method.
pub fn method_mut(&mut self) -> &mut Method {
&mut self.method
}
/// Gets a reference the request URI.
pub fn uri(&self) -> &Uri {
&self.uri
}
/// Gets a mutable reference to the request URI.
pub fn uri_mut(&mut self) -> &mut Uri {
&mut self.uri
}
/// Get the request HTTP version.
pub fn version(&self) -> Version {
self.version
}
/// Gets a mutable reference to the request HTTP version.
pub fn version_mut(&mut self) -> &mut Version {
&mut self.version
}
/// Gets a reference to the request headers.
///
/// Returns `None` if the headers has been taken by another extractor.
pub fn headers(&self) -> Option<&HeaderMap> {
self.headers.as_ref()
}
/// Gets a mutable reference to the request headers.
///
/// Returns `None` if the headers has been taken by another extractor.
pub fn headers_mut(&mut self) -> Option<&mut HeaderMap> {
self.headers.as_mut()
}
/// Takes the headers out of the request, leaving a `None` in its place.
pub fn take_headers(&mut self) -> Option<HeaderMap> {
self.headers.take()
}
/// Gets a reference to the request extensions.
///
/// Returns `None` if the extensions has been taken by another extractor.
pub fn extensions(&self) -> Option<&Extensions> {
self.extensions.as_ref()
}
/// Gets a mutable reference to the request extensions.
///
/// Returns `None` if the extensions has been taken by another extractor.
pub fn extensions_mut(&mut self) -> Option<&mut Extensions> {
self.extensions.as_mut()
}
/// Takes the extensions out of the request, leaving a `None` in its place.
pub fn take_extensions(&mut self) -> Option<Extensions> {
self.extensions.take()
}
/// Gets a reference to the request body.
///
/// Returns `None` if the body has been taken by another extractor.
pub fn body(&self) -> Option<&B> {
self.body.as_ref()
}
/// Gets a mutable reference to the request body.
///
/// Returns `None` if the body has been taken by another extractor.
pub fn body_mut(&mut self) -> Option<&mut B> {
self.body.as_mut()
}
/// Takes the body out of the request, leaving a `None` in its place.
pub fn take_body(&mut self) -> Option<B> {
self.body.take()
}
}
#[async_trait]
impl<T, B> FromRequest<B> for Option<T>
where
T: FromRequest<B>,
B: Send,
{
type Rejection = Infallible;
async fn from_request(req: &mut RequestParts<B>) -> Result<Option<T>, Self::Rejection> {
Ok(T::from_request(req).await.ok())
}
}
#[async_trait]
impl<T, B> FromRequest<B> for Result<T, T::Rejection>
where
T: FromRequest<B>,
B: Send,
{
type Rejection = Infallible;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
Ok(T::from_request(req).await)
}
}
+85
View File
@@ -0,0 +1,85 @@
//! Rejection response types.
define_rejection! {
#[status = INTERNAL_SERVER_ERROR]
#[body = "Cannot have two request body extractors for a single handler"]
/// Rejection type used if you try and extract the request body more than
/// once.
pub struct BodyAlreadyExtracted;
}
define_rejection! {
#[status = INTERNAL_SERVER_ERROR]
#[body = "Headers taken by other extractor"]
/// Rejection used if the headers has been taken by another extractor.
pub struct HeadersAlreadyExtracted;
}
define_rejection! {
#[status = INTERNAL_SERVER_ERROR]
#[body = "Extensions taken by other extractor"]
/// Rejection used if the request extension has been taken by another
/// extractor.
pub struct ExtensionsAlreadyExtracted;
}
define_rejection! {
#[status = BAD_REQUEST]
#[body = "Failed to buffer the request body"]
/// Rejection type for extractors that buffer the request body. Used if the
/// request body cannot be buffered due to an error.
pub struct FailedToBufferBody(Error);
}
define_rejection! {
#[status = BAD_REQUEST]
#[body = "Request body didn't contain valid UTF-8"]
/// Rejection type used when buffering the request into a [`String`] if the
/// body doesn't contain valid UTF-8.
pub struct InvalidUtf8(Error);
}
composite_rejection! {
/// Rejection used for [`Request<_>`].
///
/// Contains one variant for each way the [`Request<_>`] extractor can fail.
///
/// [`Request<_>`]: http::Request
pub enum RequestAlreadyExtracted {
BodyAlreadyExtracted,
HeadersAlreadyExtracted,
ExtensionsAlreadyExtracted,
}
}
composite_rejection! {
/// Rejection used for [`Bytes`](bytes::Bytes).
///
/// Contains one variant for each way the [`Bytes`](bytes::Bytes) extractor
/// can fail.
pub enum BytesRejection {
BodyAlreadyExtracted,
FailedToBufferBody,
}
}
composite_rejection! {
/// Rejection used for [`String`].
///
/// Contains one variant for each way the [`String`] extractor can fail.
pub enum StringRejection {
BodyAlreadyExtracted,
FailedToBufferBody,
InvalidUtf8,
}
}
composite_rejection! {
/// Rejection used for [`http::request::Parts`].
///
/// Contains one variant for each way the [`http::request::Parts`] extractor can fail.
pub enum RequestPartsAlreadyExtracted {
HeadersAlreadyExtracted,
ExtensionsAlreadyExtracted,
}
}
+182
View File
@@ -0,0 +1,182 @@
use super::{rejection::*, FromRequest, RequestParts};
use crate::BoxError;
use async_trait::async_trait;
use bytes::Bytes;
use http::{Extensions, HeaderMap, Method, Request, Uri, Version};
use std::convert::Infallible;
#[async_trait]
impl<B> FromRequest<B> for Request<B>
where
B: Send,
{
type Rejection = RequestAlreadyExtracted;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
let req = std::mem::replace(
req,
RequestParts {
method: req.method.clone(),
version: req.version,
uri: req.uri.clone(),
headers: None,
extensions: None,
body: None,
},
);
let err = match req.try_into_request() {
Ok(req) => return Ok(req),
Err(err) => err,
};
match err.downcast::<RequestAlreadyExtracted>() {
Ok(err) => return Err(err),
Err(err) => unreachable!(
"Unexpected error type from `try_into_request`: `{:?}`. This is a bug in axum, please file an issue",
err,
),
}
}
}
#[async_trait]
impl<B> FromRequest<B> for Method
where
B: Send,
{
type Rejection = Infallible;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
Ok(req.method().clone())
}
}
#[async_trait]
impl<B> FromRequest<B> for Uri
where
B: Send,
{
type Rejection = Infallible;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
Ok(req.uri().clone())
}
}
#[async_trait]
impl<B> FromRequest<B> for Version
where
B: Send,
{
type Rejection = Infallible;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
Ok(req.version())
}
}
#[async_trait]
impl<B> FromRequest<B> for HeaderMap
where
B: Send,
{
type Rejection = HeadersAlreadyExtracted;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
req.take_headers().ok_or(HeadersAlreadyExtracted)
}
}
#[async_trait]
impl<B> FromRequest<B> for Extensions
where
B: Send,
{
type Rejection = ExtensionsAlreadyExtracted;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
req.take_extensions().ok_or(ExtensionsAlreadyExtracted)
}
}
#[async_trait]
impl<B> FromRequest<B> for Bytes
where
B: http_body::Body + Send,
B::Data: Send,
B::Error: Into<BoxError>,
{
type Rejection = BytesRejection;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
let body = take_body(req)?;
let bytes = crate::body::to_bytes(body)
.await
.map_err(FailedToBufferBody::from_err)?;
Ok(bytes)
}
}
#[async_trait]
impl<B> FromRequest<B> for String
where
B: http_body::Body + Send,
B::Data: Send,
B::Error: Into<BoxError>,
{
type Rejection = StringRejection;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
let body = take_body(req)?;
let bytes = crate::body::to_bytes(body)
.await
.map_err(FailedToBufferBody::from_err)?
.to_vec();
let string = String::from_utf8(bytes).map_err(InvalidUtf8::from_err)?;
Ok(string)
}
}
#[async_trait]
impl<B> FromRequest<B> for http::request::Parts
where
B: Send,
{
type Rejection = RequestPartsAlreadyExtracted;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
let method = unwrap_infallible(Method::from_request(req).await);
let uri = unwrap_infallible(Uri::from_request(req).await);
let version = unwrap_infallible(Version::from_request(req).await);
let headers = HeaderMap::from_request(req).await?;
let extensions = Extensions::from_request(req).await?;
let mut temp_request = Request::new(());
*temp_request.method_mut() = method;
*temp_request.uri_mut() = uri;
*temp_request.version_mut() = version;
*temp_request.headers_mut() = headers;
*temp_request.extensions_mut() = extensions;
let (parts, _) = temp_request.into_parts();
Ok(parts)
}
}
fn unwrap_infallible<T>(result: Result<T, Infallible>) -> T {
match result {
Ok(value) => value,
Err(err) => match err {},
}
}
pub(crate) fn take_body<B>(req: &mut RequestParts<B>) -> Result<B, BodyAlreadyExtracted> {
req.take_body().ok_or(BodyAlreadyExtracted)
}
+40
View File
@@ -0,0 +1,40 @@
use super::{FromRequest, RequestParts};
use crate::{body::BoxBody, response::IntoResponse};
use async_trait::async_trait;
use http::Response;
use std::convert::Infallible;
#[async_trait]
impl<B> FromRequest<B> for ()
where
B: Send,
{
type Rejection = Infallible;
async fn from_request(_: &mut RequestParts<B>) -> Result<(), Self::Rejection> {
Ok(())
}
}
macro_rules! impl_from_request {
() => {};
( $($ty:ident),* $(,)? ) => {
#[async_trait]
#[allow(non_snake_case)]
impl<B, $($ty,)*> FromRequest<B> for ($($ty,)*)
where
$( $ty: FromRequest<B> + Send, )*
B: Send,
{
type Rejection = Response<BoxBody>;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
$( let $ty = $ty::from_request(req).await.map_err(|err| err.into_response())?; )*
Ok(($($ty,)*))
}
}
};
}
all_the_tuples!(impl_from_request);
+63
View File
@@ -0,0 +1,63 @@
//! Core types and traits for [`axum`].
//!
//! Libraries authors that want to provide [`FromRequest`] or [`IntoResponse`] implementations
//! should depend on the [`axum-core`] crate, instead of `axum` if possible.
//!
//! [`FromRequest`]: crate::extract::FromRequest
//! [`IntoResponse`]: crate::response::IntoResponse
//! [`axum`]: https://crates.io/crates/axum
//! [`axum-core`]: http://crates.io/crates/axum-core
#![warn(
clippy::all,
clippy::dbg_macro,
clippy::todo,
clippy::empty_enum,
clippy::enum_glob_use,
clippy::mem_forget,
clippy::unused_self,
clippy::filter_map_next,
clippy::needless_continue,
clippy::needless_borrow,
clippy::match_wildcard_for_single_variants,
clippy::if_let_mutex,
clippy::mismatched_target_os,
clippy::await_holding_lock,
clippy::match_on_vec_items,
clippy::imprecise_flops,
clippy::suboptimal_flops,
clippy::lossy_float_literal,
clippy::rest_pat_in_fully_bound_structs,
clippy::fn_params_excessive_bools,
clippy::exit,
clippy::inefficient_to_string,
clippy::linkedlist,
clippy::macro_use_imports,
clippy::option_option,
clippy::verbose_file_reads,
clippy::unnested_or_patterns,
clippy::str_to_string,
rust_2018_idioms,
future_incompatible,
nonstandard_style,
missing_debug_implementations,
missing_docs
)]
#![deny(unreachable_pub, private_in_public)]
#![allow(elided_lifetimes_in_paths, clippy::type_complexity)]
#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(test, allow(clippy::float_cmp))]
#[macro_use]
pub(crate) mod macros;
mod error;
pub use self::error::Error;
pub mod body;
pub mod extract;
pub mod response;
/// Alias for a type-erased error type.
pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
+159
View File
@@ -0,0 +1,159 @@
macro_rules! define_rejection {
(
#[status = $status:ident]
#[body = $body:expr]
$(#[$m:meta])*
pub struct $name:ident;
) => {
$(#[$m])*
#[derive(Debug)]
#[non_exhaustive]
pub struct $name;
#[allow(deprecated)]
impl $crate::response::IntoResponse for $name {
fn into_response(self) -> http::Response<$crate::body::BoxBody> {
let mut res = http::Response::new($crate::body::boxed(http_body::Full::from($body)));
*res.status_mut() = http::StatusCode::$status;
res
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", $body)
}
}
impl std::error::Error for $name {}
impl Default for $name {
fn default() -> Self {
Self
}
}
};
(
#[status = $status:ident]
#[body = $body:expr]
$(#[$m:meta])*
pub struct $name:ident (Error);
) => {
$(#[$m])*
#[derive(Debug)]
pub struct $name(pub(crate) crate::Error);
impl $name {
pub(crate) fn from_err<E>(err: E) -> Self
where
E: Into<crate::BoxError>,
{
Self(crate::Error::new(err))
}
}
impl crate::response::IntoResponse for $name {
fn into_response(self) -> http::Response<$crate::body::BoxBody> {
let body = http_body::Full::from(format!(concat!($body, ": {}"), self.0));
let body = $crate::body::boxed(body);
let mut res =
http::Response::new(body);
*res.status_mut() = http::StatusCode::$status;
res
}
}
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", $body)
}
}
impl std::error::Error for $name {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
};
}
macro_rules! composite_rejection {
(
$(#[$m:meta])*
pub enum $name:ident {
$($variant:ident),+
$(,)?
}
) => {
$(#[$m])*
#[derive(Debug)]
#[non_exhaustive]
pub enum $name {
$(
#[allow(missing_docs, deprecated)]
$variant($variant)
),+
}
impl $crate::response::IntoResponse for $name {
fn into_response(self) -> http::Response<$crate::body::BoxBody> {
match self {
$(
Self::$variant(inner) => inner.into_response(),
)+
}
}
}
$(
#[allow(deprecated)]
impl From<$variant> for $name {
fn from(inner: $variant) -> Self {
Self::$variant(inner)
}
}
)+
impl std::fmt::Display for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
$(
Self::$variant(inner) => write!(f, "{}", inner),
)+
}
}
}
impl std::error::Error for $name {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
$(
Self::$variant(inner) => Some(inner),
)+
}
}
}
};
}
macro_rules! all_the_tuples {
($name:ident) => {
$name!(T1);
$name!(T1, T2);
$name!(T1, T2, T3);
$name!(T1, T2, T3, T4);
$name!(T1, T2, T3, T4, T5);
$name!(T1, T2, T3, T4, T5, T6);
$name!(T1, T2, T3, T4, T5, T6, T7);
$name!(T1, T2, T3, T4, T5, T6, T7, T8);
$name!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
$name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
$name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
$name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
$name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
$name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
$name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15);
$name!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16);
};
}
+220
View File
@@ -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);
}
}
+359
View File
@@ -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
}
}