Skip to content

feat: Add Client::call_async#59

Draft
ValuedMammal wants to merge 4 commits into
bitcoindevkit:masterfrom
ValuedMammal:feat/call_async
Draft

feat: Add Client::call_async#59
ValuedMammal wants to merge 4 commits into
bitcoindevkit:masterfrom
ValuedMammal:feat/call_async

Conversation

@ValuedMammal

@ValuedMammal ValuedMammal commented May 14, 2026

Copy link
Copy Markdown
Collaborator

Description

Based on #27

Adds Client::call_async — an async counterpart to Client::call for use with async transports.

The method accepts a send_fn: AsyncFn(Value) -> Result<Response, E> closure rather than passing the Request directly to the transport. Internally, call_async builds the request, serializes the request to an owned serde_json::Value, calls the send_fn, and deserializes the response like the sync path.

A working example is included in call_async.rs using bitreq::send_async() from bitreq's async feature.

let send_fn = |value: serde_json::Value| {
    let auth_header = auth_header.clone();
    async move {
        bitreq::post(URL)
            .with_header("Authorization", auth_header)
            .with_json(&value)?
            .send_async()
            .await?
            .json::<jsonrpc::Response>()
    }
};

let block_hash = client
    .call_async::<v29::GetBestBlockHash, bitreq::Error, _>(&method, &[], send_fn)
    .await?
    .into_model()?
    .0;

Notes to the reviewers

Why AsyncFn(Value) -> Result<Response, E> instead of passing Request<'_> to the transport?

The natural design would mirror the sync API: F: for<'r> AsyncFn(Request<'r>) -> Result<Response, E>. This was explored but seems to hit a Rust language limitation. An async closure async |req| transport.send_request(req).await that captures a reference cannot satisfy for<'r> AsyncFn(Request<'r>), as the closure ties the future's lifetime to both the transport reference and the request lifetime, so the bound is never satisfied. Passing an owned Value breaks the lifetime chain entirely.

The tradeoff is that callers cannot use BitreqHttpTransport::send_request directly (its return type is tied to the borrow of self via a BoxFuture). The bitreq::post() free function shown in the example is a workaround that avoids that issue. Another alternative is to support the async flavor of Transport alongside the current bitreq client module, but this would lead to duplicating the API which is precisely what the sans-io design is meant to avoid.

Changelog notice

Added

  • Client::call_async: async RPC method accepting AsyncFn(Value) -> Result<Response, E> for use with async transports

Previously, enabling `--all-features` would cause the lowest
version feature to take precedence, which is counter-intuitive
and incorrect for CI.

Make the features additive so that `--all-features` implies
the latest version feature.

Each version feature explicitly enables the previous one. This
means `--all-features` now correctly activates the highest
version. Feature cfgs are updated throughout the library and
integration tests to match the new behavior.

`default` is now the minimum supported version (`28_0`), so that
additional features can be given without conflict.
The previous Client owned a `jsonrpc::Client<BitreqHttpTransport>`
directly, coupling all users to the bitreq HTTP transport.

Introduce a layered architecture:

`crate::Client`: transport-agnostic type that manages request
building and ID tracking. Callers supply a `send_fn` closure per
call.

`bitreq::Client`: batteries-included HTTP client behind the `bitreq`
feature flag (included in default). Owns a `Box<dyn Transport>` and
exposes all RPC methods. Auth and cookie-file parsing live here.
`with_auth` now accepts an explicit `timeout: Duration` and is
renamed to `with_auth_timeout`.

`corepc-types` is now an optional dependency pulled in by `bitreq`.

Error variants only reachable through bitreq code are gated behind
`bitreq` feature.
docs: Add `examples/call_async.rs`
deps: Add dev-deps bitreq, tokio
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant