Skip to content
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

Storing with_interceptor clients is substantially more challenging in 0.5 #730

Closed
Sushisource opened this issue Jul 20, 2021 · 19 comments
Closed
Labels

Comments

@Sushisource
Copy link
Contributor

Feature Request

Crates

tonic

Motivation

Previously, I could simply store a FooServiceClient<tonic::transport::Channel> without the need for generics or boxing in a struct, and use that. I do so to encapsulate access to the client, partly for mocking reasons, partly for enforcing certain invariants or making the API simpler.

Now, the type is something like FooServiceClient<tonic::codegen::InterceptedService<Channel, impl Fn<(tonic::Request<()>,)>>> which is a much bigger pain to store.

Proposal

Kind of like #671, but not quite, it would be nice if there was simply a FooServiceClient trait with all the rpc methods on it that the generated structs would implement (using async_trait, maybe opt-in). This would make storage and mocking much simpler.

Alternatives

Beyond having a full client trait, a trait that combines all the bounds in the big where clause on generated clients would also make this much easier. I'm referencing:

    impl<T> FooServiceClient<T>
    where
        T: tonic::client::GrpcService<tonic::body::BoxBody>,
        T::ResponseBody: Body + Send + Sync + 'static,
        T::Error: Into<StdError>,
        <T::ResponseBody as Body>::Error: Into<StdError> + Send,
@davidpdrsn
Copy link
Member

I think the right solution for this is #713. Then you can define interceptors as nameable types without using closures.

@Sushisource
Copy link
Contributor Author

I think the right solution for this is #713. Then you can define interceptors as nameable types without using closures.

Yeah that looks like a great way to solve the problem.

@mattoni
Copy link

mattoni commented Aug 4, 2021

What's the correct way to use #713 to have a client implementation as a struct field?

@yotamofek
Copy link
Contributor

yotamofek commented Aug 8, 2021

What's the correct way to use #713 to have a client implementation as a struct field?

@mattoni you mean something like this?

struct MyInterceptor {}

impl Interceptor for MyInterceptor {
    fn call(&mut self, request: tonic::Request<()>) -> Result<tonic::Request<()>, Status> {
        todo();
    }
}

struct ClientWrapper<S: Service> {
    client: InterceptedService<S, MyInterceptor>,
}

@davidpdrsn
Copy link
Member

This was fixed in #713 and released as part of tonic 0.5.1. I also added some examples here https://github.com/hyperium/tonic/blob/master/examples/src/interceptor/client.rs. Let me know if you have more questions!

@mattoni
Copy link

mattoni commented Aug 9, 2021

Awesome, thanks for the examples @davidpdrsn . One quick question, I noticed you're passing the struct in exactly here:

  let client: GreeterClient<InterceptedService<Channel, MyInterceptor>> =
      GreeterClient::with_interceptor(channel, MyInterceptor);

However, when attempting that I get the following:
image

I've rebuilt with the latest tonic-build: 0.5.1 and still get the same thing. The generated service signature is this:
image

and my AuthService is implemented as this:
image

How can I get it to follow your example?

Thanks!

@davidpdrsn
Copy link
Member

Are you certain you've updated to tonic 0.5.1 (not tonic-build)? It looks like you're still on 0.5.0.

@mattoni
Copy link

mattoni commented Aug 9, 2021

Looks like my old build artifacts were still cached even though I had done a cargo clean? Anyway, working now. Cheers!

@mattoni
Copy link

mattoni commented Aug 9, 2021

Actually I spoke too soon. I switched to using the git dependency directly and it's still having the same issue in the generated code. The actual interceptor definition looks correct, but generated params are still wrong:

image

here are the deps I have listed. Did a cargo clean and a fresh cargo build and got the error.

image

@davidpdrsn
Copy link
Member

No idea whats wrong. The code on GitHub looks correct.

@davidpdrsn
Copy link
Member

Are you able to reproduce it in a smaller example? If so, can make a repo with the code, then I'll take a look.

@mattoni
Copy link

mattoni commented Aug 10, 2021

So I found the issue - I set tonic-build to the git repo version and it worked. looks like 0.5.1 generates the wrong code.

davidpdrsn added a commit that referenced this issue Aug 10, 2021
So as highlighted by
#730 (comment) it
turns out the new `Interceptor` trait required changes to tonic-build as
well, which I forgot to publish yesterday. Didn't see it because it wasn't
clear from the commit message.

So this bumps tonic _and_ tonic-build to 0.5.2 so we can release both.
There are no code changes.
@davidpdrsn
Copy link
Member

omg you're right... I'm sorry. This is my fault. When I made the release yesterday I forgot to ship tonic-build. Gonna get it out today.

davidpdrsn added a commit that referenced this issue Aug 10, 2021
So as highlighted by
#730 (comment) it
turns out the new `Interceptor` trait required changes to tonic-build as
well, which I forgot to publish yesterday. Didn't see it because it wasn't
clear from the commit message.

So this bumps tonic _and_ tonic-build to 0.5.2 so we can release both.
There are no code changes.
@davidpdrsn
Copy link
Member

@mattoni Thanks for noticing. Tonic and tonic-build 0.5.2 are on crates.io now. I've tested things myself and everything seems to be working.

@mattoni
Copy link

mattoni commented Aug 10, 2021

Thanks so much! It works perfectly now.

@lacasaprivata2
Copy link

One thing that is much harder now as a tradeoff is capturing the variable outside the function

@lacasaprivata2
Copy link

lacasaprivata2 commented Nov 12, 2021

tough.....

type Intercept = Box<dyn Fn(Request<()>) -> Result<Request<()>, Status>>;
pub fn api(actor_id: &String) -> CatalogApiClient<InterceptedService<Channel, Intercept>> {
  let actor = actor_id.clone();
  CatalogApiClient::with_interceptor(channel::io.clone(), Box::new(move |mut ostream: Request<()>| {
    tokenize(&actor, &mut ostream);
    Ok(ostream)
  }))
}

@davidpdrsn
Copy link
Member

Please try and be more constructive than "gross". Actual people worked hard on this feature.

You're right that its a tradeoff. The new interceptor design made new things possible that hadn't been possible before. The feature you're asking about is still possible but requires typing a few more characters. Seems like a good tradeoff to me.

In that specific case I think implementing the Interceptor trait would be easier for you.

@lacasaprivata2
Copy link

I was referring to my own code; interceptor trait included above

Many approaches

(1) define Boxed closure (2) add to metadata map w/ a hack the input data then transform in middleware

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants