Skip to content

Commit

Permalink
chore(volo-http): move FromRequest from request to extract
Browse files Browse the repository at this point in the history
Signed-off-by: Yu Li <[email protected]>
  • Loading branch information
yukiiiteru committed Dec 6, 2023
1 parent 5ecbed6 commit 8a188e2
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 130 deletions.
125 changes: 123 additions & 2 deletions volo-http/src/extract.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
use std::convert::Infallible;

use bytes::Bytes;
use futures_util::Future;
use hyper::http::{Method, Uri};
use http_body_util::BodyExt;
use hyper::{
body::Incoming,
http::{header, HeaderMap, Method, StatusCode, Uri},
};
use serde::de::DeserializeOwned;
use volo::net::Address;

use crate::{HttpContext, Params, State};
use crate::{
param::Params,
response::{IntoResponse, Response},
HttpContext,
};

mod private {
#[derive(Debug, Clone, Copy)]
pub enum ViaContext {}

#[derive(Debug, Clone, Copy)]
pub enum ViaRequest {}
}

pub trait FromContext<S>: Sized {
fn from_context(
Expand All @@ -13,6 +31,19 @@ pub trait FromContext<S>: Sized {
) -> impl Future<Output = Result<Self, Infallible>> + Send;
}

pub trait FromRequest<S, M = private::ViaRequest>: Sized {
fn from(
cx: &HttpContext,
body: Incoming,
state: &S,
) -> impl Future<Output = Result<Self, Response>> + Send;
}

#[derive(Debug, Default, Clone, Copy)]
pub struct State<S>(pub S);

pub struct Json<T>(pub T);

impl<T, S> FromContext<S> for Option<T>
where
T: FromContext<S>,
Expand Down Expand Up @@ -67,3 +98,93 @@ where
Ok(State(state.clone()))
}
}

impl<T, S> FromRequest<S, private::ViaContext> for T
where
T: FromContext<S> + Sync,
S: Sync,
{
async fn from(cx: &HttpContext, _body: Incoming, state: &S) -> Result<Self, Response> {
match T::from_context(cx, state).await {
Ok(value) => Ok(value),
Err(rejection) => Err(rejection.into_response()),
}
}
}

impl<S> FromRequest<S> for Incoming
where
S: Sync,
{
async fn from(_cx: &HttpContext, body: Incoming, _state: &S) -> Result<Self, Response> {
Ok(body)
}
}

impl<T: DeserializeOwned, S> FromRequest<S> for Json<T> {
fn from(
cx: &HttpContext,
body: Incoming,
_state: &S,
) -> impl Future<Output = Result<Self, Response>> + Send {

Check warning on line 129 in volo-http/src/extract.rs

View workflow job for this annotation

GitHub Actions / clippy

this function can be simplified using the `async fn` syntax

warning: this function can be simplified using the `async fn` syntax --> volo-http/src/extract.rs:125:5 | 125 | / fn from( 126 | | cx: &HttpContext, 127 | | body: Incoming, 128 | | _state: &S, 129 | | ) -> impl Future<Output = Result<Self, Response>> + Send { | |____________________________________________________________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn = note: `#[warn(clippy::manual_async_fn)]` on by default help: make the function `async` and return the output of the future directly | 125 ~ async fn from( 126 + cx: &HttpContext, 127 + body: Incoming, 128 + _state: &S, 129 ~ ) -> Result<Self, Response> { | help: move the body of the async block to the enclosing function | 129 ~ ) -> impl Future<Output = Result<Self, Response>> + Send { 130 + if !json_content_type(&cx.headers) { 131 + return Err(Response::builder() 132 + .status(StatusCode::UNSUPPORTED_MEDIA_TYPE) 133 + .body(Bytes::new().into()) 134 + .unwrap() 135 + .into()); 136 + } 137 + 138 + match body.collect().await { 139 + Ok(body) => { 140 + let body = body.to_bytes(); 141 + match serde_json::from_slice::<T>(body.as_ref()) { 142 + Ok(t) => Ok(Self(t)), 143 + Err(e) => { 144 + tracing::warn!("json serialization error {e}"); 145 + Err(Response::builder() 146 + .status(StatusCode::BAD_REQUEST) 147 + .body(Bytes::new().into()) 148 + .unwrap() 149 + .into()) 150 + } 151 + } 152 + } 153 + Err(e) => { 154 + tracing::warn!("collect body error: {e}"); 155 + Err(Response::builder() 156 + .status(StatusCode::BAD_REQUEST) 157 + .body(Bytes::new().into()) 158 + .unwrap() 159 + .into()) 160 + } 161 + } 162 + } |
async move {
if !json_content_type(&cx.headers) {
return Err(Response::builder()
.status(StatusCode::UNSUPPORTED_MEDIA_TYPE)
.body(Bytes::new().into())
.unwrap()
.into());
}

match body.collect().await {
Ok(body) => {
let body = body.to_bytes();
match serde_json::from_slice::<T>(body.as_ref()) {
Ok(t) => Ok(Self(t)),
Err(e) => {
tracing::warn!("json serialization error {e}");
Err(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Bytes::new().into())
.unwrap()
.into())
}
}
}
Err(e) => {
tracing::warn!("collect body error: {e}");
Err(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Bytes::new().into())
.unwrap()
.into())
}
}
}
}
}

