Use tower-http service as generic inner HTTP client
⚓ Rust 📅 2026-02-10 👤 surdeus 👁️ 6I'd like to implement a client for some HTTP API and want it to be generic over the actual inner HTTP client, i.e. the user should pass their own setup reqwest or Hyper Client instance (possibly with some layers to adapt request/response types, but I didn't even get to that part yet)
use std::convert::Infallible;
use std::fmt::Debug;
use std::marker::PhantomData;
// the actual implementation would take some domain types as parameters
// and create a Full body
fn create_request() -> http::Request<impl http_body::Body<Error = Infallible>> {
http::Request::builder()
.body(http_body_util::Empty::<bytes::Bytes>::new())
.unwrap()
}
pub struct Client<S, ReqBody, ResBody> {
service: S,
_req_body: PhantomData<ReqBody>,
_res_body: PhantomData<ResBody>,
}
impl<S, ReqBody, ResBody> Client<S, ReqBody, ResBody>
where
S: tower::Service<http::Request<ReqBody>, Response = http::Response<ResBody>> + Clone,
ReqBody: http_body::Body<Error = Infallible>,
<S as tower::Service<http::Request<ReqBody>>>::Error: Debug, // for unwrap
{
pub fn new(service: S) -> Self {
Self {
service,
_req_body: PhantomData,
_res_body: PhantomData,
}
}
pub async fn send_request(&self) {
use tower::ServiceExt as _;
let request = create_request();
let response = self
.service
.clone()
.ready()
.await
.unwrap()
.call(request)
.await
.unwrap();
todo!()
}
}
This results in an error about types not matching:
error[E0308]: mismatched types
--> src/lib.rs:43:19
|
5 | fn create_request() -> http::Request<impl http_body::Body<Error = Infallible>> {
| ---------------------------------------- the found opaque type
...
18 | impl<S, ReqBody, ResBody> Client<S, ReqBody, ResBody>
| ------- expected this type parameter
...
43 | .call(request)
| ---- ^^^^^^^ expected `Request<ReqBody>`, found `Request<impl Body<Error = Infallible>>`
| |
| arguments to this method are incorrect
|
= note: expected struct `http::Request<ReqBody>`
found struct `http::Request<impl Body<Error = Infallible>>`
= help: type parameters must be constrained to match other types
= note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters
help: the return type of this call is `http::Request<impl Body<Error = Infallible>>` due to the type of the argument passed
--> src/lib.rs:37:24
|
37 | let response = self
| ________________________^
38 | | .service
39 | | .clone()
40 | | .ready()
41 | | .await
42 | | .unwrap()
43 | | .call(request)
| |___________________-------^
| |
| this argument influences the return type of `call`
note: method defined here
--> /playground/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tower-service-0.3.3/src/lib.rs:355:8
|
355 | fn call(&mut self, req: Request) -> Self::Future;
| ^^^^
I'm stuck here since I don't understand why the types aren't matching, didn't I constrain them to be bound by the same traits? Those do not show up in the error however
This is likely Rust basics and not specific to tower-http, just my first time dabbling into more complex generics and where clauses
I've briefly tried to add S::Request: TryFrom<http::Request<impl Body<Error = Infallible>>> but that just lead to this trait not being implemented (and I think this also wouldn't work with orphan rule in all those thirdparty types?)
Does this require Box<dyn trait> ? If so I think it would not be compatible with tower::Service anymore
I got it compiling by returning / using http_body_util::Full instead of impl http_body::Body, but I would like to avoid naming a concrete (and thirdparty) type
P.S.: I've posted this as a question in the tower Discord, but that only helped confirm the general approach so far, not how to solve the concrete compilation error
4 posts - 3 participants
🏷️ Rust_feed