Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to implement wrapper function that implements Handler? #1068

Closed
muusbolla opened this issue Jun 7, 2022 · 0 comments
Closed

How to implement wrapper function that implements Handler? #1068

muusbolla opened this issue Jun 7, 2022 · 0 comments

Comments

@muusbolla
Copy link

I don't think this is a bug or issue per se, but I would still love some help and the documentation around Handler is very sparse. It says "it should just work if you have the correct type", which is true for most free functions, but not for my use case. And the recommended #[debug_handler] macro also doesn't work for my use case.

Like #1052, I was inspired by https://www.fpcomplete.com/blog/axum-hyper-tonic-tower-part4/ to create a combined Tonic/Axum server. However I want to take it a step further by just having a single implementation for each RPC (the GRPC handler) and having Axum endpoints be wrappers around the GRPC handlers. For the Axum endpoint, this involves accepting JSON, deserializing into the GRPC type, calling the Tonic handler, then re-serializing the result back to JSON. This works fine with a manually crafted wrapper function like this:

struct MyHandler;

#[async_trait]
impl Echo for MyGrpcHandler {
    async fn echo(
        &self,
        request: tonic::Request<EchoRequest>,
    ) -> Result<tonic::Response<EchoReply>, tonic::Status> {
        Ok(tonic::Response::new(EchoReply {
            message: format!("Echoing back: {}", request.get_ref().message),
        }))
    }
}

async fn EchoHandlerWrapper(Json(body): Json<EchoRequest>) -> Result<Json<EchoReply>, HybridError> {
    let handler = MyGrpcHandler {};
    let r = handler.echo(tonic::Request::new(body)).await;
    match r {
        Ok(r) => Ok(Json(r.into_inner())),
        Err(e) => Err(HybridError(e)) // wrap GRPC errors into HTTP errors, not relevant to this issue
    }
}

#[tokio::main]
async fn main() {
    let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
    let axum_make_service = axum::Router::new()
        .route("/Echo", axum::handler::any(EchoHandlerWrapper))
        .into_make_service();
    let grpc_service = tonic::transport::Server::builder()
        .add_service(EchoServer::from_arc(arcHandler))
        .into_service();
    let hybrid_make_service = hybrid(axum_make_service, grpc_service);
    let server = hyper::Server::bind(&addr).serve(hybrid_make_service);
    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
}

However, I'd like to reduce the boilerplate associated with creating a new wrapper function for each new GRPC endpoint. So I attempted to create a generic wrapper function that takes a GRPC handler and transforms it into a closure that implements axum::Handler. Due to the structure of this function, I cannot apply the #[debug_handler] macro to it. Here's where I'm at now:

// This compiles. The input func is supposed to be a GRPC handler func. The output should be compatible with axum::Handler.
fn WrapGrpcHandler<'a, ReqT, ResT, FIn, FOut> (
    func: fn(&MyHandler, tonic::Request<ReqT>) -> Pin<Box<dyn Future<Output = Result<tonic::Response<ResT>, tonic::Status>> + Send + 'a>>)
       -> impl Fn(Json<ReqT>) -> Pin<Box<dyn Future<Output = Result<Json<ResT>, HybridError>> + 'a>> where //,HandlerFn
for<'de> ReqT: serde::Deserialize<'de> + 'a,
ResT: serde::Serialize + 'a{
    move |Json(req): Json<ReqT>| {Box::pin((|Json(req): Json<ReqT>| async move {
        let handler = std::sync::Arc::new(MyHandler {});
        let r = async move { func(handler.as_ref(), tonic::Request::new(req)).await }.await;
        match r {
            Ok(r) => Ok(Json(r.into_inner())),
            Err(e) => Err(HybridError(e))
        }
    })(Json(req)))}
}

But when I try to call it with a concrete function I get very unhelpful errors, and despite my best efforts I'm not able to coerce the function pointer into the right type:

// Gives error: the trait bound `impl Fn(Json<_>)-> Pin<Box<dyn futures::Future<Output = Result<Json<_>, HybridError>>>>: Handler<_, _>` is not satisfied
// the trait `Handler<ReqBody, T>` is implemented for `axum::handler::Layered<S, T>`rustcE0277
// server-hybrid.rs(263, 25): required by a bound introduced by this call
// mod.rs(52, 8): required by a bound in `axum::handler::any`
// the trait bound `impl Fn(axum::Json<_>)-> Pin<Box<dyn futures::Future<Output = Result<axum::Json<_>, HybridError>>>>: Handler<_, _>` is not satisfied
// the trait `Handler<ReqBody, T>` is implemented for `axum::handler::Layered<S, T>`rustcE0277
// server-hybrid.rs(263, 25): required by a bound introduced by this call
// mod.rs(52, 8): required by a bound in `axum::handler::any`
let axum_make_service = axum::Router::new()
        .route("/Echo", axum::handler::any(WrapGrpcHandler(MyGrpcHandler::echo)))
        .into_make_service();



// Gives error: mismatched types
// expected fn pointer `for<'r> fn(&'r MyHandler, tonic::Request<_>) -> Pin<Box<dyn futures::Future<Output = Result<tonic::Response<_>, Status>> + std::marker::Send>>`
// found fn item `fn(&MyHandler, tonic::Request<tonic_example::echo::EchoRequest>) -> Pin<Box<dyn futures::Future<Output = Result<tonic::Response<tonic_example::echo::EchoReply>, Status>> + std::marker::Send>> {<MyHandler as Echo>::echo::<'_, '_>}`rustcE0308
let x = WrapGrpcHandler(MyHandler::echo);

Apologies in advance if this is not the right forum to ask this question. But perhaps this can still be of use to others who are trying to do this kind of thing.

@tokio-rs tokio-rs locked and limited conversation to collaborators Jun 7, 2022
@jplatte jplatte converted this issue into discussion #1069 Jun 7, 2022

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant