Common JSON wrapper type for response and request (#140)

This commit is contained in:
Sunli 2021-08-07 22:07:13 +08:00 committed by GitHub
parent 3d45a97db9
commit 345163e98d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 162 additions and 141 deletions

View file

@ -1,76 +0,0 @@
use super::{has_content_type, rejection::*, take_body, FromRequest, RequestParts};
use async_trait::async_trait;
use serde::de::DeserializeOwned;
use std::ops::Deref;
/// Extractor that deserializes request bodies into some type.
///
/// `T` is expected to implement [`serde::Deserialize`].
///
/// # Example
///
/// ```rust,no_run
/// use axum::prelude::*;
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct CreateUser {
/// email: String,
/// password: String,
/// }
///
/// async fn create_user(payload: extract::Json<CreateUser>) {
/// let payload: CreateUser = payload.0;
///
/// // ...
/// }
///
/// let app = route("/users", post(create_user));
/// # async {
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # };
/// ```
///
/// If the query string cannot be parsed it will reject the request with a `400
/// Bad Request` response.
///
/// The request is required to have a `Content-Type: application/json` header.
#[derive(Debug, Clone, Copy, Default)]
pub struct Json<T>(pub T);
#[async_trait]
impl<T, B> FromRequest<B> for Json<T>
where
T: DeserializeOwned,
B: http_body::Body + Send,
B::Data: Send,
B::Error: Into<tower::BoxError>,
{
type Rejection = JsonRejection;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
use bytes::Buf;
if has_content_type(req, "application/json")? {
let body = take_body(req)?;
let buf = hyper::body::aggregate(body)
.await
.map_err(InvalidJsonBody::from_err)?;
let value = serde_json::from_reader(buf.reader()).map_err(InvalidJsonBody::from_err)?;
Ok(Json(value))
} else {
Err(MissingJsonContentType.into())
}
}
}
impl<T> Deref for Json<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}

View file

