use std::time::Duration;
use candid::utils::ArgumentEncoder;
use candid::{CandidType, Principal};
use ic_dbms_api::prelude::IcDbmsResult;
use crate::client::{Client, RawRecords};
use crate::prelude::IcDbmsCanisterClientResult;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(300);
#[derive(Clone, Debug)]
pub struct IcDbmsCanisterClient {
canister_id: Principal,
timeout: Duration,
}
impl From<Principal> for IcDbmsCanisterClient {
fn from(principal: Principal) -> Self {
Self {
canister_id: principal,
timeout: DEFAULT_TIMEOUT,
}
}
}
impl IcDbmsCanisterClient {
pub fn new(canister_id: Principal) -> Self {
Self::from(canister_id)
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
async fn call<A, R>(&self, method: &str, args: &A) -> IcDbmsCanisterClientResult<R>
where
A: ArgumentEncoder,
R: CandidType + for<'de> candid::Deserialize<'de>,
{
let response = ic_cdk::call::Call::bounded_wait(self.canister_id, method)
.with_args(args)
.change_timeout(self.timeout.as_secs() as u32)
.into_future()
.await?;
let response: R = response.candid()?;
Ok(response)
}
}
impl Client for IcDbmsCanisterClient {
fn principal(&self) -> Principal {
self.canister_id
}
async fn acl_add_principal(
&self,
principal: Principal,
) -> IcDbmsCanisterClientResult<IcDbmsResult<()>> {
self.call("acl_add_principal", &(principal,)).await
}
async fn acl_remove_principal(
&self,
principal: Principal,
) -> IcDbmsCanisterClientResult<IcDbmsResult<()>> {
self.call("acl_remove_principal", &(principal,)).await
}
async fn acl_allowed_principals(&self) -> IcDbmsCanisterClientResult<Vec<Principal>> {
self.call("acl_allowed_principals", &()).await
}
async fn begin_transaction(
&self,
) -> IcDbmsCanisterClientResult<ic_dbms_api::prelude::TransactionId> {
self.call("begin_transaction", &()).await
}
async fn commit(
&self,
transaction_id: ic_dbms_api::prelude::TransactionId,
) -> IcDbmsCanisterClientResult<IcDbmsResult<()>> {
self.call("commit", &(transaction_id,)).await
}
async fn rollback(
&self,
transaction_id: ic_dbms_api::prelude::TransactionId,
) -> IcDbmsCanisterClientResult<IcDbmsResult<()>> {
self.call("rollback", &(transaction_id,)).await
}
async fn select<T>(
&self,
table: &str,
query: ic_dbms_api::prelude::Query,
transaction_id: Option<ic_dbms_api::prelude::TransactionId>,
) -> IcDbmsCanisterClientResult<IcDbmsResult<Vec<T::Record>>>
where
T: ic_dbms_api::prelude::TableSchema,
T::Record: CandidType + for<'de> candid::Deserialize<'de>,
{
self.call(
&crate::utils::table_method(table, "select"),
&(query, transaction_id),
)
.await
}
async fn select_raw(
&self,
table: &str,
query: ic_dbms_api::prelude::Query,
transaction_id: Option<ic_dbms_api::prelude::TransactionId>,
) -> IcDbmsCanisterClientResult<IcDbmsResult<RawRecords>> {
self.call("select", &(table, query, transaction_id)).await
}
async fn insert<T>(
&self,
table: &str,
record: T::Insert,
transaction_id: Option<ic_dbms_api::prelude::TransactionId>,
) -> IcDbmsCanisterClientResult<IcDbmsResult<()>>
where
T: ic_dbms_api::prelude::TableSchema,
T::Insert: ic_dbms_api::prelude::InsertRecord<Schema = T> + CandidType,
{
self.call(
&crate::utils::table_method(table, "insert"),
&(record, transaction_id),
)
.await
}
async fn update<T>(
&self,
table: &str,
patch: T::Update,
transaction_id: Option<ic_dbms_api::prelude::TransactionId>,
) -> IcDbmsCanisterClientResult<IcDbmsResult<u64>>
where
T: ic_dbms_api::prelude::TableSchema,
T::Update: ic_dbms_api::prelude::UpdateRecord<Schema = T> + CandidType,
{
self.call(
&crate::utils::table_method(table, "update"),
&(patch, transaction_id),
)
.await
}
async fn delete<T>(
&self,
table: &str,
behaviour: ic_dbms_api::prelude::DeleteBehavior,
filter: Option<ic_dbms_api::prelude::Filter>,
transaction_id: Option<ic_dbms_api::prelude::TransactionId>,
) -> IcDbmsCanisterClientResult<IcDbmsResult<u64>>
where
T: ic_dbms_api::prelude::TableSchema,
{
self.call(
&crate::utils::table_method(table, "delete"),
&(behaviour, filter, transaction_id),
)
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_get_principal() {
let principal = Principal::from_text("rrkah-fqaaa-aaaaa-aaaaq-cai").unwrap();
let client = IcDbmsCanisterClient::new(principal);
assert_eq!(client.principal(), principal);
}
#[test]
fn test_should_set_timeout() {
let principal = Principal::from_text("rrkah-fqaaa-aaaaa-aaaaq-cai").unwrap();
let client = IcDbmsCanisterClient::new(principal).with_timeout(Duration::from_secs(600));
assert_eq!(client.timeout, Duration::from_secs(600));
}
#[test]
fn test_from_principal() {
let principal = Principal::from_text("rrkah-fqaaa-aaaaa-aaaaq-cai").unwrap();
let client: IcDbmsCanisterClient = principal.into();
assert_eq!(client.principal(), principal);
}
}