diff --git a/src/lib.rs b/src/lib.rs index e8e0d264..af992746 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,15 +4,10 @@ Improvements to make: -Somehow return generic "into response" kinda types without having to manually -create hyper::Body for everything +Break stuff up into modules Support extracting headers, perhaps via `headers::Header`? -Body to bytes extractor - -Implement `FromRequest` for more functions, with macro - Tests */ @@ -20,7 +15,7 @@ Tests use async_trait::async_trait; use bytes::Bytes; use futures_util::{future, ready}; -use http::{Method, Request, Response, StatusCode}; +use http::{header, HeaderValue, Method, Request, Response, StatusCode}; use http_body::Body as _; use pin_project::pin_project; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -69,9 +64,9 @@ pub struct RouteAt { } impl RouteAt { - pub fn get(self, handler_fn: F) -> RouteBuilder, R>> + pub fn get(self, handler_fn: F) -> RouteBuilder, R>> where - F: Handler, + F: Handler, { self.add_route(handler_fn, Method::GET) } @@ -84,9 +79,9 @@ impl RouteAt { self.add_route_service(service, Method::GET) } - pub fn post(self, handler_fn: F) -> RouteBuilder, R>> + pub fn post(self, handler_fn: F) -> RouteBuilder, R>> where - F: Handler, + F: Handler, { self.add_route(handler_fn, Method::POST) } @@ -99,9 +94,13 @@ impl RouteAt { self.add_route_service(service, Method::POST) } - fn add_route(self, handler: H, method: Method) -> RouteBuilder, R>> + fn add_route( + self, + handler: H, + method: Method, + ) -> RouteBuilder, R>> where - H: Handler, + H: Handler, { self.add_route_service(HandlerSvc::new(handler), method) } @@ -149,9 +148,9 @@ impl RouteBuilder { self.app.at(route_spec) } - pub fn get(self, handler_fn: F) -> RouteBuilder, R>> + pub fn get(self, handler_fn: F) -> RouteBuilder, R>> where - F: Handler, + F: Handler, { self.app.at_bytes(self.route_spec).get(handler_fn) } @@ -164,9 +163,9 @@ impl RouteBuilder { self.app.at_bytes(self.route_spec).get_service(service) } - pub fn post(self, handler_fn: F) -> RouteBuilder, R>> + pub fn post(self, handler_fn: F) -> RouteBuilder, R>> where - F: Handler, + F: Handler, { self.app.at_bytes(self.route_spec).post(handler_fn) } @@ -193,6 +192,9 @@ pub enum Error { #[error("failed to deserialize the request body")] DeserializeRequestBody(#[source] serde_json::Error), + #[error("failed to serialize the response body")] + SerializeResponseBody(#[source] serde_json::Error), + #[error("failed to consume the body")] ConsumeRequestBody(#[source] hyper::Error), @@ -225,8 +227,8 @@ mod sealed { } #[async_trait] -pub trait Handler: Sized { - type Response: IntoResponse; +pub trait Handler: Sized { + type Response: IntoResponse; // This seals the trait. We cannot use the regular "sealed super trait" approach // due to coherence. @@ -237,37 +239,94 @@ pub trait Handler: Sized { fn layer(self, layer: L) -> Layered where - L: Layer>, + L: Layer>, { Layered::new(layer.layer(HandlerSvc::new(self))) } } -pub trait IntoResponse { - fn into_response(self) -> Response; +pub trait IntoResponse { + fn into_response(self) -> Result, Error>; } -impl IntoResponse for Response -where - B: Into, -{ - fn into_response(self) -> Response { - self.map(Into::into) +impl IntoResponse for Response { + fn into_response(self) -> Result, Error> { + Ok(self) } } -impl IntoResponse for String { - fn into_response(self) -> Response { - Response::new(Body::from(self)) +impl IntoResponse for &'static str { + fn into_response(self) -> Result, Error> { + Ok(Response::new(Body::from(self))) + } +} + +impl IntoResponse for String { + fn into_response(self) -> Result, Error> { + Ok(Response::new(Body::from(self))) + } +} + +impl IntoResponse for Bytes { + fn into_response(self) -> Result, Error> { + Ok(Response::new(Body::from(self))) + } +} + +impl IntoResponse for &'static [u8] { + fn into_response(self) -> Result, Error> { + Ok(Response::new(Body::from(self))) + } +} + +impl IntoResponse for Vec { + fn into_response(self) -> Result, Error> { + Ok(Response::new(Body::from(self))) + } +} + +impl IntoResponse for std::borrow::Cow<'static, str> { + fn into_response(self) -> Result, Error> { + Ok(Response::new(Body::from(self))) + } +} + +impl IntoResponse for std::borrow::Cow<'static, [u8]> { + fn into_response(self) -> Result, Error> { + Ok(Response::new(Body::from(self))) + } +} + +// TODO(david): rename this to Json when its in another module +pub struct JsonBody(T); + +impl IntoResponse for JsonBody +where + T: Serialize, +{ + fn into_response(self) -> Result, Error> { + let bytes = serde_json::to_vec(&self.0).map_err(Error::SerializeResponseBody)?; + let len = bytes.len(); + let mut res = Response::new(Body::from(bytes)); + + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("application/json"), + ); + + res.headers_mut() + .insert(header::CONTENT_LENGTH, HeaderValue::from(len)); + + Ok(res) } } #[async_trait] -impl Handler<()> for F +impl Handler for F where F: Fn(Request) -> Fut + Send + Sync, Fut: Future> + Send, - Res: IntoResponse, + Res: IntoResponse, { type Response = Res; @@ -282,11 +341,11 @@ macro_rules! impl_handler { ( $head:ident $(,)? ) => { #[async_trait] #[allow(non_snake_case)] - impl Handler<($head,)> for F + impl Handler for F where F: Fn(Request, $head) -> Fut + Send + Sync, Fut: Future> + Send, - Res: IntoResponse, + Res: IntoResponse, $head: FromRequest + Send, { type Response = Res; @@ -304,11 +363,11 @@ macro_rules! impl_handler { ( $head:ident, $($tail:ident),* $(,)? ) => { #[async_trait] #[allow(non_snake_case)] - impl Handler<($head, $($tail,)*)> for F + impl Handler for F where F: Fn(Request, $head, $($tail,)*) -> Fut + Send + Sync, Fut: Future> + Send, - Res: IntoResponse, + Res: IntoResponse, $head: FromRequest + Send, $( $tail: FromRequest + Send, )* { @@ -347,10 +406,9 @@ where } #[async_trait] -impl Handler for Layered +impl Handler for Layered where - S: Service> + Send, - S::Response: IntoResponse, + S: Service, Response = Response> + Send, S::Error: Into, S::Future: Send, { @@ -375,12 +433,12 @@ impl Layered { } } -pub struct HandlerSvc { +pub struct HandlerSvc { handler: H, - _input: PhantomData T>, + _input: PhantomData (B, T)>, } -impl HandlerSvc { +impl HandlerSvc { fn new(handler: H) -> Self { Self { handler, @@ -389,7 +447,7 @@ impl HandlerSvc { } } -impl Clone for HandlerSvc +impl Clone for HandlerSvc where H: Clone, { @@ -401,25 +459,27 @@ where } } -impl Service> for HandlerSvc +impl Service> for HandlerSvc where - H: Handler + Clone + Send + 'static, + H: Handler + Clone + Send + 'static, H::Response: 'static, { - type Response = Response; + type Response = Response; type Error = Error; type Future = future::BoxFuture<'static, Result>; fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - // HandlerSvc can only be constructed from async functions which are always ready + // HandlerSvc can only be constructed from async functions which are always ready, or from + // `Layered` which bufferes in `::call` and is therefore also always + // ready. Poll::Ready(Ok(())) } fn call(&mut self, req: Request) -> Self::Future { let handler = self.handler.clone(); Box::pin(async move { - let res = Handler::call(handler, req).await?; - Ok(res.into_response()) + let res = Handler::call(handler, req).await?.into_response()?; + Ok(res) }) } } @@ -543,6 +603,31 @@ where } } +// TODO(david): rename this to Bytes when its in another module +#[derive(Debug, Clone)] +pub struct BytesBody(Bytes); + +impl BytesBody { + pub fn into_inner(self) -> Bytes { + self.0 + } +} + +impl FromRequest for BytesBody { + type Future = future::BoxFuture<'static, Result>; + + fn from_request(req: &mut Request) -> Self::Future { + let body = std::mem::take(req.body_mut()); + + Box::pin(async move { + let bytes = hyper::body::to_bytes(body) + .await + .map_err(Error::ConsumeRequestBody)?; + Ok(BytesBody(bytes)) + }) + } +} + #[derive(Clone, Copy)] pub struct EmptyRouter(()); @@ -771,7 +856,9 @@ where | Error::QueryStringMissing | Error::DeserializeQueryString(_) => make_response(StatusCode::BAD_REQUEST), - Error::MissingExtension { .. } => make_response(StatusCode::INTERNAL_SERVER_ERROR), + Error::MissingExtension { .. } | Error::SerializeResponseBody(_) => { + make_response(StatusCode::INTERNAL_SERVER_ERROR) + } Error::Service(err) => match err.downcast::() { Ok(err) => Err(*err), @@ -820,46 +907,44 @@ mod tests { Ok(Response::new(Body::empty())) } - async fn users_index( - _: Request, - pagination: Query, - ) -> Result { - let pagination = pagination.into_inner(); - assert_eq!(pagination.page, 1); - assert_eq!(pagination.per_page, 30); - Ok::<_, Error>("users#index".to_string()) - } - - let app = app() - // routes with functions - .at("/") - .get(root) - // routes with closures - .at("/users") - .get(users_index) - .post( - |_: Request, - payload: Json, - _state: Extension>| async { - let payload = payload.into_inner(); - assert_eq!(payload.username, "bob"); - Ok::<_, Error>(Response::new(Body::from("users#create"))) - }, - ) - // routes with a service - .at("/service") - .get_service(service_fn(root)) - // routes with layers applied - .at("/large-static-file") - .get( - large_static_file.layer( - ServiceBuilder::new() - .layer(TimeoutLayer::new(Duration::from_secs(30))) - .layer(CompressionLayer::new()) - .into_inner(), - ), - ) - .into_service(); + let app = + app() + // routes with functions + .at("/") + .get(root) + // routes with closures + .at("/users") + .get(|_: Request, pagination: Query| async { + let pagination = pagination.into_inner(); + assert_eq!(pagination.page, 1); + assert_eq!(pagination.per_page, 30); + Ok::<_, Error>("users#index".to_string()) + }) + .post( + |_: Request, + payload: Json, + _state: Extension>| async { + let payload = payload.into_inner(); + assert_eq!(payload.username, "bob"); + Ok::<_, Error>(JsonBody( + serde_json::json!({ "username": payload.username }), + )) + }, + ) + // routes with a service + .at("/service") + .get_service(service_fn(root)) + // routes with layers applied + .at("/large-static-file") + .get( + large_static_file.layer( + ServiceBuilder::new() + .layer(TimeoutLayer::new(Duration::from_secs(30))) + .layer(CompressionLayer::new()) + .into_inner(), + ), + ) + .into_service(); // state shared by all routes, could hold db connection etc struct State {} @@ -934,7 +1019,7 @@ mod tests { .await .unwrap(); assert_eq!(res.status(), StatusCode::OK); - assert_eq!(body_to_string(res).await, "users#create"); + assert_eq!(body_to_string(res).await, r#"{"username":"bob"}"#); } async fn body_to_string(res: Response) -> String