1use cynic::QueryBuilder;
8use futures::Stream;
9use iota_types::{Address, MovePackage, Object, Version};
10
11use crate::{
12 GraphQLClient, MoveFunction, MoveModule, Page,
13 api::define_query,
14 error::GraphQLResult,
15 pagination::{PaginationFilter, PaginationFilterResponse},
16 query_types::{
17 LatestPackageQueryFragment, MovePackageVersionFilter, NormalizedMoveFunctionQueryArgs,
18 NormalizedMoveFunctionQueryFragment, NormalizedMoveModuleQueryArgs,
19 NormalizedMoveModuleQueryFragment, PackageArgs, PackageCheckpointFilter,
20 PackageQueryFragment, PackageVersionsArgs, PackageVersionsQueryFragment, PackagesQueryArgs,
21 PackagesQueryFragment,
22 },
23 streams::stream_paginated_query,
24};
25
26define_query! {
27 #[derive(Clone)]
30 pub struct ListPackageVersionsQuery {
31 client: GraphQLClient,
32 address: Address,
33 pagination: PaginationFilter,
34 after_version: Option<Version>,
35 before_version: Option<Version>,
36 }
37 output: GraphQLResult<Page<MovePackage>>;
38}
39
40impl ListPackageVersionsQuery {
41 pub fn pagination(mut self, pagination: PaginationFilter) -> Self {
43 self.pagination = pagination;
44 self
45 }
46
47 pub fn stream(self) -> impl Stream<Item = GraphQLResult<MovePackage>> + Unpin {
50 let pagination = self.pagination.clone();
51 stream_paginated_query(move |page| self.clone().pagination(page).send(), pagination)
52 }
53
54 pub fn after_version(mut self, after_version: Version) -> Self {
56 self.after_version = Some(after_version);
57 self
58 }
59
60 pub fn before_version(mut self, before_version: Version) -> Self {
62 self.before_version = Some(before_version);
63 self
64 }
65
66 fn operation<'a>(
67 address: Address,
68 after_version: Option<Version>,
69 before_version: Option<Version>,
70 pagination: &'a PaginationFilterResponse,
71 ) -> cynic::Operation<PackageVersionsQueryFragment, PackageVersionsArgs<'a>> {
72 PackageVersionsQueryFragment::build(PackageVersionsArgs {
73 address,
74 after: pagination.after.as_deref(),
75 before: pagination.before.as_deref(),
76 first: pagination.first,
77 last: pagination.last,
78 filter: Some(MovePackageVersionFilter {
79 after_version: after_version.map(|v| v.as_u64()),
80 before_version: before_version.map(|v| v.as_u64()),
81 }),
82 })
83 }
84
85 async fn send(self) -> GraphQLResult<Page<MovePackage>> {
86 let Self {
87 client,
88 pagination,
89 address,
90 after_version,
91 before_version,
92 } = self;
93 let pagination = client.pagination_filter(pagination).await;
94 let response = client
95 .run_query(&Self::operation(
96 address,
97 after_version,
98 before_version,
99 &pagination,
100 ))
101 .await?;
102
103 let pc = response.package_versions;
104 let page_info = pc.page_info;
105 let bcs = pc
106 .nodes
107 .iter()
108 .map(|p| &p.bcs)
109 .filter_map(|b64| b64.as_ref().map(|b| crate::base64::decode(b.0.as_str())))
110 .collect::<crate::error::GraphQLResult<Vec<_>>>()?;
111 let packages = bcs
112 .iter()
113 .map(|b| {
114 Ok(bcs::from_bytes::<Object>(b)
115 .map_err(iota_types::BcsError::new)?
116 .data
117 .into_package())
118 })
119 .collect::<Result<Vec<_>, iota_types::BcsError>>()?;
120
121 Ok(Page::new(page_info, packages))
122 }
123}
124
125define_query! {
126 #[derive(Clone)]
128 pub struct ListPackagesQuery {
129 client: GraphQLClient,
130 pagination: PaginationFilter,
131 after_checkpoint: Option<u64>,
132 before_checkpoint: Option<u64>,
133 }
134 output: GraphQLResult<Page<MovePackage>>;
135}
136
137impl ListPackagesQuery {
138 pub fn pagination(mut self, pagination: PaginationFilter) -> Self {
140 self.pagination = pagination;
141 self
142 }
143
144 pub fn stream(self) -> impl Stream<Item = GraphQLResult<MovePackage>> + Unpin {
147 let pagination = self.pagination.clone();
148 stream_paginated_query(move |page| self.clone().pagination(page).send(), pagination)
149 }
150
151 pub fn after_checkpoint(mut self, after_checkpoint: u64) -> Self {
153 self.after_checkpoint = Some(after_checkpoint);
154 self
155 }
156
157 pub fn before_checkpoint(mut self, before_checkpoint: u64) -> Self {
159 self.before_checkpoint = Some(before_checkpoint);
160 self
161 }
162
163 fn operation<'a>(
164 after_checkpoint: Option<u64>,
165 before_checkpoint: Option<u64>,
166 pagination: &'a PaginationFilterResponse,
167 ) -> cynic::Operation<PackagesQueryFragment, PackagesQueryArgs<'a>> {
168 PackagesQueryFragment::build(PackagesQueryArgs {
169 after: pagination.after.as_deref(),
170 before: pagination.before.as_deref(),
171 first: pagination.first,
172 last: pagination.last,
173 filter: Some(PackageCheckpointFilter {
174 after_checkpoint,
175 before_checkpoint,
176 }),
177 })
178 }
179
180 async fn send(self) -> GraphQLResult<Page<MovePackage>> {
181 let Self {
182 client,
183 pagination,
184 after_checkpoint,
185 before_checkpoint,
186 } = self;
187 let pagination = client.pagination_filter(pagination).await;
188 let response = client
189 .run_query(&Self::operation(
190 after_checkpoint,
191 before_checkpoint,
192 &pagination,
193 ))
194 .await?;
195
196 let pc = response.packages;
197 let page_info = pc.page_info;
198 let bcs = pc
199 .nodes
200 .iter()
201 .map(|p| &p.bcs)
202 .filter_map(|b64| b64.as_ref().map(|b| crate::base64::decode(b.0.as_str())))
203 .collect::<crate::error::GraphQLResult<Vec<_>>>()?;
204 let packages = bcs
205 .iter()
206 .map(|b| {
207 Ok(bcs::from_bytes::<Object>(b)
208 .map_err(iota_types::BcsError::new)?
209 .data
210 .into_package())
211 })
212 .collect::<Result<Vec<_>, iota_types::BcsError>>()?;
213
214 Ok(Page::new(page_info, packages))
215 }
216}
217
218define_query! {
219 pub struct GetNormalizedMoveModuleQuery {
222 client: GraphQLClient,
223 package: Address,
224 module: String,
225 version: Option<Version>,
226 enums_pagination: PaginationFilter,
227 friends_pagination: PaginationFilter,
228 functions_pagination: PaginationFilter,
229 structs_pagination: PaginationFilter,
230 }
231 output: GraphQLResult<Option<MoveModule>>;
232}
233
234struct ModulePagination {
235 enums: PaginationFilterResponse,
236 friends: PaginationFilterResponse,
237 functions: PaginationFilterResponse,
238 structs: PaginationFilterResponse,
239}
240
241impl GetNormalizedMoveModuleQuery {
242 pub fn version(mut self, version: Version) -> Self {
244 self.version = Some(version);
245 self
246 }
247
248 pub fn enums_pagination(mut self, pagination: PaginationFilter) -> Self {
250 self.enums_pagination = pagination;
251 self
252 }
253
254 pub fn friends_pagination(mut self, pagination: PaginationFilter) -> Self {
256 self.friends_pagination = pagination;
257 self
258 }
259
260 pub fn functions_pagination(mut self, pagination: PaginationFilter) -> Self {
262 self.functions_pagination = pagination;
263 self
264 }
265
266 pub fn structs_pagination(mut self, pagination: PaginationFilter) -> Self {
268 self.structs_pagination = pagination;
269 self
270 }
271
272 fn operation<'a>(
273 package: Address,
274 module: &'a str,
275 version: Option<Version>,
276 pagination: &'a ModulePagination,
277 ) -> cynic::Operation<NormalizedMoveModuleQueryFragment, NormalizedMoveModuleQueryArgs<'a>>
278 {
279 let ModulePagination {
280 enums,
281 friends,
282 functions,
283 structs,
284 } = pagination;
285 NormalizedMoveModuleQueryFragment::build(NormalizedMoveModuleQueryArgs {
286 package,
287 module,
288 version: version.map(|v| v.as_u64()),
289 after_enums: enums.after.as_deref(),
290 after_functions: functions.after.as_deref(),
291 after_structs: structs.after.as_deref(),
292 after_friends: friends.after.as_deref(),
293 before_enums: enums.before.as_deref(),
294 before_functions: functions.before.as_deref(),
295 before_structs: structs.before.as_deref(),
296 before_friends: friends.before.as_deref(),
297 first_enums: enums.first,
298 first_functions: functions.first,
299 first_structs: structs.first,
300 first_friends: friends.first,
301 last_enums: enums.last,
302 last_functions: functions.last,
303 last_structs: structs.last,
304 last_friends: friends.last,
305 })
306 }
307
308 async fn send(self) -> GraphQLResult<Option<MoveModule>> {
309 let Self {
310 client,
311 package,
312 module,
313 version,
314 enums_pagination,
315 friends_pagination,
316 functions_pagination,
317 structs_pagination,
318 } = self;
319 let pagination = ModulePagination {
320 enums: client.pagination_filter(enums_pagination).await,
321 friends: client.pagination_filter(friends_pagination).await,
322 functions: client.pagination_filter(functions_pagination).await,
323 structs: client.pagination_filter(structs_pagination).await,
324 };
325 let response = client
326 .run_query(&Self::operation(package, &module, version, &pagination))
327 .await?;
328
329 Ok(response.package.and_then(|p| p.module).map(Into::into))
330 }
331}
332
333define_query! {
334 pub struct GetPackageQuery {
336 client: GraphQLClient,
337 address: Address,
338 version: Option<Version>,
339 }
340 output: GraphQLResult<Option<MovePackage>>;
341}
342
343impl GetPackageQuery {
344 pub fn version(mut self, version: Version) -> Self {
347 self.version = Some(version);
348 self
349 }
350
351 async fn send(self) -> GraphQLResult<Option<MovePackage>> {
352 let operation = PackageQueryFragment::build(PackageArgs {
353 address: self.address,
354 version: self.version.map(|v| v.as_u64()),
355 });
356
357 let response = self.client.run_query(&operation).await?;
358
359 Ok(response
360 .package
361 .and_then(|x| x.bcs)
362 .map(|bcs| crate::base64::decode(bcs.0.as_str()))
363 .transpose()?
364 .map(|bcs| bcs::from_bytes::<Object>(&bcs).map_err(iota_types::BcsError::new))
365 .transpose()?
366 .map(|obj| obj.data.into_package()))
367 }
368}
369
370define_query! {
371 pub struct GetNormalizedMoveFunctionQuery {
374 client: GraphQLClient,
375 package: Address,
376 module: String,
377 function: String,
378 version: Option<Version>,
379 }
380 output: GraphQLResult<Option<MoveFunction>>;
381}
382
383impl GetNormalizedMoveFunctionQuery {
384 pub fn version(mut self, version: Version) -> Self {
387 self.version = Some(version);
388 self
389 }
390
391 async fn send(self) -> GraphQLResult<Option<MoveFunction>> {
392 let operation =
393 NormalizedMoveFunctionQueryFragment::build(NormalizedMoveFunctionQueryArgs {
394 address: self.package,
395 module: &self.module,
396 function: &self.function,
397 version: self.version.map(|v| v.as_u64()),
398 });
399 let response = self.client.run_query(&operation).await?;
400
401 Ok(response
402 .package
403 .and_then(|p| p.module)
404 .and_then(|m| m.function)
405 .map(Into::into))
406 }
407}
408
409define_query! {
410 pub struct GetPackageLatestQuery {
413 client: GraphQLClient,
414 address: Address,
415 }
416 output: GraphQLResult<Option<MovePackage>>;
417}
418
419impl GetPackageLatestQuery {
420 async fn send(self) -> GraphQLResult<Option<MovePackage>> {
421 let operation = LatestPackageQueryFragment::build(PackageArgs {
422 address: self.address,
423 version: None,
424 });
425
426 let response = self.client.run_query(&operation).await?;
427
428 Ok(response
429 .latest_package
430 .and_then(|x| x.bcs)
431 .map(|bcs| crate::base64::decode(&bcs.0))
432 .transpose()?
433 .map(|bcs| bcs::from_bytes::<Object>(&bcs).map_err(iota_types::BcsError::new))
434 .transpose()?
435 .map(|obj| obj.data.into_package()))
436 }
437}
438
439impl GraphQLClient {
440 pub fn package(&self, address: Address) -> GetPackageQuery {
452 GetPackageQuery {
453 client: self.clone(),
454 address,
455 version: None,
456 }
457 }
458
459 pub fn package_versions(&self, address: Address) -> ListPackageVersionsQuery {
464 ListPackageVersionsQuery {
465 client: self.clone(),
466 address,
467 pagination: PaginationFilter::default(),
468 after_version: None,
469 before_version: None,
470 }
471 }
472
473 pub fn package_latest(&self, address: Address) -> GetPackageLatestQuery {
477 GetPackageLatestQuery {
478 client: self.clone(),
479 address,
480 }
481 }
482
483 pub fn packages(&self) -> ListPackagesQuery {
491 ListPackagesQuery {
492 client: self.clone(),
493 pagination: PaginationFilter::default(),
494 after_checkpoint: None,
495 before_checkpoint: None,
496 }
497 }
498
499 pub fn normalized_move_function(
502 &self,
503 package: Address,
504 module: impl Into<String>,
505 function: impl Into<String>,
506 ) -> GetNormalizedMoveFunctionQuery {
507 GetNormalizedMoveFunctionQuery {
508 client: self.clone(),
509 package,
510 module: module.into(),
511 function: function.into(),
512 version: None,
513 }
514 }
515
516 pub fn normalized_move_module(
520 &self,
521 package: Address,
522 module: impl Into<String>,
523 ) -> GetNormalizedMoveModuleQuery {
524 GetNormalizedMoveModuleQuery {
525 client: self.clone(),
526 package,
527 module: module.into(),
528 version: None,
529 enums_pagination: PaginationFilter::default(),
530 friends_pagination: PaginationFilter::default(),
531 functions_pagination: PaginationFilter::default(),
532 structs_pagination: PaginationFilter::default(),
533 }
534 }
535}
536
537#[cfg(all(test, not(target_arch = "wasm32")))]
538mod tests {
539 use iota_types::{Address, Version};
540
541 use crate::{
542 Direction, PaginationFilter,
543 test_utils::{
544 assert_backward_page, assert_forward_page, backward_page, forward_page, sent_variables,
545 test_client,
546 },
547 };
548
549 #[tokio::test]
550 async fn package_latest_sends_the_address_without_a_version() {
551 let vars = sent_variables("LatestPackageQueryFragment", |client| async move {
552 let _ = client.package_latest(Address::FRAMEWORK).await;
553 })
554 .await;
555 assert_eq!(vars["address"], Address::FRAMEWORK.to_string());
556 assert!(vars["version"].is_null());
557 }
558
559 #[tokio::test]
560 async fn package_sends_the_address_and_version() {
561 let vars = sent_variables("PackageQueryFragment", |client| async move {
562 let _ = client
563 .package(Address::FRAMEWORK)
564 .version(Version::from_u64(3))
565 .await;
566 })
567 .await;
568 assert_eq!(vars["address"], Address::FRAMEWORK.to_string());
569 assert_eq!(vars["version"], 3);
570
571 let vars = sent_variables("PackageQueryFragment", |client| async move {
572 let _ = client.package(Address::FRAMEWORK).await;
573 })
574 .await;
575 assert!(vars["version"].is_null());
576 }
577
578 #[tokio::test]
579 async fn normalized_move_function_sends_the_function_and_version() {
580 let vars = sent_variables("NormalizedMoveFunctionQueryFragment", |client| async move {
581 let _ = client
582 .normalized_move_function(Address::FRAMEWORK, "coin", "value")
583 .version(Version::from_u64(3))
584 .await;
585 })
586 .await;
587 assert_eq!(vars["address"], Address::FRAMEWORK.to_string());
588 assert_eq!(vars["module"], "coin");
589 assert_eq!(vars["function"], "value");
590 assert_eq!(vars["version"], 3);
591
592 let vars = sent_variables("NormalizedMoveFunctionQueryFragment", |client| async move {
593 let _ = client
594 .normalized_move_function(Address::FRAMEWORK, "coin", "value")
595 .await;
596 })
597 .await;
598 assert!(vars["version"].is_null());
599 }
600
601 #[tokio::test]
602 async fn package_versions_sends_the_address_versions_and_pagination() {
603 let vars = sent_variables("PackageVersionsQueryFragment", |client| async move {
604 let _ = client
605 .package_versions(Address::FRAMEWORK)
606 .after_version(Version::from_u64(2))
607 .before_version(Version::from_u64(5))
608 .pagination(backward_page())
609 .await;
610 })
611 .await;
612 assert_eq!(vars["address"], Address::FRAMEWORK.to_string());
613 assert_eq!(vars["filter"]["afterVersion"], 2);
614 assert_eq!(vars["filter"]["beforeVersion"], 5);
615 assert_backward_page(&vars);
616
617 let vars = sent_variables("PackageVersionsQueryFragment", |client| async move {
618 let _ = client
619 .package_versions(Address::FRAMEWORK)
620 .after_version(Version::from_u64(2))
621 .before_version(Version::from_u64(5))
622 .pagination(forward_page())
623 .await;
624 })
625 .await;
626 assert_forward_page(&vars);
627 }
628
629 #[tokio::test]
630 async fn packages_sends_the_checkpoints_and_pagination() {
631 let vars = sent_variables("PackagesQueryFragment", |client| async move {
632 let _ = client
633 .packages()
634 .after_checkpoint(2)
635 .before_checkpoint(5)
636 .pagination(backward_page())
637 .await;
638 })
639 .await;
640 assert_eq!(vars["filter"]["afterCheckpoint"], 2);
641 assert_eq!(vars["filter"]["beforeCheckpoint"], 5);
642 assert_backward_page(&vars);
643
644 let vars = sent_variables("PackagesQueryFragment", |client| async move {
645 let _ = client
646 .packages()
647 .after_checkpoint(2)
648 .before_checkpoint(5)
649 .pagination(forward_page())
650 .await;
651 })
652 .await;
653 assert_forward_page(&vars);
654 }
655
656 #[tokio::test]
657 async fn normalized_move_module_sends_each_pagination_to_its_field() {
658 let page = |direction, cursor: &str, limit| PaginationFilter {
659 direction,
660 cursor: Some(cursor.to_owned()),
661 limit: Some(limit),
662 };
663 let vars = sent_variables("NormalizedMoveModuleQueryFragment", |client| async move {
664 let _ = client
665 .normalized_move_module(Address::FRAMEWORK, "coin")
666 .version(Version::from_u64(4))
667 .enums_pagination(page(Direction::Forward, "enums", 1))
668 .friends_pagination(page(Direction::Backward, "friends", 2))
669 .functions_pagination(page(Direction::Forward, "functions", 3))
670 .structs_pagination(page(Direction::Backward, "structs", 5))
671 .await;
672 })
673 .await;
674 assert_eq!(vars["package"], Address::FRAMEWORK.to_string());
675 assert_eq!(vars["module"], "coin");
676 assert_eq!(vars["version"], 4);
677 for (member, direction, limit) in [
678 ("Enums", Direction::Forward, 1),
679 ("Friends", Direction::Backward, 2),
680 ("Functions", Direction::Forward, 3),
681 ("Structs", Direction::Backward, 5),
682 ] {
683 let cursor = member.to_lowercase();
684 let (used, unused) = match direction {
685 Direction::Forward => (("after", "first"), ("before", "last")),
686 Direction::Backward => (("before", "last"), ("after", "first")),
687 };
688 assert_eq!(vars[format!("{}{member}", used.0)], cursor);
689 assert_eq!(vars[format!("{}{member}", used.1)], limit);
690 assert!(vars[format!("{}{member}", unused.0)].is_null());
691 assert!(vars[format!("{}{member}", unused.1)].is_null());
692 }
693 }
694
695 #[tokio::test]
696 async fn test_package() {
697 let client = test_client();
698 client
699 .package(Address::FRAMEWORK)
700 .await
701 .map_err(|e| {
702 format!(
703 "Package query failed for {} network. Error: {e}",
704 client.rpc_server()
705 )
706 })
707 .unwrap()
708 .unwrap();
709 }
710
711 #[tokio::test]
712 async fn test_latest_package_query() {
713 let client = test_client();
714 client
715 .package_latest(Address::FRAMEWORK)
716 .await
717 .map_err(|e| {
718 format!(
719 "Latest package query failed for {} network. Error: {e}",
720 client.rpc_server()
721 )
722 })
723 .unwrap()
724 .unwrap();
725 }
726
727 #[tokio::test]
728 async fn test_packages_query() {
729 let client = test_client();
730 let packages = client
731 .packages()
732 .await
733 .map_err(|e| {
734 format!(
735 "Packages query failed for {} network. Error: {e}",
736 client.rpc_server()
737 )
738 })
739 .unwrap();
740
741 assert!(
742 !packages.is_empty(),
743 "Packages query returned no data for {} network",
744 client.rpc_server()
745 );
746 }
747}