mirror of
https://github.com/tokio-rs/axum.git
synced 2026-09-06 00:00:17 +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,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));
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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>;
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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