Skip to content

feat: added map method to query builders #45

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

Merged
merged 16 commits into from
Mar 1, 2025
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get -y install libudev-dev libsystemd-dev
- name: run cargo doc
run: RUSTDOCFLAGS="-D warnings" cargo doc
run: RUSTDOCFLAGS="-D warnings" cargo doc --all-features --document-private-items

check-windows:
needs: cargo-fmt
Expand Down
27 changes: 6 additions & 21 deletions src/chain.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use near_primitives::{
types::{BlockHeight, BlockReference},
views::BlockView,
};
use near_primitives::types::{BlockHeight, BlockReference};

use crate::{
common::query::{BlockQueryBuilder, PostprocessHandler, RpcBlockHandler, SimpleBlockRpc},
Expand Down Expand Up @@ -53,14 +50,8 @@ impl Chain {
/// # }
/// ```
pub fn block_number() -> BlockQueryBuilder<PostprocessHandler<BlockHeight, RpcBlockHandler>> {
BlockQueryBuilder::new(
SimpleBlockRpc,
BlockReference::latest(),
PostprocessHandler::new(
RpcBlockHandler,
Box::new(|data: BlockView| data.header.height),
),
)
BlockQueryBuilder::new(SimpleBlockRpc, BlockReference::latest(), RpcBlockHandler)
.map(|data| data.header.height)
}

/// Set ups a query to fetch the [CryptoHash] of the block
Expand Down Expand Up @@ -89,17 +80,11 @@ impl Chain {
/// # }
/// ```
pub fn block_hash() -> BlockQueryBuilder<PostprocessHandler<CryptoHash, RpcBlockHandler>> {
BlockQueryBuilder::new(
SimpleBlockRpc,
BlockReference::latest(),
PostprocessHandler::new(
RpcBlockHandler,
Box::new(|data: BlockView| data.header.hash.into()),
),
)
BlockQueryBuilder::new(SimpleBlockRpc, BlockReference::latest(), RpcBlockHandler)
.map(|data| data.header.hash.into())
}

/// Set ups a query to fetch the [BlockView]
/// Set ups a query to fetch the [BlockView][near_primitives::views::BlockView]
///
/// ## Fetching the latest block
///
Expand Down
114 changes: 113 additions & 1 deletion src/common/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,17 @@ pub type MultiQueryBuilder<T> = MultiRpcBuilder<T, RpcQueryRequest, BlockReferen
pub type ValidatorQueryBuilder<T> = RpcBuilder<T, RpcValidatorRequest, EpochReference>;
pub type BlockQueryBuilder<T> = RpcBuilder<T, RpcBlockRequest, BlockReference>;

/// A builder for querying multiple items at once.
///
/// Sometimes to construct some complex type, you would need to query multiple items at once, and combine them into one.
/// This is where this builder comes in handy. Almost every time, you would want to use [Self::map] method to combine the responses into your desired type.
///
/// Currently, `MultiQueryHandler` supports tuples of sizes 2 and 3.
/// For single responses, use `QueryBuilder` instead.
///
/// Here is a list of examples on how to use this:
/// - [Tokens::ft_balance](crate::tokens::Tokens::ft_balance)
/// - [StakingPool::staking_pool_info](crate::stake::Staking::staking_pool_info)
pub struct MultiRpcBuilder<Handler, Method, Reference>
where
Reference: Send + Sync,
Expand All @@ -157,6 +168,20 @@ where
handler: Handler,
}

impl<Handler, Method, Reference> MultiRpcBuilder<Handler, Method, Reference>
where
Reference: Send + Sync,
Handler: Default + Send + Sync,
{
pub fn with_reference(reference: impl Into<Reference>) -> Self {
Self {
reference: reference.into(),
requests: vec![],
handler: Default::default(),
}
}
}

impl<Handler, Method, Reference> MultiRpcBuilder<Handler, Method, Reference>
where
Handler: ResponseHandler<QueryResponse = Method::Response, Method = Method> + Send + Sync,
Expand All @@ -173,6 +198,50 @@ where
}
}

/// Map response of the queries to another type. The `map` function is executed after the queries are fetched.
///
/// The `Handler::Response` is the type returned by the handler's `process_response` method.
///
/// For single responses, use `QueryBuilder` instead.
///
/// ## Example
/// ```rust,no_run
/// use near_api::advanced::{MultiQueryHandler, CallResultHandler, MultiRpcBuilder};
/// use near_api::types::Data;
/// use std::marker::PhantomData;
/// use near_primitives::types::BlockReference;
///
/// // Create a handler for multiple query responses and specify the types of the responses
/// let handler = MultiQueryHandler::new((
/// CallResultHandler::<String>::new(),
/// CallResultHandler::<u128>::new(),
/// ));
///
/// // Create the builder with the handler
/// let builder = MultiRpcBuilder::new(handler, BlockReference::latest());
///
/// // Add queries to the builder
/// builder.add_query(todo!());
///
/// // Map the tuple of responses to a combined type
/// let mapped_builder = builder.map(|(response1, response2): (Data<String>, Data<u128>)| {
/// // Process the combined data
/// format!("{}: {}", response1.data, response2.data)
/// });
/// ```
///
/// See [Tokens::ft_balance](crate::tokens::Tokens::ft_balance) implementation for a real-world example.
pub fn map<MappedType>(
self,
map: impl Fn(Handler::Response) -> MappedType + Send + Sync + 'static,
) -> MultiRpcBuilder<PostprocessHandler<MappedType, Handler>, Method, Reference> {
MultiRpcBuilder {
handler: PostprocessHandler::new(self.handler, map),
requests: self.requests,
reference: self.reference,
}
}

