Skip to main content

ic_dbms_client/client/
agent.rs

1//! This module exposes the ic-dbms canister client which uses the ic-agent [`Agent`] to communicate with the IC.
2//!
3//! This client is intended to be used from systems that are external to the IC, such as backend services or command-line tools, which
4//! need to interact with the IC DBMS canister.
5
6use candid::utils::ArgumentEncoder;
7use candid::{CandidType, Decode, Principal};
8use ic_agent::Agent;
9use ic_dbms_api::prelude::{
10    DeleteBehavior, Filter, IcDbmsResult, InsertRecord, Query, TableSchema, TransactionId,
11    UpdateRecord,
12};
13
14use crate::client::{Client, RawRecords};
15use crate::errors::{IcAgentError, IcDbmCanisterClientError, IcDbmsCanisterClientResult};
16
17/// Client to interact with an IC DBMS Canister using ic-agent.
18#[derive(Clone, Debug)]
19pub struct IcDbmsAgentClient<'a> {
20    agent: &'a Agent,
21    canister_id: Principal,
22}
23
24impl<'a> IcDbmsAgentClient<'a> {
25    /// Initialize a new [`IcDbmsAgentClient`] with the given reference to an [`Agent`], and the canister ID.
26    pub fn new(agent: &'a Agent, canister_id: Principal) -> Self {
27        Self { agent, canister_id }
28    }
29}
30
31impl IcDbmsAgentClient<'_> {
32    /// Calls a query method on the IC DBMS Canister with the provided arguments and returns the result.
33    async fn query<E, R>(&self, method_name: &str, args: E) -> IcDbmsCanisterClientResult<R>
34    where
35        E: ArgumentEncoder,
36        R: CandidType + for<'de> candid::Deserialize<'de>,
37    {
38        let args = candid::encode_args(args).map_err(IcAgentError::from)?;
39        let result = self
40            .agent
41            .query(&self.canister_id, method_name)
42            .with_arg(args)
43            .call()
44            .await
45            .map_err(IcAgentError::from)?;
46
47        self.decode_result(result)
48    }
49
50    /// Calls an update method on the IC DBMS Canister with the provided arguments and returns the result.
51    async fn update<E, R>(&self, method_name: &str, args: E) -> IcDbmsCanisterClientResult<R>
52    where
53        E: ArgumentEncoder,
54        R: CandidType + for<'de> candid::Deserialize<'de>,
55    {
56        let args = candid::encode_args(args).map_err(IcAgentError::from)?;
57        let result = self
58            .agent
59            .update(&self.canister_id, method_name)
60            .with_arg(args)
61            .call_and_wait()
62            .await
63            .map_err(IcAgentError::from)?;
64
65        self.decode_result(result)
66    }
67
68    /// Helper function to decode the result from a canister call.
69    fn decode_result<R>(&self, data: Vec<u8>) -> IcDbmsCanisterClientResult<R>
70    where
71        R: CandidType + for<'de> candid::Deserialize<'de>,
72    {
73        Decode!(data.as_slice(), R)
74            .map_err(IcAgentError::from)
75            .map_err(IcDbmCanisterClientError::from)
76    }
77}
78
79impl Client for IcDbmsAgentClient<'_> {
80    fn principal(&self) -> Principal {
81        self.canister_id
82    }
83
84    async fn acl_add_principal(
85        &self,
86        principal: Principal,
87    ) -> IcDbmsCanisterClientResult<IcDbmsResult<()>> {
88        self.update("acl_add_principal", (principal,)).await
89    }
90
91    async fn acl_remove_principal(
92        &self,
93        principal: Principal,
94    ) -> IcDbmsCanisterClientResult<IcDbmsResult<()>> {
95        self.update("acl_remove_principal", (principal,)).await
96    }
97
98    async fn acl_allowed_principals(&self) -> IcDbmsCanisterClientResult<Vec<Principal>> {
99        self.query("acl_allowed_principals", ()).await
100    }
101
102    async fn begin_transaction(&self) -> IcDbmsCanisterClientResult<TransactionId> {
103        self.update("begin_transaction", ()).await
104    }
105
106    async fn commit(
107        &self,
108        transaction_id: TransactionId,
109    ) -> IcDbmsCanisterClientResult<IcDbmsResult<()>> {
110        self.update("commit", (transaction_id,)).await
111    }
112
113    async fn rollback(
114        &self,
115        transaction_id: TransactionId,
116    ) -> IcDbmsCanisterClientResult<IcDbmsResult<()>> {
117        self.update("rollback", (transaction_id,)).await
118    }
119
120    async fn select<T>(
121        &self,
122        table: &str,
123        query: Query,
124        transaction_id: Option<TransactionId>,
125    ) -> IcDbmsCanisterClientResult<IcDbmsResult<Vec<T::Record>>>
126    where
127        T: TableSchema,
128        T::Record: CandidType + for<'de> candid::Deserialize<'de>,
129    {
130        self.query(
131            &crate::utils::table_method(table, "select"),
132            (query, transaction_id),
133        )
134        .await
135    }
136
137    async fn select_raw(
138        &self,
139        table: &str,
140        query: Query,
141        transaction_id: Option<TransactionId>,
142    ) -> IcDbmsCanisterClientResult<IcDbmsResult<RawRecords>> {
143        self.query("select", (table, query, transaction_id)).await
144    }
145
146    async fn insert<T>(
147        &self,
148        table: &str,
149        record: T::Insert,
150        transaction_id: Option<TransactionId>,
151    ) -> IcDbmsCanisterClientResult<IcDbmsResult<()>>
152    where
153        T: TableSchema,
154        T::Insert: InsertRecord<Schema = T> + CandidType,
155    {
156        self.update(
157            &crate::utils::table_method(table, "insert"),
158            (record, transaction_id),
159        )
160        .await
161    }
162
163    async fn update<T>(
164        &self,
165        table: &str,
166        patch: T::Update,
167        transaction_id: Option<TransactionId>,
168    ) -> IcDbmsCanisterClientResult<IcDbmsResult<u64>>
169    where
170        T: TableSchema,
171        T::Update: UpdateRecord<Schema = T> + CandidType,
172    {
173        self.update(
174            &crate::utils::table_method(table, "update"),
175            (patch, transaction_id),
176        )
177        .await
178    }
179
180    async fn delete<T>(
181        &self,
182        table: &str,
183        behaviour: DeleteBehavior,
184        filter: Option<Filter>,
185        transaction_id: Option<TransactionId>,
186    ) -> IcDbmsCanisterClientResult<IcDbmsResult<u64>>
187    where
188        T: TableSchema,
189    {
190        self.update(
191            &crate::utils::table_method(table, "delete"),
192            (behaviour, filter, transaction_id),
193        )
194        .await
195    }
196}