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