sui_gql_client/queries/
full_objects.rs1use std::collections::HashMap;
2
3use af_sui_types::{Object, ObjectId};
4use futures::{StreamExt as _, TryStreamExt as _};
5use graphql_extract::extract;
6use itertools::Itertools as _;
7use sui_gql_schema::scalars::Base64Bcs;
8
9use super::fragments::{ObjectFilterV2, PageInfo, PageInfoForward};
10use super::stream;
11use crate::queries::Error;
12use crate::{missing_data, schema, GraphQlClient, GraphQlResponseExt as _};
13
14pub(super) async fn query<C: GraphQlClient>(
15 client: &C,
16 objects: impl IntoIterator<Item = ObjectId> + Send,
17 page_size: Option<u32>,
18) -> Result<HashMap<ObjectId, Object>, Error<C::Error>> {
19 let object_ids = objects.into_iter().collect_vec();
21
22 let filter = ObjectFilterV2 {
23 object_ids: Some(&object_ids),
24 ..Default::default()
25 };
26 let vars = Variables {
27 after: None,
28 first: page_size.map(|v| v.try_into().unwrap_or(i32::MAX)),
29 filter: Some(filter),
30 };
31
32 let raw_objs: HashMap<_, _> = stream::forward(client, vars, request)
33 .map(|r| -> super::Result<_, C> {
34 let (id, obj) = r?;
35 Ok((id, obj.ok_or_else(|| missing_data!("BCS for {id}"))?))
36 })
37 .try_collect()
38 .await?;
39
40 for id in object_ids {
42 raw_objs
43 .contains_key(&id)
44 .then_some(())
45 .ok_or(missing_data!("Object {id}"))?;
46 }
47
48 Ok(raw_objs)
49}
50
51async fn request<C: GraphQlClient>(
52 client: &C,
53 vars: Variables<'_>,
54) -> super::Result<
55 stream::Page<impl Iterator<Item = super::Result<(ObjectId, Option<Object>), C>> + 'static>,
56 C,
57> {
58 let data = client
59 .query::<Query, _>(vars)
60 .await
61 .map_err(Error::Client)?
62 .try_into_data()?;
63
64 extract!(data => {
65 objects {
66 nodes[] {
67 id
68 object
69 }
70 page_info
71 }
72 });
73 Ok(stream::Page::new(
74 page_info,
75 nodes.map(|r| -> super::Result<_, C> {
76 let (id, obj) = r?;
77 Ok((id, obj.map(Base64Bcs::into_inner)))
78 }),
79 ))
80}
81
82#[derive(cynic::QueryVariables, Clone, Debug)]
83struct Variables<'a> {
84 filter: Option<ObjectFilterV2<'a>>,
85 after: Option<String>,
86 first: Option<i32>,
87}
88
89impl stream::UpdatePageInfo for Variables<'_> {
90 fn update_page_info(&mut self, info: &PageInfo) {
91 self.after.clone_from(&info.end_cursor);
92 }
93}
94
95#[derive(cynic::QueryFragment, Clone, Debug)]
96#[cynic(variables = "Variables")]
97struct Query {
98 #[arguments(filter: $filter, first: $first, after: $after)]
99 objects: ObjectConnection,
100}
101
102#[derive(cynic::QueryFragment, Clone, Debug)]
108struct ObjectConnection {
109 nodes: Vec<ObjectGql>,
110 page_info: PageInfoForward,
111}
112
113#[derive(cynic::QueryFragment, Debug, Clone)]
114#[cynic(graphql_type = "Object")]
115struct ObjectGql {
116 #[cynic(rename = "address")]
117 id: ObjectId,
118 #[cynic(rename = "bcs")]
119 object: Option<Base64Bcs<Object>>,
120}
121
122#[cfg(test)]
123#[allow(clippy::unwrap_used)]
124#[test]
125fn gql_output() -> color_eyre::Result<()> {
126 use cynic::QueryBuilder as _;
127
128 let vars = Variables {
130 filter: Some(Default::default()),
131 after: None,
132 first: None,
133 };
134
135 let operation = Query::build(vars);
136 insta::assert_snapshot!(operation.query, @r###"
137 query Query($filter: ObjectFilter, $after: String, $first: Int) {
138 objects(filter: $filter, first: $first, after: $after) {
139 nodes {
140 address
141 bcs
142 }
143 pageInfo {
144 hasNextPage
145 endCursor
146 }
147 }
148 }
149 "###);
150 Ok(())
151}