Replies: 7 comments 2 replies
-
I don't have any suggestions for debugging, but it looks like at least part of the issue is missing auto traits: You return |
Beta Was this translation helpful? Give feedback.
-
Rather than returning functions (that is often hard in Rust) I'd consider writing a type that implements |
Beta Was this translation helpful? Give feedback.
-
I don't think the problem is with the return type. I tried simplifying it greatly, down to a single-function wrapper that is not generic over request/response type, and rather than returning a closure, it returns the result directly. Then I try to build the closure on the outside of the wrapper manually. I am now basing this off the rest-grpc-multiplex example. I am also able to successfully attach the #[debug_handler] macro to the wrapper functions and it doesn't show any errors. // example of the simple wrapper. it works fine
#[debug_handler]
async fn HelloWrapper(Json(body): Json<HelloRequest>) -> Result<Json<HelloReply>, HybridError> {
let handler = GrpcServiceImpl {};
let r = handler.say_hello(tonic::Request::new(body)).await;
match r {
Ok(r) => Ok(Json(r.into_inner())),
Err(e) => Err(HybridError(e))
}
}
// generic wrapper that is implemented only for a single request/response type.
// debug_handler doesn't error
#[debug_handler]
async fn GenericWrapper(func: fn(&GrpcServiceImpl, tonic::Request<HelloRequest>) -> tonic::codegen::Pin<Box<dyn tonic::codegen::Future<Output = Result<tonic::Response<HelloReply>, tonic::Status>> + Send>>,
Json(body): Json<HelloRequest>) -> Result<Json<HelloReply>, HybridError> {
let handler = GrpcServiceImpl {};
let r = func(&handler, tonic::Request::new(body)).await;
match r {
Ok(r) => Ok(Json(r.into_inner())),
Err(e) => Err(HybridError(e))
}
}
#[tokio::main]
async fn main() {
// I'm able to convert the basic wrapper into a service
let rest = Router::new().route("/Hello", axum::routing::any(HelloWrapper));
// basic closure over the raw implementation. works fine
let h = GrpcServiceImpl {};
let a = Box::pin((|| async {h.say_hello(tonic::Request::<HelloRequest>::new(HelloRequest {name:"asdf".to_string()}))})());
// closure over basic wrapper. it works fine also
let b = Box::pin((|| async {HelloWrapper(Json(HelloRequest {name: "asdf".to_string()})).await})());
// errors on the type of say_hello
let c = Box::pin((|| async {GenericWrapper(GrpcServiceImpl::say_hello, Json(HelloRequest {name: "asdf".to_string()})).await})());
// just attempting to pass the say_hello function to the GenericWrapper, without all the async await stuff, gives the same error
let d = GenericWrapper(GrpcServiceImpl::say_hello, Json(HelloRequest {name: "asdf".to_string()}));
} When I inspect the return type inlays with rust-analyzer, it says that But
This is pretty much exactly the same as the original error, but it's clear now that it isn't tied to the return type of the function. |
Beta Was this translation helpful? Give feedback.
-
You're right. I should have clarified, the issue is not with the return type of the wrapping function, but with the wrapped function. The problem is that the #[async_trait] macro generates a Pin<Box> without an explicit lifetime, but the func parameter to my wrapper function demands a 'static lifetime. And a func that satisfies my wrapper function doesn't implement the trait that the tonic generator creates, so in order to make both work, I have to implement an inner function with a 'static lifetime and wrap it for the GRPC trait implementation. Here is a working example: #[derive(Default)]
struct GrpcServiceImpl {}
impl GrpcServiceImpl {
// manually write code that's equivalent to that generated by #[async_trait]
// except add the 'static lifetime parameter
fn inner_hello(
&self,
request: tonic::Request<HelloRequest>,
) -> tonic::codegen::Pin<Box<dyn tonic::codegen::Future<Output = Result<tonic::Response<HelloReply>, tonic::Status>> + Send + 'static>> {
async fn run(request: tonic::Request<HelloRequest>) -> Result<tonic::Response<HelloReply>, tonic::Status>{
tracing::info!("Got a request from {:?}", request.remote_addr());
let reply = HelloReply {
message: format!("Hello {}!", request.into_inner().name),
};
Ok(TonicResponse::new(reply))
}
Box::pin(run(request))
}
}
#[tonic::async_trait]
impl Greeter for GrpcServiceImpl {
async fn say_hello(
&self,
request: tonic::Request<HelloRequest>,
) -> Result<tonic::Response<HelloReply>, tonic::Status> {
GrpcServiceImpl::inner_hello(self, request).await
}
}
#[tokio::main]
async fn main() {
// build the rest service
let rest = Router::new()
.route("/Hello2", axum::routing::any(|Json(req): Json<HelloRequest>| async {GenericWrapper(GrpcServiceImpl::inner_hello, Json(req)).await}));
} This does NOT succeed in meeting my primary objective of reducing boilerplate. Anyway, it's clearly not an Axum problem at this point, so I'll take up the issue with tonic or async_trait developers. Thanks for your time. Unless you can tell me how to easily coerce these function types... |
Beta Was this translation helpful? Give feedback.
-
Axum and tonic are both based on tower. So maybe you're able to express things as just |
Beta Was this translation helpful? Give feedback.
-
Have you tried using macro to solve this problem? |
Beta Was this translation helpful? Give feedback.
-
I was able to resolve the issue. Once I got the return type nailed down, I was running afoul of one of these compiler bugs: See PR #1082. Maybe this can be useful to others too. |
Beta Was this translation helpful? Give feedback.
-
edit: Solution here: #1082
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:
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:
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:
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.
Beta Was this translation helpful? Give feedback.
All reactions