diff --git a/sdk/src/client/node_api/core/routes.rs b/sdk/src/client/node_api/core/routes.rs index 33cd2da771..b19da9631f 100644 --- a/sdk/src/client/node_api/core/routes.rs +++ b/sdk/src/client/node_api/core/routes.rs @@ -10,6 +10,7 @@ use url::Url; use crate::{ client::{ constants::{DEFAULT_API_TIMEOUT, DEFAULT_USER_AGENT}, + node_api::query_tuples_to_query_string, node_manager::node::{Node, NodeAuth}, Client, ClientInner, Result, }, @@ -115,19 +116,28 @@ impl ClientInner { pub async fn get_committee(&self, epoch_index: impl Into> + Send) -> Result { const PATH: &str = "api/core/v3/committee"; - let epoch_index = epoch_index.into().map(|i| format!("epochIndex={i}")); - self.get_request(PATH, epoch_index.as_deref(), false, false).await + let query = query_tuples_to_query_string([epoch_index.into().map(|i| ("epochIndex", i.to_string()))]); + + self.get_request(PATH, query.as_deref(), false, false).await } // Validators routes. /// Returns information of all registered validators and if they are active. /// GET JSON to /api/core/v3/validators - pub async fn get_validators(&self, page_size: Option) -> Result { + pub async fn get_validators( + &self, + page_size: impl Into> + Send, + cursor: impl Into> + Send, + ) -> Result { const PATH: &str = "api/core/v3/validators"; - let page_size = page_size.map(|i| format!("pageSize={i}")); - self.get_request(PATH, page_size.as_deref(), false, false).await + let query = query_tuples_to_query_string([ + page_size.into().map(|i| ("pageSize", i.to_string())), + cursor.into().map(|i| ("cursor", i)), + ]); + + self.get_request(PATH, query.as_deref(), false, false).await } /// Return information about a validator. diff --git a/sdk/src/client/node_api/indexer/query_parameters.rs b/sdk/src/client/node_api/indexer/query_parameters.rs index d9e1d54333..e52b97377d 100644 --- a/sdk/src/client/node_api/indexer/query_parameters.rs +++ b/sdk/src/client/node_api/indexer/query_parameters.rs @@ -8,7 +8,7 @@ use std::fmt; use serde::{Deserialize, Serialize}; use crate::{ - client::{Error, Result}, + client::{node_api::query_tuples_to_query_string, Error, Result}, types::block::{address::Bech32Address, slot::SlotIndex}, }; @@ -58,17 +58,7 @@ impl QueryParameters { /// Converts parameters to a single String. pub fn to_query_string(&self) -> Option { - if self.0.is_empty() { - None - } else { - Some( - self.0 - .iter() - .map(QueryParameter::to_query_string) - .collect::>() - .join("&"), - ) - } + query_tuples_to_query_string(self.0.iter().map(|q| Some(q.to_query_tuple()))) } } @@ -131,32 +121,32 @@ pub enum QueryParameter { } impl QueryParameter { - fn to_query_string(&self) -> String { + fn to_query_tuple(&self) -> (&'static str, String) { match self { - Self::Address(v) => format!("address={v}"), - Self::AccountAddress(v) => format!("accountAddress={v}"), - Self::CreatedAfter(v) => format!("createdAfter={v}"), - Self::CreatedBefore(v) => format!("createdBefore={v}"), - Self::Cursor(v) => format!("cursor={v}"), - Self::ExpirationReturnAddress(v) => format!("expirationReturnAddress={v}"), - Self::ExpiresAfter(v) => format!("expiresAfter={v}"), - Self::ExpiresBefore(v) => format!("expiresBefore={v}"), - Self::Governor(v) => format!("governor={v}"), - Self::HasExpiration(v) => format!("hasExpiration={v}"), - Self::HasNativeTokens(v) => format!("hasNativeTokens={v}"), - Self::HasStorageDepositReturn(v) => format!("hasStorageDepositReturn={v}"), - Self::HasTimelock(v) => format!("hasTimelock={v}"), - Self::Issuer(v) => format!("issuer={v}"), - Self::MaxNativeTokenCount(v) => format!("maxNativeTokenCount={v}"), - Self::MinNativeTokenCount(v) => format!("minNativeTokenCount={v}"), - Self::PageSize(v) => format!("pageSize={v}"), - Self::Sender(v) => format!("sender={v}"), - Self::StateController(v) => format!("stateController={v}"), - Self::StorageDepositReturnAddress(v) => format!("storageDepositReturnAddress={v}"), - Self::Tag(v) => format!("tag={v}"), - Self::TimelockedAfter(v) => format!("timelockedAfter={v}"), - Self::TimelockedBefore(v) => format!("timelockedBefore={v}"), - Self::UnlockableByAddress(v) => format!("unlockableByAddress={v}"), + Self::Address(v) => ("address", v.to_string()), + Self::AccountAddress(v) => ("accountAddress", v.to_string()), + Self::CreatedAfter(v) => ("createdAfter", v.to_string()), + Self::CreatedBefore(v) => ("createdBefore", v.to_string()), + Self::Cursor(v) => ("cursor", v.to_string()), + Self::ExpirationReturnAddress(v) => ("expirationReturnAddress", v.to_string()), + Self::ExpiresAfter(v) => ("expiresAfter", v.to_string()), + Self::ExpiresBefore(v) => ("expiresBefore", v.to_string()), + Self::Governor(v) => ("governor", v.to_string()), + Self::HasExpiration(v) => ("hasExpiration", v.to_string()), + Self::HasNativeTokens(v) => ("hasNativeTokens", v.to_string()), + Self::HasStorageDepositReturn(v) => ("hasStorageDepositReturn", v.to_string()), + Self::HasTimelock(v) => ("hasTimelock", v.to_string()), + Self::Issuer(v) => ("issuer", v.to_string()), + Self::MaxNativeTokenCount(v) => ("maxNativeTokenCount", v.to_string()), + Self::MinNativeTokenCount(v) => ("minNativeTokenCount", v.to_string()), + Self::PageSize(v) => ("pageSize", v.to_string()), + Self::Sender(v) => ("sender", v.to_string()), + Self::StateController(v) => ("stateController", v.to_string()), + Self::StorageDepositReturnAddress(v) => ("storageDepositReturnAddress", v.to_string()), + Self::Tag(v) => ("tag", v.to_string()), + Self::TimelockedAfter(v) => ("timelockedAfter", v.to_string()), + Self::TimelockedBefore(v) => ("timelockedBefore", v.to_string()), + Self::UnlockableByAddress(v) => ("unlockableByAddress", v.to_string()), } } @@ -192,7 +182,8 @@ impl QueryParameter { impl fmt::Display for QueryParameter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.to_query_string()) + let query_tuple = self.to_query_tuple(); + write!(f, "{}={}", query_tuple.0, query_tuple.1) } } diff --git a/sdk/src/client/node_api/mod.rs b/sdk/src/client/node_api/mod.rs index 2fbb76dedf..b88a53afae 100644 --- a/sdk/src/client/node_api/mod.rs +++ b/sdk/src/client/node_api/mod.rs @@ -13,3 +13,14 @@ pub mod mqtt; #[cfg_attr(docsrs, doc(cfg(feature = "participation")))] pub mod participation; pub mod plugin; + +pub(crate) fn query_tuples_to_query_string( + tuples: impl IntoIterator>, +) -> Option { + let query = tuples + .into_iter() + .filter_map(|tuple| tuple.map(|(key, value)| format!("{}={}", key, value))) + .collect::>(); + + if query.is_empty() { None } else { Some(query.join("&")) } +} diff --git a/sdk/src/client/node_api/participation.rs b/sdk/src/client/node_api/participation.rs index 928a63a56c..5a8cf71863 100644 --- a/sdk/src/client/node_api/participation.rs +++ b/sdk/src/client/node_api/participation.rs @@ -6,7 +6,7 @@ //! use crate::{ - client::{ClientInner, Result}, + client::{node_api::query_tuples_to_query_string, ClientInner, Result}, types::{ api::plugins::participation::{ responses::{AddressOutputsResponse, EventsResponse, OutputStatusResponse}, @@ -24,12 +24,9 @@ impl ClientInner { pub async fn events(&self, event_type: Option) -> Result { let route = "api/participation/v1/events"; - let query = event_type.map(|event_type| match event_type { - ParticipationEventType::Voting => "type=0", - ParticipationEventType::Staking => "type=1", - }); + let query = query_tuples_to_query_string([event_type.map(|t| ("type", (t as u8).to_string()))]); - self.get_request(route, query, false, false).await + self.get_request(route, query.as_deref(), false, false).await } /// RouteParticipationEvent is the route to access a single participation by its ID. @@ -47,13 +44,9 @@ impl ClientInner { ) -> Result { let route = format!("api/participation/v1/events/{event_id}/status"); - self.get_request( - &route, - milestone_index.map(|index| index.to_string()).as_deref(), - false, - false, - ) - .await + let query = query_tuples_to_query_string([milestone_index.map(|i| ("milestoneIndex", i.to_string()))]); + + self.get_request(&route, query.as_deref(), false, false).await } /// RouteOutputStatus is the route to get the vote status for a given output ID.