fn json_content_type(headers: &HeaderMap) -> bool {
let content_type = if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
content_type
} else {
return false;
};

let content_type = if let Ok(content_type) = content_type.to_str() {
content_type
} else {
return false;
};

let mime = if let Ok(mime) = content_type.parse::<mime::Mime>() {
mime
} else {
return false;
};

let is_json_content_type = mime.type_() == "application"
&& (mime.subtype() == "json" || mime.suffix().map_or(false, |name| name == "json"));

is_json_content_type
}
3 changes: 1 addition & 2 deletions volo-http/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ use hyper::body::Incoming;
use motore::Service;

use crate::{
extract::FromContext,
extract::{FromContext, FromRequest},
macros::{all_the_tuples, all_the_tuples_no_last_special_case},
request::FromRequest,
response::{IntoResponse, Response},
DynService, HttpContext,
};
Expand Down
6 changes: 2 additions & 4 deletions volo-http/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,15 @@ pub use hyper::{
pub use volo::net::Address;

pub use crate::{
extract::{Json, State},
param::Params,
request::{Json, Request},
request::Request,
response::Response,
server::Server,
};

pub type DynService = motore::BoxCloneService<HttpContext, Incoming, Response, Infallible>;

#[derive(Debug, Default, Clone, Copy)]
pub struct State<S>(pub S);

pub struct HttpContext {
pub peer: Address,
pub method: Method,
Expand Down
123 changes: 1 addition & 122 deletions volo-http/src/request.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,6 @@
use std::ops::{Deref, DerefMut};

use bytes::Bytes;
use futures_util::Future;
use http_body_util::BodyExt;
use hyper::{
body::Incoming,
http::{header, request::Builder, HeaderMap, StatusCode},
};
use serde::de::DeserializeOwned;

use crate::{
extract::FromContext,
response::{IntoResponse, Response},
HttpContext,
};
use hyper::{body::Incoming, http::request::Builder};

pub struct Request(pub(crate) hyper::http::Request<hyper::body::Incoming>);

Expand Down Expand Up @@ -42,111 +29,3 @@ impl From<hyper::http::Request<Incoming>> for Request {
Self(value)
}
}

mod private {
#[derive(Debug, Clone, Copy)]
pub enum ViaContext {}

#[derive(Debug, Clone, Copy)]
pub enum ViaRequest {}
}

pub trait FromRequest<S, M = private::ViaRequest>: Sized {
fn from(
cx: &HttpContext,
body: Incoming,
state: &S,
) -> impl Future<Output = Result<Self, Response>> + Send;
}

impl<T, S> FromRequest<S, private::ViaContext> for T
where
T: FromContext<S> + Sync,
S: Sync,
{
async fn from(cx: &HttpContext, _body: Incoming, state: &S) -> Result<Self, Response> {
match T::from_context(cx, state).await {
Ok(value) => Ok(value),
Err(rejection) => Err(rejection.into_response()),
}
}
}

impl<S> FromRequest<S> for Incoming
where
S: Sync,
{
async fn from(_cx: &HttpContext, body: Incoming, _state: &S) -> Result<Self, Response> {
Ok(body)
}
}

pub struct Json<T>(pub T);

impl<T: DeserializeOwned, S> FromRequest<S> for Json<T> {
fn from(
cx: &HttpContext,
body: Incoming,
_state: &S,
) -> impl Future<Output = Result<Self, Response>> + Send {
async move {
if !json_content_type(&cx.headers) {
return Err(Response::builder()
.status(StatusCode::UNSUPPORTED_MEDIA_TYPE)
.body(Bytes::new().into())
.unwrap()
.into());
}

match body.collect().await {
Ok(body) => {
let body = body.to_bytes();
match serde_json::from_slice::<T>(body.as_ref()) {
Ok(t) => Ok(Self(t)),
Err(e) => {
tracing::warn!("json serialization error {e}");
Err(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Bytes::new().into())
.unwrap()
.into())
}
}
}
Err(e) => {
tracing::warn!("collect body error: {e}");
Err(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Bytes::new().into())
.unwrap()
.into())
}
}
}
}
}

fn json_content_type(headers: &HeaderMap) -> bool {
let content_type = if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
content_type
} else {
return false;
};

let content_type = if let Ok(content_type) = content_type.to_str() {
content_type
} else {
return false;
};

let mime = if let Ok(mime) = content_type.parse::<mime::Mime>() {
mime
} else {
return false;
};

let is_json_content_type = mime.type_() == "application"
&& (mime.subtype() == "json" || mime.suffix().map_or(false, |name| name == "json"));

is_json_content_type
}

0 comments on commit 8a188e2

Please sign in to comment.