Skip to content

Commit

Permalink
feat(volo-http): use generic types for resp and err (#361)
Browse files Browse the repository at this point in the history
* feat(volo-http): support generic type for response and error

* chore(volo-http): update examples

---------

Signed-off-by: Yu Li <[email protected]>
  • Loading branch information
yukiiiteru authored Feb 18, 2024
1 parent 3eac9c7 commit f468e3f
Show file tree
Hide file tree
Showing 8 changed files with 414 additions and 257 deletions.
101 changes: 95 additions & 6 deletions examples/src/http/example-http-server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::{convert::Infallible, net::SocketAddr, sync::Arc, time::Duration};
use async_stream::stream;
use bytes::Bytes;
use faststr::FastStr;
use motore::{layer::layer_fn, service::Service};
use serde::{Deserialize, Serialize};
use tokio_stream::StreamExt;
use volo_http::{
Expand All @@ -17,7 +18,7 @@ use volo_http::{
middleware::{self, Next},
request::ServerRequest,
response::{IntoResponse, ServerResponse},
route::{from_handler, get, post, service_fn, MethodRouter, Router},
route::{from_handler, from_service, get, post, service_fn, MethodRouter, Router},
Address, CookieJar, Json, Params, Server,
};

Expand Down Expand Up @@ -147,11 +148,8 @@ async fn extension(Extension(state): Extension<Arc<State>>) -> String {
format!("State {{ foo: {}, bar: {} }}\n", state.foo, state.bar)
}

async fn service_fn_test(
cx: &mut ServerContext,
req: ServerRequest,
) -> Result<ServerResponse, Infallible> {
Ok(format!("cx: {cx:?}, req: {req:?}").into_response())
async fn service_fn_test(cx: &mut ServerContext, req: ServerRequest) -> Result<String, Infallible> {
Ok(format!("cx: {cx:?}, req: {req:?}"))
}

fn index_router() -> Router {
Expand Down Expand Up @@ -238,6 +236,96 @@ fn test_router() -> Router {
}))
}

#[derive(Clone)]
enum ErrTestService<T> {
Val(T),
Err(usize),
}

impl<T> Service<ServerContext, ServerRequest> for ErrTestService<T>
where
T: IntoResponse + Clone + Send + Sync,
{
type Response = T;
type Error = usize;

async fn call(
&self,
_: &mut ServerContext,
_: ServerRequest,
) -> Result<Self::Response, Self::Error> {
match self {
Self::Val(val) => Ok(val.clone()),
Self::Err(err) => Err(*err),
}
}
}

#[derive(Clone)]
struct ErrMapService<S> {
inner: S,
}

impl<S, Resp> Service<ServerContext, ServerRequest> for ErrMapService<S>
where
S: Service<ServerContext, ServerRequest, Response = Resp, Error = usize> + Clone + Send + Sync,
Resp: IntoResponse,
{
type Response = ServerResponse;
type Error = Infallible;

async fn call(
&self,
cx: &mut ServerContext,
req: ServerRequest,
) -> Result<Self::Response, Self::Error> {
match self.inner.call(cx, req).await {
Ok(resp) => Ok(resp.into_response()),
Err(val) => {
let status = if val == 0 {
StatusCode::OK
} else {
StatusCode::IM_A_TEAPOT
};
Ok(status.into_response())
}
}
}
}

fn err_router() -> Router {
fn map_err_layer<Resp, S>(
s: S,
) -> impl Service<ServerContext, ServerRequest, Response = ServerResponse, Error = Infallible>
+ Clone
+ Send
+ Sync
where
S: Service<ServerContext, ServerRequest, Response = Resp, Error = usize>
+ Clone
+ Send
+ Sync,
Resp: IntoResponse,
{
ErrMapService { inner: s }
}

Router::new()
.route(
"/err/str",
MethodRouter::builder()
.get(from_service(ErrTestService::Val(String::from("114514"))))
.build(),
)
.route(
"/err/err",
MethodRouter::builder()
.get(from_service(ErrTestService::<()>::Err(1)))
.build(),
)
.layer(layer_fn(map_err_layer))
}

// You can use the following commands for testing cookies
//
// ```bash
Expand Down Expand Up @@ -315,6 +403,7 @@ async fn main() {
.merge(user_json_router())
.merge(user_form_router())
.merge(test_router())
.merge(err_router())
.layer(Extension(Arc::new(State {
foo: "Foo".to_string(),
bar: 114514,
Expand Down
13 changes: 6 additions & 7 deletions volo-http/src/extension.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::convert::Infallible;

use http::{request::Parts, StatusCode};
use motore::{layer::Layer, service::Service};
use volo::context::Context;
Expand All @@ -16,7 +14,9 @@ pub struct Extension<T>(pub T);

impl<S, T> Layer<S> for Extension<T>
where
S: Service<ServerContext, ServerRequest, Response = ServerResponse> + Send + Sync + 'static,
S: Service<ServerContext, ServerRequest> + Send + Sync + 'static,
S::Response: IntoResponse,
S::Error: IntoResponse,
T: Sync,
{
type Service = ExtensionService<S, T>;
Expand All @@ -34,10 +34,9 @@ pub struct ExtensionService<I, T> {

impl<S, T> Service<ServerContext, ServerRequest> for ExtensionService<S, T>
where
S: Service<ServerContext, ServerRequest, Response = ServerResponse, Error = Infallible>
+ Send
+ Sync
+ 'static,
S: Service<ServerContext, ServerRequest> + Send + Sync + 'static,
S::Response: IntoResponse,
S::Error: IntoResponse,
T: Clone + Send + Sync + 'static,
{
type Response = S::Response;
Expand Down
32 changes: 16 additions & 16 deletions volo-http/src/handler.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use std::{
convert::Infallible,
future::Future,
marker::PhantomData,
pin::Pin,
Expand All @@ -19,22 +18,22 @@ use crate::{
response::{IntoResponse, ServerResponse},
};

pub trait Handler<T>: Sized {
pub trait Handler<T, E>: Sized {
fn handle(
self,
cx: &mut ServerContext,
req: ServerRequest,
) -> impl Future<Output = ServerResponse> + Send;

fn into_service(self) -> HandlerService<Self, T> {
fn into_service(self) -> HandlerService<Self, T, E> {
HandlerService {
handler: self,
_marker: PhantomData,
}
}
}

impl<F, Fut, Res> Handler<((),)> for F
impl<F, Fut, Res, E> Handler<((),), E> for F
where
F: FnOnce() -> Fut + Clone + Send,
Fut: Future<Output = Res> + Send,
Expand All @@ -50,7 +49,7 @@ macro_rules! impl_handler {
[$($ty:ident),*], $last:ident
) => {
#[allow(non_snake_case, unused_mut, unused_variables)]
impl<F, Fut, Res, M, $($ty,)* $last> Handler<(M, $($ty,)* $last,)> for F
impl<F, Fut, Res, M, $($ty,)* $last, E> Handler<(M, $($ty,)* $last,), E> for F
where
F: FnOnce($($ty,)* $last) -> Fut + Clone + Send,
Fut: Future<Output = Res> + Send,
Expand Down Expand Up @@ -78,12 +77,12 @@ macro_rules! impl_handler {

all_the_tuples!(impl_handler);

pub struct HandlerService<H, T> {
pub struct HandlerService<H, T, E> {
handler: H,
_marker: PhantomData<fn(T)>,
_marker: PhantomData<fn(T, E)>,
}

impl<H, T> Clone for HandlerService<H, T>
impl<H, T, E> Clone for HandlerService<H, T, E>
where
H: Clone,
{
Expand All @@ -95,12 +94,12 @@ where
}
}

impl<H, T> Service<ServerContext, ServerRequest> for HandlerService<H, T>
impl<H, T, E> Service<ServerContext, ServerRequest> for HandlerService<H, T, E>
where
H: Handler<T> + Clone + Send + Sync,
H: Handler<T, E> + Clone + Send + Sync,
{
type Response = ServerResponse;
type Error = Infallible;
type Error = E;

async fn call(
&self,
Expand Down Expand Up @@ -159,32 +158,33 @@ macro_rules! impl_handler_without_request {

all_the_tuples_no_last_special_case!(impl_handler_without_request);

pub trait MiddlewareHandlerFromFn<'r, T>: Sized {
pub trait MiddlewareHandlerFromFn<'r, T, E>: Sized {
type Future: Future<Output = ServerResponse> + Send + 'r;

fn handle(&self, cx: &'r mut ServerContext, req: ServerRequest, next: Next) -> Self::Future;
fn handle(&self, cx: &'r mut ServerContext, req: ServerRequest, next: Next<E>) -> Self::Future;
}

macro_rules! impl_middleware_handler_from_fn {
(
[$($ty:ident),*], $last:ident
) => {
#[allow(non_snake_case, unused_mut, unused_variables)]
impl<'r, F, Fut, Res, M, $($ty,)* $last> MiddlewareHandlerFromFn<'r, (M, $($ty,)* $last)> for F
impl<'r, F, Fut, Res, M, $($ty,)* $last, E> MiddlewareHandlerFromFn<'r, (M, $($ty,)* $last), E> for F
where
F: Fn($($ty,)* &'r mut ServerContext, $last, Next) -> Fut + Copy + Send + Sync + 'static,
F: Fn($($ty,)* &'r mut ServerContext, $last, Next<E>) -> Fut + Copy + Send + Sync + 'static,
Fut: Future<Output = Res> + Send + 'r,
Res: IntoResponse + 'r,
$( $ty: FromContext + Send + 'r, )*
$last: FromRequest<M> + Send + 'r,
E: IntoResponse + 'r,
{
type Future = ResponseFuture<'r, ServerResponse>;

fn handle(
&self,
cx: &'r mut ServerContext,
req: ServerRequest,
next: Next,
next: Next<E>,
) -> Self::Future {
let f = *self;

Expand Down
28 changes: 15 additions & 13 deletions volo-http/src/layer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{convert::Infallible, marker::PhantomData, time::Duration};
use std::{marker::PhantomData, time::Duration};

use http::StatusCode;
use motore::{layer::Layer, service::Service};
Expand Down Expand Up @@ -51,15 +51,14 @@ pub struct Filter<S, H, R, T> {

impl<S, H, R, T> Service<ServerContext, ServerRequest> for Filter<S, H, R, T>
where
S: Service<ServerContext, ServerRequest, Response = ServerResponse, Error = Infallible>
+ Send
+ Sync
+ 'static,
S: Service<ServerContext, ServerRequest> + Send + Sync + 'static,
S::Response: IntoResponse,
S::Error: IntoResponse,
H: HandlerWithoutRequest<T, Result<(), R>> + Clone + Send + Sync + 'static,
R: IntoResponse + Send + Sync,
T: Sync,
{
type Response = S::Response;
type Response = ServerResponse;
type Error = S::Error;

async fn call<'s, 'cx>(
Expand All @@ -72,7 +71,11 @@ where
let req = ServerRequest::from_parts(parts, body);
match res {
// do not filter it, call the service
Ok(Ok(())) => self.service.call(cx, req).await,
Ok(Ok(())) => self
.service
.call(cx, req)
.await
.map(IntoResponse::into_response),
// filter it and return the specified response
Ok(Err(res)) => Ok(res.into_response()),
// something wrong while extracting
Expand Down Expand Up @@ -117,12 +120,11 @@ pub struct Timeout<S> {

impl<S> Service<ServerContext, ServerRequest> for Timeout<S>
where
S: Service<ServerContext, ServerRequest, Response = ServerResponse, Error = Infallible>
+ Send
+ Sync
+ 'static,
S: Service<ServerContext, ServerRequest> + Send + Sync + 'static,
S::Response: IntoResponse,
S::Error: IntoResponse,
{
type Response = S::Response;
type Response = ServerResponse;
type Error = S::Error;

async fn call<'s, 'cx>(
Expand All @@ -134,7 +136,7 @@ where
let fut_timeout = tokio::time::sleep(self.duration);

tokio::select! {
resp = fut_service => resp,
resp = fut_service => resp.map(IntoResponse::into_response),
_ = fut_timeout => {
Ok(StatusCode::REQUEST_TIMEOUT.into_response())
},
Expand Down
Loading

0 comments on commit f468e3f

Please sign in to comment.