iota_sdk_graphql_client/api/
objects.rs1use cynic::QueryBuilder;
8use futures::Stream;
9use iota_types::{Object, ObjectId, Version};
10
11use crate::{
12 GraphQLClient,
13 api::define_query,
14 error::GraphQLResult,
15 pagination::{Page, PaginationFilter, PaginationFilterResponse},
16 query_types::{
17 MoveObjectContentsBcsQueryFragment, MoveObjectContentsJsonQueryFragment, ObjectFilter,
18 ObjectQueryArgs, ObjectQueryFragment, ObjectsQueryArgs, ObjectsQueryFragment,
19 },
20 streams::stream_paginated_query,
21};
22
23define_query! {
24 #[derive(Clone)]
26 pub struct ListObjectsQuery {
27 client: GraphQLClient,
28 filter: Option<ObjectFilter>,
29 pagination: PaginationFilter,
30 }
31 output: GraphQLResult<Page<Object>>;
32}
33
34impl ListObjectsQuery {
35 pub(crate) fn new(client: GraphQLClient) -> Self {
36 Self {
37 client,
38 filter: None,
39 pagination: PaginationFilter::default(),
40 }
41 }
42
43 pub fn filter(mut self, filter: ObjectFilter) -> Self {
45 self.filter = Some(filter);
46 self
47 }
48
49 pub fn pagination(mut self, pagination: PaginationFilter) -> Self {
51 self.pagination = pagination;
52 self
53 }
54
55 pub fn stream(self) -> impl Stream<Item = GraphQLResult<Object>> + Unpin {
58 let pagination = self.pagination.clone();
59 stream_paginated_query(move |page| self.clone().pagination(page).send(), pagination)
60 }
61
62 fn operation(
63 filter: Option<ObjectFilter>,
64 pagination: PaginationFilterResponse,
65 ) -> cynic::Operation<ObjectsQueryFragment, ObjectsQueryArgs> {
66 ObjectsQueryFragment::build(ObjectsQueryArgs {
67 after: pagination.after,
68 before: pagination.before,
69 filter,
70 first: pagination.first,
71 last: pagination.last,
72 })
73 }
74
75 async fn send(self) -> GraphQLResult<Page<Object>> {
76 let Self {
77 client,
78 pagination,
79 filter,
80 } = self;
81 let pagination = client.pagination_filter(pagination).await;
82 let response = client
83 .run_query(&Self::operation(filter, pagination))
84 .await?;
85
86 let oc = response.objects;
87 let page_info = oc.page_info;
88 let bcs = oc
89 .nodes
90 .iter()
91 .map(|o| &o.bcs)
92 .filter_map(|b64| b64.as_ref().map(|b| crate::base64::decode(b.0.as_str())))
93 .collect::<crate::error::GraphQLResult<Vec<_>>>()?;
94 let objects = bcs
95 .iter()
96 .map(|b| bcs::from_bytes::<iota_types::Object>(b).map_err(iota_types::BcsError::new))
97 .collect::<Result<Vec<_>, iota_types::BcsError>>()?;
98
99 Ok(Page::new(page_info, objects))
100 }
101}
102
103define_query! {
104 pub struct GetObjectQuery {
106 client: GraphQLClient,
107 object_id: ObjectId,
108 version: Option<Version>,
109 }
110 output: GraphQLResult<Option<Object>>;
111}
112
113impl GetObjectQuery {
114 pub fn version(mut self, version: Version) -> Self {
116 self.version = Some(version);
117 self
118 }
119
120 async fn send(self) -> GraphQLResult<Option<Object>> {
121 let operation = ObjectQueryFragment::build(ObjectQueryArgs {
122 object_id: self.object_id,
123 version: self.version.map(|v| v.as_u64()),
124 });
125
126 let response = self.client.run_query(&operation).await?;
127
128 let obj = response.object;
129 let bcs = obj
130 .and_then(|o| o.bcs)
131 .map(|bcs| crate::base64::decode(bcs.0.as_str()))
132 .transpose()?;
133
134 let object = bcs
135 .map(|b| bcs::from_bytes::<iota_types::Object>(&b).map_err(iota_types::BcsError::new))
136 .transpose()?;
137
138 Ok(object)
139 }
140}
141
142define_query! {
143 pub struct GetMoveObjectContentsQuery {
146 client: GraphQLClient,
147 object_id: ObjectId,
148 version: Option<Version>,
149 }
150 output: GraphQLResult<Option<serde_json::Value>>;
151}
152
153impl GetMoveObjectContentsQuery {
154 pub fn version(mut self, version: Version) -> Self {
156 self.version = Some(version);
157 self
158 }
159
160 async fn send(self) -> GraphQLResult<Option<serde_json::Value>> {
161 let operation = MoveObjectContentsJsonQueryFragment::build(ObjectQueryArgs {
162 object_id: self.object_id,
163 version: self.version.map(|v| v.as_u64()),
164 });
165
166 let response = self.client.run_query(&operation).await?;
167
168 Ok(response
169 .object
170 .and_then(|o| o.as_move_object)
171 .and_then(|o| o.contents)
172 .map(|mv| mv.json))
173 }
174}
175
176define_query! {
177 pub struct GetMoveObjectContentsBcsQuery {
180 client: GraphQLClient,
181 object_id: ObjectId,
182 version: Option<Version>,
183 }
184 output: GraphQLResult<Option<Vec<u8>>>;
185}
186
187impl GetMoveObjectContentsBcsQuery {
188 pub fn version(mut self, version: Version) -> Self {
190 self.version = Some(version);
191 self
192 }
193
194 async fn send(self) -> GraphQLResult<Option<Vec<u8>>> {
195 let operation = MoveObjectContentsBcsQueryFragment::build(ObjectQueryArgs {
196 object_id: self.object_id,
197 version: self.version.map(|v| v.as_u64()),
198 });
199
200 let response = self.client.run_query(&operation).await?;
201
202 response
203 .object
204 .and_then(|o| o.as_move_object)
205 .and_then(|o| o.contents)
206 .map(|bcs| crate::base64::decode(bcs.bcs.0.as_str()))
207 .transpose()
208 }
209}
210
211define_query! {
212 pub struct GetObjectBcsQuery {
214 client: GraphQLClient,
215 object_id: ObjectId,
216 }
217 output: GraphQLResult<Option<Vec<u8>>>;
218}
219
220impl GetObjectBcsQuery {
221 async fn send(self) -> GraphQLResult<Option<Vec<u8>>> {
222 let operation = ObjectQueryFragment::build(ObjectQueryArgs {
223 object_id: self.object_id,
224 version: None,
225 });
226
227 let response = self.client.run_query(&operation).await?;
228
229 response
230 .object
231 .and_then(|o| o.bcs.map(|bcs| crate::base64::decode(bcs.0.as_str())))
232 .transpose()
233 }
234}
235
236impl GraphQLClient {
237 pub fn object(&self, object_id: ObjectId) -> GetObjectQuery {
243 GetObjectQuery {
244 client: self.clone(),
245 object_id,
246 version: None,
247 }
248 }
249
250 pub fn objects(&self) -> ListObjectsQuery {
263 ListObjectsQuery::new(self.clone())
264 }
265
266 pub fn object_bcs(&self, object_id: ObjectId) -> GetObjectBcsQuery {
269 GetObjectBcsQuery {
270 client: self.clone(),
271 object_id,
272 }
273 }
274
275 pub fn move_object_contents(&self, object_id: ObjectId) -> GetMoveObjectContentsQuery {
281 GetMoveObjectContentsQuery {
282 client: self.clone(),
283 object_id,
284 version: None,
285 }
286 }
287
288 pub fn move_object_contents_bcs(&self, object_id: ObjectId) -> GetMoveObjectContentsBcsQuery {
294 GetMoveObjectContentsBcsQuery {
295 client: self.clone(),
296 object_id,
297 version: None,
298 }
299 }
300}
301
302#[cfg(all(test, not(target_arch = "wasm32")))]
303mod tests {
304 use iota_types::{Address, ObjectId, Version};
305
306 use crate::{
307 query_types::ObjectFilter,
308 test_utils::{
309 assert_backward_page, assert_forward_page, backward_page, forward_page, sent_variables,
310 test_client,
311 },
312 };
313
314 #[tokio::test]
315 async fn object_bcs_sends_the_object_id_without_a_version() {
316 let vars = sent_variables("ObjectQueryFragment", |client| async move {
317 let _ = client.object_bcs(ObjectId::SYSTEM_STATE).await;
318 })
319 .await;
320 assert_eq!(vars["objectId"], ObjectId::SYSTEM_STATE.to_string());
321 assert!(vars["version"].is_null());
322 }
323
324 #[tokio::test]
325 async fn object_queries_send_the_object_id_and_version() {
326 let vars = sent_variables("ObjectQueryFragment", |client| async move {
327 let _ = client
328 .object(ObjectId::SYSTEM_STATE)
329 .version(Version::from_u64(3))
330 .await;
331 })
332 .await;
333 assert_eq!(vars["objectId"], ObjectId::SYSTEM_STATE.to_string());
334 assert_eq!(vars["version"], 3);
335
336 let vars = sent_variables("MoveObjectContentsJsonQueryFragment", |client| async move {
337 let _ = client
338 .move_object_contents(ObjectId::SYSTEM_STATE)
339 .version(Version::from_u64(4))
340 .await;
341 })
342 .await;
343 assert_eq!(vars["objectId"], ObjectId::SYSTEM_STATE.to_string());
344 assert_eq!(vars["version"], 4);
345
346 let vars = sent_variables("MoveObjectContentsBcsQueryFragment", |client| async move {
347 let _ = client
348 .move_object_contents_bcs(ObjectId::SYSTEM_STATE)
349 .version(Version::from_u64(5))
350 .await;
351 })
352 .await;
353 assert_eq!(vars["objectId"], ObjectId::SYSTEM_STATE.to_string());
354 assert_eq!(vars["version"], 5);
355
356 let vars = sent_variables("ObjectQueryFragment", |client| async move {
357 let _ = client.object(ObjectId::SYSTEM_STATE).await;
358 })
359 .await;
360 assert!(vars["version"].is_null());
361
362 let vars = sent_variables("MoveObjectContentsJsonQueryFragment", |client| async move {
363 let _ = client.move_object_contents(ObjectId::SYSTEM_STATE).await;
364 })
365 .await;
366 assert!(vars["version"].is_null());
367
368 let vars = sent_variables("MoveObjectContentsBcsQueryFragment", |client| async move {
369 let _ = client
370 .move_object_contents_bcs(ObjectId::SYSTEM_STATE)
371 .await;
372 })
373 .await;
374 assert!(vars["version"].is_null());
375 }
376
377 #[tokio::test]
378 async fn objects_sends_the_filter_and_pagination() {
379 let vars = sent_variables("ObjectsQueryFragment", |client| async move {
380 let _ = client
381 .objects()
382 .filter(ObjectFilter::default().with_owner(Address::FRAMEWORK))
383 .pagination(backward_page())
384 .await;
385 })
386 .await;
387 assert_eq!(vars["filter"]["owner"], Address::FRAMEWORK.to_string());
388 assert_backward_page(&vars);
389
390 let vars = sent_variables("ObjectsQueryFragment", |client| async move {
391 let _ = client
392 .objects()
393 .filter(ObjectFilter::default().with_owner(Address::FRAMEWORK))
394 .pagination(forward_page())
395 .await;
396 })
397 .await;
398 assert_forward_page(&vars);
399 }
400
401 #[tokio::test]
402 async fn test_objects_query() {
403 let client = test_client();
404 let objects = client
405 .objects()
406 .await
407 .map_err(|e| {
408 format!(
409 "Objects query failed for {} network: Error {e}",
410 client.rpc_server()
411 )
412 })
413 .unwrap();
414 assert!(
415 !objects.is_empty(),
416 "Objects query returned no data for {} network",
417 client.rpc_server()
418 );
419 }
420
421 #[tokio::test]
422 async fn test_object_query() {
423 let client = test_client();
424 client
425 .object(ObjectId::SYSTEM_STATE)
426 .await
427 .map_err(|e| {
428 format!(
429 "Object query failed for {} network: Error {e}",
430 client.rpc_server()
431 )
432 })
433 .unwrap()
434 .unwrap();
435 }
436
437 #[tokio::test]
438 async fn test_object_bcs_query() {
439 let client = test_client();
440 client
441 .object_bcs(ObjectId::SYSTEM_STATE)
442 .await
443 .map_err(|e| {
444 format!(
445 "Object bcs query failed for {} network: Error {e}",
446 client.rpc_server()
447 )
448 })
449 .unwrap()
450 .unwrap();
451 }
452}