iota_sdk_graphql_client/api/
dynamic_fields.rs1use base64ct::Encoding;
8use cynic::QueryBuilder;
9use futures::Stream;
10use iota_types::{Address, TypeTag};
11
12use crate::{
13 DynamicFieldOutput, GraphQLClient, NameValue,
14 api::define_query,
15 error::GraphQLResult,
16 pagination::{Page, PaginationFilter, PaginationFilterResponse},
17 query_types::{
18 Base64, DynamicFieldArgs, DynamicFieldConnectionArgs, DynamicFieldName,
19 DynamicFieldQueryFragment, DynamicFieldsOwnerQueryFragment,
20 DynamicObjectFieldQueryFragment,
21 },
22 streams::stream_paginated_query,
23};
24
25define_query! {
26 #[derive(Clone)]
29 pub struct ListDynamicFieldsQuery {
30 client: GraphQLClient,
31 address: Address,
32 pagination: PaginationFilter,
33 }
34 output: GraphQLResult<Page<DynamicFieldOutput>>;
35}
36
37impl ListDynamicFieldsQuery {
38 pub fn pagination(mut self, pagination: PaginationFilter) -> Self {
40 self.pagination = pagination;
41 self
42 }
43
44 pub fn stream(self) -> impl Stream<Item = GraphQLResult<DynamicFieldOutput>> + Unpin {
47 let pagination = self.pagination.clone();
48 stream_paginated_query(move |page| self.clone().pagination(page).send(), pagination)
49 }
50
51 fn operation<'a>(
52 address: Address,
53 pagination: &'a PaginationFilterResponse,
54 ) -> cynic::Operation<DynamicFieldsOwnerQueryFragment, DynamicFieldConnectionArgs<'a>> {
55 DynamicFieldsOwnerQueryFragment::build(DynamicFieldConnectionArgs {
56 address,
57 after: pagination.after.as_deref(),
58 before: pagination.before.as_deref(),
59 first: pagination.first,
60 last: pagination.last,
61 })
62 }
63
64 async fn send(self) -> GraphQLResult<Page<DynamicFieldOutput>> {
65 let Self {
66 client,
67 pagination,
68 address,
69 } = self;
70 let pagination = client.pagination_filter(pagination).await;
71 let response = client
72 .run_query(&Self::operation(address, &pagination))
73 .await?;
74
75 let DynamicFieldsOwnerQueryFragment { owner: Some(dfs) } = response else {
76 return Ok(Page::new_empty());
77 };
78
79 Ok(Page::new(
80 dfs.dynamic_fields.page_info,
81 dfs.dynamic_fields
82 .nodes
83 .into_iter()
84 .map(TryInto::try_into)
85 .collect::<GraphQLResult<Vec<_>>>()?,
86 ))
87 }
88}
89
90fn dynamic_field_name(type_tag: TypeTag, name: impl Into<NameValue>) -> DynamicFieldName {
91 DynamicFieldName {
92 type_tag: type_tag.to_string(),
93 bcs: Base64(base64ct::Base64::encode_string(&name.into().0)),
94 }
95}
96
97define_query! {
98 pub struct GetDynamicFieldQuery {
101 client: GraphQLClient,
102 address: Address,
103 name: DynamicFieldName,
104 }
105 output: GraphQLResult<Option<DynamicFieldOutput>>;
106}
107
108impl GetDynamicFieldQuery {
109 async fn send(self) -> GraphQLResult<Option<DynamicFieldOutput>> {
110 let operation = DynamicFieldQueryFragment::build(DynamicFieldArgs {
111 address: self.address,
112 name: self.name,
113 });
114
115 let response = self.client.run_query(&operation).await?;
116
117 let result = response
118 .owner
119 .and_then(|o| o.dynamic_field)
120 .map(|df| df.try_into())
121 .transpose()?;
122
123 Ok(result)
124 }
125}
126
127define_query! {
128 pub struct GetDynamicObjectFieldQuery {
131 client: GraphQLClient,
132 address: Address,
133 name: DynamicFieldName,
134 }
135 output: GraphQLResult<Option<DynamicFieldOutput>>;
136}
137
138impl GetDynamicObjectFieldQuery {
139 async fn send(self) -> GraphQLResult<Option<DynamicFieldOutput>> {
140 let operation = DynamicObjectFieldQueryFragment::build(DynamicFieldArgs {
141 address: self.address,
142 name: self.name,
143 });
144
145 let response = self.client.run_query(&operation).await?;
146
147 let result: Option<DynamicFieldOutput> = response
148 .owner
149 .and_then(|o| o.dynamic_object_field)
150 .map(|df| df.try_into())
151 .transpose()?;
152 Ok(result)
153 }
154}
155
156impl GraphQLClient {
157 pub fn dynamic_field(
179 &self,
180 address: Address,
181 type_tag: TypeTag,
182 name: impl Into<NameValue>,
183 ) -> GetDynamicFieldQuery {
184 GetDynamicFieldQuery {
185 client: self.clone(),
186 address,
187 name: dynamic_field_name(type_tag, name),
188 }
189 }
190
191 pub fn dynamic_object_field(
201 &self,
202 address: Address,
203 type_tag: TypeTag,
204 name: impl Into<NameValue>,
205 ) -> GetDynamicObjectFieldQuery {
206 GetDynamicObjectFieldQuery {
207 client: self.clone(),
208 address,
209 name: dynamic_field_name(type_tag, name),
210 }
211 }
212
213 pub fn dynamic_fields(&self, address: Address) -> ListDynamicFieldsQuery {
216 ListDynamicFieldsQuery {
217 client: self.clone(),
218 address,
219 pagination: PaginationFilter::default(),
220 }
221 }
222}
223
224#[cfg(all(test, not(target_arch = "wasm32")))]
225mod tests {
226 use base64ct::Encoding;
227 use iota_types::{ObjectId, TypeTag};
228
229 use crate::{
230 BcsName,
231 test_utils::{
232 assert_backward_page, assert_forward_page, backward_page, forward_page, sent_variables,
233 test_client,
234 },
235 };
236
237 #[tokio::test]
238 async fn dynamic_field_getters_send_the_address_and_name() {
239 let address = ObjectId::SYSTEM_STATE.into();
240 let vars = sent_variables("DynamicFieldQueryFragment", |client| async move {
241 let _ = client.dynamic_field(address, TypeTag::U64, 2u64).await;
242 })
243 .await;
244 assert_eq!(vars["address"], address.to_string());
245 assert_eq!(vars["name"]["type"], "u64");
246 assert_eq!(vars["name"]["bcs"], "AgAAAAAAAAA=");
247
248 let vars = sent_variables("DynamicObjectFieldQueryFragment", |client| async move {
249 let _ = client
250 .dynamic_object_field(address, TypeTag::U64, 2u64)
251 .await;
252 })
253 .await;
254 assert_eq!(vars["address"], address.to_string());
255 assert_eq!(vars["name"]["type"], "u64");
256 assert_eq!(vars["name"]["bcs"], "AgAAAAAAAAA=");
257 }
258
259 #[tokio::test]
260 async fn dynamic_fields_sends_the_address_and_pagination() {
261 let address = ObjectId::SYSTEM_STATE.into();
262 let vars = sent_variables("DynamicFieldsOwnerQueryFragment", |client| async move {
263 let _ = client
264 .dynamic_fields(address)
265 .pagination(backward_page())
266 .await;
267 })
268 .await;
269 assert_eq!(vars["address"], address.to_string());
270 assert_backward_page(&vars);
271
272 let vars = sent_variables("DynamicFieldsOwnerQueryFragment", |client| async move {
273 let _ = client
274 .dynamic_fields(address)
275 .pagination(forward_page())
276 .await;
277 })
278 .await;
279 assert_forward_page(&vars);
280 }
281
282 #[tokio::test]
283 async fn test_dynamic_field_query() {
284 let client = test_client();
285 let bcs = base64ct::Base64::decode_vec("AgAAAAAAAAA=").unwrap();
286 client
287 .dynamic_field(ObjectId::SYSTEM_STATE.into(), TypeTag::U64, BcsName(bcs))
288 .await
289 .map_err(|e| {
290 format!(
291 "Dynamic field query failed for {} network. Error: {e}",
292 client.rpc_server()
293 )
294 })
295 .unwrap();
296
297 client
298 .dynamic_field(ObjectId::SYSTEM_STATE.into(), TypeTag::U64, 2u64)
299 .await
300 .map_err(|e| {
301 format!(
302 "Dynamic field query failed for {} network. Error: {e}",
303 client.rpc_server()
304 )
305 })
306 .unwrap();
307 }
308
309 #[tokio::test]
310 async fn test_dynamic_fields_query() {
311 let client = test_client();
312 client
313 .dynamic_fields(ObjectId::SYSTEM_STATE.into())
314 .await
315 .map_err(|e| {
316 format!(
317 "Dynamic fields query failed for {} network. Error: {e}",
318 client.rpc_server()
319 )
320 })
321 .unwrap();
322 }
323}