From 0d7e1e74c4eb42850a3deedf1be8956c51c4bf9c Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Tue, 1 Jun 2021 21:15:48 +0200 Subject: [PATCH] Adding layers to the whole thing --- src/lib.rs | 22 ++++++++------------ src/routing.rs | 55 +++++++++++++++++++++++++++++++++++--------------- src/tests.rs | 23 ++++++++++++++++++++- 3 files changed, 69 insertions(+), 31 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3a6a13f8..bb634ba7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,6 +41,10 @@ pub struct App { } impl App { + fn new(service_tree: R) -> Self { + Self { service_tree } + } + pub fn at(self, route_spec: &str) -> RouteAt { self.at_bytes(Bytes::copy_from_slice(route_spec.as_bytes())) } @@ -53,19 +57,9 @@ impl App { } } +#[derive(Clone)] pub struct IntoService { - app: App, -} - -impl Clone for IntoService -where - R: Clone, -{ - fn clone(&self) -> Self { - Self { - app: self.app.clone(), - } - } + service_tree: R } impl Service for IntoService @@ -78,14 +72,14 @@ where type Future = R::Future; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - match ready!(self.app.service_tree.poll_ready(cx)) { + match ready!(self.service_tree.poll_ready(cx)) { Ok(_) => Poll::Ready(Ok(())), Err(err) => match err {}, } } fn call(&mut self, req: T) -> Self::Future { - self.app.service_tree.call(req) + self.service_tree.call(req) } } diff --git a/src/routing.rs b/src/routing.rs index 04920cf5..21117c37 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -1,7 +1,8 @@ use crate::{ body::{Body, BoxBody}, handler::{Handler, HandlerSvc}, - App, IntoService, ResultExt, + response::IntoResponse, + App, HandleError, IntoService, ResultExt, }; use bytes::Bytes; use futures_util::{future, ready}; @@ -17,7 +18,7 @@ use std::{ use tower::{ buffer::{Buffer, BufferLayer}, util::BoxService, - BoxError, Service, ServiceBuilder, + BoxError, Layer, Service, ServiceBuilder, }; #[derive(Clone, Copy)] @@ -152,6 +153,13 @@ where } impl RouteBuilder { + fn new(app: App, route_spec: impl Into) -> Self { + Self { + app, + route_spec: route_spec.into(), + } + } + pub fn at(self, route_spec: &str) -> RouteAt { self.app.at(route_spec) } @@ -167,11 +175,32 @@ impl RouteBuilder { define_route_at_methods!(RouteBuilder: trace, trace_service, TRACE); pub fn into_service(self) -> IntoService { - IntoService { app: self.app } + IntoService { + service_tree: self.app.service_tree, + } } - // TODO(david): Add `layer` method here that applies a `tower::Layer` inside the service tree - // that way we get to map errors + pub fn layer(self, layer: L) -> RouteBuilder + where + L: Layer, + { + let layered = layer.layer(self.app.service_tree); + let app = App::new(layered); + RouteBuilder::new(app, self.route_spec) + } + + pub fn handle_error(self, f: F) -> RouteBuilder> + where + R: Service, Response = Response>, + F: FnOnce(R::Error) -> Res, + Res: IntoResponse, + B: http_body::Body + Send + Sync + 'static, + B::Error: Into + Send + Sync + 'static, + { + let svc = HandleError::new(self.app.service_tree, f); + let app = App::new(svc); + RouteBuilder::new(app, self.route_spec) + } pub fn boxed(self) -> RouteBuilder> where @@ -184,17 +213,11 @@ impl RouteBuilder { .layer(BoxService::layer()) .service(self.app.service_tree); - let app = App { - service_tree: BoxServiceTree { - inner: svc, - poll_ready_error: None, - }, - }; - - RouteBuilder { - app, - route_spec: self.route_spec, - } + let app = App::new(BoxServiceTree { + inner: svc, + poll_ready_error: None, + }); + RouteBuilder::new(app, self.route_spec) } } diff --git a/src/tests.rs b/src/tests.rs index b3d2ecf4..836c63c3 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -375,7 +375,28 @@ async fn handling_errors_from_layered_single_routes() { assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR); } -// TODO(david): .layer() on RouteBuilder +#[tokio::test] +async fn layer_on_whole_router() { + use tower::timeout::TimeoutLayer; + + async fn handle(_req: Request) -> &'static str { + tokio::time::sleep(Duration::from_secs(10)).await; + "" + } + + let app = app() + .at("/") + .get(handle) + .layer(TimeoutLayer::new(Duration::from_millis(100))) + .handle_error(|_err: BoxError| StatusCode::INTERNAL_SERVER_ERROR) + .into_service(); + + let addr = run_in_background(app).await; + + let res = reqwest::get(format!("http://{}", addr)).await.unwrap(); + assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR); +} + // TODO(david): composing two apps // TODO(david): composing two apps with one at a "sub path" // TODO(david): composing two boxed apps