@ -257,7 +257,6 @@ pub mod rejection;
mod content_length_limit;
mod extension;
mod form;
mod json;
mod path;
mod query;
mod raw_query;
@ -274,7 +273,6 @@ pub use self::{
extension::Extension,
extractor_middleware::extractor_middleware,
form::Form,
json::Json,
path::Path,
query::Query,
raw_query::RawQuery,
@ -282,6 +280,8 @@ pub use self::{
url_params::UrlParams,
url_params_map::UrlParamsMap,
};
#[doc(no_inline)]
pub use crate::Json;
#[cfg(feature = "multipart")]
#[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
@ -568,7 +568,7 @@ where
}
}
fn has_content_type<B>(
pub(crate) fn has_content_type<B>(
req: &RequestParts<B>,
expected_content_type: &str,
) -> Result<bool, HeadersAlreadyExtracted> {
@ -591,6 +591,6 @@ fn has_content_type<B>(
Ok(content_type.starts_with(expected_content_type))
}
fn take_body<B>(req: &mut RequestParts<B>) -> Result<B, BodyAlreadyExtracted> {
pub(crate) fn take_body<B>(req: &mut RequestParts<B>) -> Result<B, BodyAlreadyExtracted> {
req.take_body().ok_or(BodyAlreadyExtracted)
}

150
src/json.rs Normal file
View file

@ -0,0 +1,150 @@
use crate::{
extract::{has_content_type, rejection::*, take_body, FromRequest, RequestParts},
prelude::response::IntoResponse,
Body,
};
use async_trait::async_trait;
use http::{
header::{self, HeaderValue},
StatusCode,
};
use hyper::Response;
use serde::{de::DeserializeOwned, Serialize};
use std::ops::{Deref, DerefMut};
/// JSON Extractor/Response
///
/// When used as an extractor, it can deserialize request bodies into some type that
/// implements [`serde::Serialize`]. If the request body cannot be parsed, or it does not contain
/// the `Content-Type: application/json` header, it will reject the request and return a
/// `400 Bad Request` response.
///
/// # Extractor example
///
/// ```rust,no_run
/// use axum::prelude::*;
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// struct CreateUser {
/// email: String,
/// password: String,
/// }
///
/// async fn create_user(extract::Json(payload): extract::Json<CreateUser>) {
/// // payload is a `CreateUser`
///
/// // ...
/// }
///
/// let app = route("/users", post(create_user));
/// # async {
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # };
/// ```
///
/// When used as a response, it can serialize any type that implements [`serde::Serialize`] to `JSON`,
/// and will automatically set `Content-Type: application/json` header.
///
/// # Response example
///
/// ```
/// use axum::{
/// prelude::*,
/// extract::Path,
/// Json,
/// };
/// use serde::Serialize;
/// use uuid::Uuid;
///
/// #[derive(Serialize)]
/// struct User {
/// name: String,
/// email: String,
/// }
///
/// async fn get_user(Path(user_id) : Path<Uuid>) -> Json<User> {
/// todo!()
/// }
///
/// let app = route("/users/:id", get(get_user));
/// # async {
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
/// # };
/// ```
#[derive(Debug, Clone, Copy, Default)]
pub struct Json<T>(pub T);
#[async_trait]
impl<T, B> FromRequest<B> for Json<T>
where
T: DeserializeOwned,
B: http_body::Body + Send,
B::Data: Send,
B::Error: Into<tower::BoxError>,
{
type Rejection = JsonRejection;
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
use bytes::Buf;
if has_content_type(req, "application/json")? {
let body = take_body(req)?;
let buf = hyper::body::aggregate(body)
.await
.map_err(InvalidJsonBody::from_err)?;
let value = serde_json::from_reader(buf.reader()).map_err(InvalidJsonBody::from_err)?;
Ok(Json(value))
} else {
Err(MissingJsonContentType.into())
}
}
}
impl<T> Deref for Json<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for Json<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T> From<T> for Json<T> {
fn from(inner: T) -> Self {
Self(inner)
}
}
impl<T> IntoResponse for Json<T>
where
T: Serialize,
{
fn into_response(self) -> Response<Body> {
let bytes = match serde_json::to_vec(&self.0) {
Ok(res) => res,
Err(err) => {
return Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.header(header::CONTENT_TYPE, "text/plain")
.body(Body::from(err.to_string()))
.unwrap();
}
};
let mut res = Response::new(Body::from(bytes));
res.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("application/json"),
);
res
}
}

View file

@ -713,6 +713,7 @@ use tower::Service;
pub(crate) mod macros;
mod buffer;
mod json;
mod util;
pub mod body;
@ -737,6 +738,8 @@ pub use http;
pub use hyper::Server;
pub use tower_http::add_extension::{AddExtension, AddExtensionLayer};
pub use crate::json::Json;
pub mod prelude {
//! Re-exports of important traits, types, and functions used with axum. Meant to be glob
//! imported.

View file

@ -65,10 +65,10 @@ macro_rules! define_rejection {
) => {
$(#[$m])*
#[derive(Debug)]
pub struct $name(pub(super) tower::BoxError);
pub struct $name(pub(crate) tower::BoxError);
impl $name {
pub(super) fn from_err<E>(err: E) -> Self
pub(crate) fn from_err<E>(err: E) -> Self
where
E: Into<tower::BoxError>,
{

View file

@ -3,10 +3,12 @@
use crate::Body;
use bytes::Bytes;
use http::{header, HeaderMap, HeaderValue, Response, StatusCode};
use serde::Serialize;
use std::{borrow::Cow, convert::Infallible};
use tower::util::Either;
#[doc(no_inline)]
pub use crate::Json;
/// Trait for generating responses.
///
/// Types that implement `IntoResponse` can be returned from handlers.
@ -207,64 +209,6 @@ impl<T> From<T> for Html<T> {
}
}
/// A JSON response.
///
/// Can be created from any type that implements [`serde::Serialize`].
///
/// Will automatically get `Content-Type: application/json`.
///
/// # Example
///
/// ```
/// use serde_json::json;
/// use axum::{body::Body, response::{Json, IntoResponse}};
/// use http::{Response, header::CONTENT_TYPE};
///
/// let json = json!({
/// "data": 42,
/// });
///
/// let response: Response<Body> = Json(json).into_response();
///
/// assert_eq!(
/// response.headers().get(CONTENT_TYPE).unwrap(),
/// "application/json",
/// );
/// ```
#[derive(Clone, Copy, Debug)]
pub struct Json<T>(pub T);
impl<T> IntoResponse for Json<T>
where
T: Serialize,
{
fn into_response(self) -> Response<Body> {
let bytes = match serde_json::to_vec(&self.0) {
Ok(res) => res,
Err(err) => {
return Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.header(header::CONTENT_TYPE, "text/plain")
.body(Body::from(err.to_string()))
.unwrap();
}
};
let mut res = Response::new(Body::from(bytes));
res.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("application/json"),
);
res
}
}
impl<T> From<T> for Json<T> {
fn from(inner: T) -> Self {
Self(inner)
}
}
#[cfg(test)]
mod tests {
use super::*;