/// Add a query to the queried items. Sometimes you might need to query multiple items at once.
/// To combine the result of multiple queries into one.
pub fn add_query(
Expand Down Expand Up @@ -298,6 +367,36 @@ where
}
}

/// Post-process the response of the query.
///
/// This is useful if you want to convert one type to another.
///
/// ## Example
/// ```rust,no_run
/// use near_api::*;
///
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// let balance: NearToken = Contract("some_contract.testnet".parse()?)
/// .call_function("get_balance", ())?
/// .read_only()
/// .map(|balance: Data<u128>| NearToken::from_yoctonear(balance.data))
/// .fetch_from_testnet()
/// .await?;
/// println!("Balance: {}", balance);
/// # Ok(())
/// # }
/// ```
pub fn map<MappedType>(
self,
map: impl Fn(Handler::Response) -> MappedType + Send + Sync + 'static,
) -> RpcBuilder<PostprocessHandler<MappedType, Handler>, Method, Reference> {
RpcBuilder {
handler: PostprocessHandler::new(self.handler, map),
request: self.request,
reference: self.reference,
}
}

/// Fetch the query from the provided network.
#[instrument(skip(self, network))]
pub async fn fetch_from(
Expand Down Expand Up @@ -412,6 +511,13 @@ impl<Handlers> MultiQueryHandler<Handlers> {
Self { handlers }
}
}

impl<Handlers: Default> Default for MultiQueryHandler<Handlers> {
fn default() -> Self {
Self::new(Default::default())
}
}

pub struct PostprocessHandler<PostProcessed, Handler: ResponseHandler>
where
<Handler::Method as RpcMethod>::Error: std::fmt::Display + std::fmt::Debug,
Expand Down Expand Up @@ -461,7 +567,13 @@ where
}

#[derive(Default, Debug, Clone)]
pub struct CallResultHandler<Response: Send + Sync>(pub PhantomData<Response>);
pub struct CallResultHandler<Response: Send + Sync>(PhantomData<Response>);

impl<Response: Send + Sync> CallResultHandler<Response> {
pub const fn new() -> Self {
Self(PhantomData::<Response>)
}
}

impl<Response> ResponseHandler for CallResultHandler<Response>
where
Expand Down
27 changes: 8 additions & 19 deletions src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{marker::PhantomData, sync::Arc};
use std::sync::Arc;

use near_gas::NearGas;

Expand Down Expand Up @@ -157,23 +157,12 @@ impl Contract {
&self,
) -> QueryBuilder<PostprocessHandler<Option<near_abi::AbiRoot>, CallResultHandler<Vec<u8>>>>
{
let request = near_primitives::views::QueryRequest::CallFunction {
account_id: self.0.clone(),
method_name: "__contract_abi".to_owned(),
args: near_primitives::types::FunctionArgs::from(vec![]),
};

QueryBuilder::new(
SimpleQuery { request },
BlockReference::latest(),
PostprocessHandler::new(
CallResultHandler::default(),
Box::new(|data: Data<Vec<u8>>| {
serde_json::from_slice(zstd::decode_all(data.data.as_slice()).ok()?.as_slice())
.ok()
}),
),
)
self.call_function("__contract_abi", ())
.expect("arguments are always serializable")
.read_only()
.map(|data: Data<Vec<u8>>| {
serde_json::from_slice(zstd::decode_all(data.data.as_slice()).ok()?.as_slice()).ok()
})
}

/// Prepares a query to fetch the wasm code ([Data]<[ContractCodeView](near_primitives::views::ContractCodeView)>) of the contract.
Expand Down Expand Up @@ -416,7 +405,7 @@ impl CallFunctionBuilder {
QueryBuilder::new(
SimpleQuery { request },
BlockReference::latest(),
CallResultHandler(PhantomData),
CallResultHandler::<Response>::new(),
)
}

Expand Down
4 changes: 2 additions & 2 deletions src/fastnear.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl<T, PostProcessed> FastNearBuilder<T, PostProcessed>
where
T: DeserializeOwned + Send + Sync,
{
pub fn with_postprocess<F>(query: String, func: F) -> Self
pub fn map<F>(query: String, func: F) -> Self
where
F: Fn(T) -> PostProcessed + Send + Sync + 'static,
{
Expand Down Expand Up @@ -67,7 +67,7 @@ impl FastNear {
&self,
account_id: &AccountId,
) -> Result<FastNearBuilder<StakingResponse, BTreeSet<AccountId>>, FastNearError> {
let query_builder = FastNearBuilder::with_postprocess(
let query_builder = FastNearBuilder::map(
format!("v1/account/{}/staking", account_id),
|response: StakingResponse| {
response
Expand Down
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ pub use crate::{
},
};

pub mod advanced {
pub use crate::common::query::*;
pub use crate::common::send::*;
}

pub use near_primitives;

pub use near_account_id::AccountId;
Expand Down
Loading
Loading