Skip to content

Commit 708d421

Browse files
authored
feat(client): support request id as Strings. (#659)
* feat(client): support request id as Strings. * add tests for Id::String * address grumbles: move id_kind to RequestManager * Update client/http-client/src/client.rs * types: take ref to `ID` get rid of some `Clone` * remove more clone * grumbles: rename tests
1 parent c0f343d commit 708d421

File tree

12 files changed

+302
-115
lines changed

12 files changed

+302
-115
lines changed

benches/bench.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,15 @@ pub fn jsonrpsee_types_v2(crit: &mut Criterion) {
6161
b.iter(|| {
6262
let params = &[1_u64.into(), 2_u32.into()];
6363
let params = ParamsSer::ArrayRef(params);
64-
let request = RequestSer::new(Id::Number(0), "say_hello", Some(params));
64+
let request = RequestSer::new(&Id::Number(0), "say_hello", Some(params));
6565
v2_serialize(request);
6666
})
6767
});
6868

6969
crit.bench_function("jsonrpsee_types_v2_vec", |b| {
7070
b.iter(|| {
7171
let params = ParamsSer::Array(vec![1_u64.into(), 2_u32.into()]);
72-
let request = RequestSer::new(Id::Number(0), "say_hello", Some(params));
72+
let request = RequestSer::new(&Id::Number(0), "say_hello", Some(params));
7373
v2_serialize(request);
7474
})
7575
});

client/http-client/src/client.rs

+21-13
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use std::time::Duration;
3030
use crate::transport::HttpTransportClient;
3131
use crate::types::{ErrorResponse, Id, NotificationSer, ParamsSer, RequestSer, Response};
3232
use async_trait::async_trait;
33-
use jsonrpsee_core::client::{CertificateStore, ClientT, RequestIdManager, Subscription, SubscriptionClientT};
33+
use jsonrpsee_core::client::{CertificateStore, ClientT, IdKind, RequestIdManager, Subscription, SubscriptionClientT};
3434
use jsonrpsee_core::{Error, TEN_MB_SIZE_BYTES};
3535
use rustc_hash::FxHashMap;
3636
use serde::de::DeserializeOwned;
@@ -42,6 +42,7 @@ pub struct HttpClientBuilder {
4242
request_timeout: Duration,
4343
max_concurrent_requests: usize,
4444
certificate_store: CertificateStore,
45+
id_kind: IdKind,
4546
}
4647

