1use af_sui_types::{ObjectArg, ObjectId, Version};
3use bimap::BiMap;
4use futures::TryStreamExt as _;
5use itertools::Itertools as _;
6use sui_gql_schema::scalars;
7
8use super::fragments::ObjectFilterV2;
9use super::objects_flat::Variables;
10use crate::{GraphQlClient, GraphQlErrors, GraphQlResponseExt as _, schema};
11
12type Query = super::objects_flat::Query<Object>;
13
14type Res<T, C> = std::result::Result<T, Error<<C as GraphQlClient>::Error>>;
17
18#[derive(thiserror::Error, Debug)]
19pub enum Error<T> {
20 #[error(transparent)]
21 Client(T),
22 #[error(transparent)]
23 Server(#[from] GraphQlErrors),
24 #[error("No data in object args query response")]
25 NoData,
26 #[error("Response missing object args for pairs: {0:?}")]
27 MissingNamedArgs(Vec<(String, ObjectId)>),
28}
29
30pub(super) async fn query<C: GraphQlClient>(
34 client: &C,
35 mut names: BiMap<String, ObjectId>,
36 page_size: Option<u32>,
37) -> Res<BiMap<String, ObjectArg>, C> {
38 let object_ids = names.right_values().cloned().collect_vec();
39 let filter = ObjectFilterV2 {
40 object_ids: Some(&object_ids),
41 type_: None,
42 owner: None,
43 };
44 let vars = Variables {
45 filter: Some(filter),
46 after: None,
47 first: page_size.map(|n| n as i32),
48 };
49
50 let mut stream = std::pin::pin!(super::stream::forward(client, vars, request));
51
52 let mut result = BiMap::new();
53
54 while let Some(arg) = stream.try_next().await? {
55 if let Some((key, _)) = names.remove_by_right(arg.id_borrowed()) {
56 result.insert(key, arg);
57 }
58 }
59
60 if !names.is_empty() {
61 return Err(Error::MissingNamedArgs(names.into_iter().collect()));
62 }
63
64 Ok(result)
65}
66
67async fn request<C: GraphQlClient>(
68 client: &C,
69 vars: Variables<'_>,
70) -> Res<super::stream::Page<impl Iterator<Item = Res<ObjectArg, C>> + 'static + use<C>>, C> {
71 let objects = client
72 .query::<Query, _>(vars)
73 .await
74 .map_err(Error::Client)?
75 .try_into_data()?
76 .ok_or(Error::NoData)?
77 .objects;
78
79 Ok(super::stream::Page {
80 info: objects.page_info.into(),
81 data: objects
82 .nodes
83 .into_iter()
84 .filter_map(Object::object_arg)
85 .map(Ok),
86 })
87}
88
89#[cfg(test)]
90#[allow(clippy::unwrap_used)]
91#[test]
92fn gql_output() {
93 use cynic::QueryBuilder as _;
94 let vars = Variables {
95 filter: None,
96 first: None,
97 after: None,
98 };
99 let operation = Query::build(vars);
100 insta::assert_snapshot!(operation.query, @r###"
101 query Query($filter: ObjectFilter, $after: String, $first: Int) {
102 objects(filter: $filter, first: $first, after: $after) {
103 nodes {
104 address
105 version
106 digest
107 owner {
108 __typename
109 ... on Immutable {
110 _
111 }
112 ... on Shared {
113 __typename
114 initialSharedVersion
115 }
116 ... on Parent {
117 __typename
118 }
119 ... on AddressOwner {
120 __typename
121 }
122 }
123 }
124 pageInfo {
125 hasNextPage
126 endCursor
127 }
128 }
129 }
130 "###);
131}
132
133#[macro_export]
164macro_rules! object_args {
165 (
166 {$($tt:tt)*}
167 with { $client:expr_2021 } $(paged by $page_size:expr_2021)?
168 ) => {
169 $crate::object_args!(@Names $($tt)*);
170 {
171 use $crate::queries::GraphQlClientExt as _;
172 let mut names = $crate::queries::BiMap::new();
173 $crate::object_args! { @Map names $($tt)* }
174 let mut oargs = $crate::queries::GraphQlClientExt::object_args(
175 $client,
176 names,
177 $crate::object_args!(@PageSize $($page_size)?)
178 ).await?;
179 $crate::object_args! { @Result oargs $($tt)* }
180 }
181 };
182
183 (@Names mut $name:ident: $_:expr_2021 $(, $($rest:tt)*)?) => {
184 $crate::object_args!(@Names $name: $_ $(, $($rest)*)?)
185 };
186
187 (@Names $name:ident: $_:expr_2021 $(, $($rest:tt)*)?) => {
188 let $name;
189 $crate::object_args!{ @Names $($($rest)*)? }
190 };
191
192 (@Names ) => {};
193
194 (@Map $map:ident mut $name:ident: $object_id:expr_2021 $(, $($rest:tt)*)?) => {
195 $crate::object_args! { @Map $map $name: $object_id $(, $($rest)*)? }
196 };
197
198 (@Map $map:ident $name:ident: $object_id:expr_2021 $(, $($rest:tt)*)?) => {
199 $map.insert(stringify!($name).to_owned(), $object_id);
200 $crate::object_args!{ @Map $map $($($rest)*)? }
201 };
202
203 (@Map $map:ident) => {};
204
205 (@Result $oargs:ident mut $name:ident: $_:expr_2021 $(, $($rest:tt)*)?) => {
206 let mut arg = $oargs
207 .remove_by_left(stringify!($name))
208 .expect("request_named_object_args should fail if any names are missing")
209 .1;
210 arg.set_mutable(true)?;
211 $name = arg;
212 $crate::object_args! {@Result $oargs $($($rest)*)?}
213 };
214
215 (@Result $oargs:ident $name:ident: $_:expr_2021 $(, $($rest:tt)*)?) => {
216 $name = $oargs
217 .remove_by_left(stringify!($name))
218 .expect("request_named_object_args should fail if any names are missing")
219 .1;
220 $crate::object_args! { @Result $oargs $($($rest)*)? }
221 };
222
223 (@Result $oargs:ident ) => {
224 };
225
226 (@PageSize $page_size:expr_2021) => { Some($page_size) };
227 (@PageSize) => { None };
228}
229
230#[derive(cynic::QueryFragment, Debug)]
235struct Object {
236 #[cynic(rename = "address")]
237 object_id: ObjectId,
238 version: Version,
239 digest: Option<scalars::Digest>,
240 owner: Option<ObjectOwner>,
241}
242
243impl Object {
244 fn object_arg(self) -> Option<ObjectArg> {
248 let Self {
249 object_id,
250 version,
251 digest,
252 owner: Some(owner),
253 } = self
254 else {
255 return None;
256 };
257
258 build_object_arg_default(object_id, version, owner, digest)
259 }
260}
261
262fn build_object_arg_default(
263 id: ObjectId,
264 version: Version,
265 owner: ObjectOwner,
266 digest: Option<scalars::Digest>,
267) -> Option<ObjectArg> {
268 Some(match owner {
269 ObjectOwner::Immutable(_) | ObjectOwner::Parent(_) | ObjectOwner::AddressOwner(_) => {
270 ObjectArg::ImmOrOwnedObject((id, version, digest?.0.into()))
271 }
272 ObjectOwner::Shared(Shared {
273 initial_shared_version,
274 ..
275 }) => ObjectArg::SharedObject {
276 id,
277 initial_shared_version,
278 mutable: false,
279 },
280 ObjectOwner::Unknown => return None,
281 })
282}
283
284pub(super) fn build_oarg_set_mut(
285 object_id: ObjectId,
286 version: Version,
287 owner: Option<ObjectOwner>,
288 digest: Option<scalars::Digest>,
289 mutable_: bool,
290) -> Option<ObjectArg> {
291 let mut oarg = build_object_arg_default(object_id, version, owner?, digest)?;
292 if let ObjectArg::SharedObject {
293 ref mut mutable, ..
294 } = oarg
295 {
296 *mutable = mutable_;
297 }
298 Some(oarg)
299}
300
301#[derive(cynic::InlineFragments, Debug)]
302pub(super) enum ObjectOwner {
303 #[allow(dead_code)]
304 Immutable(Immutable),
305
306 Shared(Shared),
307
308 #[allow(dead_code)]
309 Parent(Parent),
310
311 #[allow(dead_code)]
312 AddressOwner(AddressOwner),
313
314 #[cynic(fallback)]
315 Unknown,
316}
317
318#[derive(cynic::QueryFragment, Debug)]
319pub(super) struct Immutable {
320 #[cynic(rename = "_")]
321 __underscore: Option<bool>,
322}
323
324#[derive(cynic::QueryFragment, Debug)]
325pub(super) struct Shared {
326 __typename: String,
327 initial_shared_version: Version,
328}
329
330#[derive(cynic::QueryFragment, Debug)]
331pub(super) struct Parent {
332 __typename: String,
333}
334
335#[derive(cynic::QueryFragment, Debug)]
336pub(super) struct AddressOwner {
337 __typename: String,
338}