sui_gql_client/queries/
latest_objects_version.rs1use std::collections::HashMap;
2
3use af_sui_types::{Address, Version};
4use graphql_extract::extract;
5
6use super::fragments::PageInfoForward;
7use crate::queries::Error;
8use crate::{GraphQlClient, GraphQlResponseExt, missing_data, scalars, schema};
9
10#[derive(cynic::QueryVariables, Clone, Debug)]
11struct Variables<'a> {
12 filter: Option<ObjectFilter<'a>>,
13 after: Option<String>,
14 first: Option<i32>,
15}
16
17impl Variables<'_> {
18 fn next_variables(mut self, page_info: &PageInfoForward) -> Option<Self> {
19 let PageInfoForward {
20 has_next_page,
21 end_cursor,
22 } = page_info;
23 if *has_next_page {
24 self.after.clone_from(end_cursor);
25 Some(self)
26 } else {
27 None
28 }
29 }
30}
31
32#[derive(cynic::InputObject, Clone, Debug, Default)]
33struct ObjectFilter<'a> {
34 #[cynic(rename = "type")]
35 type_: Option<&'a scalars::TypeTag>,
36 owner: Option<&'a Address>,
37 object_ids: Option<&'a [Address]>,
38}
39
40pub async fn query<C: GraphQlClient>(
41 client: &C,
42 object_ids: &[Address],
43) -> super::Result<(u64, HashMap<Address, u64>), C> {
44 let vars = Variables {
45 after: None,
46 first: None,
47 filter: Some(ObjectFilter {
48 object_ids: Some(object_ids),
49 ..Default::default()
50 }),
51 };
52 let init = client
53 .query::<Query, _>(vars.clone())
54 .await
55 .map_err(Error::Client)?
56 .try_into_data()?;
57
58 extract!(init => {
59 checkpoint? {
60 sequence_number
61 }
62 objects {
63 nodes
64 page_info
65 }
66 });
67 let ckpt_num = sequence_number;
68 let init_nodes = nodes;
69
70 let mut next_vars = vars.next_variables(&page_info);
71 let mut pages = vec![];
72 while let Some(vars) = next_vars {
73 let Some(next_page) = client
74 .query::<QueryPage, _>(vars.clone())
75 .await
76 .map_err(Error::Client)?
77 .try_into_data()?
78 else {
79 break;
80 };
81 next_vars = vars.next_variables(&next_page.objects.page_info);
82 pages.push(next_page);
83 }
84
85 let mut raw_objs = HashMap::new();
86 let page_nodes = pages.into_iter().flat_map(|q| q.objects.nodes);
87 for object in init_nodes.into_iter().chain(page_nodes) {
88 let object_id = object.object_id;
89 let version = object.version;
90 raw_objs.insert(object_id, version);
91 }
92 for id in object_ids {
94 raw_objs
95 .contains_key(id)
96 .then_some(())
97 .ok_or(missing_data!("Object version for {id}"))?;
98 }
99
100 Ok((ckpt_num, raw_objs))
101}
102
103#[derive(cynic::QueryFragment, Clone, Debug)]
104#[cynic(variables = "Variables")]
105struct Query {
106 checkpoint: Option<Checkpoint>,
107
108 #[arguments(filter: $filter, first: $first, after: $after)]
109 objects: ObjectConnection,
110}
111
112#[derive(cynic::QueryFragment, Debug)]
117#[cynic(graphql_type = "Query", variables = "Variables")]
118struct QueryPage {
119 #[arguments(filter: $filter, first: $first, after: $after)]
120 objects: ObjectConnection,
121}
122
123#[derive(cynic::QueryFragment, Debug, Clone)]
128struct Object {
129 version: Version,
130 #[cynic(rename = "address")]
131 object_id: Address,
132}
133
134#[derive(cynic::QueryFragment, Debug, Clone)]
135struct Checkpoint {
136 sequence_number: Version,
137}
138
139#[derive(cynic::QueryFragment, Clone, Debug)]
141#[cynic(graphql_type = "ObjectConnection")]
142struct ObjectConnection {
143 nodes: Vec<Object>,
144 page_info: PageInfoForward,
145}
146
147#[cfg(test)]
148#[allow(clippy::unwrap_used)]
149#[test]
150fn gql_output() {
151 use cynic::QueryBuilder as _;
152
153 let ids = vec![
154 "0x4264c07a42f9d002c1244e43a1f0fa21c49e4a25c7202c597b8476ef6bb57113"
155 .parse()
156 .unwrap(),
157 "0x60d1a85f81172a7418206f4b16e1e07e40c91cf58783f63f18a25efc81442dcb"
158 .parse()
159 .unwrap(),
160 ];
161
162 let vars = Variables {
163 filter: Some(ObjectFilter {
164 object_ids: Some(&ids),
165 ..Default::default()
166 }),
167 after: None,
168 first: None,
169 };
170
171 let operation = Query::build(vars);
172 insta::assert_snapshot!(operation.query, @r###"
173 query Query($filter: ObjectFilter, $after: String, $first: Int) {
174 checkpoint {
175 sequenceNumber
176 }
177 objects(filter: $filter, first: $first, after: $after) {
178 nodes {
179 version
180 address
181 }
182 pageInfo {
183 hasNextPage
184 endCursor
185 }
186 }
187 }
188 "###);
189}
190
191#[cfg(test)]
192#[allow(clippy::unwrap_used)]
193#[test]
194fn page_gql_output() {
195 use cynic::QueryBuilder as _;
196 let ids = vec![
197 "0x4264c07a42f9d002c1244e43a1f0fa21c49e4a25c7202c597b8476ef6bb57113"
198 .parse()
199 .unwrap(),
200 "0x60d1a85f81172a7418206f4b16e1e07e40c91cf58783f63f18a25efc81442dcb"
201 .parse()
202 .unwrap(),
203 ];
204 let vars = Variables {
205 filter: Some(ObjectFilter {
206 object_ids: Some(&ids),
207 ..Default::default()
208 }),
209 after: None,
210 first: None,
211 };
212 let operation = QueryPage::build(vars);
213 insta::assert_snapshot!(operation.query, @r###"
214 query QueryPage($filter: ObjectFilter, $after: String, $first: Int) {
215 objects(filter: $filter, first: $first, after: $after) {
216 nodes {
217 version
218 address
219 }
220 pageInfo {
221 hasNextPage
222 endCursor
223 }
224 }
225 }
226 "###);
227}