4748
impl HttpClientBuilder {
@@ -69,13 +70,19 @@ impl HttpClientBuilder {
6970
self
7071
}
7172

73+
/// Configure the data type of the request object ID (default is number).
74+
pub fn id_format(mut self, id_kind: IdKind) -> Self {
75+
self.id_kind = id_kind;
76+
self
77+
}
78+
7279
/// Build the HTTP client with target to connect to.
7380
pub fn build(self, target: impl AsRef<str>) -> Result<HttpClient, Error> {
7481
let transport = HttpTransportClient::new(target, self.max_request_body_size, self.certificate_store)
7582
.map_err(|e| Error::Transport(e.into()))?;
7683
Ok(HttpClient {
7784
transport,
78-
id_manager: Arc::new(RequestIdManager::new(self.max_concurrent_requests)),
85+
id_manager: Arc::new(RequestIdManager::new(self.max_concurrent_requests, self.id_kind)),
7986
request_timeout: self.request_timeout,
8087
})
8188
}
@@ -88,6 +95,7 @@ impl Default for HttpClientBuilder {
8895
request_timeout: Duration::from_secs(60),
8996
max_concurrent_requests: 256,
9097
certificate_store: CertificateStore::Native,
98+
id_kind: IdKind::Number,
9199
}
92100
}
93101
}
@@ -120,8 +128,9 @@ impl ClientT for HttpClient {
120128
where
121129
R: DeserializeOwned,
122130
{
123-
let id = self.id_manager.next_request_id()?;
124-
let request = RequestSer::new(Id::Number(*id.inner()), method, params);
131+
let guard = self.id_manager.next_request_id()?;
132+
let id = guard.inner();
133+
let request = RequestSer::new(&id, method, params);
125134

126135
let fut = self.transport.send_and_read_body(serde_json::to_string(&request).map_err(Error::ParseError)?);
127136
let body = match tokio::time::timeout(self.request_timeout, fut).await {
@@ -142,9 +151,7 @@ impl ClientT for HttpClient {
142151
}
143152
};
144153

145-
let response_id = response.id.as_number().copied().ok_or(Error::InvalidRequestId)?;
146-
147-
if response_id == *id.inner() {
154+
if response.id == id {
148155
Ok(response.result)
149156
} else {
150157
Err(Error::InvalidRequestId)
@@ -155,16 +162,18 @@ impl ClientT for HttpClient {
155162
where
156163
R: DeserializeOwned + Default + Clone,
157164
{
165+
let guard = self.id_manager.next_request_ids(batch.len())?;
166+
let ids: Vec<Id> = guard.inner();
167+
158168
let mut batch_request = Vec::with_capacity(batch.len());
159169
// NOTE(niklasad1): `ID` is not necessarily monotonically increasing.
160170
let mut ordered_requests = Vec::with_capacity(batch.len());
161171
let mut request_set = FxHashMap::with_capacity_and_hasher(batch.len(), Default::default());
162172

163-
let ids = self.id_manager.next_request_ids(batch.len())?;
164173
for (pos, (method, params)) in batch.into_iter().enumerate() {
165-
batch_request.push(RequestSer::new(Id::Number(ids.inner()[pos]), method, params));
166-
ordered_requests.push(ids.inner()[pos]);
167-
request_set.insert(ids.inner()[pos], pos);
174+
batch_request.push(RequestSer::new(&ids[pos], method, params));
175+
ordered_requests.push(&ids[pos]);
176+
request_set.insert(&ids[pos], pos);
168177
}
169178

170179
let fut = self.transport.send_and_read_body(serde_json::to_string(&batch_request).map_err(Error::ParseError)?);
@@ -184,8 +193,7 @@ impl ClientT for HttpClient {
184193
// NOTE: `R::default` is placeholder and will be replaced in loop below.
185194
let mut responses = vec![R::default(); ordered_requests.len()];
186195
for rp in rps {
187-
let response_id = rp.id.as_number().copied().ok_or(Error::InvalidRequestId)?;
188-
let pos = match request_set.get(&response_id) {
196+
let pos = match request_set.get(&rp.id) {
189197
Some(pos) => *pos,
190198
None => return Err(Error::InvalidRequestId),
191199
};

client/http-client/src/tests.rs

+29-4
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,12 @@
2727
use crate::types::error::{ErrorCode, ErrorObject, ErrorResponse};
2828
use crate::types::ParamsSer;
2929
use crate::HttpClientBuilder;
30-
use jsonrpsee_core::client::ClientT;
30+
use jsonrpsee_core::client::{ClientT, IdKind};
3131
use jsonrpsee_core::rpc_params;
3232
use jsonrpsee_core::Error;
3333
use jsonrpsee_test_utils::helpers::*;
3434
use jsonrpsee_test_utils::mocks::Id;
3535
use jsonrpsee_test_utils::TimeoutFutureExt;
36-
use serde_json::value::Value as JsonValue;
3736

3837
#[tokio::test]
3938
async fn method_call_works() {
@@ -42,7 +41,33 @@ async fn method_call_works() {
4241
.await
4342
.unwrap()
4443
.unwrap();
45-
assert_eq!(JsonValue::String("hello".into()), result);
44+
assert_eq!("hello", &result);
45+
}
46+
47+
#[tokio::test]
48+
async fn method_call_with_wrong_id_kind() {
49+
let exp = "id as string";
50+
let server_addr =
51+
http_server_with_hardcoded_response(ok_response(exp.into(), Id::Num(0))).with_default_timeout().await.unwrap();
52+
let uri = format!("http://{}", server_addr);
53+
let client = HttpClientBuilder::default().id_format(IdKind::String).build(&uri).unwrap();
54+
assert!(matches!(
55+
client.request::<String>("o", None).with_default_timeout().await.unwrap(),
56+
Err(Error::InvalidRequestId)
57+
));
58+
}
59+
60+
#[tokio::test]
61+
async fn method_call_with_id_str() {
62+
let exp = "id as string";
63+
let server_addr = http_server_with_hardcoded_response(ok_response(exp.into(), Id::Str("0".into())))
64+
.with_default_timeout()
65+
.await
66+
.unwrap();
67+
let uri = format!("http://{}", server_addr);
68+
let client = HttpClientBuilder::default().id_format(IdKind::String).build(&uri).unwrap();
69+
let response: String = client.request::<String>("o", None).with_default_timeout().await.unwrap().unwrap();
70+
assert_eq!(&response, exp);
4671
}
4772

4873
#[tokio::test]
@@ -139,7 +164,7 @@ async fn run_batch_request_with_response<'a>(
139164
client.batch_request(batch).with_default_timeout().await.unwrap()
140165
}
141166

142-
async fn run_request_with_response(response: String) -> Result<JsonValue, Error> {
167+
async fn run_request_with_response(response: String) -> Result<String, Error> {
143168
let server_addr = http_server_with_hardcoded_response(response).with_default_timeout().await.unwrap();
144169
let uri = format!("http://{}", server_addr);
145170
let client = HttpClientBuilder::default().build(&uri).unwrap();

client/ws-client/src/lib.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub use jsonrpsee_types as types;
4343
use std::time::Duration;
4444

4545
use jsonrpsee_client_transport::ws::{Header, InvalidUri, Uri, WsTransportClientBuilder};
46-
use jsonrpsee_core::client::{CertificateStore, ClientBuilder};
46+
use jsonrpsee_core::client::{CertificateStore, ClientBuilder, IdKind};
4747
use jsonrpsee_core::{Error, TEN_MB_SIZE_BYTES};
4848

4949
/// Builder for [`WsClient`].
@@ -77,6 +77,7 @@ pub struct WsClientBuilder<'a> {
7777
max_concurrent_requests: usize,
7878
max_notifs_per_subscription: usize,
7979
max_redirections: usize,
80+
id_kind: IdKind,
8081
}
8182

8283
impl<'a> Default for WsClientBuilder<'a> {
@@ -90,6 +91,7 @@ impl<'a> Default for WsClientBuilder<'a> {
9091
max_concurrent_requests: 256,
9192
max_notifs_per_subscription: 1024,
9293
max_redirections: 5,
94+
id_kind: IdKind::Number,
9395
}
9496
}
9597
}
@@ -143,6 +145,12 @@ impl<'a> WsClientBuilder<'a> {
143145
self
144146
}
145147

148+
/// See documentation for [`ClientBuilder::id_format`] (default is Number).
149+
pub fn id_format(mut self, kind: IdKind) -> Self {
150+
self.id_kind = kind;
151+
self
152+
}
153+
146154
/// Build the client with specified URL to connect to.
147155
/// You must provide the port number in the URL.
148156
///
@@ -165,6 +173,7 @@ impl<'a> WsClientBuilder<'a> {
165173
.max_notifs_per_subscription(self.max_notifs_per_subscription)
166174
.request_timeout(self.request_timeout)
167175
.max_concurrent_requests(self.max_concurrent_requests)
176+
.id_format(self.id_kind)
168177
.build(sender, receiver))
169178
}
170179
}

client/ws-client/src/tests.rs

+38-3
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
use crate::types::error::{ErrorCode, ErrorObject, ErrorResponse};
2929
use crate::types::ParamsSer;
3030
use crate::WsClientBuilder;
31-
use jsonrpsee_core::client::Subscription;
3231
use jsonrpsee_core::client::{ClientT, SubscriptionClientT};
32+
use jsonrpsee_core::client::{IdKind, Subscription};
3333
use jsonrpsee_core::rpc_params;
3434
use jsonrpsee_core::Error;
3535
use jsonrpsee_test_utils::helpers::*;
@@ -44,7 +44,42 @@ async fn method_call_works() {
4444
.await
4545
.unwrap()
4646
.unwrap();
47-
assert_eq!(JsonValue::String("hello".into()), result);
47+
assert_eq!("hello", &result);
48+
}
49+
50+
#[tokio::test]
51+
async fn method_call_with_wrong_id_kind() {
52+
let exp = "id as string";
53+
let server = WebSocketTestServer::with_hardcoded_response(
54+
"127.0.0.1:0".parse().unwrap(),
55+
ok_response(exp.into(), Id::Num(0)),
56+
)
57+
.with_default_timeout()
58+
.await
59+
.unwrap();
60+
let uri = format!("ws://{}", server.local_addr());
61+
let client =
62+
WsClientBuilder::default().id_format(IdKind::String).build(&uri).with_default_timeout().await.unwrap().unwrap();
63+
64+
let err = client.request::<String>("o", None).with_default_timeout().await.unwrap();
65+
assert!(matches!(err, Err(Error::RestartNeeded(e)) if e == "Invalid request ID"));
66+
}
67+
68+
#[tokio::test]
69+
async fn method_call_with_id_str() {
70+
let exp = "id as string";
71+
let server = WebSocketTestServer::with_hardcoded_response(
72+
"127.0.0.1:0".parse().unwrap(),
73+
ok_response(exp.into(), Id::Str("0".into())),
74+
)
75+
.with_default_timeout()
76+
.await
77+
.unwrap();
78+
let uri = format!("ws://{}", server.local_addr());
79+
let client =
80+
WsClientBuilder::default().id_format(IdKind::String).build(&uri).with_default_timeout().await.unwrap().unwrap();
81+
let response: String = client.request::<String>("o", None).with_default_timeout().await.unwrap().unwrap();
82+
assert_eq!(&response, exp);
4883
}
4984

5085
#[tokio::test]
@@ -237,7 +272,7 @@ async fn run_batch_request_with_response<'a>(
237272
client.batch_request(batch).with_default_timeout().await.unwrap()
238273
}
239274

240-
async fn run_request_with_response(response: String) -> Result<JsonValue, Error> {
275+
async fn run_request_with_response(response: String) -> Result<String, Error> {
241276
let server = WebSocketTestServer::with_hardcoded_response("127.0.0.1:0".parse().unwrap(), response)
242277
.with_default_timeout()
243278
.await

core/src/client/async_client/helpers.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ pub(crate) fn process_batch_response(manager: &mut RequestManager, rps: Vec<Resp
4545
let mut rps_unordered: Vec<_> = Vec::with_capacity(rps.len());
4646

4747
for rp in rps {
48-
let id = rp.id.as_number().copied().ok_or(Error::InvalidRequestId)?;
49-
digest.push(id);
48+
let id = rp.id.into_owned();
49+
digest.push(id.clone());
5050
rps_unordered.push((id, rp.result));
5151
}
5252

@@ -131,7 +131,7 @@ pub(crate) fn process_single_response(
131131
response: Response<JsonValue>,
132132
max_capacity_per_subscription: usize,
133133
) -> Result<Option<RequestMessage>, Error> {
134-
let response_id = response.id.as_number().copied().ok_or(Error::InvalidRequestId)?;
134+
let response_id = response.id.into_owned();
135135
match manager.request_status(&response_id) {
136136
RequestStatus::PendingMethodCall => {
137137
let send_back_oneshot = match manager.complete_pending_call(response_id) {
@@ -144,7 +144,7 @@ pub(crate) fn process_single_response(
144144
}
145145
RequestStatus::PendingSubscription => {
146146
let (unsub_id, send_back_oneshot, unsubscribe_method) =
147-
manager.complete_pending_subscription(response_id).ok_or(Error::InvalidRequestId)?;
147+
manager.complete_pending_subscription(response_id.clone()).ok_or(Error::InvalidRequestId)?;
148148

149149
let sub_id: Result<SubscriptionId, _> = response.result.try_into();
150150
let sub_id = match sub_id {
@@ -157,7 +157,7 @@ pub(crate) fn process_single_response(
157157

158158
let (subscribe_tx, subscribe_rx) = mpsc::channel(max_capacity_per_subscription);
159159
if manager
160-
.insert_subscription(response_id, unsub_id, sub_id.clone(), subscribe_tx, unsubscribe_method)
160+
.insert_subscription(response_id.clone(), unsub_id, sub_id.clone(), subscribe_tx, unsubscribe_method)
161161
.is_ok()
162162
{
163163
match send_back_oneshot.send(Ok((subscribe_rx, sub_id.clone()))) {
@@ -191,14 +191,14 @@ pub(crate) async fn stop_subscription(
191191
/// Builds an unsubscription message.
192192
pub(crate) fn build_unsubscribe_message(
193193
manager: &mut RequestManager,
194-
sub_req_id: u64,
194+
sub_req_id: Id<'static>,
195195
sub_id: SubscriptionId<'static>,
196196
) -> Option<RequestMessage> {
197197
let (unsub_req_id, _, unsub, sub_id) = manager.remove_subscription(sub_req_id, sub_id)?;
198198
let sub_id_slice: &[JsonValue] = &[sub_id.into()];
199199
// TODO: https://github.com/paritytech/jsonrpsee/issues/275
200200
let params = ParamsSer::ArrayRef(sub_id_slice);
201-
let raw = serde_json::to_string(&RequestSer::new(Id::Number(unsub_req_id), &unsub, Some(params))).ok()?;
201+
let raw = serde_json::to_string(&RequestSer::new(&unsub_req_id, &unsub, Some(params))).ok()?;
202202
Some(RequestMessage { raw, id: unsub_req_id, send_back: None })
203203
}
204204

@@ -207,7 +207,7 @@ pub(crate) fn build_unsubscribe_message(
207207
/// Returns `Ok` if the response was successfully sent.
208208
/// Returns `Err(_)` if the response ID was not found.
209209
pub(crate) fn process_error_response(manager: &mut RequestManager, err: ErrorResponse) -> Result<(), Error> {
210-
let id = err.id.as_number().copied().ok_or(Error::InvalidRequestId)?;
210+
let id = err.id.clone().into_owned();
211211
match manager.request_status(&id) {
212212
RequestStatus::PendingMethodCall => {
213213
let send_back = manager.complete_pending_call(id).expect("State checked above; qed");

0 commit comments

Comments
 (0)