use hedera_proto::services;
use hedera_proto::services::crypto_service_client::CryptoServiceClient;
use services::crypto_get_account_balance_query::BalanceSource;
use tonic::transport::Channel;
use crate::query::{
AnyQueryData,
Query,
QueryExecute,
ToQueryProtobuf,
};
use crate::{
AccountBalance,
AccountId,
BoxGrpcFuture,
ContractId,
Error,
LedgerId,
ToProtobuf,
ValidateChecksums,
};
pub type AccountBalanceQuery = Query<AccountBalanceQueryData>;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "ffi", derive(serde::Serialize, serde::Deserialize))]
pub struct AccountBalanceQueryData {
#[cfg_attr(feature = "ffi", serde(flatten))]
source: AccountBalanceSource,
}
impl Default for AccountBalanceQueryData {
fn default() -> Self {
Self { source: AccountBalanceSource::AccountId(AccountId::from(0)) }
}
}
impl From<AccountBalanceQueryData> for AnyQueryData {
#[inline]
fn from(data: AccountBalanceQueryData) -> Self {
Self::AccountBalance(data)
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "ffi", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "ffi", serde(rename_all = "camelCase"))]
enum AccountBalanceSource {
AccountId(AccountId),
ContractId(ContractId),
}
impl AccountBalanceQuery {
#[must_use]
pub fn get_account_id(&self) -> Option<AccountId> {
match self.data.source {
AccountBalanceSource::AccountId(it) => Some(it),
AccountBalanceSource::ContractId(_) => None,
}
}
pub fn account_id(&mut self, id: AccountId) -> &mut Self {
self.data.source = AccountBalanceSource::AccountId(id);
self
}
#[must_use]
pub fn get_contract_id(&self) -> Option<ContractId> {
match self.data.source {
AccountBalanceSource::ContractId(it) => Some(it),
AccountBalanceSource::AccountId(_) => None,
}
}
pub fn contract_id(&mut self, id: ContractId) -> &mut Self {
self.data.source = AccountBalanceSource::ContractId(id);
self
}
}
impl ToQueryProtobuf for AccountBalanceQueryData {
fn to_query_protobuf(&self, header: services::QueryHeader) -> services::Query {
let source = Some(&self.source).as_ref().map(|source| match source {
AccountBalanceSource::AccountId(id) => BalanceSource::AccountId(id.to_protobuf()),
AccountBalanceSource::ContractId(id) => BalanceSource::ContractId(id.to_protobuf()),
});
services::Query {
query: Some(services::query::Query::CryptogetAccountBalance(
services::CryptoGetAccountBalanceQuery {
balance_source: source,
header: Some(header),
},
)),
}
}
}
impl QueryExecute for AccountBalanceQueryData {
type Response = AccountBalance;
fn is_payment_required(&self) -> bool {
false
}
fn execute(
&self,
channel: Channel,
request: services::Query,
) -> BoxGrpcFuture<'_, services::Response> {
Box::pin(async { CryptoServiceClient::new(channel).crypto_get_balance(request).await })
}
}
impl ValidateChecksums for AccountBalanceQueryData {
fn validate_checksums(&self, ledger_id: &LedgerId) -> Result<(), Error> {
match self.source {
AccountBalanceSource::AccountId(account_id) => account_id.validate_checksums(ledger_id),
AccountBalanceSource::ContractId(contract_id) => {
contract_id.validate_checksums(ledger_id)
}
}
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "ffi")]
mod ffi {
use assert_matches::assert_matches;
use crate::account::account_balance_query::AccountBalanceSource;
use crate::query::AnyQueryData;
use crate::{
AccountBalanceQuery,
AccountId,
AnyQuery,
};
const ACCOUNT_BALANCE: &str = r#"{
"$type": "accountBalance",
"accountId": "0.0.1001"
}"#;
#[test]
fn it_should_serialize() -> anyhow::Result<()> {
let mut query = AccountBalanceQuery::new();
query.account_id(AccountId::from(1001));
let s = serde_json::to_string_pretty(&query)?;
assert_eq!(s, ACCOUNT_BALANCE);
Ok(())
}
#[test]
fn it_should_deserialize() -> anyhow::Result<()> {
let query: AnyQuery = serde_json::from_str(ACCOUNT_BALANCE)?;
let data = assert_matches!(query.data, AnyQueryData::AccountBalance(query) => query);
let source = assert_matches!(data.source, AccountBalanceSource::AccountId(id) => id);
assert_eq!(source.num, 1001);
Ok(())
}
}
}