Calling a Service inside another Service (Tower, Axum)

I would like to implement a logic that authenticates a user and then decides if they are authorized to view a resource. I would like to achieve that feature by implementing two services:

I list only Future implementations below for the sake of brevity.

Authenticate Service

Service attempts to retrieve a user from DB.

impl Future for AuthenticateServiceFuture {
    type Output = Result<User, Option<UserError>>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        if self.public_id.is_none() {
            return Poll::Ready(Err(None));
        }

        let public_id = self.public_id.unwrap();
        let future = User::get(GetUserBy::PublicId(public_id.as_str()), &self.db_pool);
        let mut boxed_future = Box::pin(future);
        let polled_result = boxed_future.as_mut().poll(cx);

        match polled_result {
            Poll::Ready(result) => {
                if let Ok(user) = result {
                    Poll::Ready(Ok(user))
                } else {
                    Poll::Ready(Err(result.err()))
                }
            }
            Poll::Pending => Poll::Pending,
        }
    }
}

Where User::get is another Future.

Protect Route Service

Service wraps some other service. It also retrieves a User via AuthenticateService call. If the user is authenticated, access is granted; the inner service response is returned. Otherwise a redirect should be made.

impl<F, ReqBody, ResBody> Future for ProtectRouteResponseFuture<F, ReqBody>
where
    F: Future<Output = BoxFuture<'static, Result<Response<ResBody>, Redirect>>>,
{
    type Output = BoxFuture<'static, Result<Response<ResBody>, Redirect>>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        let this = self.project();
        let inner_service = ready!(this.inner.poll(cx));

        Box::pin(async {
            match this.authenticate_service.poll_ready(cx) {
                Poll::Ready(Ok(())) => this
                    .authenticate_service
                    .call(self.req)
                    .await
                    .and_then(|_| Ok(inner_service))
                    .or(Err(Redirect::to("/auth"))),
                Poll::Ready(Err(Some(user_error))) => Err(Redirect::to("/auth")),
                Poll::Ready(Err(None)) => Err(Redirect::to("/auth")),
                Poll::Pending => Err(Redirect::to("/auth")),
            }
        })
    }
}

However, the code does not compile. It seems to me that I misunderstand how Services can call each other and how, in turns, I should describe Future Output types. Unfortunately, articles from Tower did not help ://

You shouldn't manually implement the Future trait and especially not in this way. The poll method of a Future is called repeatedly until it completes, and the Future itself should store any temporary state inside itself. So for example:

This is making a new get request every time the Future is polled, which is not what you want, you want a single get request which is continuously polled with your Future, which is very cumbersome to write manually.

Here you're not even returning a Poll, but a whole different Future.

For now (at least until we get async fn in traits) your service's Future (and not the Future's output) should just be a BoxFuture<'static, Result<Response<ResBody>, Request>>

Alright, how would you write down (without so much of a detail) a scenario that does the following:

Service A wraps some inner Service B. Service A makes a call to Service C before resolving with a response made by Service B, if Service C allows to do so. If Service C succeeds, Service A returns with Response from Service B. Otherwise it builds its own Response and returns it.

How would Future fn poll return type look like in that case, given that i need to .await some other Service?

It should be something like this (note the comments though for why this doesn't fully work):

struct ServiceA {
    service_b: ServiceB,
    service_c: ServiceC,
}
impl<Req> Service<Req> for ServiceA {
    type Response = <ServiceB as Service<Req>>::Response;
    type Error = <ServiceB as Service<Req>>::Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
    
    fn poll_ready(
        &mut self, 
        cx: &mut Context<'_>
    ) -> Poll<Result<(), Self::Error>> {
        // Might want to `poll_ready` on `service_b`/`service_c` here.
        Poll::Ready(Ok(()))
    }
    
    fn call(&mut self, req: Req) -> Self::Future {
        // Note that you can't use `self` in the `async` block due to it
        // having to be `'static`.
        // Hence you have to prepare the futures here.
        
        // Also, this is invalid because `req` is moved in the first `call`.
        // You'll need to rethink what you want to do here.
        let future_b = self.service_b.call(req);
        let future_c = self.service_c.call(req);
    
        Box::new(async move {
            // Put here your `async` code with all the `.await`s you need
        })
    }
}

My point is that you don't implement Future and hence you don't write a poll function.


Rethinking this, maybe you want a middleware? Try checking out the axum::middleware module, in particular you might be interested in the from_fn function.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.