Skip to content

Commit

Permalink
Add RouteDsl::or to combine routes
Browse files Browse the repository at this point in the history
With this you'll be able to do:

```rust
let one = route("/foo", get(|| async { "foo" }))
    .route("/bar", get(|| async { "bar" }));

let two = route("/baz", get(|| async { "baz" }));

let app = one.or(two);
```

Fixes #101
  • Loading branch information
davidpdrsn committed Aug 3, 2021
1 parent f84f52d commit b2cb6e9
Showing 1 changed file with 78 additions and 2 deletions.
80 changes: 78 additions & 2 deletions src/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,16 @@ pub trait RoutingDsl: crate::sealed::Sealed + Sized {
{
IntoMakeServiceWithConnectInfo::new(self)
}

// TODO(david): Could doing `.or` with some random service lead to strange
// behavior?
// Could `where S: RoutingDsl` prevent misuse?
fn or<S>(self, other: S) -> Or<Self, S> {
Or {
first: self,
second: other,
}
}
}

impl<S, F> RoutingDsl for Route<S, F> {}
Expand Down Expand Up @@ -507,7 +517,11 @@ impl<E> RoutingDsl for EmptyRouter<E> {}

impl<E> crate::sealed::Sealed for EmptyRouter<E> {}

impl<B, E> Service<Request<B>> for EmptyRouter<E> {
impl<B, E> Service<Request<B>> for EmptyRouter<E>
where
// TODO(david): breaking change
B: Send + Sync + 'static,
{
type Response = Response<BoxBody>;
type Error = E;
type Future = EmptyRouterFuture<E>;
Expand All @@ -516,8 +530,9 @@ impl<B, E> Service<Request<B>> for EmptyRouter<E> {
Poll::Ready(Ok(()))
}

fn call(&mut self, _req: Request<B>) -> Self::Future {
fn call(&mut self, request: Request<B>) -> Self::Future {
let mut res = Response::new(crate::body::empty());
res.extensions_mut().insert(FromEmptyRouter { request });
*res.status_mut() = self.status;
EmptyRouterFuture {
future: future::ok(res),
Expand All @@ -531,6 +546,16 @@ opaque_future! {
future::Ready<Result<Response<BoxBody>, E>>;
}

/// Response extension used by [`EmptyRouter`] to send the request back to [`Or`] so
/// the other service can be called.
///
/// Without this we would loose ownership of the response when calling the first
/// service in [`Or`]. We also wouldn't be able to identify if the response came
/// from [`EmptyRouter`] and therefore can be discarded in [`Or`].
struct FromEmptyRouter<B> {
request: Request<B>,
}

#[derive(Debug, Clone)]
pub(crate) struct PathPattern(Arc<Inner>);

Expand Down Expand Up @@ -1001,6 +1026,57 @@ fn strip_prefix(uri: &Uri, prefix: &str) -> Uri {
Uri::from_parts(parts).unwrap()
}

#[derive(Debug, Clone, Copy)]
pub struct Or<A, B> {
first: A,
second: B,
}

impl<A, B> RoutingDsl for Or<A, B> {}

impl<A, B> crate::sealed::Sealed for Or<A, B> {}

#[allow(warnings)]
impl<A, B, ReqBody> Service<Request<ReqBody>> for Or<A, B>
where
A: Service<Request<ReqBody>, Response = Response<BoxBody>> + Clone,
B: Service<Request<ReqBody>, Response = Response<BoxBody>, Error = A::Error> + Clone,
ReqBody: Send + Sync + 'static,
A: Send + 'static,
B: Send + 'static,
A::Future: Send + 'static,
B::Future: Send + 'static,
{
type Response = Response<BoxBody>;
type Error = A::Error;
// TODO(david): don't use a boxed future here
type Future = futures_util::future::BoxFuture<'static, Result<Self::Response, Self::Error>>;

fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}

fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
let mut first = self.first.clone();
let mut second = self.second.clone();

Box::pin(async move {
let mut response: Response<BoxBody> = first.oneshot(req).await?;

let req = if let Some(ext) = response
.extensions_mut()
.remove::<FromEmptyRouter<ReqBody>>()
{
ext.request
} else {
return Ok(response);
};

second.oneshot(req).await
})
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down

0 comments on commit b2cb6e9

Please sign in to comment.