rusthyperrust-axumrust-tonic

Cannot return Status type on tonic layer


I follow https://github.com/hyperium/tonic/blob/master/examples/src/tower/server.rs to capture body content of a rpc request before calling logic request.

But I get:

 type mismatch resolving `<Route as Service<Request<Body>>>::Error == Status`
   --> server/src/rpc.rs:645:85
    |
645 |     routes.routes().into_axum_router().layer(GrpcWebLayer::new()).layer(cors_layer).layer(layer)
    |                                                                                     ^^^^^ expected `Status`, found `Infallible`
    |
note: required for `AuthMiddleware<Route>` to implement `Service<http::Request<AxumBody>>`
   --> server/src/rpc.rs:666:18
    |
666 | impl<S, ResBody> Service<http::Request<AxumBody>> for AuthMiddleware<S>
    |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^     ^^^^^^^^^^^^^^^^^
...
671 |             Error = Status,
    |             -------------- unsatisfied trait bound introduced here

I do:

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

type BoxFuture<'a, T> = Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>;

impl<S, ResBody> Service<http::Request<AxumBody>> for AuthMiddleware<S>
where
    S: Service<
            http::Request<AxumBody>,
            Response = http::Response<ResBody>,
            Error = Status,
        > + Clone
        + Send
        + 'static,
    S::Future: Send + 'static,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;

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

    fn call(&mut self, req: http::Request<axum::body::Body>) -> Self::Future {
        let clone = self.inner.clone();
        let mut inner = std::mem::replace(&mut self.inner, clone);

        Box::pin(async move {
            let (parts, body) = req.into_parts();
                        let content = body
                .collect()
                .await
                .map_err(|_err| {
                    println!("error");
                })
                .unwrap()
                .to_bytes();
         
                 let content_without_path = "";

                 match parts.headers.get("authorization") {
                Some(t) => {
                    let validation = &mut Validation::new(Algorithm::ES256);
                    validation.validate_exp = false;
                    let t_string = t.to_str().unwrap().replace("Bearer ", "");
                    match decode::<Claims>(&t_string, decoding_key, validation) {
                        Ok(token_data) => {
                            // Compare body with scope
                            if token_data.claims.scope == content_without_path {
                                let body = AxumBody::from(content);
                                let response = inner
                                    .call(http::Request::from_parts(parts, body))
                                    .await
                                    .map_err(|_err| {
                                        println!("error");
                                    })
                                    .unwrap();
                                Ok(response)
                            } else {
                                Err(Status::unauthenticated("Not allowed"))
                            }
                        }
                        Err(err) => Err(Status::unauthenticated(err.to_string())),
                    }
                }
                None => Err(Status::unauthenticated("No token found")),
            }
        })
    }
}

I declare return type to be Status on Error, I don't declare or use std::convert::Infallible.

I never use Infallible, I don't know what it's for So I don't inderstand what is wrong.


Solution

  • The example you are following is using tower directly. However you adding layers after .into_axum_router() which means the they have to follow axum's conventions. Specifically axum expects middleware to ultimately be infallible - errors should be converted to responses.

    From axum::middleware:

    If you choose to implement a custom error type such as type Error = BoxError (a boxed opaque error), or any other error type that is not Infallible, you must use a HandleErrorLayer, here is an example using a ServiceBuilder:

    ServiceBuilder::new()
        .layer(HandleErrorLayer::new(|_: BoxError| async {
            // because axum uses infallible errors, you must handle your custom error type from your middleware here
            StatusCode::BAD_REQUEST
        }))
        .layer(
             // <your actual layer which DOES return an error>
        );
    

    You can use the above wrapper or it'd likely be better to just change your service to be Infallible and return your errors as Response<Body>. If Status is the one from tonic, then .to_http() should be able to do that.